From a8d1d2ed12fdbd8ddce15b16e28e28c0acc87712 Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Sat, 19 Oct 2024 16:12:38 +1000 Subject: [PATCH 001/116] Modified clock example to make the clock more readable. Added numbers on the clock face and took the portion of the hour passed into consideration for the hour hand. It now looks like a reasonable clock. --- examples/clock/src/main.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index ef3064c711..a7e86ff133 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,4 +1,4 @@ -use iced::alignment; +use iced::{alignment, Radians}; use iced::mouse; use iced::time; use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; @@ -117,9 +117,12 @@ impl canvas::Program for Clock { }; frame.translate(Vector::new(center.x, center.y)); + let minutes_portion = Radians::from(hand_rotation(self.now.minute(), 60)) / 12.0; + let hour_hand_angle = Radians::from(hand_rotation(self.now.hour(), 12)) + minutes_portion; + frame.with_save(|frame| { - frame.rotate(hand_rotation(self.now.hour(), 12)); + frame.rotate(hour_hand_angle); frame.stroke(&short_hand, wide_stroke()); }); @@ -155,10 +158,31 @@ impl canvas::Program for Clock { ..canvas::Text::default() }); }); + + // Draw clock numbers + for i in 1..=12 { + let distance_out = radius * 1.05; + let angle = + Radians::from(hand_rotation(i, 12)) - Radians::from(Degrees(90.0)); + let x = distance_out * angle.0.cos(); + let y = distance_out * angle.0.sin(); + + frame.fill_text(canvas::Text { + content: format!("{}", i), + size: (radius / 15.0).into(), + position: Point::new(x * 0.85, y * 0.85), + color: palette.secondary.strong.text, + horizontal_alignment: alignment::Horizontal::Center, + vertical_alignment: alignment::Vertical::Center, + font: Font::MONOSPACE, + ..canvas::Text::default() + }); + } }); vec![clock] } + } fn hand_rotation(n: u32, total: u32) -> Degrees { From 4b81ebc6f394c03d66f5e68fc83736325cd39965 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 2 May 2023 15:48:20 -0700 Subject: [PATCH 002/116] feat: pop-os megasquash x11: Workaround nvidia driver lacking DRI feat(mouse area): add double click mouse area: add double click compositor: Add code to extract adapter from x11 refactor: Extract ids_from_dev from wayland specific code wayland: Don't crash if libwayland isn't available feat(sctk): support for overflow widget sctk: Fixes for cursor icon * With multiple windows, `SetCursor` is only sent for the focused window. Fixing a flicker between icons when two windows are using different cursors. * If there is a drag surface, let that surface set the cursor. And not any other. * Set cursor on `enter`, and when switching between CSDs and app area. Fixes https://github.com/pop-os/libcosmic/issues/533. improv(sctk): per-surface cursor position tracking feat(sctk): support ShowWindowMenu Make text wrap configurable fix(core): state order and handling of new trees fix: settings.decorations enables SSD refactor(sctk): convert window actions fix: enable the tokio feature for accesskit_unix fix: only try to connect to clipboard if on linux iced_wgpu: don't query Wayland on macos Update `window_clipboard` sctk: Unmap subsurfaces instead of immediately destroying them Destroying a surface is immediate, rather than synchronized with commits. This fixes a flickering behavior with drag and drop in cosmic-workspaces. sctk: Add alpha setting to `Subsurface` widget sctk: Update `sctk`, `wayland-protocols` Update for cosmic-text undefined buffer size Adapt to cosmic-text undefined width change fix: unset VK_LOADER_DRIVERS_DISABLE after enumeration Allows applications to be launched on the NVIDIA GPU with Vulkan support Adapt to new cosmic-text wgpu: Fix wayland device id conversion wgpu: Fix querying adapter, even if we already have one wgpu: fix nvidia gpu powering up in hybrid setups cargo fmt fix: refactor dnd impl to support responsive widget fix: update read and write methods so they don't recurse fix(core): replace debug_assert in diff fix: avoid with_borrow_mut fix: better handling of state tree This persists widget state associated with widgets assigned custom IDs even when the tree structure changes, but resets state if the custom ID is not found. fix: emit Event::Resized to fix nav bar in cosmic-settings fix(image): guess the image format before decoding iced_wgpu: Query wayland for the device to use, if possible sctk: Support `start_drag` with drags started from touch events sctk: Add touch support fix: update widnow-clipboard tag fix: clean up dnd surfaces when a window is removed Adjust to line ending needing to be specified as part of cosmic_text::BufferLine sctk: Add support for drag-and-drop surface offsets This adds an offset `Vector` as an argument to `on_drag`, and allows passing an offset to `start_drag`. Some applications using drag and drop want the top left corner of the drag surface (as happens without an offset). But others want the drag surface to be offset based on where the cursor is on the widget when starting the drag. This can just be `-1 * offset`, but may be scaled if the drag surface is a different size from the original widget. fix(sctk): nested popup parent feat(mouseare): mouse enter and exit fix(tiny_skia): damage fix(scrollable): filter scroll events in the wrong direction sctk: Use empty input region for subsurfaces This seems to work, and is a better way to deal with subsurface input if there aren't any problems. This way, input events simply go to the parent surface, so we don't have to deal with various edge cases related to that. (Though for compositor-side issues, we still need to fix those for other clients.) This helps with an issue with drag-and-drop and subsurfaces on Smithay, and a different issue on Kwin (in KDE 5.27, at least). Send `DataSource` events to all surfaces Previously these events are directed to the first surface, then removed from `sctk_events`. Which is definitely not right. slider & toggler roundness Update window_clipboard to pop-dnd-4 fix(tiny-skia): non-simple border scaling the issue can be seen with sharp corners when using the screenshot portal with scaling Add read_primary/write_primary chore: update tag fix: translate offer positions in scrollable fix(winit multi-window): handle exit_on_close request fix(scrollable): pass child layout when calculating drag destinations fix(container): id and set_id should use content Clean up after lock surfaces are destroyed Call unlock on session lock chore: update tag fixes for dnd sctk: Fix handling of DnD with subsurfaces (#122) Map subsurface to parent and add offset. refactor: remove Sync bound for Message fix: pass correct state and layout for container widgets fix: docs feat: update advertised drag destinations after rebuilding an interface fix: color format & multi-window fix: doc feat: winit dnd fix: ambiguous import chore: reexport mime from window_clipboard chore: use tag clippy feat: add actions and commands for new clipboard methods cleanup docs feat: custom mime types for Clipboard sctk: Fix handling of layer surface `pointer_interactivity` (#115) A null `region` represents an infinite region (the default). To set an empty region, we need to create a `wl_region`. fix(tiny_skia): disable shadows due to rendering glitch fix(winit): add static lifetimes to multi-window application update fix(winit): add static lifetimes to application update Use `TypeId` to identify `subscription::Map` (cherry picked from commit f39a5fd8953494fd8e41c05bc053519740d09612) fix(sctk): destroy drag icon and send event after cancel action fix: clipboard cleanup fix(sctk): clipboard dummy impl typo refactor(sctk): optional clipboard fix(sctk): broadcast events after update when broadcasting events for no specific surface, it should be done after update so that the runtime subscription is current fix(multi_window): enable drag resize sctk: Map subsurface pointer events to parent surface, with offset sctk_subsurface: Use two surfaces, handle button presses Useful for testing pointer input to subsurfaces. sctk: Add `subsurface_ids` mapping subsurface to parent and offset sctk_subsurface_gst: NV12 surface suppport; disabled Whether or not this works seems to depend on driver, or gstreamer version... Handle frame callbacks for subsurfaces, and `commit` parent surface If the main surface is occluded completely by opaque subsurfaces, it may not receive `frame` events. So we need to request frame events for all subsurfaces as well. Additionally, with "synchronized" subsurfaces, we need to `commit` the parent surface for subsurface changes to take effect. Fixes issues with subsurfaces updating slowly, or only when mouse moved under some circumstances. examples/sctk_subsurface_gst: Cache `BufferSource` in `BufferRef` qdata Similar to `waylandsink`. Allows us to avoid creating a buffer source (and ultimately `wl_buffer`) for every buffer swap. sctk/subsurface: Cache `wl_buffer`s Creating a new `wl_buffer` each frame seems to perform poorly. We can instead keep a cache of `wl_buffer`s we have created from a `BufferSource`. sctk/subsurface: Avoid unnecessary subsurface commits if unchanged feat(slider): add breakpoints fix: autosize surface layout Autosized surfaces perform the layout step to get the size and then again when building the interface, but sometimes the calculated size is not enough space when used as a bound, so we need to add a tiny amount to the calculated size. This also makes the event loop timeout duration configurable. Viewport physical size is calculated directly from the logical size now as well in iced-sctk to avoid inconsistencies that resulted from recalculating the logical size after using it to calculate the physical size. fix(sctk): send close event instead of close requested when a window is closed sctk: add command to set maximize state Add `show_window_menu` action Winit currently supports this only on Windows and Wayland. This requests that a context menu is shown at the cursor position, like the menu normally triggered by right clicking the title bar. This is important for implementing client side decorations with Iced widgets. Remove unnecessary redraw request This was particularly visible on Redox where there is no vsync, but also causes unnecessary redraws on Linux chore: update accesskit Disable broken rustdoc links sctk: Add `Subsurface` widget (#79) This adds a widget that attaches an shm or dma buffer to a subsurface, scaled with `wp_viewporter`. By exposing this as a widget, rather than as a type of window, it can be positioned and scaled like any other iced widget. It provides an API that's similar to an iced image. The initial version of this just took a `wl_buffer`. But this makes buffer re-use problematic. In particular, the docs for `wl_surface::attach` note that `wl_buffer::release` events become unreliable if a buffer is attached to multiple surfaces. And indicates that a client should create multiple `wl_buffer` instances, or use `wp_linux_buffer_release`. So we store information about the buffer, and create `wl_buffer`s as needed. `SubsurfaceBuffer::new` also returns a future that's signaled when all references are destroyed, both `wl_buffer`s and any instance of the `SubsurfaceBuffer` that might still be used in the `view`. So this seems like the best solution for now, within the model-view-update architecture. This has two examples: `sctk_subsurface`, showing a single-color shm buffer, and `sctk_subsurface_gst`, which plays an h264 video to a subsurface with vaapi decoding. chore: use pop-os fork of winit chore: unpin cosmic-text Update wgpu to a commit that fixes use on Nvidia drivers This can be tested with something like `VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json cargo run -p tour --features iced/wgpu`. On Nvidia I'm seeing a flood of `Suboptimal present of frame` warnings. So some improvement may still be needed here. But if it doesn't regress behavior on other hardware, that seems like an improvement over freezing. fix(winit): pass text with modifiers in event chore: update cosmic-text and glyphon fix: distinguish between the key character and the utf8 of a key event feat(wgpu): use alpha modes for compositing if available chore: use updated softbuffer fix: typo fix: downgrade resvg fix: core/serde chore: remove default features typo: add rev to glyphon Update to cosmic-text refactor Fix docs error Add function to fill a Raw Fixes for last commit fix: broadcast surface events dnd_listener: Fix behavior when there are multiple listeners (#87) A `dnd_listener` widget shouldn't handle a DnD event when the dnd drag isn't within the widget's bounds. So add a few more checks for this. Enter/leave events generated by `DndOfferEvent::Motion` also don't behave as one might expect, since the enter may occur before the leave depending on the order it calls `on_event` on the widget. Not sure how to address that, but cosmic-workspaces can just ignore the leave events for now. Otherwise, this seems to be working fine, after these changes. chore: fix sctk multi-window dependency cleanup: formatting and clippy fix(example): sctk_drag id fix: translate the wayland event position for content inside a scrollable fix: set web-sys to =0.3.64 fix: clip mask checks chore: use advanced text shaping for pick list fix: dnd widget layout fix: ambiguous palette import chore: remove artifacts job fix: CI tests fix: add back the window id to the frames subscription fix: tooltip children and diff refactor: udpate gradient angles for slider reexport limits fix: editor and sctk_todos examples cleanup: clippy cleanup git workflows chore: cleanup iced_widget refactor Update mod.rs chore: update softbuffer Hack to remove image blur iced_core: feature for serde serialization of KeyCode fix(wgpu): handle border_radius property with image raster feat: add border radius to image rendering feat: Add side mouse button events cleanup: clippy fixes and formatting Part of this is a refactor of the ID cleanup: clippy and fmt fix: test workflow fix: add note in CHANGELOG fix: clippy refactor: restore default style of slider feat: allow setting the width and height of a rule fix: slider gradient angle feat: gradient backgground for the slider rail feat(mouse-area): added on_drag method fix(widget): container inherited wrong icon color from renderer fix(button): inherit icon color if set to none feat(renderer): define default icon color By default, this is the same as the text color for best visibility. feat(winit): client-side resize drag support feat(winit): client-side resize drag support Make vertical scroll properties optional fix: quad rendering including border only inside of the bounds Move `Screenshot` inside `window` module Added offscreen rendering support for wgpu & tiny-skia exposed with the window::screenshot command. Provide access to font from each crate Use nested for lazy widgets Use layout with children for nesting Introduce internal `overlay::Nested` for `UserInterface` fix: reset button state if the cursor leaves runtime: Handle widget operations in `program::State` helper (#46) chore: default line height, text size, and shaping for cosmic feat: sctk shell fix: quad rendering including border only inside of the bounds fix: better slider drawing (it allows just the border part of the handle quad outside of the layout bouds, which isn't great, but is ok for our purposes due to being transparent) cleanup: fix & format fix: use iced_core::Font cleanup fix: allow leaving out winit & iced-sctk fix: settings fix: slider draw improvements fix: websocket example fix: modal example fix: scrollable example fix: toast example fix: avoid panicking in iced_sctk with lazy widgets in auto-size surfaces fix: todos panic fix: only diff auto-sized surfaces in iced_sctk build_user_interface & improve sctk examples wip (iced-sctk): window resize with icons feat (iced-sctk): support for setting cursor refactor: default decorations to client fix: set window geometry after receiving configure fix: size limits with no max bound must be cut off fix: send size update when autosized surface resizes fix: use ceil size for positioner cleanup: remove dbg statement fix: remove a destroyed surface from compositor surfaces fix errors after rebase and wip scaling support fix: handling of scale factor in set_logical_size fix (sctk_drag example): add .into for border radius fix: fractional scaling sctk: Fire RedrawRequests wip: animations via frame event fix / refactor iced-sctk redraw & frame event handling cleanup: note about frame request in iced-sctk fix: send resize when necessary for layer surface and popups too fix: always request redraw for a new surface fix: scaling and autosize surface improvements refactor: sctk_lazy keyboard interactivity feat(sctk): configurable natural_scroll property feat: send state and capabilities events when there are changes fix: redraw when an update is needed and clean up the logic Update sctk to latest commit Fix compilation of sctk drag example fix(sctk): update interface before checking if it has a redraw request refactor: after autosize surface resize wait to redraw until the resize has been applied refactor: better handling of autosize surfaces chore: update sctk chore: update sctk fixes sctk_drag example fix: default to ControlFlow::Wait for applications with no surface this seems to help CPU usage for app library and launcher default to 250ms timeout in the event loop Update sctk sctk: Implement xdg-activation support fix: don't require Flags to be clone for settings on wayland chore: error if neither winit or wayland feature is set chore: Allow compiling without windowing system (#65) fix(iced-sctk): handle exit_on_close_request fix: make sure that each widget operation operates on every interface This should be ok even for widget actions like focus next because there can only ever be a single focused widget cargo fmt cleanup: dbg statement fix(iced-sctk): replace panic with handling for remaining enum variants refactor: use iced clipboard for interacting with the selection refactor: allow passing an activation token when creating a window sctk: Add support for `ext-session-lock` protocol fix(sctk): build and use tree for layout of autosize surfaces Update winit to latest commit used by upstream iced fix(sctk): send key characters fix(sctk): check if key is a named key first refactor(sctk): keep compositor surface in state feat: accessibility with some widget impls feat: stable ids a11y: Don't unconditionally pull winit (#43) Update conversion.rs integration fixes integration integration integration integration s integration some integration work more integration Update Cargo.toml Update mod.rs Update multi_window.rs s more integration more integration (ryanabx wip #100000) more integration!! integration 2 integration more integration (rbx) s integration integration work integration Update Cargo.toml integration simple integration things int integration to 175 integration(170) Co-Authored-By: Ashley Wulber <48420062+wash2@users.noreply.github.com> Co-Authored-By: Victoria Brekenfeld <4404502+Drakulix@users.noreply.github.com> Co-Authored-By: Eduardo Flores Co-Authored-By: Michael Murphy Co-Authored-By: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Co-Authored-By: Jeremy Soller Co-Authored-By: Ryan Brue <56272643+ryanabx@users.noreply.github.com> fix(column): handle keys len change fix: iced-sctk a11y wip: winit single window updates tokio feature hangs even without a11y feature fix: multiwindow a11y fixes fix: component update winit wip: sctk integration to winit shell refactor: remove accesskit_unix fix: svg fix: remove wayland default feature feat: derive Hash for image Handle fix: cleanup 0.13 rebase errors fix: remove path dependencies conversion for Radius conversion for Padding setter for Svg border radius re-export Limits fix: connect clipboard if disconnected on layer surface or popup creation fix: connect clipboard if disconnected on session lock surface creation fix: update size of layer surface after configure fix: insert user interfaces for popup and lock surfaces on creation fix: svg scaling feat: popups on winit windows fix: default text shaping to advanced fix: fallback to renderer icon style if svg is symbolic fixes fix: sctk frame handling feat: autosize handling fix: better autosize handling fix: avoid duplicate window events from sctk fix: better handling of popups fix: refactor redraw handling for sctk fix: include id in frames fix: image fix: scrollable delta direction sctk: unregister clipboard when surface is done set min / max size when size is requested fix: popups filter pointer events feat: add Hide variant to mouse Interaction dnd fixes fix: use physical width for DnD surface fix: tiny-skia svg quality refactor: peek_dnd try to parse data cleanup text conversion cleanup svg scaling fixes background color fix Introduce consecutive_click_distance like other toolkits do such as gtk,qt,imgui. Update Cargo.toml Signed-off-by: Ryan Brue --- .github/workflows/audit.yml | 12 - .github/workflows/lint.yml | 7 +- .github/workflows/test.yml | 36 +- CHANGELOG.md | 6 + Cargo.toml | 88 +- accessibility/Cargo.toml | 24 + accessibility/src/a11y_tree.rs | 80 + accessibility/src/id.rs | 215 +++ accessibility/src/lib.rs | 17 + accessibility/src/node.rs | 41 + accessibility/src/traits.rs | 19 + core/Cargo.toml | 25 + core/src/border.rs | 51 + core/src/clipboard.rs | 172 ++ core/src/element.rs | 93 +- core/src/event.rs | 28 +- core/src/event/wayland/layer.rs | 10 + core/src/event/wayland/mod.rs | 39 + core/src/event/wayland/output.rs | 34 + core/src/event/wayland/popup.rs | 21 + core/src/event/wayland/seat.rs | 9 + core/src/event/wayland/session_lock.rs | 19 + core/src/event/wayland/window.rs | 8 + core/src/id.rs | 155 ++ core/src/image.rs | 19 +- core/src/keyboard/key.rs | 2 + core/src/lib.rs | 6 + core/src/mouse/click.rs | 1 - core/src/mouse/interaction.rs | 1 + core/src/overlay.rs | 6 +- core/src/overlay/group.rs | 1 + core/src/padding.rs | 23 + core/src/rectangle.rs | 20 + core/src/renderer.rs | 6 + core/src/renderer/null.rs | 16 +- core/src/svg.rs | 10 + core/src/text.rs | 12 +- core/src/theme/palette.rs | 10 +- core/src/widget.rs | 39 +- core/src/widget/id.rs | 43 - core/src/widget/operation.rs | 5 +- core/src/widget/operation/focusable.rs | 3 +- core/src/widget/operation/search_id.rs | 42 + core/src/widget/text.rs | 75 +- core/src/widget/tree.rs | 298 +++- core/src/window/id.rs | 10 +- core/src/window/settings.rs | 8 +- examples/editor/Cargo.toml | 2 +- examples/game_of_life/Cargo.toml | 2 +- examples/gradient/Cargo.toml | 2 +- examples/integration/Cargo.toml | 2 +- examples/integration/src/controls.rs | 1 + examples/integration/src/main.rs | 7 +- examples/lazy/Cargo.toml | 2 +- examples/loading_spinners/Cargo.toml | 2 +- examples/multi_window/Cargo.toml | 9 +- examples/multi_window/src/main.rs | 7 +- examples/pane_grid/Cargo.toml | 2 +- examples/pokedex/Cargo.toml | 2 +- examples/screenshot/Cargo.toml | 2 +- examples/scrollable/Cargo.toml | 4 +- examples/scrollable/src/main.rs | 4 +- examples/sctk_drag/Cargo.toml | 19 + examples/sctk_drag/src/dnd_destination.rs | 728 ++++++++ examples/sctk_drag/src/dnd_source.rs | 382 ++++ examples/sctk_drag/src/main.rs | 202 +++ examples/sctk_lazy/Cargo.toml | 17 + examples/sctk_lazy/src/main.rs | 255 +++ examples/sctk_session_lock/Cargo.toml | 18 + examples/sctk_session_lock/src/main.rs | 96 + examples/sctk_subsurface/Cargo.toml | 19 + examples/sctk_subsurface/src/main.rs | 123 ++ examples/sctk_subsurface/src/wayland.rs | 128 ++ examples/sctk_subsurface_gst/Cargo.toml | 21 + examples/sctk_subsurface_gst/src/main.rs | 84 + examples/sctk_subsurface_gst/src/pipewire.rs | 185 ++ examples/sctk_todos/Cargo.toml | 32 + examples/sctk_todos/README.md | 20 + examples/sctk_todos/fonts/icons.ttf | Bin 0 -> 5596 bytes examples/sctk_todos/iced-todos.desktop | 4 + examples/sctk_todos/index.html | 12 + examples/sctk_todos/src/main.rs | 648 +++++++ examples/svg/Cargo.toml | 2 +- examples/system_information/Cargo.toml | 12 - examples/system_information/src/main.rs | 142 -- examples/toast/src/main.rs | 10 +- examples/todos/Cargo.toml | 26 +- examples/todos/src/main.rs | 62 +- examples/tooltip/Cargo.toml | 2 +- examples/tour/Cargo.toml | 2 +- examples/websocket/src/main.rs | 1 + futures/Cargo.toml | 1 + futures/src/subscription.rs | 9 +- graphics/src/compositor.rs | 12 + graphics/src/geometry/text.rs | 10 +- graphics/src/image.rs | 27 +- graphics/src/settings.rs | 4 +- graphics/src/text.rs | 9 +- graphics/src/text/cache.rs | 2 +- graphics/src/text/editor.rs | 26 +- graphics/src/text/paragraph.rs | 15 +- graphics/src/viewport.rs | 17 + renderer/Cargo.toml | 1 + renderer/src/compositor.rs | 1 - renderer/src/fallback.rs | 29 +- runtime/Cargo.toml | 9 +- runtime/src/clipboard.rs | 83 +- runtime/src/dnd.rs | 129 ++ runtime/src/lib.rs | 50 + runtime/src/multi_window/state.rs | 1 + runtime/src/platform_specific/mod.rs | 26 + .../platform_specific/wayland/activation.rs | 42 + .../wayland/layer_surface.rs | 185 ++ runtime/src/platform_specific/wayland/mod.rs | 42 + .../src/platform_specific/wayland/popup.rs | 141 ++ .../platform_specific/wayland/session_lock.rs | 45 + runtime/src/program/state.rs | 11 + runtime/src/user_interface.rs | 52 +- runtime/src/window.rs | 26 +- runtime/src/window/screenshot.rs | 2 +- src/application.rs | 8 +- src/daemon.rs | 9 +- src/error.rs | 7 + src/lib.rs | 69 +- src/program.rs | 9 +- src/settings.rs | 12 +- src/wayland/application.rs | 211 +++ src/wayland/mod.rs | 4 + src/wayland/program.rs | 833 +++++++++ src/window.rs | 5 + src/window/icon.rs | 5 +- tiny_skia/fonts/Iced-Icons.ttf | Bin 0 -> 5108 bytes tiny_skia/src/engine.rs | 99 +- tiny_skia/src/geometry.rs | 10 +- tiny_skia/src/layer.rs | 27 +- tiny_skia/src/lib.rs | 31 +- tiny_skia/src/raster.rs | 149 +- tiny_skia/src/settings.rs | 2 +- tiny_skia/src/text.rs | 6 +- tiny_skia/src/vector.rs | 1 + tiny_skia/src/window/compositor.rs | 17 +- wgpu/Cargo.toml | 17 + wgpu/src/geometry.rs | 10 +- wgpu/src/image/mod.rs | 30 +- wgpu/src/layer.rs | 19 +- wgpu/src/lib.rs | 39 +- wgpu/src/offscreen.rs | 102 ++ wgpu/src/settings.rs | 4 +- wgpu/src/shader/offscreen_blit.wgsl | 22 + wgpu/src/window.rs | 36 + wgpu/src/window/compositor.rs | 105 +- wgpu/src/window/wayland.rs | 115 ++ wgpu/src/window/x11.rs | 171 ++ widget/Cargo.toml | 9 +- widget/src/button.rs | 249 ++- widget/src/canvas.rs | 1 + widget/src/checkbox.rs | 137 +- widget/src/column.rs | 46 +- widget/src/container.rs | 92 +- widget/src/helpers.rs | 14 +- widget/src/image.rs | 158 +- widget/src/image/viewer.rs | 12 +- widget/src/keyed/column.rs | 10 +- widget/src/lazy.rs | 47 +- widget/src/lazy/component.rs | 51 +- widget/src/lazy/responsive.rs | 65 +- widget/src/mouse_area.rs | 137 +- widget/src/overlay/menu.rs | 17 +- widget/src/pane_grid.rs | 10 +- widget/src/pane_grid/content.rs | 8 +- widget/src/pane_grid/title_bar.rs | 18 +- widget/src/pick_list.rs | 32 +- widget/src/radio.rs | 2 +- widget/src/row.rs | 48 +- widget/src/rule.rs | 18 + widget/src/scrollable.rs | 348 +++- widget/src/shader.rs | 4 + widget/src/slider.rs | 271 ++- widget/src/stack.rs | 5 +- widget/src/svg.rs | 134 +- widget/src/text_input.rs | 2 +- widget/src/themer.rs | 7 +- widget/src/toggler.rs | 211 ++- widget/src/tooltip.rs | 16 +- widget/src/vertical_slider.rs | 6 +- winit/Cargo.toml | 32 +- winit/src/a11y.rs | 64 + winit/src/application/drag_resize.rs | 138 ++ winit/src/application/state.rs | 231 +++ winit/src/clipboard.rs | 258 ++- winit/src/conversion.rs | 82 +- winit/src/error.rs | 2 +- winit/src/lib.rs | 5 + winit/src/platform_specific/mod.rs | 174 ++ .../wayland/commands/activation.rs | 31 + .../wayland/commands/layer_surface.rs | 116 ++ .../platform_specific/wayland/commands/mod.rs | 6 + .../wayland/commands/popup.rs | 42 + .../wayland/commands/session_lock.rs | 42 + .../platform_specific/wayland/conversion.rs | 120 ++ .../wayland/event_loop/control_flow.rs | 56 + .../wayland/event_loop/mod.rs | 387 +++++ .../wayland/event_loop/proxy.rs | 66 + .../wayland/event_loop/state.rs | 1133 ++++++++++++ .../wayland/handlers/activation.rs | 47 + .../wayland/handlers/compositor.rs | 67 + .../platform_specific/wayland/handlers/mod.rs | 37 + .../wayland/handlers/output.rs | 49 + .../wayland/handlers/seat/keyboard.rs | 255 +++ .../wayland/handlers/seat/mod.rs | 5 + .../wayland/handlers/seat/pointer.rs | 184 ++ .../wayland/handlers/seat/seat.rs | 214 +++ .../wayland/handlers/seat/touch.rs | 157 ++ .../wayland/handlers/session_lock.rs | 58 + .../wayland/handlers/shell/layer.rs | 118 ++ .../wayland/handlers/shell/mod.rs | 3 + .../wayland/handlers/shell/xdg_popup.rs | 92 + .../wayland/handlers/shell/xdg_window.rs | 27 + .../wayland/handlers/subcompositor.rs | 4 + .../wayland/handlers/wp_fractional_scaling.rs | 91 + .../wayland/handlers/wp_viewporter.rs | 71 + winit/src/platform_specific/wayland/keymap.rs | 895 ++++++++++ winit/src/platform_specific/wayland/mod.rs | 211 +++ .../platform_specific/wayland/sctk_event.rs | 1210 +++++++++++++ .../wayland/subsurface_widget.rs | 702 ++++++++ .../platform_specific/wayland/winit_window.rs | 437 +++++ winit/src/program.rs | 1547 +++++++++++++---- winit/src/program/state.rs | 54 +- winit/src/program/window_manager.rs | 43 +- winit/src/proxy.rs | 20 +- 230 files changed, 18924 insertions(+), 1202 deletions(-) create mode 100644 accessibility/Cargo.toml create mode 100644 accessibility/src/a11y_tree.rs create mode 100644 accessibility/src/id.rs create mode 100644 accessibility/src/lib.rs create mode 100644 accessibility/src/node.rs create mode 100644 accessibility/src/traits.rs create mode 100644 core/src/event/wayland/layer.rs create mode 100644 core/src/event/wayland/mod.rs create mode 100644 core/src/event/wayland/output.rs create mode 100644 core/src/event/wayland/popup.rs create mode 100644 core/src/event/wayland/seat.rs create mode 100644 core/src/event/wayland/session_lock.rs create mode 100644 core/src/event/wayland/window.rs create mode 100644 core/src/id.rs delete mode 100644 core/src/widget/id.rs create mode 100644 core/src/widget/operation/search_id.rs create mode 100644 examples/sctk_drag/Cargo.toml create mode 100644 examples/sctk_drag/src/dnd_destination.rs create mode 100644 examples/sctk_drag/src/dnd_source.rs create mode 100644 examples/sctk_drag/src/main.rs create mode 100644 examples/sctk_lazy/Cargo.toml create mode 100644 examples/sctk_lazy/src/main.rs create mode 100644 examples/sctk_session_lock/Cargo.toml create mode 100644 examples/sctk_session_lock/src/main.rs create mode 100644 examples/sctk_subsurface/Cargo.toml create mode 100644 examples/sctk_subsurface/src/main.rs create mode 100644 examples/sctk_subsurface/src/wayland.rs create mode 100644 examples/sctk_subsurface_gst/Cargo.toml create mode 100644 examples/sctk_subsurface_gst/src/main.rs create mode 100644 examples/sctk_subsurface_gst/src/pipewire.rs create mode 100644 examples/sctk_todos/Cargo.toml create mode 100644 examples/sctk_todos/README.md create mode 100644 examples/sctk_todos/fonts/icons.ttf create mode 100644 examples/sctk_todos/iced-todos.desktop create mode 100644 examples/sctk_todos/index.html create mode 100644 examples/sctk_todos/src/main.rs delete mode 100644 examples/system_information/Cargo.toml delete mode 100644 examples/system_information/src/main.rs delete mode 100644 renderer/src/compositor.rs create mode 100644 runtime/src/dnd.rs create mode 100644 runtime/src/platform_specific/mod.rs create mode 100644 runtime/src/platform_specific/wayland/activation.rs create mode 100644 runtime/src/platform_specific/wayland/layer_surface.rs create mode 100644 runtime/src/platform_specific/wayland/mod.rs create mode 100644 runtime/src/platform_specific/wayland/popup.rs create mode 100644 runtime/src/platform_specific/wayland/session_lock.rs create mode 100644 src/wayland/application.rs create mode 100644 src/wayland/mod.rs create mode 100644 src/wayland/program.rs create mode 100644 tiny_skia/fonts/Iced-Icons.ttf create mode 100644 wgpu/src/offscreen.rs create mode 100644 wgpu/src/shader/offscreen_blit.wgsl create mode 100644 wgpu/src/window/wayland.rs create mode 100644 wgpu/src/window/x11.rs create mode 100644 winit/src/a11y.rs create mode 100644 winit/src/application/drag_resize.rs create mode 100644 winit/src/application/state.rs create mode 100644 winit/src/platform_specific/mod.rs create mode 100644 winit/src/platform_specific/wayland/commands/activation.rs create mode 100644 winit/src/platform_specific/wayland/commands/layer_surface.rs create mode 100644 winit/src/platform_specific/wayland/commands/mod.rs create mode 100644 winit/src/platform_specific/wayland/commands/popup.rs create mode 100644 winit/src/platform_specific/wayland/commands/session_lock.rs create mode 100644 winit/src/platform_specific/wayland/conversion.rs create mode 100644 winit/src/platform_specific/wayland/event_loop/control_flow.rs create mode 100644 winit/src/platform_specific/wayland/event_loop/mod.rs create mode 100644 winit/src/platform_specific/wayland/event_loop/proxy.rs create mode 100644 winit/src/platform_specific/wayland/event_loop/state.rs create mode 100644 winit/src/platform_specific/wayland/handlers/activation.rs create mode 100644 winit/src/platform_specific/wayland/handlers/compositor.rs create mode 100644 winit/src/platform_specific/wayland/handlers/mod.rs create mode 100644 winit/src/platform_specific/wayland/handlers/output.rs create mode 100644 winit/src/platform_specific/wayland/handlers/seat/keyboard.rs create mode 100644 winit/src/platform_specific/wayland/handlers/seat/mod.rs create mode 100644 winit/src/platform_specific/wayland/handlers/seat/pointer.rs create mode 100644 winit/src/platform_specific/wayland/handlers/seat/seat.rs create mode 100644 winit/src/platform_specific/wayland/handlers/seat/touch.rs create mode 100644 winit/src/platform_specific/wayland/handlers/session_lock.rs create mode 100644 winit/src/platform_specific/wayland/handlers/shell/layer.rs create mode 100644 winit/src/platform_specific/wayland/handlers/shell/mod.rs create mode 100644 winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs create mode 100644 winit/src/platform_specific/wayland/handlers/shell/xdg_window.rs create mode 100644 winit/src/platform_specific/wayland/handlers/subcompositor.rs create mode 100644 winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs create mode 100644 winit/src/platform_specific/wayland/handlers/wp_viewporter.rs create mode 100644 winit/src/platform_specific/wayland/keymap.rs create mode 100644 winit/src/platform_specific/wayland/mod.rs create mode 100755 winit/src/platform_specific/wayland/sctk_event.rs create mode 100644 winit/src/platform_specific/wayland/subsurface_widget.rs create mode 100644 winit/src/platform_specific/wayland/winit_window.rs diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 40e9235a63..6e81ee0720 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -16,15 +16,3 @@ jobs: run: cargo update - name: Audit vulnerabilities run: cargo audit - - # artifacts: - # runs-on: ubuntu-latest - # steps: - # - uses: hecrj/setup-rust-action@v2 - # - name: Install cargo-outdated - # run: cargo install cargo-outdated - # - uses: actions/checkout@master - # - name: Delete `web-sys` dependency from `integration` example - # run: sed -i '$d' examples/integration/Cargo.toml - # - name: Find outdated dependencies - # run: cargo outdated --workspace --exit-code 1 --ignore raw-window-handle diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 16ee8bf9b9..36d43c0a3b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,11 +7,14 @@ jobs: - uses: hecrj/setup-rust-action@v2 with: components: clippy + - uses: actions/checkout@master - name: Install dependencies run: | export DEBIAN_FRONTED=noninteractive sudo apt-get -qq update - sudo apt-get install -y libxkbcommon-dev libgtk-3-dev + sudo apt-get install -y libxkbcommon-dev libgtk-3-dev libwayland-dev - name: Check lints - run: cargo lint + run: | + cargo clippy --no-default-features --features "winit" --all-targets + cargo clippy --no-default-features --features "wayland wgpu svg canvas qr_code lazy debug tokio palette web-colors a11y" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea941509b1..2bff7b6ab1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,8 +19,38 @@ jobs: run: | export DEBIAN_FRONTED=noninteractive sudo apt-get -qq update - sudo apt-get install -y libxkbcommon-dev libgtk-3-dev + sudo apt-get install -y libxkbcommon-dev libwayland-dev - name: Run tests run: | - cargo test --verbose --workspace - cargo test --verbose --workspace --all-features + cargo test --verbose --features "winit wgpu svg canvas qr_code lazy debug tokio palette web-colors a11y" + cargo test -p iced_accessibility + cargo test -p iced_core + cargo test -p iced_futures + cargo test -p iced_graphics + cargo test -p iced_renderer + cargo test -p iced_runtime + cargo test -p iced_tiny_skia + cargo test -p iced_widget + cargo test -p iced_wgpu + - name: test wayland + if: matrix.os == 'ubuntu-latest' + run: | + cargo test --verbose --features "wayland wgpu svg canvas qr_code lazy debug tokio palette web-colors a11y" + cargo test -p iced_sctk + + web: + runs-on: ubuntu-latest + steps: + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: stable + targets: wasm32-unknown-unknown + - uses: actions/checkout@master + - name: Run checks + run: cargo check --package iced --target wasm32-unknown-unknown --no-default-features --features "winit" + - name: Check compilation of `tour` example + run: cargo build --package tour --target wasm32-unknown-unknown + - name: Check compilation of `todos` example + run: cargo build --package todos --target wasm32-unknown-unknown + - name: Check compilation of `integration` example + run: cargo build --package integration --target wasm32-unknown-unknown diff --git a/CHANGELOG.md b/CHANGELOG.md index fff7d3417f..83fbc378e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -227,6 +227,8 @@ Many thanks to... - @wiiznokes - @woelfman - @Zaubentrucker +- @ryanabx +- @edfloreshz ## [0.12.1] - 2024-02-22 ### Added @@ -413,6 +415,10 @@ Many thanks to... - @william-shere - @wyatt-herkamp +Many thanks to... +- @jackpot51 +- @wash2 + ## [0.10.0] - 2023-07-28 ### Added - Text shaping, font fallback, and `iced_wgpu` overhaul. [#1697](https://github.com/iced-rs/iced/pull/1697) diff --git a/Cargo.toml b/Cargo.toml index 2db66da2e1..6e2a413a4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ all-features = true maintenance = { status = "actively-developed" } [features] -default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"] +default = ["tiny-skia"] # Enable the `wgpu` GPU-accelerated renderer backend wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] # Enable the `tiny-skia` software renderer backend @@ -42,11 +42,11 @@ markdown = ["iced_widget/markdown"] # Enables lazy widgets lazy = ["iced_widget/lazy"] # Enables a debug view in native platforms (press F12) -debug = ["iced_winit/debug"] +debug = ["iced_winit?/debug"] # Enables `tokio` as the `executor::Default` on native platforms -tokio = ["iced_futures/tokio"] +tokio = ["iced_futures/tokio", "iced_accessibility?/tokio"] # Enables `async-std` as the `executor::Default` on native platforms -async-std = ["iced_futures/async-std"] +async-std = ["iced_futures/async-std", "iced_accessibility?/async-io"] # Enables `smol` as the `executor::Default` on native platforms smol = ["iced_futures/smol"] # Enables querying system information @@ -58,16 +58,29 @@ webgl = ["iced_renderer/webgl"] # Enables the syntax `highlighter` module highlighter = ["iced_highlighter", "iced_widget/highlighter"] # Enables experimental multi-window support. -multi-window = ["iced_winit/multi-window"] +multi-window = ["iced_winit?/multi-window"] # Enables the advanced module advanced = ["iced_core/advanced", "iced_widget/advanced"] # Enables embedding Fira Sans as the default font on Wasm builds fira-sans = ["iced_renderer/fira-sans"] + + # Enables auto-detecting light/dark mode for the built-in theme auto-detect-theme = ["iced_core/auto-detect-theme"] -# Enables strict assertions for debugging purposes at the expense of performance strict-assertions = ["iced_renderer/strict-assertions"] +# Enables the `accesskit` accessibility library +a11y = [ + "iced_accessibility", + "iced_core/a11y", + "iced_widget/a11y", + "iced_winit?/a11y", +] +# Enables the winit shell. Conflicts with `wayland` and `glutin`. +winit = ["iced_winit", "iced_accessibility?/accesskit_winit"] + +# Enables the sctk shell. +wayland = ["iced_widget/wayland", "iced_core/wayland", "iced_winit/wayland"] [dependencies] iced_core.workspace = true iced_futures.workspace = true @@ -75,11 +88,15 @@ iced_renderer.workspace = true iced_widget.workspace = true iced_winit.features = ["program"] iced_winit.workspace = true - +iced_winit.optional = true iced_highlighter.workspace = true iced_highlighter.optional = true - +iced_accessibility.workspace = true +iced_accessibility.optional = true thiserror.workspace = true +window_clipboard.workspace = true +mime.workspace = true +dnd.workspace = true image.workspace = true image.optional = true @@ -116,7 +133,9 @@ members = [ "widget", "winit", "examples/*", + "accessibility", ] +exclude = ["examples/integration"] [workspace.package] version = "0.14.0-dev" @@ -141,16 +160,27 @@ iced_tiny_skia = { version = "0.14.0-dev", path = "tiny_skia" } iced_wgpu = { version = "0.14.0-dev", path = "wgpu" } iced_widget = { version = "0.14.0-dev", path = "widget" } iced_winit = { version = "0.14.0-dev", path = "winit" } +iced_accessibility = { version = "0.1", path = "accessibility" } async-std = "1.0" -bitflags = "2.0" +# bitflags = "2.0" +bitflags = "2.5" bytemuck = { version = "1.0", features = ["derive"] } bytes = "1.6" -cosmic-text = "0.12" + +cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git" } +# cosmic-text = "0.12" + + dark-light = "1.0" futures = "0.3" glam = "0.25" -glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "09712a70df7431e9a3b1ac1bbd4fb634096cb3b4" } +glyphon = { git = "https://github.com/pop-os/glyphon.git", branch="iced-0.13" } +# glyphon = { path = "../glyphon" } +resvg = "0.42" + + +web-sys = "0.3.69" guillotiere = "0.6" half = "2.2" image = { version = "0.24", default-features = false } @@ -166,12 +196,12 @@ palette = "0.7" pulldown-cmark = "0.11" qrcode = { version = "0.13", default-features = false } raw-window-handle = "0.6" -resvg = "0.42" rustc-hash = "2.0" +sctk = { package = "smithay-client-toolkit", version = "0.19.1" } smol = "1.0" smol_str = "0.2" -softbuffer = "0.4" -syntect = "5.1" +softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } +syntect = "5.2" sysinfo = "0.30" thiserror = "1.0" tiny-skia = "0.11" @@ -181,18 +211,30 @@ unicode-segmentation = "1.0" url = "2.5" wasm-bindgen-futures = "0.4" wasm-timer = "0.2" -web-sys = "0.3.69" web-time = "1.1" wgpu = "23.0" +wayland-protocols = { version = "0.32.1", features = ["staging"] } +# web-time = "1.1" + winapi = "0.3" -window_clipboard = "0.4.1" -winit = { git = "https://github.com/iced-rs/winit.git", rev = "254d6b3420ce4e674f516f7a2bd440665e05484d" } +# window_clipboard = "0.4.1" + +window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-0.13" } +dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-0.13" } +mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-0.13" } +winit = { git = "https://github.com/pop-os/winit.git", tag = "iced-xdg-surface-0.13" } +# winit = { path = "../../winit" } +# winit = { git = "https://github.com/iced-rs/winit.git", rev = "254d6b3420ce4e674f516f7a2bd440665e05484d" } +# winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" } +# winit = { path = "../../../winit" } + [workspace.lints.rust] -rust_2018_idioms = { level = "forbid", priority = -1 } -missing_debug_implementations = "deny" -missing_docs = "deny" -unsafe_code = "deny" +# rust_2018_idioms = { level = "forbid", priority = -1 } +# missing_debug_implementations = "deny" +# missing_docs = "deny" +# unsafe_code = "deny" +# TODO(POP): We have some unsafe code that needs to be fixed unused_results = "deny" [workspace.lints.clippy] @@ -212,3 +254,7 @@ useless_conversion = "deny" [workspace.lints.rustdoc] broken_intra_doc_links = "forbid" + +# [patch."https://github.com/rust-windowing/winit.git"] +# winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" } +# winit = { path = "../../../winit" } diff --git a/accessibility/Cargo.toml b/accessibility/Cargo.toml new file mode 100644 index 0000000000..78af375be6 --- /dev/null +++ b/accessibility/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "iced_accessibility" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +# TODO Ashley re-export more platform adapters +[features] +async-io = ["accesskit_winit?/async-io"] +tokio = ["accesskit_winit?/tokio"] + +[dependencies] +accesskit = { git = "https://github.com/wash2/accesskit", tag = "iced-xdg-surface-0.13" } +accesskit_windows = { git = "https://github.com/wash2/accesskit", tag = "iced-xdg-surface-0.13", optional = true } +accesskit_macos = { git = "https://github.com/wash2/accesskit", tag = "iced-xdg-surface-0.13", optional = true } +accesskit_winit = { git = "https://github.com/wash2/accesskit", tag = "iced-xdg-surface-0.13", optional = true, default-features = false, features = [ + "rwh_06", +] } +# accesskit = { path = "../../../../accesskit/common" } +# accesskit_windows = { path = "../../../../accesskit/platforms/windows", optional = true } +# accesskit_macos = { path = "../../../../accesskit/platforms/macos", optional = true } +# accesskit_winit = { path = "../../../../accesskit/platforms/winit", optional = true, default-features = false, features = [ +# "rwh_06", +# ] } diff --git a/accessibility/src/a11y_tree.rs b/accessibility/src/a11y_tree.rs new file mode 100644 index 0000000000..bb61981571 --- /dev/null +++ b/accessibility/src/a11y_tree.rs @@ -0,0 +1,80 @@ +use crate::{A11yId, A11yNode, IdEq}; + +#[derive(Debug, Clone, Default)] +/// Accessible tree of nodes +pub struct A11yTree { + /// The root of the current widget, children of the parent widget or the Window if there is no parent widget + root: Vec, + /// The children of a widget and its children + children: Vec, +} + +impl A11yTree { + /// Create a new A11yTree + /// XXX if you use this method, you will need to manually add the children of the root nodes + pub fn new(root: Vec, children: Vec) -> Self { + Self { root, children } + } + + pub fn leaf>(node: accesskit::NodeBuilder, id: T) -> Self { + Self { + root: vec![A11yNode::new(node, id)], + children: vec![], + } + } + + /// Helper for creating an A11y tree with a single root node and some children + pub fn node_with_child_tree(mut root: A11yNode, child_tree: Self) -> Self { + root.add_children( + child_tree.root.iter().map(|n| n.id()).cloned().collect(), + ); + Self { + root: vec![root], + children: child_tree + .children + .into_iter() + .chain(child_tree.root) + .collect(), + } + } + + /// Joins multiple trees into a single tree + pub fn join>(trees: T) -> Self { + trees.fold(Self::default(), |mut acc, A11yTree { root, children }| { + acc.root.extend(root); + acc.children.extend(children); + acc + }) + } + + pub fn root(&self) -> &Vec { + &self.root + } + + pub fn children(&self) -> &Vec { + &self.children + } + + pub fn root_mut(&mut self) -> &mut Vec { + &mut self.root + } + + pub fn children_mut(&mut self) -> &mut Vec { + &mut self.children + } + + pub fn contains(&self, id: &A11yId) -> bool { + self.root.iter().any(|n| IdEq::eq(n.id(), id)) + || self.children.iter().any(|n| IdEq::eq(n.id(), id)) + } +} + +impl From for Vec<(accesskit::NodeId, accesskit::Node)> { + fn from(tree: A11yTree) -> Vec<(accesskit::NodeId, accesskit::Node)> { + tree.root + .into_iter() + .map(|node| node.into()) + .chain(tree.children.into_iter().map(|node| node.into())) + .collect() + } +} diff --git a/accessibility/src/id.rs b/accessibility/src/id.rs new file mode 100644 index 0000000000..752e51c192 --- /dev/null +++ b/accessibility/src/id.rs @@ -0,0 +1,215 @@ +//! Widget and Window IDs. + +use std::borrow; +use std::hash::Hash; +use std::sync::atomic::{self, AtomicU64}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum A11yId { + Window(u64), + Widget(Id), +} + +// impl A11yId { +// pub fn new_widget() -> Self { +// Self::Widget(Id::unique()) +// } + +// pub fn new_window() -> Self { +// Self::Window(window_node_id()) +// } +// } + +impl From for A11yId { + fn from(id: u64) -> Self { + Self::Window(id) + } +} + +impl From for A11yId { + fn from(id: Id) -> Self { + assert!(!matches!(id.0, Internal::Set(_))); + Self::Widget(id) + } +} + +impl IdEq for A11yId { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (A11yId::Widget(self_), A11yId::Widget(other)) => { + IdEq::eq(self_, other) + } + _ => self == other, + } + } +} + +impl From for A11yId { + fn from(value: accesskit::NodeId) -> Self { + let val = u64::from(value.0); + if val > u32::MAX as u64 { + Self::Window(value.0) + } else { + Self::Widget(Id::from(val as u64)) + } + } +} + +impl From for accesskit::NodeId { + fn from(value: A11yId) -> Self { + let node_id = match value { + A11yId::Window(id) => id, + A11yId::Widget(id) => id.into(), + }; + accesskit::NodeId(node_id) + } +} + +static NEXT_ID: AtomicU64 = AtomicU64::new(1); +static NEXT_WINDOW_ID: AtomicU64 = AtomicU64::new(1); + +/// The identifier of a generic widget. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(pub Internal); + +impl Id { + /// Creates a custom [`Id`]. + pub fn new(id: impl Into>) -> Self { + Self(Internal::Custom(Self::next(), id.into())) + } + + /// resets the id counter + pub fn reset() { + NEXT_ID.store(1, atomic::Ordering::Relaxed); + } + + fn next() -> u64 { + NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed) + } + + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. + pub fn unique() -> Self { + let id = Self::next(); + + Self(Internal::Unique(id)) + } +} + +impl IdEq for Id { + fn eq(&self, other: &Self) -> bool { + IdEq::eq(&self.0, &other.0) + } +} +// Not meant to be used directly +impl From for Id { + fn from(value: u64) -> Self { + Self(Internal::Unique(value)) + } +} + +// Not meant to be used directly +impl From for u64 { + fn from(val: Id) -> u64 { + match &val.0 { + Internal::Unique(id) => *id, + Internal::Custom(id, _) => *id, + // this is a set id, which is not a valid id and will not ever be converted to a NonZeroU128 + // so we panic + Internal::Set(_) => { + panic!("Cannot convert a set id to a NonZeroU128") + } + } + } +} + +impl ToString for Id { + fn to_string(&self) -> String { + match &self.0 { + Internal::Unique(_) => "Undefined".to_string(), + Internal::Custom(_, id) => id.to_string(), + Internal::Set(_) => "Set".to_string(), + } + } +} + +// XXX WIndow IDs are made unique by adding u32::MAX to them +/// get window node id that won't conflict with other node ids for the duration of the program +pub fn window_node_id() -> u64 { + u32::MAX as u64 + + NEXT_WINDOW_ID.fetch_add(1, atomic::Ordering::Relaxed) as u64 +} + +// TODO refactor to make panic impossible? +#[derive(Debug, Clone, Eq)] +/// Internal representation of an [`Id`]. +pub enum Internal { + /// a unique id + Unique(u64), + /// a custom id, which is equal to any [`Id`] with a matching number or string + Custom(u64, borrow::Cow<'static, str>), + /// XXX Do not use this as an id for an accessibility node, it will panic! + /// XXX Only meant to be used for widgets that have multiple accessibility nodes, each with a + /// unique or custom id + /// an Id Set, which is equal to any [`Id`] with a matching number or string + Set(Vec), +} + +impl PartialEq for Internal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Unique(l0), Self::Unique(r0)) => l0 == r0, + (Self::Custom(_, l1), Self::Custom(_, r1)) => l1 == r1, + (Self::Set(l0), Self::Set(r0)) => l0 == r0, + _ => false, + } + } +} + +/// Similar to PartialEq, but only intended for use when comparing Ids +pub trait IdEq { + fn eq(&self, other: &Self) -> bool; +} + +impl IdEq for Internal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Unique(l0), Self::Unique(r0)) => l0 == r0, + (Self::Custom(l0, l1), Self::Custom(r0, r1)) => { + l0 == r0 || l1 == r1 + } + // allow custom ids to be equal to unique ids + (Self::Unique(l0), Self::Custom(r0, _)) + | (Self::Custom(l0, _), Self::Unique(r0)) => l0 == r0, + (Self::Set(l0), Self::Set(r0)) => l0 == r0, + // allow set ids to just be equal to any of their members + (Self::Set(l0), r) | (r, Self::Set(l0)) => { + l0.iter().any(|l| l == r) + } + } + } +} + +impl Hash for Internal { + fn hash(&self, state: &mut H) { + match self { + Self::Unique(id) => id.hash(state), + Self::Custom(name, _) => name.hash(state), + Self::Set(ids) => ids.hash(state), + } + } +} + +#[cfg(test)] +mod tests { + use super::Id; + + #[test] + fn unique_generates_different_ids() { + let a = Id::unique(); + let b = Id::unique(); + + assert_ne!(a, b); + } +} diff --git a/accessibility/src/lib.rs b/accessibility/src/lib.rs new file mode 100644 index 0000000000..273a4737c6 --- /dev/null +++ b/accessibility/src/lib.rs @@ -0,0 +1,17 @@ +mod a11y_tree; +pub mod id; +mod node; +mod traits; + +pub use a11y_tree::*; +pub use accesskit; +pub use id::*; +pub use node::*; +pub use traits::*; + +#[cfg(feature = "accesskit_macos")] +pub use accesskit_macos; +#[cfg(feature = "accesskit_windows")] +pub use accesskit_windows; +#[cfg(feature = "accesskit_winit")] +pub use accesskit_winit; diff --git a/accessibility/src/node.rs b/accessibility/src/node.rs new file mode 100644 index 0000000000..bfe89c20f0 --- /dev/null +++ b/accessibility/src/node.rs @@ -0,0 +1,41 @@ +use crate::A11yId; + +#[derive(Debug, Clone, PartialEq)] +pub struct A11yNode { + node: accesskit::NodeBuilder, + id: A11yId, +} + +impl A11yNode { + pub fn new>(node: accesskit::NodeBuilder, id: T) -> Self { + Self { + node, + id: id.into(), + } + } + + pub fn id(&self) -> &A11yId { + &self.id + } + + pub fn node_mut(&mut self) -> &mut accesskit::NodeBuilder { + &mut self.node + } + + pub fn node(&self) -> &accesskit::NodeBuilder { + &self.node + } + + pub fn add_children(&mut self, children: Vec) { + let mut children = + children.into_iter().map(|id| id.into()).collect::>(); + children.extend_from_slice(self.node.children()); + self.node.set_children(children); + } +} + +impl From for (accesskit::NodeId, accesskit::Node) { + fn from(node: A11yNode) -> Self { + (node.id.into(), node.node.build()) + } +} diff --git a/accessibility/src/traits.rs b/accessibility/src/traits.rs new file mode 100644 index 0000000000..88e1d9208e --- /dev/null +++ b/accessibility/src/traits.rs @@ -0,0 +1,19 @@ +use std::borrow::Cow; + +use crate::A11yId; + +#[derive(Debug, Clone, PartialEq)] +pub enum Description<'a> { + Text(Cow<'a, str>), + Id(Vec), +} + +// Describes a widget +pub trait Describes { + fn description(&self) -> Vec; +} + +// Labels a widget +pub trait Labels { + fn label(&self) -> Vec; +} diff --git a/core/Cargo.toml b/core/Cargo.toml index a12289099c..ce5b4d1dd2 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -16,6 +16,8 @@ workspace = true [features] auto-detect-theme = ["dep:dark-light"] advanced = [] +a11y = ["iced_accessibility"] +wayland = ["sctk"] [dependencies] bitflags.workspace = true @@ -30,8 +32,31 @@ smol_str.workspace = true thiserror.workspace = true web-time.workspace = true +# TODO(POP): I think some of these dependencies were removed. Check on that +# xxhash-rust.workspace = true +window_clipboard.workspace = true +dnd.workspace = true +mime.workspace = true + +sctk.workspace = true +sctk.optional = true +# /TODO(POP) + dark-light.workspace = true dark-light.optional = true +[dependencies.serde] +version = "1" +optional = true +features = ["serde_derive"] + + +[target.'cfg(windows)'.dependencies] +raw-window-handle.workspace = true + [dev-dependencies] approx = "0.5" +[dependencies.iced_accessibility] +version = "0.1.0" +path = "../accessibility" +optional = true diff --git a/core/src/border.rs b/core/src/border.rs index da0aaa2808..8d9c6a21be 100644 --- a/core/src/border.rs +++ b/core/src/border.rs @@ -263,3 +263,54 @@ impl From for [f32; 4] { ] } } + +impl From<[f32; 4]> for Radius { + /// [ + /// radi.top_left, + /// radi.top_right, + /// radi.bottom_right, + /// radi.bottom_left, + /// ] + fn from(value: [f32; 4]) -> Self { + Self { + top_left: value[0], + top_right: value[1], + bottom_right: value[2], + bottom_left: value[3], + } + } +} + +impl From<[u8; 4]> for Radius { + /// [ + /// radi.top_left, + /// radi.top_right, + /// radi.bottom_right, + /// radi.bottom_left, + /// ] + fn from(value: [u8; 4]) -> Self { + Self { + top_left: f32::from(value[0]), + top_right: f32::from(value[1]), + bottom_right: f32::from(value[2]), + bottom_left: f32::from(value[3]), + } + } +} + +impl From<[u16; 4]> for Radius { + /// [ + /// radi.top_left, + /// radi.top_right, + /// radi.bottom_right, + /// radi.bottom_left, + /// ] + fn from(value: [u16; 4]) -> Self { + Self { + top_left: f32::from(value[0]), + top_right: f32::from(value[1]), + bottom_right: f32::from(value[2]), + bottom_left: f32::from(value[3]), + } + } +} diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs index 5df3e26758..3e7458c024 100644 --- a/core/src/clipboard.rs +++ b/core/src/clipboard.rs @@ -1,5 +1,12 @@ //! Access the clipboard. +use std::{any::Any, sync::Arc}; + +use dnd::{DndAction, DndDestinationRectangle, DndSurface}; +use mime::{self, AllowedMimeTypes, AsMimeTypes, ClipboardStoreData}; + +use crate::{widget::tree::State, window, Element}; + /// A buffer for short-term storage and transfer within and between /// applications. pub trait Clipboard { @@ -8,6 +15,62 @@ pub trait Clipboard { /// Writes the given text contents to the [`Clipboard`]. fn write(&mut self, kind: Kind, contents: String); + + /// Consider using [`read_data`] instead + /// Reads the current content of the [`Clipboard`] as text. + fn read_data( + &self, + kind: Kind, + _mimes: Vec, + ) -> Option<(Vec, String)> { + None + } + + /// Writes the given contents to the [`Clipboard`]. + fn write_data( + &mut self, + kind: Kind, + _contents: ClipboardStoreData< + Box, + >, + ) { + } + + /// Starts a DnD operation. + fn register_dnd_destination( + &self, + _surface: DndSurface, + _rectangles: Vec, + ) { + } + + /// Set the final action for the DnD operation. + /// Only should be done if it is requested. + fn set_action(&self, _action: DndAction) {} + + /// Registers Dnd destinations + fn start_dnd( + &mut self, + _internal: bool, + _source_surface: Option, + _icon_surface: Option>, + _content: Box, + _actions: DndAction, + ) { + } + + /// Ends a DnD operation. + fn end_dnd(&self) {} + + /// Consider using [`peek_dnd`] instead + /// Peeks the data on the DnD with a specific mime type. + /// Will return an error if there is no ongoing DnD operation. + fn peek_dnd(&self, _mime: String) -> Option<(Vec, String)> { + None + } + + /// Request window size + fn request_logical_window_size(&self, width: f32, height: f32) {} } /// The kind of [`Clipboard`]. @@ -21,6 +84,28 @@ pub enum Kind { Primary, } +/// Starts a DnD operation. +/// icon surface is a tuple of the icon element and optionally the icon element state. +pub fn start_dnd( + clipboard: &mut dyn Clipboard, + internal: bool, + source_surface: Option, + icon_surface: Option<(Element<'static, M, T, R>, State)>, + content: Box, + actions: DndAction, +) { + clipboard.start_dnd( + internal, + source_surface, + icon_surface.map(|i| { + let i: Box = Box::new(Arc::new(i)); + i + }), + content, + actions, + ); +} + /// A null implementation of the [`Clipboard`] trait. #[derive(Debug, Clone, Copy)] pub struct Null; @@ -32,3 +117,90 @@ impl Clipboard for Null { fn write(&mut self, _kind: Kind, _contents: String) {} } + +/// Reads the current content of the [`Clipboard`]. +pub fn read_data( + clipboard: &mut dyn Clipboard, +) -> Option { + clipboard + .read_data(Kind::Standard, T::allowed().into()) + .and_then(|data| T::try_from(data).ok()) +} + +/// Reads the current content of the primary [`Clipboard`]. +pub fn read_primary_data( + clipboard: &mut dyn Clipboard, +) -> Option { + clipboard + .read_data(Kind::Primary, T::allowed().into()) + .and_then(|data| T::try_from(data).ok()) +} + +/// Reads the current content of the primary [`Clipboard`]. +pub fn peek_dnd( + clipboard: &mut dyn Clipboard, + mime: Option, +) -> Option { + let Some(mime) = mime.or_else(|| T::allowed().first().cloned().into()) + else { + return None; + }; + clipboard + .peek_dnd(mime) + .and_then(|data| T::try_from(data).ok()) +} + +/// Source of a DnD operation. +#[derive(Debug, Clone)] +pub enum DndSource { + /// A widget is the source of the DnD operation. + Widget(crate::id::Id), + /// A surface is the source of the DnD operation. + Surface(window::Id), +} + +/// A list of DnD destination rectangles. +#[derive(Debug, Clone)] +pub struct DndDestinationRectangles { + /// The rectangle of the DnD destination. + rectangles: Vec, +} + +impl DndDestinationRectangles { + /// Creates a new [`DndDestinationRectangles`]. + pub fn new() -> Self { + Self { + rectangles: Vec::new(), + } + } + + /// Creates a new [`DndDestinationRectangles`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self { + rectangles: Vec::with_capacity(capacity), + } + } + + /// Pushes a new rectangle to the list of DnD destination rectangles. + pub fn push(&mut self, rectangle: DndDestinationRectangle) { + self.rectangles.push(rectangle); + } + + /// Appends the list of DnD destination rectangles to the current list. + pub fn append(&mut self, other: &mut Vec) { + self.rectangles.append(other); + } + + /// Returns the list of DnD destination rectangles. + /// This consumes the [`DndDestinationRectangles`]. + pub fn into_rectangles(mut self) -> Vec { + self.rectangles.reverse(); + self.rectangles + } +} + +impl AsRef<[DndDestinationRectangle]> for DndDestinationRectangles { + fn as_ref(&self) -> &[DndDestinationRectangle] { + &self.rectangles + } +} diff --git a/core/src/element.rs b/core/src/element.rs index 6ebb8a1576..3cb0f17a81 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -1,4 +1,5 @@ use crate::event::{self, Event}; +use crate::id::Id; use crate::layout; use crate::mouse; use crate::overlay; @@ -10,7 +11,7 @@ use crate::{ Widget, }; -use std::borrow::Borrow; +use std::borrow::{Borrow, BorrowMut}; /// A generic [`Widget`]. /// @@ -239,6 +240,37 @@ impl<'a, Message, Theme, Renderer> } } +impl<'a, Message, Theme, Renderer> + Borrow + 'a> + for &mut Element<'a, Message, Theme, Renderer> +{ + fn borrow(&self) -> &(dyn Widget + 'a) { + self.widget.borrow() + } +} + +impl<'a, Message, Theme, Renderer> + BorrowMut + 'a> + for &mut Element<'a, Message, Theme, Renderer> +{ + fn borrow_mut( + &mut self, + ) -> &mut (dyn Widget + 'a) { + self.widget.borrow_mut() + } +} + +impl<'a, Message, Theme, Renderer> + BorrowMut + 'a> + for Element<'a, Message, Theme, Renderer> +{ + fn borrow_mut( + &mut self, + ) -> &mut (dyn Widget + 'a) { + self.widget.borrow_mut() + } +} + struct Map<'a, A, B, Theme, Renderer> { widget: Box + 'a>, mapper: Box B + 'a>, @@ -278,8 +310,8 @@ where self.widget.children() } - fn diff(&self, tree: &mut Tree) { - self.widget.diff(tree); + fn diff(&mut self, tree: &mut Tree) { + self.widget.diff(tree) } fn size(&self) -> Size { @@ -378,6 +410,35 @@ where .overlay(tree, layout, renderer, translation) .map(move |overlay| overlay.map(mapper)) } + + #[cfg(feature = "a11y")] + fn a11y_nodes( + &self, + _layout: Layout<'_>, + _state: &Tree, + _cursor_position: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + self.widget.a11y_nodes(_layout, _state, _cursor_position) + } + + fn id(&self) -> Option { + self.widget.id() + } + + fn set_id(&mut self, id: Id) { + self.widget.set_id(id); + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut crate::clipboard::DndDestinationRectangles, + ) { + self.widget + .drag_destinations(state, layout, renderer, dnd_rectangles); + } } struct Explain<'a, Message, Theme, Renderer: crate::Renderer> { @@ -422,7 +483,7 @@ where self.element.widget.children() } - fn diff(&self, tree: &mut Tree) { + fn diff(&mut self, tree: &mut Tree) { self.element.widget.diff(tree); } @@ -527,4 +588,28 @@ where .widget .overlay(state, layout, renderer, translation) } + + fn id(&self) -> Option { + self.element.widget.id() + } + + fn set_id(&mut self, id: Id) { + self.element.widget.set_id(id); + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut crate::clipboard::DndDestinationRectangles, + ) { + self.element.widget.drag_destinations( + state, + layout, + renderer, + dnd_rectangles, + ); + } + // TODO maybe a11y_nodes } diff --git a/core/src/event.rs b/core/src/event.rs index b6cf321ec3..791a790fc1 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,9 +1,14 @@ //! Handle events of a user interface. +use dnd::DndEvent; +use dnd::DndSurface; + use crate::keyboard; use crate::mouse; use crate::touch; use crate::window; - +#[cfg(feature = "wayland")] +/// A platform specific event for wayland +pub mod wayland; /// A user interface event. /// /// _**Note:** This type is largely incomplete! If you need to track @@ -23,6 +28,27 @@ pub enum Event { /// A touch event Touch(touch::Event), + + #[cfg(feature = "a11y")] + /// An Accesskit event for a specific Accesskit Node in an accessible widget + A11y( + crate::widget::Id, + iced_accessibility::accesskit::ActionRequest, + ), + + /// A DnD event. + Dnd(DndEvent), + + /// Platform specific events + PlatformSpecific(PlatformSpecific), +} + +/// A platform specific event +#[derive(Debug, Clone, PartialEq)] +pub enum PlatformSpecific { + #[cfg(feature = "wayland")] + /// A Wayland specific event + Wayland(wayland::Event), } /// The status of an [`Event`] after being processed. diff --git a/core/src/event/wayland/layer.rs b/core/src/event/wayland/layer.rs new file mode 100644 index 0000000000..c1928ad36e --- /dev/null +++ b/core/src/event/wayland/layer.rs @@ -0,0 +1,10 @@ +/// layer surface events +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LayerEvent { + /// layer surface Done + Done, + /// layer surface focused + Focused, + /// layer_surface unfocused + Unfocused, +} diff --git a/core/src/event/wayland/mod.rs b/core/src/event/wayland/mod.rs new file mode 100644 index 0000000000..0e908d6708 --- /dev/null +++ b/core/src/event/wayland/mod.rs @@ -0,0 +1,39 @@ +mod layer; +mod output; +mod popup; +mod seat; +mod session_lock; +mod window; + +use crate::{time::Instant, window::Id}; +use sctk::reexports::client::protocol::{ + wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface, +}; + +pub use layer::*; +pub use output::*; +pub use popup::*; +pub use seat::*; +pub use session_lock::*; +pub use window::*; + +/// wayland events +#[derive(Debug, Clone, PartialEq)] +pub enum Event { + /// layer surface event + Layer(LayerEvent, WlSurface, Id), + /// popup event + Popup(PopupEvent, WlSurface, Id), + /// output event + Output(OutputEvent, WlOutput), + /// window event + Window(WindowEvent), + /// Seat Event + Seat(SeatEvent, WlSeat), + /// Session lock events + SessionLock(SessionLockEvent), + /// Frame events + Frame(Instant, WlSurface, Id), + /// Request Resize + RequestResize, +} diff --git a/core/src/event/wayland/output.rs b/core/src/event/wayland/output.rs new file mode 100644 index 0000000000..c5024e85b7 --- /dev/null +++ b/core/src/event/wayland/output.rs @@ -0,0 +1,34 @@ +use sctk::output::OutputInfo; + +/// output events +#[derive(Debug, Clone)] +pub enum OutputEvent { + /// created output + Created(Option), + /// removed output + Removed, + /// Output Info + InfoUpdate(OutputInfo), +} + +impl Eq for OutputEvent {} + +impl PartialEq for OutputEvent { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Created(l0), Self::Created(r0)) => { + if let Some((l0, r0)) = l0.as_ref().zip(r0.as_ref()) { + l0.id == r0.id && l0.make == r0.make && l0.model == r0.model + } else { + l0.is_none() && r0.is_none() + } + } + (Self::InfoUpdate(l0), Self::InfoUpdate(r0)) => { + l0.id == r0.id && l0.make == r0.make && l0.model == r0.model + } + _ => { + core::mem::discriminant(self) == core::mem::discriminant(other) + } + } + } +} diff --git a/core/src/event/wayland/popup.rs b/core/src/event/wayland/popup.rs new file mode 100644 index 0000000000..ff925870b2 --- /dev/null +++ b/core/src/event/wayland/popup.rs @@ -0,0 +1,21 @@ +/// popup events +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PopupEvent { + /// Done + Done, + /// repositioned, + Configured { + /// x position + x: i32, + /// y position + y: i32, + /// width + width: u32, + /// height + height: u32, + }, + /// popup focused + Focused, + /// popup unfocused + Unfocused, +} diff --git a/core/src/event/wayland/seat.rs b/core/src/event/wayland/seat.rs new file mode 100644 index 0000000000..3da4374e71 --- /dev/null +++ b/core/src/event/wayland/seat.rs @@ -0,0 +1,9 @@ +/// seat events +/// Only one seat can interact with an iced_sctk application at a time, but many may interact with the application over the lifetime of the application +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SeatEvent { + /// A new seat is interacting with the application + Enter, + /// A seat is not interacting with the application anymore + Leave, +} diff --git a/core/src/event/wayland/session_lock.rs b/core/src/event/wayland/session_lock.rs new file mode 100644 index 0000000000..db99566d95 --- /dev/null +++ b/core/src/event/wayland/session_lock.rs @@ -0,0 +1,19 @@ +use crate::window::Id; +use sctk::reexports::client::protocol::wl_surface::WlSurface; + +/// session lock events +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SessionLockEvent { + /// Compositor has activated lock + Locked, + /// Lock rejected / canceled by compositor + Finished, + /// Session lock protocol not supported + NotSupported, + /// Session lock surface focused + Focused(WlSurface, Id), + /// Session lock surface unfocused + Unfocused(WlSurface, Id), + /// Session unlock has been processed by server + Unlocked, +} diff --git a/core/src/event/wayland/window.rs b/core/src/event/wayland/window.rs new file mode 100644 index 0000000000..70085dac5b --- /dev/null +++ b/core/src/event/wayland/window.rs @@ -0,0 +1,8 @@ +#![allow(missing_docs)] + +/// window events +#[derive(Debug, PartialEq, Clone)] +pub enum WindowEvent { + /// Window suggested bounds. + SuggestedBounds(Option), +} diff --git a/core/src/id.rs b/core/src/id.rs new file mode 100644 index 0000000000..3fc74ce925 --- /dev/null +++ b/core/src/id.rs @@ -0,0 +1,155 @@ +//! Widget and Window IDs. + +use std::borrow; +use std::num::NonZeroU128; +use std::sync::atomic::{self, AtomicU64}; + +static NEXT_ID: AtomicU64 = AtomicU64::new(1); +static NEXT_WINDOW_ID: AtomicU64 = AtomicU64::new(1); + +/// The identifier of a generic widget. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(pub Internal); + +impl Id { + /// Creates a custom [`Id`]. + pub fn new(id: impl Into>) -> Self { + Self(Internal::Custom(Self::next(), id.into())) + } + + /// resets the id counter + pub fn reset() { + NEXT_ID.store(1, atomic::Ordering::Relaxed); + } + + fn next() -> u64 { + NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed) + } + + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. + pub fn unique() -> Self { + let id = Self::next(); + + Self(Internal::Unique(id)) + } +} + +// Not meant to be used directly +impl From for Id { + fn from(value: u64) -> Self { + Self(Internal::Unique(value)) + } +} + +// Not meant to be used directly +impl From for NonZeroU128 { + fn from(id: Id) -> NonZeroU128 { + match &id.0 { + Internal::Unique(id) => NonZeroU128::try_from(*id as u128).unwrap(), + Internal::Custom(id, _) => { + NonZeroU128::try_from(*id as u128).unwrap() + } + // this is a set id, which is not a valid id and will not ever be converted to a NonZeroU128 + // so we panic + Internal::Set(_) => { + panic!("Cannot convert a set id to a NonZeroU128") + } + } + } +} + +impl ToString for Id { + fn to_string(&self) -> String { + match &self.0 { + Internal::Unique(_) => "Undefined".to_string(), + Internal::Custom(_, id) => id.to_string(), + Internal::Set(_) => "Set".to_string(), + } + } +} + +// XXX WIndow IDs are made unique by adding u64::MAX to them +/// get window node id that won't conflict with other node ids for the duration of the program +pub fn window_node_id() -> NonZeroU128 { + std::num::NonZeroU128::try_from( + u64::MAX as u128 + + NEXT_WINDOW_ID.fetch_add(1, atomic::Ordering::Relaxed) as u128, + ) + .unwrap() +} + +// TODO refactor to make panic impossible? +#[derive(Debug, Clone, Eq)] +/// Internal representation of an [`Id`]. +pub enum Internal { + /// a unique id + Unique(u64), + /// a custom id, which is equal to any [`Id`] with a matching number or string + Custom(u64, borrow::Cow<'static, str>), + /// XXX Do not use this as an id for an accessibility node, it will panic! + /// XXX Only meant to be used for widgets that have multiple accessibility nodes, each with a + /// unique or custom id + /// an Id Set, which is equal to any [`Id`] with a matching number or string + Set(Vec), +} + +impl PartialEq for Internal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Unique(l0), Self::Unique(r0)) => l0 == r0, + (Self::Custom(_, l1), Self::Custom(_, r1)) => l1 == r1, + (Self::Set(l0), Self::Set(r0)) => l0 == r0, + _ => false, + } + } +} + +/// Similar to PartialEq, but only intended for use when comparing Ids +pub trait IdEq { + /// Compare two Ids for equality based on their number or name + fn eq(&self, other: &Self) -> bool; +} + +impl IdEq for Internal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Unique(l0), Self::Unique(r0)) => l0 == r0, + (Self::Custom(l0, l1), Self::Custom(r0, r1)) => { + l0 == r0 || l1 == r1 + } + // allow custom ids to be equal to unique ids + (Self::Unique(l0), Self::Custom(r0, _)) + | (Self::Custom(l0, _), Self::Unique(r0)) => l0 == r0, + (Self::Set(l0), Self::Set(r0)) => l0 == r0, + // allow set ids to just be equal to any of their members + (Self::Set(l0), r) | (r, Self::Set(l0)) => { + l0.iter().any(|l| l == r) + } + } + } +} + +impl std::hash::Hash for Internal { + fn hash(&self, state: &mut H) { + match self { + Self::Unique(id) => id.hash(state), + Self::Custom(name, _) => name.hash(state), + Self::Set(ids) => ids.hash(state), + } + } +} + +#[cfg(test)] +mod tests { + use super::Id; + + #[test] + fn unique_generates_different_ids() { + let a = Id::unique(); + let b = Id::unique(); + + assert_ne!(a, b); + } +} diff --git a/core/src/image.rs b/core/src/image.rs index f985636a39..0dd1d60efc 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -29,6 +29,9 @@ pub struct Image { /// This can avoid graphical glitches, specially when using /// [`FilterMethod::Nearest`]. pub snap: bool, + + /// The border radii of the image + pub border_radius: [f32; 4], } impl Image { @@ -40,6 +43,7 @@ impl Image { rotation: Radians(0.0), opacity: 1.0, snap: false, + border_radius: [0.0; 4], } } @@ -75,7 +79,7 @@ impl From<&Handle> for Image { } /// A handle of some image data. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Hash)] pub enum Handle { /// A file handle. The image data will be read /// from the file path. @@ -239,6 +243,15 @@ pub trait Renderer: crate::Renderer { /// Returns the dimensions of an image for the given [`Handle`]. fn measure_image(&self, handle: &Self::Handle) -> Size; - /// Draws an [`Image`] inside the provided `bounds`. - fn draw_image(&mut self, image: Image, bounds: Rectangle); + /// Draws an image with the given [`Handle`] and inside the provided + /// `bounds`. + fn draw_image( + &mut self, + handle: Self::Handle, + filter_method: FilterMethod, + bounds: Rectangle, + rotation: Radians, + opacity: f32, + border_radius: [f32; 4], + ); } diff --git a/core/src/keyboard/key.rs b/core/src/keyboard/key.rs index 69a91902fc..00f6fd42ae 100644 --- a/core/src/keyboard/key.rs +++ b/core/src/keyboard/key.rs @@ -7,6 +7,7 @@ use crate::SmolStr; /// /// [`winit`]: https://docs.rs/winit/0.29.10/winit/keyboard/enum.Key.html #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Key { /// A key with an established name. Named(Named), @@ -38,6 +39,7 @@ impl Key { /// /// [`winit`]: https://docs.rs/winit/0.29.10/winit/keyboard/enum.Key.html #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[allow(missing_docs)] pub enum Named { /// The `Alt` (Alternative) key. diff --git a/core/src/lib.rs b/core/src/lib.rs index df599f45df..12c1e42a15 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -35,6 +35,9 @@ mod background; mod color; mod content_fit; mod element; +mod hasher; +#[cfg(not(feature = "a11y"))] +pub mod id; mod length; mod pixels; mod point; @@ -57,6 +60,9 @@ pub use element::Element; pub use event::Event; pub use font::Font; pub use gradient::Gradient; +pub use hasher::Hasher; +#[cfg(feature = "a11y")] +pub use iced_accessibility::id; pub use image::Image; pub use layout::Layout; pub use length::Length; diff --git a/core/src/mouse/click.rs b/core/src/mouse/click.rs index 0a373878c0..944347d883 100644 --- a/core/src/mouse/click.rs +++ b/core/src/mouse/click.rs @@ -81,7 +81,6 @@ impl Click { } else { None }; - self.position.distance(new_position) < 6.0 && duration .map(|duration| duration.as_millis() <= 300) diff --git a/core/src/mouse/interaction.rs b/core/src/mouse/interaction.rs index aad6a3ea93..36cf293a1d 100644 --- a/core/src/mouse/interaction.rs +++ b/core/src/mouse/interaction.rs @@ -22,4 +22,5 @@ pub enum Interaction { Move, Copy, Help, + Hide, } diff --git a/core/src/overlay.rs b/core/src/overlay.rs index f09de8312b..1cb98f8982 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -9,7 +9,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; -use crate::widget; +use crate::widget::Operation; use crate::widget::Tree; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; @@ -36,12 +36,12 @@ where cursor: mouse::Cursor, ); - /// Applies a [`widget::Operation`] to the [`Overlay`]. + /// Applies an [`Operation`] to the [`Overlay`]. fn operate( &mut self, _layout: Layout<'_>, _renderer: &Renderer, - _operation: &mut dyn widget::Operation, + _operation: &mut dyn crate::widget::Operation, ) { } diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index 6541d311a0..d31f2234a6 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -4,6 +4,7 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; +use crate::widget::Operation; use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size}; /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] diff --git a/core/src/padding.rs b/core/src/padding.rs index e26cdd9bde..97bc56565c 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -175,6 +175,17 @@ impl From<[u16; 2]> for Padding { } } +impl From<[u16; 4]> for Padding { + fn from(p: [u16; 4]) -> Self { + Padding { + top: f32::from(p[0]), + right: f32::from(p[1]), + bottom: f32::from(p[2]), + left: f32::from(p[3]), + } + } +} + impl From for Padding { fn from(p: f32) -> Self { Padding { @@ -197,6 +208,18 @@ impl From<[f32; 2]> for Padding { } } +impl From<[f32; 4]> for Padding { + /// [top, rght, bottom, left] + fn from(p: [f32; 4]) -> Self { + Padding { + top: p[0], + right: p[1], + bottom: p[2], + left: p[3], + } + } +} + impl From for Size { fn from(padding: Padding) -> Self { Self::new(padding.horizontal(), padding.vertical()) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index cff33991c0..a6058185bb 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -143,6 +143,16 @@ impl Rectangle { && point.y < self.y + self.height } + /// Returns true if the given [`Point`] is contained in the [`Rectangle`]. + /// The [`Point`] must be strictly contained, i.e. it must not be on the + /// border. + pub fn contains_strict(&self, point: Point) -> bool { + self.x < point.x + && point.x < self.x + self.width + && self.y < point.y + && point.y < self.y + self.height + } + /// Returns true if the current [`Rectangle`] is completely within the given /// `container`. pub fn is_within(&self, container: &Rectangle) -> bool { @@ -152,6 +162,16 @@ impl Rectangle { ) } + /// Returns true if the current [`Rectangle`] is completely within the given + /// `container`. The [`Rectangle`] must be strictly contained, i.e. it must + /// not be on the border. + pub fn is_within_strict(&self, container: &Rectangle) -> bool { + container.contains_strict(self.position()) + && container.contains_strict( + self.position() + Vector::new(self.width, self.height), + ) + } + /// Computes the intersection with the given [`Rectangle`]. pub fn intersection( &self, diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 6684517f0f..e617a6c25c 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -89,14 +89,20 @@ impl Default for Quad { /// The styling attributes of a [`Renderer`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { + /// The color to apply to symbolic icons. + pub icon_color: Color, /// The text color pub text_color: Color, + /// The scale factor + pub scale_factor: f64, } impl Default for Style { fn default() -> Self { Style { + icon_color: Color::BLACK, text_color: Color::BLACK, + scale_factor: 1.0, } } } diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index bbcdd8ffd3..36d73e2b54 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -30,6 +30,7 @@ impl text::Renderer for () { type Font = Font; type Paragraph = (); type Editor = (); + type Raw = (); const ICON_FONT: Font = Font::DEFAULT; const CHECKMARK_ICON: char = '0'; @@ -40,7 +41,7 @@ impl text::Renderer for () { } fn default_size(&self) -> Pixels { - Pixels(16.0) + Pixels(14.0) } fn fill_paragraph( @@ -61,6 +62,8 @@ impl text::Renderer for () { ) { } + fn fill_raw(&mut self, _raw: Self::Raw) {} + fn fill_text( &mut self, _paragraph: Text, @@ -184,7 +187,16 @@ impl image::Renderer for () { Size::default() } - fn draw_image(&mut self, _image: Image, _bounds: Rectangle) {} + fn draw_image( + &mut self, + _handle: Self::Handle, + _filter_method: image::FilterMethod, + _bounds: Rectangle, + _rotation: crate::Radians, + _opacity: f32, + _border_radius: [f32; 4], + ) { + } } impl svg::Renderer for () { diff --git a/core/src/svg.rs b/core/src/svg.rs index ac19b22337..fddaf90cdd 100644 --- a/core/src/svg.rs +++ b/core/src/svg.rs @@ -29,6 +29,9 @@ pub struct Svg { /// /// 0 means transparent. 1 means opaque. pub opacity: f32, + + /// The border radius for the svg + pub border_radius: [f32; 4], } impl Svg { @@ -39,6 +42,7 @@ impl Svg { color: None, rotation: Radians(0.0), opacity: 1.0, + border_radius: [0.0; 4], } } @@ -59,6 +63,12 @@ impl Svg { self.opacity = opacity.into(); self } + + /// Sets the border radius of the [`Svg`] + pub fn border_radius(mut self, border_radius: impl Into<[f32; 4]>) -> Self { + self.border_radius = border_radius.into(); + self + } } impl From<&Handle> for Svg { diff --git a/core/src/text.rs b/core/src/text.rs index a9e3dce5db..a9a80e9f88 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -57,8 +57,6 @@ pub enum Shaping { /// You should use this strategy when you have complete control of the text /// and the font you are displaying in your application. /// - /// This is the default. - #[default] Basic, /// Advanced text shaping and font fallback. /// @@ -67,6 +65,8 @@ pub enum Shaping { /// may be needed to display all of the glyphs. /// /// Advanced shaping is expensive! You should only enable it when necessary. + /// This is the default. + #[default] Advanced, } @@ -108,7 +108,7 @@ impl LineHeight { impl Default for LineHeight { fn default() -> Self { - Self::Relative(1.3) + Self::Relative(1.4) } } @@ -193,6 +193,9 @@ pub trait Renderer: crate::Renderer { /// The [`Editor`] of this [`Renderer`]. type Editor: Editor + 'static; + /// The Raw of this [`Renderer`]. + type Raw; + /// The icon font of the backend. const ICON_FONT: Self::Font; @@ -232,6 +235,9 @@ pub trait Renderer: crate::Renderer { clip_bounds: Rectangle, ); + /// Draws the given Raw + fn fill_raw(&mut self, raw: Self::Raw); + /// Draws the given [`Text`] at the given position and with the given /// [`Color`]. fn fill_text( diff --git a/core/src/theme/palette.rs b/core/src/theme/palette.rs index e0ff397ab0..1e5e748f5f 100644 --- a/core/src/theme/palette.rs +++ b/core/src/theme/palette.rs @@ -420,12 +420,15 @@ impl Extended { } } -/// A pair of background and text colors. +/// Recommended background, icon, and text [`Color`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Pair { /// The background color. pub color: Color, + /// The icon color, which defaults to the text color. + pub icon: Color, + /// The text color. /// /// It's guaranteed to be readable on top of the background [`color`]. @@ -437,9 +440,12 @@ pub struct Pair { impl Pair { /// Creates a new [`Pair`] from a background [`Color`] and some text [`Color`]. pub fn new(color: Color, text: Color) -> Self { + let text = readable(color, text); + Self { color, - text: readable(color, text), + icon: text, + text, } } } diff --git a/core/src/widget.rs b/core/src/widget.rs index 9cfff83d46..d758f1c362 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -3,9 +3,7 @@ pub mod operation; pub mod text; pub mod tree; -mod id; - -pub use id::Id; +pub use crate::id::Id; pub use operation::Operation; pub use text::Text; pub use tree::Tree; @@ -96,8 +94,8 @@ where Vec::new() } - /// Reconciles the [`Widget`] with the provided [`Tree`]. - fn diff(&self, _tree: &mut Tree) {} + /// Reconciliates the [`Widget`] with the provided [`Tree`]. + fn diff(&mut self, _tree: &mut Tree) {} /// Applies an [`Operation`] to the [`Widget`]. fn operate( @@ -150,4 +148,35 @@ where ) -> Option> { None } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget and its children + fn a11y_nodes( + &self, + _layout: Layout<'_>, + _state: &Tree, + _cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + iced_accessibility::A11yTree::default() + } + + /// Returns the id of the widget + fn id(&self) -> Option { + None + } + + /// Sets the id of the widget + /// This may be called while diffing the widget tree + fn set_id(&mut self, _id: Id) {} + + /// Adds the drag destination rectangles of the widget. + /// Runs after the layout phase for each widget in the tree. + fn drag_destinations( + &self, + _state: &Tree, + _layout: Layout<'_>, + _renderer: &Renderer, + _dnd_rectangles: &mut crate::clipboard::DndDestinationRectangles, + ) { + } } diff --git a/core/src/widget/id.rs b/core/src/widget/id.rs deleted file mode 100644 index ae739bb73d..0000000000 --- a/core/src/widget/id.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::borrow; -use std::sync::atomic::{self, AtomicUsize}; - -static NEXT_ID: AtomicUsize = AtomicUsize::new(0); - -/// The identifier of a generic widget. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(Internal); - -impl Id { - /// Creates a custom [`Id`]. - pub fn new(id: impl Into>) -> Self { - Self(Internal::Custom(id.into())) - } - - /// Creates a unique [`Id`]. - /// - /// This function produces a different [`Id`] every time it is called. - pub fn unique() -> Self { - let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed); - - Self(Internal::Unique(id)) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum Internal { - Unique(usize), - Custom(borrow::Cow<'static, str>), -} - -#[cfg(test)] -mod tests { - use super::Id; - - #[test] - fn unique_generates_different_ids() { - let a = Id::unique(); - let b = Id::unique(); - - assert_ne!(a, b); - } -} diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index 097c360133..a912eb1bf7 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -1,6 +1,7 @@ //! Query or update internal widget state. pub mod focusable; pub mod scrollable; +pub mod search_id; pub mod text_input; pub use focusable::Focusable; @@ -46,7 +47,7 @@ pub trait Operation: Send { /// 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. + /// Operates on a custom widget. fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {} /// Finishes the [`Operation`] and returns its [`Outcome`]. @@ -412,7 +413,7 @@ where /// Produces an [`Operation`] that applies the given [`Operation`] to the /// children of a container with the given [`Id`]. -pub fn scope( +pub fn scoped( target: Id, operation: impl Operation + 'static, ) -> impl Operation { diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index 867c682e52..6b5be19d53 100644 --- a/core/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs @@ -1,4 +1,5 @@ //! Operate on widgets that can be focused. +use crate::id::IdEq; use crate::widget::operation::{self, Operation, Outcome}; use crate::widget::Id; use crate::Rectangle; @@ -34,7 +35,7 @@ pub fn focus(target: Id) -> impl Operation { impl Operation for Focus { fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { match id { - Some(id) if id == &self.target => { + Some(id) if IdEq::eq(&id.0, &self.target.0) => { state.focus(); } _ => { diff --git a/core/src/widget/operation/search_id.rs b/core/src/widget/operation/search_id.rs new file mode 100644 index 0000000000..b6e330f779 --- /dev/null +++ b/core/src/widget/operation/search_id.rs @@ -0,0 +1,42 @@ +//! Search for widgets with the target Id. + +use super::Operation; +use crate::{id::Id, widget::operation::Outcome, Rectangle}; + +/// Produces an [`Operation`] that searches for the Id +pub fn search_id(target: Id) -> impl Operation { + struct Find { + found: bool, + target: Id, + } + + impl Operation for Find { + fn custom(&mut self, _state: &mut dyn std::any::Any, id: Option<&Id>) { + if Some(&self.target) == id { + self.found = true; + } + } + + fn container( + &mut self, + _id: Option<&Id>, + _bounds: Rectangle, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self); + } + + fn finish(&self) -> Outcome { + if self.found { + Outcome::Some(self.target.clone()) + } else { + Outcome::None + } + } + } + + Find { + found: false, + target, + } +} diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index b34c5632d9..d95095ba5d 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -24,14 +24,16 @@ use crate::alignment; use crate::layout; use crate::mouse; use crate::renderer; -use crate::text; use crate::text::paragraph::{self, Paragraph}; +use crate::text::{self, Fragment}; use crate::widget::tree::{self, Tree}; use crate::{ Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme, Widget, }; +use std::borrow::Cow; + pub use text::{LineHeight, Shaping, Wrapping}; /// A bunch of text. @@ -62,7 +64,8 @@ where Theme: Catalog, Renderer: text::Renderer, { - fragment: text::Fragment<'a>, + fragment: Fragment<'a>, + id: crate::widget::Id, size: Option, line_height: LineHeight, width: Length, @@ -84,6 +87,7 @@ where pub fn new(fragment: impl text::IntoFragment<'a>) -> Self { Text { fragment: fragment.into_fragment(), + id: crate::widget::Id::unique(), size: None, line_height: LineHeight::default(), font: None, @@ -267,6 +271,50 @@ where draw(renderer, defaults, layout, state.0.raw(), style, viewport); } + + #[cfg(feature = "a11y")] + fn a11y_nodes( + &self, + layout: Layout<'_>, + _state: &Tree, + _: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::{ + accesskit::{Live, NodeBuilder, Rect, Role}, + A11yTree, + }; + + let Rectangle { + x, + y, + width, + height, + } = layout.bounds(); + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, + ); + + let mut node = NodeBuilder::new(Role::Paragraph); + + // TODO is the name likely different from the content? + node.set_name(self.fragment.to_string().into_boxed_str()); + node.set_bounds(bounds); + + // TODO make this configurable + node.set_live(Live::Polite); + A11yTree::leaf(node, self.id.clone()) + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: crate::widget::Id) { + self.id = id + } } /// Produces the [`layout::Node`] of a [`Text`] widget. @@ -367,6 +415,29 @@ where } } +// impl<'a, Theme, Renderer> Clone for Text<'a, Theme, Renderer> +// where +// Renderer: text::Renderer, +// { +// fn clone(&self) -> Self { +// Self { +// id: self.id.clone(), +// content: self.content.clone(), +// size: self.size, +// line_height: self.line_height, +// width: self.width, +// height: self.height, +// horizontal_alignment: self.horizontal_alignment, +// vertical_alignment: self.vertical_alignment, +// font: self.font, +// style: self.style, +// shaping: self.shaping, +// wrap: self.wrap, +// } +// } +// } +// TODO(POP): Clone no longer can be implemented because of style being a Box(style) + impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer> where Theme: Catalog + 'a, diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index 2600cfc6dd..af8ba86361 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -1,9 +1,16 @@ //! Store internal widget state in a state tree to ensure continuity. +use crate::id::{Id, Internal}; use crate::Widget; - use std::any::{self, Any}; -use std::borrow::Borrow; -use std::fmt; +use std::borrow::{Borrow, BorrowMut, Cow}; +use std::collections::HashMap; +use std::hash::Hash; +use std::{fmt, mem}; + +thread_local! { + /// A map of named widget states. +pub static NAMED: std::cell::RefCell, (State, Vec<(usize, Tree)>)>> = std::cell::RefCell::new(HashMap::new()); +} /// A persistent state widget tree. /// @@ -13,6 +20,9 @@ pub struct Tree { /// The tag of the [`Tree`]. pub tag: Tag, + /// the Id of the [`Tree`] + pub id: Option, + /// The [`State`] of the [`Tree`]. pub state: State, @@ -24,6 +34,7 @@ impl Tree { /// Creates an empty, stateless [`Tree`] with no children. pub fn empty() -> Self { Self { + id: None, tag: Tag::stateless(), state: State::None, children: Vec::new(), @@ -40,13 +51,104 @@ impl Tree { let widget = widget.borrow(); Self { + id: widget.id(), tag: widget.tag(), state: widget.state(), children: widget.children(), } } - /// Reconciles the current tree with the provided [`Widget`]. + /// Takes all named widgets from the tree. + pub fn take_all_named( + &mut self, + ) -> HashMap, (State, Vec<(usize, Tree)>)> { + let mut named = HashMap::new(); + struct Visit { + parent: Cow<'static, str>, + index: usize, + visited: bool, + } + // tree traversal to find all named widgets + // and keep their state and children + let mut stack = vec![(self, None)]; + while let Some((tree, visit)) = stack.pop() { + if let Some(Id(Internal::Custom(_, n))) = tree.id.clone() { + let state = mem::replace(&mut tree.state, State::None); + let children_count = tree.children.len(); + let children = + tree.children.iter_mut().enumerate().rev().map(|(i, c)| { + if matches!(c.id, Some(Id(Internal::Custom(_, _)))) { + (c, None) + } else { + ( + c, + Some(Visit { + index: i, + parent: n.clone(), + visited: false, + }), + ) + } + }); + _ = named.insert( + n.clone(), + (state, Vec::with_capacity(children_count)), + ); + stack.extend(children); + } else if let Some(visit) = visit { + if visit.visited { + named.get_mut(&visit.parent).unwrap().1.push(( + visit.index, + mem::replace( + tree, + Tree { + id: tree.id.clone(), + tag: tree.tag, + ..Tree::empty() + }, + ), + )); + } else { + let ptr = tree as *mut Tree; + + stack.push(( + // TODO remove this unsafe block + #[allow(unsafe_code)] + // SAFETY: when the reference is finally accessed, all the children references will have been processed first. + unsafe { + ptr.as_mut().unwrap() + }, + Some(Visit { + visited: true, + ..visit + }), + )); + stack.extend(tree.children.iter_mut().map(|c| (c, None))); + } + } else { + stack.extend(tree.children.iter_mut().map(|s| (s, None))); + } + } + + named + } + + /// Finds a widget state in the tree by its id. + pub fn find<'a>(&'a self, id: &Id) -> Option<&'a Tree> { + if self.id == Some(id.clone()) { + return Some(self); + } + + for child in self.children.iter() { + if let Some(tree) = child.find(id) { + return Some(tree); + } + } + + None + } + + /// Reconciliates the current tree with the provided [`Widget`]. /// /// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the /// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called). @@ -56,53 +158,203 @@ impl Tree { /// [`Widget::diff`]: crate::Widget::diff pub fn diff<'a, Message, Theme, Renderer>( &mut self, - new: impl Borrow + 'a>, + mut new: impl BorrowMut + 'a>, ) where Renderer: crate::Renderer, { - if self.tag == new.borrow().tag() { - new.borrow().diff(self); + let borrowed: &mut dyn Widget = + new.borrow_mut(); + let mut needs_reset = false; + let tag_match = self.tag == borrowed.tag(); + if let Some(Id(Internal::Custom(_, n))) = borrowed.id() { + if let Some((mut state, children)) = NAMED + .with(|named| named.borrow_mut().remove(&n)) + .or_else(|| { + //check self.id + if let Some(Id(Internal::Custom(_, ref name))) = self.id { + if name == &n { + Some(( + mem::replace(&mut self.state, State::None), + self.children + .iter_mut() + .map(|s| { + // take the data + mem::replace( + s, + Tree { + id: s.id.clone(), + tag: s.tag, + ..Tree::empty() + }, + ) + }) + .enumerate() + .collect(), + )) + } else { + None + } + } else { + None + } + }) + { + std::mem::swap(&mut self.state, &mut state); + let widget_children = borrowed.children(); + if !tag_match || self.children.len() != widget_children.len() { + self.children = borrowed.children(); + } else { + for (old_i, mut old) in children { + let Some(my_state) = self.children.get_mut(old_i) + else { + continue; + }; + if my_state.tag != old.tag || { + !match (&old.id, &my_state.id) { + ( + Some(Id(Internal::Custom(_, ref old_name))), + Some(Id(Internal::Custom(_, ref my_name))), + ) => old_name == my_name, + ( + Some(Id(Internal::Set(a))), + Some(Id(Internal::Set(b))), + ) => a.len() == b.len(), + ( + Some(Id(Internal::Unique(_))), + Some(Id(Internal::Unique(_))), + ) => true, + (None, None) => true, + _ => false, + } + } { + continue; + } + + mem::swap(my_state, &mut old); + } + } + } else { + needs_reset = true; + } + } else if tag_match { + if let Some(id) = self.id.clone() { + borrowed.set_id(id); + } + if self.children.len() != borrowed.children().len() { + self.children = borrowed.children(); + } + } else { + needs_reset = true; + } + if needs_reset { + *self = Self::new(borrowed); + let borrowed = new.borrow_mut(); + borrowed.diff(self); } else { - *self = Self::new(new); + borrowed.diff(self); } } /// Reconciles the children of the tree with the provided list of widgets. pub fn diff_children<'a, Message, Theme, Renderer>( &mut self, - new_children: &[impl Borrow + 'a>], + new_children: &mut [impl BorrowMut< + dyn Widget + 'a, + >], ) where Renderer: crate::Renderer, { self.diff_children_custom( new_children, - |tree, widget| tree.diff(widget.borrow()), - |widget| Self::new(widget.borrow()), - ); + new_children.iter().map(|c| c.borrow().id()).collect(), + |tree, widget| { + let borrowed: &mut dyn Widget<_, _, _> = widget.borrow_mut(); + tree.diff(borrowed) + }, + |widget| { + let borrowed: &dyn Widget<_, _, _> = widget.borrow(); + Self::new(borrowed) + }, + ) } /// Reconciles the children of the tree with the provided list of widgets using custom /// logic both for diffing and creating new widget state. pub fn diff_children_custom( &mut self, - new_children: &[T], - diff: impl Fn(&mut Tree, &T), + new_children: &mut [T], + new_ids: Vec>, + diff: impl Fn(&mut Tree, &mut T), new_state: impl Fn(&T) -> Self, ) { if self.children.len() > new_children.len() { self.children.truncate(new_children.len()); } - for (child_state, new) in - self.children.iter_mut().zip(new_children.iter()) + let len_changed = self.children.len() != new_children.len(); + + let children_len = self.children.len(); + let (mut id_map, mut id_list): ( + HashMap, + Vec<&mut Tree>, + ) = self.children.iter_mut().fold( + (HashMap::new(), Vec::with_capacity(children_len)), + |(mut id_map, mut id_list), c| { + if let Some(id) = c.id.as_ref() { + if let Internal::Custom(_, ref name) = id.0 { + let _ = id_map.insert(name.to_string(), c); + } else { + id_list.push(c); + } + } else { + id_list.push(c); + } + (id_map, id_list) + }, + ); + + let mut child_state_i = 0; + let mut new_trees: Vec<(Tree, usize)> = + Vec::with_capacity(new_children.len()); + for (i, (new, new_id)) in + new_children.iter_mut().zip(new_ids.iter()).enumerate() { + let child_state = if let Some(c) = new_id.as_ref().and_then(|id| { + if let Internal::Custom(_, ref name) = id.0 { + id_map.remove(name.as_ref()) + } else { + None + } + }) { + c + } else if child_state_i < id_list.len() + && !matches!( + id_list[child_state_i].id, + Some(Id(Internal::Custom(_, _))) + ) + { + let c = &mut id_list[child_state_i]; + if len_changed { + c.id.clone_from(new_id); + } + child_state_i += 1; + c + } else { + let mut my_new_state = new_state(new); + diff(&mut my_new_state, new); + new_trees.push((my_new_state, i)); + continue; + }; + diff(child_state, new); } - if self.children.len() < new_children.len() { - self.children.extend( - new_children[self.children.len()..].iter().map(new_state), - ); + for (new_tree, i) in new_trees { + if self.children.len() > i { + self.children[i] = new_tree; + } else { + self.children.push(new_tree); + } } } } @@ -114,8 +366,8 @@ impl Tree { /// `maybe_changed` closure. pub fn diff_children_custom_with_search( current_children: &mut Vec, - new_children: &[T], - diff: impl Fn(&mut Tree, &T), + new_children: &mut [T], + diff: impl Fn(&mut Tree, &mut T), maybe_changed: impl Fn(usize) -> bool, new_state: impl Fn(&T) -> Tree, ) { @@ -183,7 +435,7 @@ pub fn diff_children_custom_with_search( // TODO: Merge loop with extend logic (?) for (child_state, new) in - current_children.iter_mut().zip(new_children.iter()) + current_children.iter_mut().zip(new_children.iter_mut()) { diff(child_state, new); } diff --git a/core/src/window/id.rs b/core/src/window/id.rs index 5d5a817e48..c006a08259 100644 --- a/core/src/window/id.rs +++ b/core/src/window/id.rs @@ -9,9 +9,17 @@ pub struct Id(u64); static COUNT: AtomicU64 = AtomicU64::new(1); impl Id { + /// No window will match this Id + pub const NONE: Id = Id(0); + /// Creates a new unique window [`Id`]. pub fn unique() -> Id { - Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed)) + let id = Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed)); + if id.0 == 0 { + Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed)) + } else { + id + } } } diff --git a/core/src/window/settings.rs b/core/src/window/settings.rs index fbbf86abd8..c5a16d743a 100644 --- a/core/src/window/settings.rs +++ b/core/src/window/settings.rs @@ -34,6 +34,9 @@ pub struct Settings { /// The initial logical dimensions of the window. pub size: Size, + /// The border area for the drag resize handle. + pub resize_border: u32, + /// The initial position of the window. pub position: Position, @@ -76,9 +79,10 @@ pub struct Settings { } impl Default for Settings { - fn default() -> Self { - Self { + fn default() -> Settings { + Settings { size: Size::new(1024.0, 768.0), + resize_border: 8, position: Position::default(), min_size: None, max_size: None, diff --git a/examples/editor/Cargo.toml b/examples/editor/Cargo.toml index dc885728ca..683bb55c5b 100644 --- a/examples/editor/Cargo.toml +++ b/examples/editor/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["highlighter", "tokio", "debug"] +iced.features = ["highlighter", "tokio", "debug", "winit", "tiny-skia"] tokio.workspace = true tokio.features = ["fs"] diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index 7596844cd6..c328029458 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug", "canvas", "tokio"] +iced.features = ["debug", "canvas", "tokio", "winit", "tiny-skia"] itertools = "0.12" rustc-hash.workspace = true diff --git a/examples/gradient/Cargo.toml b/examples/gradient/Cargo.toml index 8102b8665f..9f9347cc23 100644 --- a/examples/gradient/Cargo.toml +++ b/examples/gradient/Cargo.toml @@ -6,6 +6,6 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug"] +iced.features = ["debug", "winit", "wgpu"] tracing-subscriber = "0.3" diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index 7f8feb3f52..9d3cd0864b 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -22,4 +22,4 @@ iced_wgpu.features = ["webgl"] console_error_panic_hook = "0.1" console_log = "1.0" wasm-bindgen = "0.2" -web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] } +web-sys = { version = "=0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] } diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 0b11a323d4..72293ff95c 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,3 +1,4 @@ +use iced_wgpu::core::window::Id; use iced_wgpu::Renderer; use iced_widget::{column, container, row, slider, text, text_input}; use iced_winit::core::{Color, Element, Length::*, Theme}; diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 87a5b22b46..4a1e80e5f5 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -4,6 +4,7 @@ mod scene; use controls::Controls; use scene::Scene; +use iced_wgpu::core::window::Id; use iced_wgpu::graphics::Viewport; use iced_wgpu::{wgpu, Engine, Renderer}; use iced_winit::conversion; @@ -34,7 +35,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { enum Runner { Loading, Ready { - window: Arc, + window: Arc, device: wgpu::Device, queue: wgpu::Queue, surface: wgpu::Surface<'static>, @@ -53,7 +54,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { } impl winit::application::ApplicationHandler for Runner { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + fn resumed(&mut self, event_loop: &dyn winit::event_loop::ActiveEventLoop) { if let Self::Loading = self { let window = Arc::new( event_loop @@ -187,7 +188,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { fn window_event( &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, + event_loop: &dyn winit::event_loop::ActiveEventLoop, _window_id: winit::window::WindowId, event: WindowEvent, ) { diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml index 4ccb958422..779e659ed9 100644 --- a/examples/lazy/Cargo.toml +++ b/examples/lazy/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug", "lazy"] +iced.features = ["debug", "lazy", "async-std", "winit", "tiny-skia"] diff --git a/examples/loading_spinners/Cargo.toml b/examples/loading_spinners/Cargo.toml index a32da3864c..0eaacfdb7b 100644 --- a/examples/loading_spinners/Cargo.toml +++ b/examples/loading_spinners/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["advanced", "canvas"] +iced.features = ["advanced", "canvas", "winit"] lyon_algorithms = "1.0" once_cell.workspace = true \ No newline at end of file diff --git a/examples/multi_window/Cargo.toml b/examples/multi_window/Cargo.toml index 2e222dfbb1..abe762c9b5 100644 --- a/examples/multi_window/Cargo.toml +++ b/examples/multi_window/Cargo.toml @@ -6,4 +6,11 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug", "multi-window"] } +iced = { path = "../..", default-features = false, features = [ + "a11y", + "tokio", + "debug", + "winit", + "multi-window", + "tiny-skia", +] } diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index b43a627a0d..33fadea9db 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -3,7 +3,11 @@ use iced::widget::{ text_input, }; use iced::window; -use iced::{Center, Element, Fill, Subscription, Task, Theme, Vector}; +use iced::Length::Fill; +use iced::{ + id::Id, Alignment, Center, Element, Length, Point, Settings, Subscription, + Task, Theme, Vector, +}; use std::collections::BTreeMap; @@ -25,6 +29,7 @@ struct Window { scale_input: String, current_scale: f64, theme: Theme, + input_id: text_input::Id, } #[derive(Debug, Clone)] diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 095ecd1088..fff22c8df1 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug", "lazy"] +iced.features = ["debug", "lazy", "winit"] diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml index 1a6d54453e..21d2f8f85c 100644 --- a/examples/pokedex/Cargo.toml +++ b/examples/pokedex/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["image", "debug", "tokio"] +iced.features = ["image", "debug", "tokio", "winit", "tiny-skia"] serde_json = "1.0" diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 77b108bd51..479772afc0 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -14,4 +14,4 @@ image.features = ["png"] tokio.workspace = true -tracing-subscriber = "0.3" \ No newline at end of file +tracing-subscriber = "0.3" diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml index f8c735c014..ab3892eb68 100644 --- a/examples/scrollable/Cargo.toml +++ b/examples/scrollable/Cargo.toml @@ -7,6 +7,6 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug"] - +iced.features = ["debug", "winit", "tiny-skia"] +iced_core.workspace = true once_cell.workspace = true diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index de4f2f9ada..785ef624f2 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -1,12 +1,14 @@ +use iced::widget::scrollable::{self, Properties, Scrollbar, Scroller}; use iced::widget::{ button, column, container, horizontal_space, progress_bar, radio, row, scrollable, slider, text, vertical_space, }; use iced::{Border, Center, Color, Element, Fill, Task, Theme}; +use iced_core::id::Id; use once_cell::sync::Lazy; -static SCROLLABLE_ID: Lazy = Lazy::new(scrollable::Id::unique); +static SCROLLABLE_ID: Lazy = Lazy::new(|| Id::new("scrollable")); pub fn main() -> iced::Result { iced::application( diff --git a/examples/sctk_drag/Cargo.toml b/examples/sctk_drag/Cargo.toml new file mode 100644 index 0000000000..e118b277f2 --- /dev/null +++ b/examples/sctk_drag/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sctk_drag" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced_core = { path = "../../core" } +iced = { path = "../..", default-features = false, features = [ + "tiny-skia", + "tokio", + "winit", + "wayland", + "debug", +] } +env_logger = "0.10" +# sctk = { package = "smithay-client-toolkit", path = "../../../fork/client-toolkit/" } +sctk.workspace = true diff --git a/examples/sctk_drag/src/dnd_destination.rs b/examples/sctk_drag/src/dnd_destination.rs new file mode 100644 index 0000000000..8c4a624560 --- /dev/null +++ b/examples/sctk_drag/src/dnd_destination.rs @@ -0,0 +1,728 @@ +use std::{ + borrow::Cow, + sync::atomic::{AtomicU64, Ordering}, +}; + +use iced::{ + clipboard::{ + dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent}, + mime::AllowedMimeTypes, + }, + event, + id::Internal, + mouse, overlay, Event, Length, Rectangle, +}; +use iced::{id::Id, Element}; +use iced_core::{ + self, layout, + widget::{tree, Tree}, + Clipboard, Layout, Shell, Widget, +}; + +pub fn dnd_destination<'a, Message: 'static>( + child: impl Into>, + mimes: Vec>, +) -> DndDestination<'a, Message> { + DndDestination::new(child, mimes) +} + +pub fn dnd_destination_for_data( + child: impl Into>, + on_finish: impl Fn(Option, DndAction) -> Message + 'static, +) -> DndDestination<'static, Message> { + DndDestination::for_data(child, on_finish) +} + +static DRAG_ID_COUNTER: AtomicU64 = AtomicU64::new(0); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct DragId(pub u128); + +impl DragId { + pub fn new() -> Self { + DragId( + u128::from(u64::MAX) + + u128::from(DRAG_ID_COUNTER.fetch_add(1, Ordering::Relaxed)), + ) + } +} + +#[allow(clippy::new_without_default)] +impl Default for DragId { + fn default() -> Self { + DragId::new() + } +} + +pub struct DndDestination<'a, Message> { + id: Id, + drag_id: Option, + preferred_action: DndAction, + action: DndAction, + container: Element<'a, Message>, + mime_types: Vec>, + forward_drag_as_cursor: bool, + on_hold: Option Message>>, + on_drop: Option Message>>, + on_enter: Option) -> Message>>, + on_leave: Option Message>>, + on_motion: Option Message>>, + on_action_selected: Option Message>>, + on_data_received: Option) -> Message>>, + on_finish: + Option, DndAction, f64, f64) -> Message>>, +} + +impl<'a, Message: 'static> DndDestination<'a, Message> { + pub fn new( + child: impl Into>, + mimes: Vec>, + ) -> Self { + Self { + id: Id::unique(), + drag_id: None, + mime_types: mimes, + preferred_action: DndAction::Move, + action: DndAction::Copy | DndAction::Move, + container: child.into(), + forward_drag_as_cursor: false, + on_hold: None, + on_drop: None, + on_enter: None, + on_leave: None, + on_motion: None, + on_action_selected: None, + on_data_received: None, + on_finish: None, + } + } + + pub fn for_data( + child: impl Into>, + on_finish: impl Fn(Option, DndAction) -> Message + 'static, + ) -> Self { + Self { + id: Id::unique(), + drag_id: None, + mime_types: T::allowed().iter().cloned().map(Cow::Owned).collect(), + preferred_action: DndAction::Move, + action: DndAction::Copy | DndAction::Move, + container: child.into(), + forward_drag_as_cursor: false, + on_hold: None, + on_drop: None, + on_enter: None, + on_leave: None, + on_motion: None, + on_action_selected: None, + on_data_received: None, + on_finish: Some(Box::new(move |mime, data, action, _, _| { + on_finish(T::try_from((data, mime)).ok(), action) + })), + } + } + + #[must_use] + pub fn data_received_for( + mut self, + f: impl Fn(Option) -> Message + 'static, + ) -> Self { + self.on_data_received = + Some(Box::new( + move |mime, data| f(T::try_from((data, mime)).ok()), + )); + self + } + + pub fn with_id( + child: impl Into>, + id: Id, + mimes: Vec>, + ) -> Self { + Self { + id, + drag_id: None, + mime_types: mimes, + preferred_action: DndAction::Move, + action: DndAction::Copy | DndAction::Move, + container: child.into(), + forward_drag_as_cursor: false, + on_hold: None, + on_drop: None, + on_enter: None, + on_leave: None, + on_motion: None, + on_action_selected: None, + on_data_received: None, + on_finish: None, + } + } + + #[must_use] + pub fn drag_id(mut self, id: u64) -> Self { + self.drag_id = Some(id); + self + } + + #[must_use] + pub fn action(mut self, action: DndAction) -> Self { + self.action = action; + self + } + + #[must_use] + pub fn preferred_action(mut self, action: DndAction) -> Self { + self.preferred_action = action; + self + } + + #[must_use] + pub fn forward_drag_as_cursor(mut self, forward: bool) -> Self { + self.forward_drag_as_cursor = forward; + self + } + + #[must_use] + pub fn on_hold( + mut self, + f: impl Fn(f64, f64) -> Message + 'static, + ) -> Self { + self.on_hold = Some(Box::new(f)); + self + } + + #[must_use] + pub fn on_drop( + mut self, + f: impl Fn(f64, f64) -> Message + 'static, + ) -> Self { + self.on_drop = Some(Box::new(f)); + self + } + + #[must_use] + pub fn on_enter( + mut self, + f: impl Fn(f64, f64, Vec) -> Message + 'static, + ) -> Self { + self.on_enter = Some(Box::new(f)); + self + } + + #[must_use] + pub fn on_leave(mut self, m: impl Fn() -> Message + 'static) -> Self { + self.on_leave = Some(Box::new(m)); + self + } + + #[must_use] + pub fn on_finish( + mut self, + f: impl Fn(String, Vec, DndAction, f64, f64) -> Message + 'static, + ) -> Self { + self.on_finish = Some(Box::new(f)); + self + } + + #[must_use] + pub fn on_motion( + mut self, + f: impl Fn(f64, f64) -> Message + 'static, + ) -> Self { + self.on_motion = Some(Box::new(f)); + self + } + + #[must_use] + pub fn on_action_selected( + mut self, + f: impl Fn(DndAction) -> Message + 'static, + ) -> Self { + self.on_action_selected = Some(Box::new(f)); + self + } + + #[must_use] + pub fn on_data_received( + mut self, + f: impl Fn(String, Vec) -> Message + 'static, + ) -> Self { + self.on_data_received = Some(Box::new(f)); + self + } + + /// Returns the drag id of the destination. + /// + /// # Panics + /// Panics if the destination has been assigned a Set id, which is invalid. + #[must_use] + pub fn get_drag_id(&self) -> u128 { + u128::from(self.drag_id.unwrap_or_else(|| match &self.id.0 { + Internal::Unique(id) | Internal::Custom(id, _) => *id, + Internal::Set(_) => { + panic!("Invalid Id assigned to dnd destination.") + } + })) + } +} + +impl<'a, Message: 'static> Widget + for DndDestination<'a, Message> +{ + fn children(&self) -> Vec { + vec![Tree::new(&self.container)] + } + + fn tag(&self) -> iced_core::widget::tree::Tag { + tree::Tag::of::>() + } + + fn diff(&mut self, tree: &mut Tree) { + tree.children[0].diff(self.container.as_widget_mut()); + } + + fn state(&self) -> iced_core::widget::tree::State { + tree::State::new(State::<()>::new()) + } + + fn size(&self) -> iced_core::Size { + self.container.as_widget().size() + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &iced::Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.container.as_widget().layout( + &mut tree.children[0], + renderer, + limits, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: layout::Layout<'_>, + renderer: &iced::Renderer, + operation: &mut dyn iced_core::widget::Operation<()>, + ) { + self.container.as_widget().operate( + &mut tree.children[0], + layout, + renderer, + operation, + ); + } + + #[allow(clippy::too_many_lines)] + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: layout::Layout<'_>, + cursor: mouse::Cursor, + renderer: &iced::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let s = self.container.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ); + if matches!(s, event::Status::Captured) { + return event::Status::Captured; + } + + let state = tree.state.downcast_mut::>(); + + let my_id = self.get_drag_id(); + + match event { + Event::Dnd(DndEvent::Offer( + id, + OfferEvent::Enter { + x, y, mime_types, .. + }, + )) if id == Some(my_id) => { + if let Some(msg) = state.on_enter( + x, + y, + mime_types, + self.on_enter.as_ref().map(std::convert::AsRef::as_ref), + (), + ) { + shell.publish(msg); + } + if self.forward_drag_as_cursor { + #[allow(clippy::cast_possible_truncation)] + let drag_cursor = + mouse::Cursor::Available((x as f32, y as f32).into()); + let event = Event::Mouse(mouse::Event::CursorMoved { + position: drag_cursor.position().unwrap(), + }); + self.container.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout, + drag_cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + return event::Status::Captured; + } + Event::Dnd(DndEvent::Offer(id, OfferEvent::Leave)) + if id == Some(my_id) => + { + state.on_leave( + self.on_leave.as_ref().map(std::convert::AsRef::as_ref), + ); + + if self.forward_drag_as_cursor { + let drag_cursor = mouse::Cursor::Unavailable; + let event = Event::Mouse(mouse::Event::CursorLeft); + self.container.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout, + drag_cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + return event::Status::Captured; + } + Event::Dnd(DndEvent::Offer(id, OfferEvent::Motion { x, y })) + if id == Some(my_id) => + { + if let Some(msg) = state.on_motion( + x, + y, + self.on_motion.as_ref().map(std::convert::AsRef::as_ref), + self.on_enter.as_ref().map(std::convert::AsRef::as_ref), + (), + ) { + shell.publish(msg); + } + + if self.forward_drag_as_cursor { + #[allow(clippy::cast_possible_truncation)] + let drag_cursor = + mouse::Cursor::Available((x as f32, y as f32).into()); + let event = Event::Mouse(mouse::Event::CursorMoved { + position: drag_cursor.position().unwrap(), + }); + self.container.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout, + drag_cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + return event::Status::Captured; + } + Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) + if id == Some(my_id) => + { + if let Some(msg) = state.on_leave( + self.on_leave.as_ref().map(std::convert::AsRef::as_ref), + ) { + shell.publish(msg); + } + return event::Status::Captured; + } + Event::Dnd(DndEvent::Offer(id, OfferEvent::Drop)) + if id == Some(my_id) => + { + if let Some(msg) = state.on_drop( + self.on_drop.as_ref().map(std::convert::AsRef::as_ref), + ) { + shell.publish(msg); + } + return event::Status::Captured; + } + Event::Dnd(DndEvent::Offer( + id, + OfferEvent::SelectedAction(action), + )) if id == Some(my_id) => { + if let Some(msg) = state.on_action_selected( + action, + self.on_action_selected + .as_ref() + .map(std::convert::AsRef::as_ref), + ) { + shell.publish(msg); + } + return event::Status::Captured; + } + Event::Dnd(DndEvent::Offer( + id, + OfferEvent::Data { data, mime_type }, + )) if id == Some(my_id) => { + dbg!("got data"); + if let (Some(msg), ret) = state.on_data_received( + mime_type, + data, + self.on_data_received + .as_ref() + .map(std::convert::AsRef::as_ref), + self.on_finish.as_ref().map(std::convert::AsRef::as_ref), + ) { + shell.publish(msg); + return ret; + } + return event::Status::Captured; + } + _ => {} + } + event::Status::Ignored + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: layout::Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + renderer: &iced::Renderer, + ) -> mouse::Interaction { + self.container.as_widget().mouse_interaction( + &tree.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut iced::Renderer, + theme: &iced::Theme, + renderer_style: &iced_core::renderer::Style, + layout: layout::Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + ) { + self.container.as_widget().draw( + &tree.children[0], + renderer, + theme, + renderer_style, + layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &iced::Renderer, + translation: iced::Vector, + ) -> Option> + { + None + } + + fn drag_destinations( + &self, + state: &Tree, + layout: layout::Layout<'_>, + renderer: &iced::Renderer, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, + ) { + let bounds = layout.bounds(); + let my_id = self.get_drag_id(); + let my_dest = DndDestinationRectangle { + id: my_id, + rectangle: dnd::Rectangle { + x: f64::from(bounds.x), + y: f64::from(bounds.y), + width: f64::from(bounds.width), + height: f64::from(bounds.height), + }, + mime_types: self.mime_types.clone(), + actions: self.action, + preferred: self.preferred_action, + }; + dnd_rectangles.push(my_dest); + + self.container.as_widget().drag_destinations( + &state.children[0], + layout, + renderer, + dnd_rectangles, + ); + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } +} + +#[derive(Default)] +pub struct State { + pub drag_offer: Option>, +} + +pub struct DragOffer { + pub x: f64, + pub y: f64, + pub dropped: bool, + pub selected_action: DndAction, + pub data: T, +} + +impl State { + #[must_use] + pub fn new() -> Self { + Self { drag_offer: None } + } + + pub fn on_enter( + &mut self, + x: f64, + y: f64, + mime_types: Vec, + on_enter: Option) -> Message>, + data: T, + ) -> Option { + self.drag_offer = Some(DragOffer { + x, + y, + dropped: false, + selected_action: DndAction::empty(), + data, + }); + on_enter.map(|f| f(x, y, mime_types)) + } + + pub fn on_leave( + &mut self, + on_leave: Option<&dyn Fn() -> Message>, + ) -> Option { + if self.drag_offer.as_ref().is_some_and(|d| !d.dropped) { + self.drag_offer = None; + on_leave.map(|f| f()) + } else { + None + } + } + + pub fn on_motion( + &mut self, + x: f64, + y: f64, + on_motion: Option Message>, + on_enter: Option) -> Message>, + data: T, + ) -> Option { + if let Some(s) = self.drag_offer.as_mut() { + s.x = x; + s.y = y; + } else { + self.drag_offer = Some(DragOffer { + x, + y, + dropped: false, + selected_action: DndAction::empty(), + data, + }); + if let Some(f) = on_enter { + return Some(f(x, y, vec![])); + } + } + on_motion.map(|f| f(x, y)) + } + + pub fn on_drop( + &mut self, + on_drop: Option Message>, + ) -> Option { + if let Some(offer) = self.drag_offer.as_mut() { + offer.dropped = true; + if let Some(f) = on_drop { + return Some(f(offer.x, offer.y)); + } + } + None + } + + pub fn on_action_selected( + &mut self, + action: DndAction, + on_action_selected: Option Message>, + ) -> Option { + if let Some(s) = self.drag_offer.as_mut() { + s.selected_action = action; + } + if let Some(f) = on_action_selected { + f(action).into() + } else { + None + } + } + + pub fn on_data_received( + &mut self, + mime: String, + data: Vec, + on_data_received: Option) -> Message>, + on_finish: Option< + impl Fn(String, Vec, DndAction, f64, f64) -> Message, + >, + ) -> (Option, event::Status) { + dbg!("data received"); + let Some(dnd) = self.drag_offer.as_ref() else { + self.drag_offer = None; + return (None, event::Status::Ignored); + }; + + if dnd.dropped { + let ret = ( + on_finish + .map(|f| f(mime, data, dnd.selected_action, dnd.x, dnd.y)), + event::Status::Captured, + ); + self.drag_offer = None; + ret + } else if let Some(f) = on_data_received { + (Some(f(mime, data)), event::Status::Captured) + } else { + (None, event::Status::Ignored) + } + } +} + +impl<'a, Message: 'static> From> + for Element<'a, Message> +{ + fn from(wrapper: DndDestination<'a, Message>) -> Self { + Element::new(wrapper) + } +} diff --git a/examples/sctk_drag/src/dnd_source.rs b/examples/sctk_drag/src/dnd_source.rs new file mode 100644 index 0000000000..ffa0dad638 --- /dev/null +++ b/examples/sctk_drag/src/dnd_source.rs @@ -0,0 +1,382 @@ +use std::any::Any; + +use iced::id::Id; +use iced::widget::container; +use iced::Element; +use iced::{ + clipboard::dnd::{DndAction, DndEvent, SourceEvent}, + event, mouse, overlay, Event, Length, Point, Rectangle, +}; +use iced_core::{ + layout, renderer, + widget::{tree, Tree}, + Clipboard, Shell, +}; +use iced_core::{Layout, Widget}; + +pub fn dnd_source< + 'a, + Message: 'static, + AppMessage: 'static, + D: iced::clipboard::mime::AsMimeTypes + Send + 'static, +>( + child: impl Into>, +) -> DndSource<'a, Message, AppMessage, D> { + DndSource::new(child) +} + +pub struct DndSource<'a, Message, AppMessage, D> { + id: Id, + action: DndAction, + container: Element<'a, Message>, + drag_content: Option D>>, + drag_icon: + Option (Element<'static, AppMessage>, tree::State)>>, + drag_threshold: f32, + _phantom: std::marker::PhantomData, +} + +impl< + 'a, + Message: 'static, + AppMessage: 'static, + D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, + > DndSource<'a, Message, AppMessage, D> +{ + pub fn new(child: impl Into>) -> Self { + Self { + id: Id::unique(), + action: DndAction::Copy | DndAction::Move, + container: container(child).into(), + drag_content: None, + drag_icon: None, + drag_threshold: 8.0, + _phantom: std::marker::PhantomData, + } + } + + pub fn with_id(child: impl Into>, id: Id) -> Self { + Self { + id, + action: DndAction::Copy | DndAction::Move, + container: container(child).into(), + drag_content: None, + drag_icon: None, + drag_threshold: 8.0, + _phantom: std::marker::PhantomData, + } + } + + #[must_use] + pub fn action(mut self, action: DndAction) -> Self { + self.action = action; + self + } + + #[must_use] + pub fn drag_content(mut self, f: impl Fn() -> D + 'static) -> Self { + self.drag_content = Some(Box::new(f)); + self + } + + #[must_use] + pub fn drag_icon( + mut self, + f: impl Fn() -> (Element<'static, AppMessage>, tree::State) + 'static, + ) -> Self { + self.drag_icon = Some(Box::new(f)); + self + } + + #[must_use] + pub fn drag_threshold(mut self, threshold: f32) -> Self { + self.drag_threshold = threshold; + self + } + + pub fn start_dnd(&self, clipboard: &mut dyn Clipboard, bounds: Rectangle) { + let Some(content) = self.drag_content.as_ref().map(|f| f()) else { + return; + }; + iced_core::clipboard::start_dnd( + clipboard, + false, + Some(iced_core::clipboard::DndSource::Widget(self.id.clone())), + self.drag_icon.as_ref().map(|f| { + let (icon, state) = f(); + ( + container(icon) + .width(Length::Fixed(bounds.width)) + .height(Length::Fixed(bounds.height)) + .into(), + state, + ) + }), + Box::new(content), + self.action, + ); + } +} + +impl< + 'a, + Message: 'static, + AppMessage: 'static, + D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, + > Widget + for DndSource<'a, Message, AppMessage, D> +{ + fn children(&self) -> Vec { + vec![Tree::new(&self.container)] + } + + fn tag(&self) -> iced_core::widget::tree::Tag { + tree::Tag::of::() + } + + fn diff(&mut self, tree: &mut Tree) { + tree.children[0].diff(self.container.as_widget_mut()); + } + + fn state(&self) -> iced_core::widget::tree::State { + tree::State::new(State::new()) + } + + fn size(&self) -> iced_core::Size { + self.container.as_widget().size() + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &iced::Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let state = tree.state.downcast_mut::(); + let node = self.container.as_widget().layout( + &mut tree.children[0], + renderer, + limits, + ); + state.cached_bounds = node.bounds(); + node + } + + fn operate( + &self, + tree: &mut Tree, + layout: layout::Layout<'_>, + renderer: &iced::Renderer, + operation: &mut dyn iced_core::widget::Operation<()>, + ) { + operation.custom((&mut tree.state) as &mut dyn Any, Some(&self.id)); + operation.container( + Some(&self.id), + layout.bounds(), + &mut |operation| { + self.container.as_widget().operate( + &mut tree.children[0], + layout, + renderer, + operation, + ) + }, + ); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: layout::Layout<'_>, + cursor: mouse::Cursor, + renderer: &iced::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let ret = self.container.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ); + + let state = tree.state.downcast_mut::(); + + match event { + Event::Mouse(mouse_event) => match mouse_event { + mouse::Event::ButtonPressed(mouse::Button::Left) => { + if let Some(position) = cursor.position() { + if !state.hovered { + return ret; + } + + state.left_pressed_position = Some(position); + // dbg!(&state, &self.id); + return event::Status::Captured; + } + } + mouse::Event::ButtonReleased(mouse::Button::Left) + if state.left_pressed_position.is_some() => + { + state.left_pressed_position = None; + return event::Status::Captured; + } + mouse::Event::CursorMoved { .. } => { + if let Some(position) = cursor.position() { + if state.hovered { + // We ignore motion if we do not possess drag content by now. + if self.drag_content.is_none() { + state.left_pressed_position = None; + return ret; + } + if let Some(left_pressed_position) = + state.left_pressed_position + { + // dbg!(&state); + if position.distance(left_pressed_position) + > self.drag_threshold + { + self.start_dnd( + clipboard, + state.cached_bounds, + ); + state.is_dragging = true; + state.left_pressed_position = None; + } + } + if !cursor.is_over(layout.bounds()) { + state.hovered = false; + + return ret; + } + } else if cursor.is_over(layout.bounds()) { + state.hovered = true; + } + return event::Status::Captured; + } + } + _ => return ret, + }, + Event::Dnd(DndEvent::Source( + SourceEvent::Cancelled | SourceEvent::Finished, + )) => { + if state.is_dragging { + state.is_dragging = false; + return event::Status::Captured; + } + return ret; + } + _ => return ret, + } + ret + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: layout::Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + renderer: &iced::Renderer, + ) -> mouse::Interaction { + let state = tree.state.downcast_ref::(); + if state.is_dragging { + return mouse::Interaction::Grabbing; + } + self.container.as_widget().mouse_interaction( + &tree.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut iced::Renderer, + theme: &iced::Theme, + renderer_style: &renderer::Style, + layout: layout::Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + ) { + self.container.as_widget().draw( + &tree.children[0], + renderer, + theme, + renderer_style, + layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &iced::Renderer, + translation: iced::Vector, + ) -> Option> + { + None + } + + fn drag_destinations( + &self, + state: &Tree, + layout: layout::Layout<'_>, + renderer: &iced::Renderer, + dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, + ) { + self.container.as_widget().drag_destinations( + &state.children[0], + layout, + renderer, + dnd_rectangles, + ); + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } +} + +impl< + 'a, + Message: 'static, + AppMessage: 'static, + D: iced::clipboard::mime::AsMimeTypes + std::marker::Send + 'static, + > From> for Element<'a, Message> +{ + fn from(e: DndSource<'a, Message, AppMessage, D>) -> Element<'a, Message> { + Element::new(e) + } +} + +/// Local state of the [`MouseListener`]. +#[derive(Debug, Default)] +struct State { + hovered: bool, + left_pressed_position: Option, + is_dragging: bool, + cached_bounds: Rectangle, +} + +impl State { + fn new() -> Self { + Self::default() + } +} diff --git a/examples/sctk_drag/src/main.rs b/examples/sctk_drag/src/main.rs new file mode 100644 index 0000000000..f2d1a5e527 --- /dev/null +++ b/examples/sctk_drag/src/main.rs @@ -0,0 +1,202 @@ +mod dnd_destination; +mod dnd_source; + +use std::{borrow::Cow, convert::Infallible}; + +use dnd_destination::dnd_destination; +use iced::{ + clipboard::mime::{AllowedMimeTypes, AsMimeTypes}, + platform_specific::{ + runtime::wayland::layer_surface::SctkLayerSurfaceSettings, + shell::commands::layer_surface::get_layer_surface, + }, + widget::{column, container, text}, + window, Element, Length, Task, +}; +use iced_core::{ + widget::{tree, Text}, + Widget, +}; + +fn main() -> iced::Result { + iced::daemon(DndTest::title, DndTest::update, DndTest::view) + .run_with(DndTest::new) + // iced::application(Todos::title, Todos::update, Todos::view) + // .subscription(Todos::subscription) + // .font(include_bytes!("../fonts/icons.ttf").as_slice()) + // .window_size((500.0, 800.0)) + // .run_with(Todos::new) +} + +const SUPPORTED_MIME_TYPES: &'static [&'static str; 6] = &[ + "text/plain;charset=utf-8", + "text/plain;charset=UTF-8", + "UTF8_STRING", + "STRING", + "text/plain", + "TEXT", +]; + +#[derive(Debug, Default, Clone)] +pub struct MyDndString(String); + +impl AllowedMimeTypes for MyDndString { + fn allowed() -> std::borrow::Cow<'static, [String]> { + std::borrow::Cow::Owned(vec![ + "text/plain;charset=utf-8".to_string(), + "text/plain;charset=UTF-8".to_string(), + "UTF8_STRING".to_string(), + "STRING".to_string(), + "text/plain".to_string(), + "TEXT".to_string(), + ]) + } +} + +impl TryFrom<(Vec, String)> for MyDndString { + type Error = Infallible; + + fn try_from(value: (Vec, String)) -> Result { + Ok(MyDndString( + String::from_utf8_lossy(value.0.as_slice()).to_string(), + )) + } +} + +impl AsMimeTypes for MyDndString { + fn available(&self) -> std::borrow::Cow<'static, [String]> { + std::borrow::Cow::Owned(vec![ + "text/plain;charset=utf-8".to_string(), + "text/plain;charset=UTF-8".to_string(), + "UTF8_STRING".to_string(), + "STRING".to_string(), + "text/plain".to_string(), + "TEXT".to_string(), + ]) + } + + fn as_bytes( + &self, + _mime_type: &str, + ) -> Option> { + Some(Cow::Owned(self.0.clone().into_bytes())) + } +} + +#[derive(Debug, Clone)] +pub struct DndTest { + /// option with the dragged text + source: Option, + /// is the dnd over the target + current_text: String, + /// main id + id: iced_core::window::Id, +} + +#[derive(Debug, Clone)] +pub enum Message { + Drag, + DndData(MyDndString), +} + +impl DndTest { + fn new() -> (DndTest, Task) { + let current_text = String::from("Hello, world!"); + let mut s = SctkLayerSurfaceSettings::default(); + s.size_limits = s.size_limits.min_width(100.0).max_width(400.0); + s.size = Some((Some(500), Some(600))); + // s.anchor = Anchor::TOP.union(Anchor::BOTTOM); + ( + DndTest { + current_text, + source: None, + id: iced_core::window::Id::unique(), + }, + get_layer_surface(s), + ) + } + + fn title(&self, _id: window::Id) -> String { + String::from("DndTest") + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::DndData(s) => { + dbg!(&s); + self.current_text = s.0; + } + _ => {} + } + Task::none() + } + + fn view(&self, _id: window::Id) -> Element { + let s = self.current_text.chars().rev().collect::(); + let s2 = s.clone(); + column![ + dnd_destination::dnd_destination_for_data::( + container(text(format!( + "Drag text here: {}", + &self.current_text + ))) + .width(Length::Fill) + .height(Length::FillPortion(1)) + .padding(20), + |data, _| { + dbg!("got data"); + Message::DndData(data.unwrap_or_default()) + } + ) + .drag_id(1) + .on_enter(|_, _, m| { + dbg!(m); + Message::Drag + }) + .on_action_selected(|a| { + dbg!(a); + Message::Drag + }) + .on_drop(|_, _| { + dbg!("drop"); + Message::Drag + }) + .on_motion(|x, y| { + dbg!(x, y); + Message::Drag + }), + dnd_source::dnd_source( + container(text(format!( + "Drag me: {}", + &self.current_text.chars().rev().collect::() + ))) + .width(Length::Fill) + .height(Length::FillPortion(1)) + .padding(20) + ) + .drag_threshold(5.0) + .drag_icon(move || { + let t: Text<'static, iced::Theme, iced::Renderer> = + text(s.clone()); + let state = as iced_core::Widget<(), iced::Theme, iced::Renderer>>::state( + &t, + ); + ( + Element::<'static, (), iced::Theme, iced::Renderer>::from( + t, + ), + state, + ) + }) + .drag_content(move || { MyDndString(s2.clone()) }) + ] + .width(Length::Fill) + .into() + } +} + +pub struct CustomTheme; diff --git a/examples/sctk_lazy/Cargo.toml b/examples/sctk_lazy/Cargo.toml new file mode 100644 index 0000000000..512c00a0e2 --- /dev/null +++ b/examples/sctk_lazy/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "sctk_lazy" +version = "0.1.0" +authors = ["Nick Senger "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = [ + "debug", + "lazy", + "wayland", + "winit", + "tokio", + "tiny-skia", + "advanced", +], default-features = false } diff --git a/examples/sctk_lazy/src/main.rs b/examples/sctk_lazy/src/main.rs new file mode 100644 index 0000000000..47560ee39d --- /dev/null +++ b/examples/sctk_lazy/src/main.rs @@ -0,0 +1,255 @@ +use iced::advanced::layout::Limits; +use iced::platform_specific::runtime::wayland::layer_surface::SctkLayerSurfaceSettings; +use iced::platform_specific::shell::commands::layer_surface::{ + get_layer_surface, KeyboardInteractivity, +}; + +use iced::widget::{ + button, column, horizontal_space, lazy, pick_list, row, scrollable, text, + text_input, +}; +use iced::window::Id; +use iced::Task; +use iced::{Element, Length}; + +use std::collections::HashSet; +use std::hash::Hash; + +pub fn main() -> iced::Result { + iced::daemon(App::title, App::update, App::view).run_with(App::new) +} + +struct App { + version: u8, + items: HashSet, + input: String, + order: Order, +} + +impl Default for App { + fn default() -> Self { + Self { + version: 0, + items: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + .into_iter() + .map(From::from) + .collect(), + input: Default::default(), + order: Order::Ascending, + } + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +enum Color { + #[default] + Black, + Red, + Orange, + Yellow, + Green, + Blue, + Purple, +} + +impl Color { + const ALL: &'static [Color] = &[ + Color::Black, + Color::Red, + Color::Orange, + Color::Yellow, + Color::Green, + Color::Blue, + Color::Purple, + ]; +} + +impl std::fmt::Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Black => "Black", + Self::Red => "Red", + Self::Orange => "Orange", + Self::Yellow => "Yellow", + Self::Green => "Green", + Self::Blue => "Blue", + Self::Purple => "Purple", + }) + } +} + +impl From for iced::Color { + fn from(value: Color) -> Self { + match value { + Color::Black => iced::Color::from_rgb8(0, 0, 0), + Color::Red => iced::Color::from_rgb8(220, 50, 47), + Color::Orange => iced::Color::from_rgb8(203, 75, 22), + Color::Yellow => iced::Color::from_rgb8(181, 137, 0), + Color::Green => iced::Color::from_rgb8(133, 153, 0), + Color::Blue => iced::Color::from_rgb8(38, 139, 210), + Color::Purple => iced::Color::from_rgb8(108, 113, 196), + } + } +} + +#[derive(Clone, Debug, Eq)] +struct Item { + name: String, + color: Color, +} + +impl Hash for Item { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl PartialEq for Item { + fn eq(&self, other: &Self) -> bool { + self.name.eq(&other.name) + } +} + +impl From<&str> for Item { + fn from(s: &str) -> Self { + Self { + name: s.to_owned(), + color: Default::default(), + } + } +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + ToggleOrder, + DeleteItem(Item), + AddItem(String), + ItemColorChanged(Item, Color), +} + +impl App { + fn new() -> (App, Task) { + let mut initial_surface = SctkLayerSurfaceSettings::default(); + initial_surface.keyboard_interactivity = + KeyboardInteractivity::OnDemand; + initial_surface.size_limits = Limits::NONE + .min_width(1.0) + .min_height(1.0) + .max_height(500.0) + .max_width(900.0); + initial_surface.size = Some((Some(500), Some(500))); + (Self::default(), get_layer_surface(initial_surface)) + } + + fn title(&self, _id: Id) -> String { + String::from("Lazy - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::InputChanged(input) => { + self.input = input; + } + Message::ToggleOrder => { + self.version = self.version.wrapping_add(1); + self.order = match self.order { + Order::Ascending => Order::Descending, + Order::Descending => Order::Ascending, + } + } + Message::AddItem(name) => { + self.version = self.version.wrapping_add(1); + self.items.insert(name.as_str().into()); + self.input.clear(); + } + Message::DeleteItem(item) => { + self.version = self.version.wrapping_add(1); + self.items.remove(&item); + } + Message::ItemColorChanged(item, color) => { + self.version = self.version.wrapping_add(1); + if self.items.remove(&item) { + self.items.insert(Item { + name: item.name, + color, + }); + } + } + } + } + + fn view(&self, _: Id) -> Element { + let options = lazy(self.version, |_| { + let mut items: Vec<_> = self.items.iter().cloned().collect(); + + items.sort_by(|a, b| match self.order { + Order::Ascending => { + a.name.to_lowercase().cmp(&b.name.to_lowercase()) + } + Order::Descending => { + b.name.to_lowercase().cmp(&a.name.to_lowercase()) + } + }); + + column(items.into_iter().map(|item| { + let button = button("Delete") + .on_press(Message::DeleteItem(item.clone())); + + row![ + text(item.name.clone()), + horizontal_space(), + pick_list(Color::ALL, Some(item.color), move |color| { + Message::ItemColorChanged(item.clone(), color) + }), + button + ] + .spacing(20) + .into() + })) + .spacing(10) + }); + + column![ + scrollable(options).height(Length::Fill), + row![ + text_input("Add a new option", &self.input) + .on_input(Message::InputChanged) + .on_submit(Message::AddItem(self.input.clone())), + button(text(format!("Toggle Order ({})", self.order))) + .on_press(Message::ToggleOrder) + ] + .spacing(10) + ] + .spacing(20) + .padding(20) + .into() + } + + fn theme(&self) -> iced::Theme { + iced::Theme::default() + } + + fn scale_factor(&self) -> f64 { + 1.0 + } +} + +#[derive(Debug, Hash)] +enum Order { + Ascending, + Descending, +} + +impl std::fmt::Display for Order { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} diff --git a/examples/sctk_session_lock/Cargo.toml b/examples/sctk_session_lock/Cargo.toml new file mode 100644 index 0000000000..c383ef3a2c --- /dev/null +++ b/examples/sctk_session_lock/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "sctk_session_lock" +version = "0.1.0" +edition = "2021" + +[dependencies] +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "828b1eb" } +iced = { path = "../..", default-features = false, features = [ + "async-std", + "winit", + "wayland", + "debug", + "tiny-skia", + # "a11y", +] } +iced_runtime = { path = "../../runtime" } +env_logger = "0.10" +async-std = "1" diff --git a/examples/sctk_session_lock/src/main.rs b/examples/sctk_session_lock/src/main.rs new file mode 100644 index 0000000000..8d42973f0e --- /dev/null +++ b/examples/sctk_session_lock/src/main.rs @@ -0,0 +1,96 @@ +use iced::event::listen_raw; +use iced::Task; +use iced::{ + event::wayland::{Event as WaylandEvent, OutputEvent, SessionLockEvent}, + platform_specific::shell::commands::session_lock, + widget::text, + window, Element, Subscription, +}; + +fn main() -> iced::Result { + iced::daemon(Locker::title, Locker::update, Locker::view) + .subscription(Locker::subscription) + .run_with(Locker::new) +} + +#[derive(Debug, Clone, Default)] +struct Locker { + _exit: bool, +} + +#[derive(Debug, Clone)] +pub enum Message { + WaylandEvent(WaylandEvent), + TimeUp, + Ignore, +} + +impl Locker { + fn new() -> (Locker, Task) { + ( + Locker { + ..Locker::default() + }, + session_lock::lock(), + ) + } + + fn title(&self, _id: window::Id) -> String { + String::from("Locker") + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::WaylandEvent(evt) => match evt { + WaylandEvent::Output(evt, output) => match evt { + OutputEvent::Created(_) => { + return session_lock::get_lock_surface( + window::Id::unique(), + output, + ); + } + OutputEvent::Removed => {} + _ => {} + }, + WaylandEvent::SessionLock(evt) => match evt { + SessionLockEvent::Locked => { + return iced::Task::perform( + async_std::task::sleep( + std::time::Duration::from_secs(5), + ), + |_| Message::TimeUp, + ); + } + SessionLockEvent::Unlocked => { + // Server has processed unlock, so it's safe to exit + std::process::exit(0); + } + _ => {} + }, + _ => {} + }, + Message::TimeUp => { + return session_lock::unlock(); + } + Message::Ignore => {} + } + Task::none() + } + + fn view(&self, id: window::Id) -> Element { + text(format!("Lock Surface {:?}", id)).into() + } + + fn subscription(&self) -> Subscription { + listen_raw(|evt, _, _| { + if let iced::Event::PlatformSpecific( + iced::event::PlatformSpecific::Wayland(evt), + ) = evt + { + Some(Message::WaylandEvent(evt)) + } else { + None + } + }) + } +} diff --git a/examples/sctk_subsurface/Cargo.toml b/examples/sctk_subsurface/Cargo.toml new file mode 100644 index 0000000000..30949510b2 --- /dev/null +++ b/examples/sctk_subsurface/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sctk_subsurface" +version = "0.1.0" +edition = "2021" + +[dependencies] +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "828b1eb" } +iced = { path = "../..", default-features = false, features = [ + "tokio", + "wayland", + "winit", + "debug", + "tiny-skia", +] } +iced_runtime = { path = "../../runtime" } +env_logger = "0.10" +futures-channel = "0.3.29" +calloop = "0.12.3" +rustix = { version = "0.38.30", features = ["fs", "shm"] } diff --git a/examples/sctk_subsurface/src/main.rs b/examples/sctk_subsurface/src/main.rs new file mode 100644 index 0000000000..21a9a75fed --- /dev/null +++ b/examples/sctk_subsurface/src/main.rs @@ -0,0 +1,123 @@ +// Shows a subsurface with a 1x1 px red buffer, stretch to window size + +use iced::{ + event::wayland::Event as WaylandEvent, + platform_specific::shell::subsurface_widget::{self, SubsurfaceBuffer}, + widget::text, + window::{self, Id, Settings}, + Element, Length, Subscription, Task, +}; +use sctk::reexports::client::{Connection, Proxy}; + +mod wayland; + +fn main() -> iced::Result { + iced::daemon( + SubsurfaceApp::title, + SubsurfaceApp::update, + SubsurfaceApp::view, + ) + .subscription(SubsurfaceApp::subscription) + .run_with(SubsurfaceApp::new) +} + +#[derive(Debug, Clone, Default)] +struct SubsurfaceApp { + connection: Option, + red_buffer: Option, +} + +#[derive(Debug, Clone)] +pub enum Message { + WaylandEvent(WaylandEvent), + Wayland(wayland::Event), + Pressed(&'static str), + Id(Id), +} + +impl SubsurfaceApp { + fn new() -> (SubsurfaceApp, Task) { + ( + SubsurfaceApp { + ..SubsurfaceApp::default() + }, + iced::window::open(Settings { + ..Default::default() + }) + .1 + .map(Message::Id), + ) + } + + fn title(&self, _id: window::Id) -> String { + String::from("SubsurfaceApp") + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::WaylandEvent(evt) => match evt { + WaylandEvent::Output(_evt, output) => { + if self.connection.is_none() { + if let Some(backend) = output.backend().upgrade() { + self.connection = + Some(Connection::from_backend(backend)); + } + } + } + _ => {} + }, + Message::Wayland(evt) => match evt { + wayland::Event::RedBuffer(buffer) => { + self.red_buffer = Some(buffer); + } + }, + Message::Pressed(side) => println!("{side} surface pressed"), + Message::Id(_) => {} + } + Task::none() + } + + fn view(&self, _id: window::Id) -> Element { + if let Some(buffer) = &self.red_buffer { + iced::widget::row![ + iced::widget::button( + subsurface_widget::Subsurface::new(1, 1, buffer) + .width(Length::Fill) + .height(Length::Fill) + ) + .width(Length::Fill) + .height(Length::Fill) + .on_press(Message::Pressed("left")), + iced::widget::button( + subsurface_widget::Subsurface::new(1, 1, buffer) + .width(Length::Fill) + .height(Length::Fill) + ) + .width(Length::Fill) + .height(Length::Fill) + .on_press(Message::Pressed("right")) + ] + .into() + } else { + text("No subsurface").into() + } + } + + fn subscription(&self) -> Subscription { + let mut subscriptions = vec![iced::event::listen_with(|evt, _, _| { + if let iced::Event::PlatformSpecific( + iced::event::PlatformSpecific::Wayland(evt), + ) = evt + { + Some(Message::WaylandEvent(evt)) + } else { + None + } + })]; + if let Some(connection) = &self.connection { + subscriptions + .push(wayland::subscription(connection).map(Message::Wayland)); + } + Subscription::batch(subscriptions) + } +} diff --git a/examples/sctk_subsurface/src/wayland.rs b/examples/sctk_subsurface/src/wayland.rs new file mode 100644 index 0000000000..315f168199 --- /dev/null +++ b/examples/sctk_subsurface/src/wayland.rs @@ -0,0 +1,128 @@ +use futures_channel::mpsc; +use iced::{ + futures::{FutureExt, SinkExt}, + platform_specific::shell::subsurface_widget::{Shmbuf, SubsurfaceBuffer}, +}; +use iced_runtime::futures::subscription; +use rustix::{io::Errno, shm::ShmOFlags}; +use sctk::{ + reexports::{ + calloop_wayland_source::WaylandSource, + client::{ + delegate_noop, + globals::registry_queue_init, + protocol::{wl_buffer::WlBuffer, wl_shm}, + Connection, + }, + }, + registry::{ProvidesRegistryState, RegistryState}, + shm::{Shm, ShmHandler}, +}; +use std::{ + os::fd::OwnedFd, + sync::Arc, + thread, + time::{SystemTime, UNIX_EPOCH}, +}; + +#[derive(Debug, Clone)] +pub enum Event { + RedBuffer(SubsurfaceBuffer), +} + +struct AppData { + registry_state: RegistryState, + shm_state: Shm, +} + +impl ProvidesRegistryState for AppData { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + + sctk::registry_handlers!(); +} + +impl ShmHandler for AppData { + fn shm_state(&mut self) -> &mut Shm { + &mut self.shm_state + } +} + +pub fn subscription(connection: &Connection) -> iced::Subscription { + let connection = connection.clone(); + subscription::Subscription::run_with_id( + "wayland-sub", + async { start(connection).await }.flatten_stream(), + ) +} + +async fn start(conn: Connection) -> mpsc::Receiver { + let (mut sender, receiver) = mpsc::channel(20); + + let (globals, event_queue) = registry_queue_init(&conn).unwrap(); + let qh = event_queue.handle(); + + let mut app_data = AppData { + registry_state: RegistryState::new(&globals), + shm_state: Shm::bind(&globals, &qh).unwrap(), + }; + + let fd = create_memfile().unwrap(); + rustix::io::write(&fd, &[0, 255, 0, 255]).unwrap(); + + let shmbuf = Shmbuf { + fd, + offset: 0, + width: 1, + height: 1, + stride: 4, + format: wl_shm::Format::Xrgb8888, + }; + + let buffer = SubsurfaceBuffer::new(Arc::new(shmbuf.into())).0; + let _ = sender.send(Event::RedBuffer(buffer)).await; + + thread::spawn(move || { + let mut event_loop = calloop::EventLoop::try_new().unwrap(); + WaylandSource::new(conn, event_queue) + .insert(event_loop.handle()) + .unwrap(); + loop { + event_loop.dispatch(None, &mut app_data).unwrap(); + } + }); + + receiver +} + +fn create_memfile() -> rustix::io::Result { + loop { + let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; + + let time = SystemTime::now(); + let name = format!( + "/iced-sctk-{}", + time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() + ); + + match rustix::io::retry_on_intr(|| { + rustix::shm::shm_open(&name, flags, 0600.into()) + }) { + Ok(fd) => match rustix::shm::shm_unlink(&name) { + Ok(_) => return Ok(fd), + Err(errno) => { + return Err(errno.into()); + } + }, + Err(Errno::EXIST) => { + continue; + } + Err(err) => return Err(err.into()), + } + } +} + +delegate_noop!(AppData: ignore WlBuffer); +sctk::delegate_registry!(AppData); +sctk::delegate_shm!(AppData); diff --git a/examples/sctk_subsurface_gst/Cargo.toml b/examples/sctk_subsurface_gst/Cargo.toml new file mode 100644 index 0000000000..4b8095ced7 --- /dev/null +++ b/examples/sctk_subsurface_gst/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sctk_subsurface_gst" +version = "0.1.0" +edition = "2021" + +[dependencies] +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "828b1eb" } +iced = { path = "../..", default-features = false, features = [ + "wayland", + "debug", + "a11y", +] } +iced_runtime = { path = "../../runtime" } +env_logger = "0.10" +futures-channel = "0.3.29" +calloop = "0.12.3" +gst = { package = "gstreamer", version = "0.21.3" } +gst-app = { package = "gstreamer-app", version = "0.21.2" } +gst-video = { package = "gstreamer-video", version = "0.21.2" } +gst-allocators = { package = "gstreamer-allocators", version = "0.21.2" } +drm-fourcc = "2.2.0" diff --git a/examples/sctk_subsurface_gst/src/main.rs b/examples/sctk_subsurface_gst/src/main.rs new file mode 100644 index 0000000000..09119117bb --- /dev/null +++ b/examples/sctk_subsurface_gst/src/main.rs @@ -0,0 +1,84 @@ +// Shows a subsurface with a 1x1 px red buffer, stretch to window size + +use iced::{ + wayland::InitialSurface, widget::text, window, Application, Command, + Element, Length, Subscription, Theme, +}; +use iced_sctk::subsurface_widget::SubsurfaceBuffer; +use std::{env, path::Path}; + +mod pipewire; + +fn main() { + let args = env::args(); + if args.len() != 2 { + eprintln!("usage: sctk_subsurface_gst [h264 mp4 path]"); + return; + } + let path = args.skip(1).next().unwrap(); + if !Path::new(&path).exists() { + eprintln!("File `{path}` not found."); + return; + } + let mut settings = iced::Settings::with_flags(path); + settings.initial_surface = InitialSurface::XdgWindow(Default::default()); + SubsurfaceApp::run(settings).unwrap(); +} + +#[derive(Debug, Clone, Default)] +struct SubsurfaceApp { + path: String, + buffer: Option, +} + +#[derive(Debug, Clone)] +pub enum Message { + Pipewire(pipewire::Event), +} + +impl Application for SubsurfaceApp { + type Executor = iced::executor::Default; + type Message = Message; + type Flags = String; + type Theme = Theme; + + fn new(flags: String) -> (SubsurfaceApp, Command) { + ( + SubsurfaceApp { + path: flags, + ..SubsurfaceApp::default() + }, + Command::none(), + ) + } + + fn title(&self, _id: window::Id) -> String { + String::from("SubsurfaceApp") + } + + fn update(&mut self, message: Self::Message) -> Command { + match message { + Message::Pipewire(evt) => match evt { + pipewire::Event::Frame(subsurface_buffer) => { + self.buffer = Some(subsurface_buffer); + } + }, + } + Command::none() + } + + fn view(&self, _id: window::Id) -> Element { + if let Some(buffer) = &self.buffer { + iced_sctk::subsurface_widget::Subsurface::new(1, 1, buffer) + .width(Length::Fill) + .height(Length::Fill) + .into() + } else { + text("No subsurface").into() + } + } + + fn subscription(&self) -> Subscription { + pipewire::subscription(&self.path).map(Message::Pipewire) + } +} diff --git a/examples/sctk_subsurface_gst/src/pipewire.rs b/examples/sctk_subsurface_gst/src/pipewire.rs new file mode 100644 index 0000000000..9933ee099d --- /dev/null +++ b/examples/sctk_subsurface_gst/src/pipewire.rs @@ -0,0 +1,185 @@ +use drm_fourcc::{DrmFourcc, DrmModifier}; +use gst::glib::{self, translate::IntoGlib}; +use gst::prelude::*; +use iced::futures::{executor::block_on, SinkExt}; +use iced_sctk::subsurface_widget::{ + BufferSource, Dmabuf, Plane, SubsurfaceBuffer, +}; +use std::{ffi::c_void, os::unix::io::BorrowedFd, sync::Arc, thread}; + +const USE_NV12: bool = false; + +// Store a reference to the `BufferSource` in the data assocaited with the `BufferRef`. +// So the `BufferSource` can be re-used, instead of dupping fds and creating a new +// `wl_buffer` each buffer swap. +// +// See https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/main/subprojects/gst-plugins-bad/gst-libs/gst/wayland/gstwlbuffer.c +// for information about how `waylandsink` does this. +fn get_buffer_source(buffer: &gst::BufferRef) -> Option> { + let buffer_source_quark = glib::Quark::from_str("SctkBufferSource"); + unsafe { + let data = gst::ffi::gst_mini_object_get_qdata( + buffer.upcast_ref().as_mut_ptr(), + buffer_source_quark.into_glib(), + ); + if data.is_null() { + None + } else { + Arc::increment_strong_count(data as *const BufferSource); + Some(Arc::from_raw(data as *const BufferSource)) + } + } +} + +fn set_buffer_source(buffer: &gst::BufferRef, source: Arc) { + let buffer_source_quark = glib::Quark::from_str("SctkBufferSource"); + unsafe extern "C" fn destroy_buffer_source(data: *mut c_void) { + Arc::from_raw(data); + } + unsafe { + gst::ffi::gst_mini_object_set_qdata( + buffer.upcast_ref().as_mut_ptr(), + buffer_source_quark.into_glib(), + Arc::into_raw(source) as *mut c_void, + Some(destroy_buffer_source), + ); + } +} + +#[derive(Debug, Clone)] +pub enum Event { + Frame(SubsurfaceBuffer), +} + +pub fn subscription(path: &str) -> iced::Subscription { + let path = path.to_string(); + iced::subscription::channel("pw", 16, |sender| async { + thread::spawn(move || pipewire_thread(&path, sender)); + std::future::pending().await + }) +} + +fn pipewire_thread( + path: &str, + mut sender: futures_channel::mpsc::Sender, +) { + gst::init().unwrap(); + + let pipeline = gst::parse_launch(&format!( + "filesrc name=filesrc ! + qtdemux ! + h264parse ! + vah264dec ! + vapostproc name=postproc ! + capsfilter name=capfilter ! + appsink name=sink", + )) + .unwrap() + .dynamic_cast::() + .unwrap(); + pipeline + .by_name("filesrc") + .unwrap() + .set_property("location", path); + + let format = if USE_NV12 { + /* + pipeline + .remove(&pipeline.by_name("postproc").unwrap()) + .unwrap(); + */ + gst_video::VideoFormat::Nv12 + } else { + gst_video::VideoFormat::Bgra + }; + pipeline.by_name("capfilter").unwrap().set_property( + "caps", + gst_video::VideoCapsBuilder::new() + .features(["memory:DMABuf"]) + .format(format) + .build(), + ); + + let appsink = pipeline + .by_name("sink") + .unwrap() + .dynamic_cast::() + .unwrap(); + + let mut subsurface_release = None; + + appsink.set_callbacks( + gst_app::AppSinkCallbacks::builder() + .new_sample(move |appsink| { + let sample = + appsink.pull_sample().map_err(|_| gst::FlowError::Eos)?; + + let buffer = sample.buffer().unwrap(); + let meta = buffer.meta::().unwrap(); + + let buffer_source = if let Some(buffer_source) = get_buffer_source(buffer) { + buffer_source + } else { + let planes = (0..meta.n_planes()) + .map(|plane_idx| { + let memory = buffer + .memory(plane_idx) + .unwrap() + .downcast_memory::() + .unwrap(); + + // TODO avoid dup? + let fd = unsafe { BorrowedFd::borrow_raw(memory.fd()) } + .try_clone_to_owned() + .unwrap(); + + Plane { + fd, + plane_idx, + offset: meta.offset()[plane_idx as usize] as u32, + stride: meta.stride()[plane_idx as usize] as u32, + } + }) + .collect(); + + let format = if USE_NV12 { + DrmFourcc::Nv12 + } else { + DrmFourcc::Argb8888 + }; + let dmabuf = Dmabuf { + width: meta.width() as i32, + height: meta.height() as i32, + planes, + // TODO should use dmabuf protocol to get supported formats, + // convert if needed. + format: format as u32, + // TODO modifier negotiation + modifier: DrmModifier::Linear.into(), + }; + + let buffer_source = Arc::new(BufferSource::from(dmabuf)); + set_buffer_source(buffer, buffer_source.clone()); + buffer_source + }; + + let (buffer, new_subsurface_release) = + SubsurfaceBuffer::new(buffer_source); + block_on(sender.send(Event::Frame(buffer))).unwrap(); + + // Wait for server to release other buffer + // TODO is gstreamer using triple buffering? + if let Some(release) = subsurface_release.take() { + block_on(release); + } + subsurface_release = Some(new_subsurface_release); + + Ok(gst::FlowSuccess::Ok) + }) + .build(), + ); + + pipeline.set_state(gst::State::Playing).unwrap(); + let bus = pipeline.bus().unwrap(); + for _msg in bus.iter_timed(gst::ClockTime::NONE) {} +} diff --git a/examples/sctk_todos/Cargo.toml b/examples/sctk_todos/Cargo.toml new file mode 100644 index 0000000000..1638e2e1e7 --- /dev/null +++ b/examples/sctk_todos/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "sctk_todos" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +publish = false + +[dependencies] +iced.workspace = true +iced.features = [ + "tokio", + "wayland", + "winit", + "debug", + "tiny-skia", + "a11y", + "wgpu", +] +# iced.features = ["async-std", "wayland", "debug", "wayland-clipboard", "a11y", "tiny-skia"] +# TODO(POP): Fix a11y not working with new winit +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +iced_core.workspace = true +once_cell = "1.15" +sctk.workspace = true +log = "0.4.17" +env_logger = "0.10.0" +async-std = "1.0" +directories-next = "2.0.0" + +[profile.release-opt] +debug = true diff --git a/examples/sctk_todos/README.md b/examples/sctk_todos/README.md new file mode 100644 index 0000000000..9c2598b95e --- /dev/null +++ b/examples/sctk_todos/README.md @@ -0,0 +1,20 @@ +## Todos + +A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them. + +All the example code is located in the __[`main`]__ file. + + + +You can run the native version with `cargo run`: +``` +cargo run --package todos +``` +We have not yet implemented a `LocalStorage` version of the auto-save feature. Therefore, it does not work on web _yet_! + +[`main`]: src/main.rs +[TodoMVC]: http://todomvc.com/ diff --git a/examples/sctk_todos/fonts/icons.ttf b/examples/sctk_todos/fonts/icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4498299db26811ff8490001ea6f681529a4118f4 GIT binary patch literal 5596 zcmd^CO>7&-6@Ih3q$ui#vPDs`8QVjxer$0`$&xI|w&c*HBs!7Y$g=E^c3g8sQ9q6( zDiV`AMGpat2C14RX%9hBwCJS&3oU>ym|BH?TQ2u`RGN`X*GHNLR@~m?aR>o8~5~+=ev5wtmzPu zm_++xcG+J2m73zgx)Jvw;tO{uYtj4}1Rt6jj6eMYJc-Ze3U|T(3U?L~duC zrF_;F-zSXiei2IVvVDD3Xf>ar{R-N0#a_<+=6i?Wulr4m|J&94dg1KPCZ;g|S70Ar zUCXWh?Q|HvG)<%Z67kx-)J>;I8yTCJrurqjutNLEfSxb5-;Kr6;=E0svPHngRsoG5 zcSWneCSE5O=Kr$BtA3><#b4>D(4Zxk4($W3$+^*4ihWr7v93>TU!zO<6uki&`%t6(!CLa0IgB(OS@0VE->+IH z0Da{!ASxe1!#BtHqfbgV$Ms{__%27E^qbnZsfiB6_WJ}0kt9uMd2|waaOV8Ye%;j^ z7XB*XZs`#1eUFL$ojY@(m3W zKF+S~qP|zJ!O1;DT{J+KDHXdgeq8gokA(K^sTOW*X_9KG%3WKP@d^*QJ=1kHn%mGc z-sX%;*F<%-m}V)eQ&cUgC(@}4Q%{~vjwNF4EsgDbnf0y%;kG?}?P1a4ZrbAyoD@C% z1E0|ry&dfxrn}133cRwX}EaFp!@^!5Y}2|UC>ucy`Hbsn$X zfvr4E6 z2-bQ|yM%C^$I$=zXLKYU)f$~CuQWX>4*IX)=-^Z#u4lDvoMu1mqgHw;)_hQCt^6%Wu5IFhCakY0c73(0E=E{?%W2<4pR>PQe3t<>y3PKo9ks*xnV61&Nlk&TX z>DXSPkbI=M!B>r4LuEuD!_5O7RZYE3qR(tW{xtb}dj>>*N3$@G3BONt3~(w1e%*7U z_l&q&>oT_9GwNK1=+Y0~-s--spY>n4eZ_w=aKm5Kd!u(Kskv*7t=}7Xim37I?X9j4 z#CHxfUHRbYrluPKPJFH=xZj|c8m{_={zWzVZC3yJqf<_WC`m->HZqXw3{Hb{p^sC$ zi22*wc=AYhUj|#8dk-Yu6wWnB?~8dLW*hW0=Ql2m9z{)C2U@J*`p?&1`peFk$Ivc~ z&lUJs8EaHU!)2^PKT^g9@I)EAsD`G?*bV*FGWJrK=F7N-8tGenwvB4cbB%76v7iRw zD`So7#i26Rp^ucY0X$sBE((aVW$cDNTgG0xDAvokhT6ri68Y5^))wa%3i5E`i0tV; zdR)%DAoEZyuGmY`ey*^PUt5@G%&p|s>_TpqM_+$_zNb)_lXGkNWjR(JSFWz*ujR6Z=t7~edZMeV(v#<-1m$U! zUZ6EvM5q?1K#~qadjzrvuhOG*9B2j%31*44NGoL15;QhFhaL-#WgYDp?m4tppv{4? z1RSL-p3A%RQ((-a{}M)7+hx6fl#5`mA$b;^(Ixzf!n^xfNw8KNrtNqz3x7(!uha9G ztq0lyda;*lj#rY#oDuK%D-jR2UBft8u%k{?3ecWFY3|xJXJviJs>-=R>3QH~2u0Aux2x|X7WwfblOxjnaZWp5v5ylR4Sv*hC{BzWJd7ge4)qFk1$N`yDDeNJfHXqs^oAvWW-(q`tA$YOlu>Wru=OR|$SiR{}3&42Aj2G!+V(p>$^`qUx-orj4pudnBUjEi6Dv zRhxK%*9Bn4)2fbJQ)tzp6;VD6)8K?eA_7^st?CmQxsj2o9zlz!25WpeRWxQt(ygj4 zXI_t}J=XZS)cE<5G8lrs(b4a*Ou;_;14aj!e9*22LSgvpP!HHIUq$tnt# z0mPYQvsKhtK4KLOmiw%ti{*Z+=w=zTid8HhwTd2=2drW>%YzoFy71?4;^}S?mn)l0uOYH^%VG#Q-eoNnS;eH(S(&8#k%3>1G{99wf0~{^;ps7p@{1JEGjZA z3wj^6f&y(aDwBLN5yHneHj-u%l^}(hjhct!+ABnpAM+nW2?-$k@#j!fbt0VGh?-Ik zZD6eaJ7yUzjiC&T36@kDKFqOmsau-VW$>2PuJ2FBxxjf)Dls2sG{&_Zk(o7>p0H<8W3+@F1kR*!Fz@eU!zEN*bIcwLnwVh>>w<7*!FUgt1debeG;q2R zdlwQ3b^AU~FrtmlZH^Oo;x)o0?9N=sk^zo^#O$v2atzENgl5oDD-TYulw)R+C*$2Z z?u3jNP>v`~r=oHQFFy9Tti)hv5QNUah5#+MQe(v%E9#F``bCJxElxCd2RE z`fs%=!>)9_hjYqO$HEoMJ%c`Gss8W= za)^^<1IKaK#MqXo3S<756E04`N_087Oq_}+4oS(!(L||Q=tJ~l zsI|i1sCvLjTB;A?3`cDgag}3uXI0|#xW(zH&LFH$Serzr0mcCYg9&R>IGVEnj^+!@ ziNo|Ha~MoAhrv1KFqmS_DS-3LVKB`c1{ava;39Kk08cT8L5evH(#&CSi8%>?%gkZ$ zG;R;?y#JKq-DUsc98 J@S+$Y`Y%9~)?EMq literal 0 HcmV?d00001 diff --git a/examples/sctk_todos/iced-todos.desktop b/examples/sctk_todos/iced-todos.desktop new file mode 100644 index 0000000000..dd7ce53dad --- /dev/null +++ b/examples/sctk_todos/iced-todos.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Name=Todos - Iced +Exec=iced-todos +Type=Application diff --git a/examples/sctk_todos/index.html b/examples/sctk_todos/index.html new file mode 100644 index 0000000000..ee5570fb9e --- /dev/null +++ b/examples/sctk_todos/index.html @@ -0,0 +1,12 @@ + + + + + + Todos - Iced + + + + + + diff --git a/examples/sctk_todos/src/main.rs b/examples/sctk_todos/src/main.rs new file mode 100644 index 0000000000..ce8e2c3f9e --- /dev/null +++ b/examples/sctk_todos/src/main.rs @@ -0,0 +1,648 @@ +use env_logger::Env; +use iced::alignment::{self, Alignment}; +use iced::event::{self, listen_raw, Event}; +use iced::platform_specific::shell::commands::layer_surface::{ + get_layer_surface, Anchor, +}; +use iced::theme::{self, Theme}; +use iced::widget::{ + self, button, checkbox, column, container, row, scrollable, text, + text_input, Text, +}; +use iced::window::Settings; +use iced::{window, Application, Element, Program, Task}; +use iced::{Color, Font, Length, Subscription}; +use iced_core::id::Id; +use iced_core::keyboard::key::Named; +use iced_core::layout::Limits; +use iced_core::{id, keyboard}; + +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +static INPUT_ID: Lazy = Lazy::new(|| text_input::Id::unique()); + +pub fn main() -> iced::Result { + let env = Env::default() + .filter_or("MY_LOG_LEVEL", "info") + .write_style_or("MY_LOG_STYLE", "always"); + + env_logger::init_from_env(env); + iced::daemon(Todos::title, Todos::update, Todos::view) + .subscription(Todos::subscription) + .font(include_bytes!("../fonts/icons.ttf").as_slice()) + .run_with(Todos::new) +} + +#[derive(Debug)] +enum Todos { + Loading, + Loaded(State), +} + +#[derive(Debug, Default)] +struct State { + window_id_ctr: u128, + input_value: String, + filter: Filter, + tasks: Vec, + dirty: bool, + saving: bool, +} + +#[derive(Clone)] +enum Message { + Loaded(Result), + Saved(Result<(), SaveError>), + InputChanged(String), + CreateTask, + FilterChanged(Filter), + TaskMessage(usize, TaskMessage), + TabPressed { shift: bool }, + CloseRequested(window::Id), + Ignore, +} + +impl Debug for Message { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Message::Loaded(_) => write!(f, "Message::Loaded(_)"), + Message::Saved(_) => write!(f, "Message::Saved(_)"), + Message::InputChanged(_) => write!(f, "Message::InputChanged(_)"), + Message::CreateTask => write!(f, "Message::CreateTask"), + Message::FilterChanged(_) => write!(f, "Message::FilterChanged(_)"), + Message::TaskMessage(_, _) => { + write!(f, "Message::TaskMessage(_, _)") + } + Message::TabPressed { shift: _ } => { + write!(f, "Message::TabPressed {{ shift: _ }}") + } + Message::CloseRequested(_) => { + write!(f, "Message::CloseRequested(_)") + } + + Message::Ignore => write!(f, "Message::Ignore"), + } + } +} + +impl Todos { + fn new() -> (Todos, Task) { + ( + Todos::Loading, + Task::batch(vec![ + Task::perform(SavedState::load(), Message::Loaded), + get_layer_surface(iced::platform_specific::runtime::wayland::layer_surface::SctkLayerSurfaceSettings { + size: Some((None, Some(500))), + pointer_interactivity: true, + keyboard_interactivity: sctk::shell::wlr_layer::KeyboardInteractivity::OnDemand, + anchor: Anchor::LEFT.union(Anchor::RIGHT).union(Anchor::TOP), + ..Default::default() + }), + ]), + ) + } + + fn title(&self, _id: window::Id) -> String { + let dirty = match self { + Todos::Loading => false, + Todos::Loaded(state) => state.dirty, + }; + + format!("Todos{} - Iced", if dirty { "*" } else { "" }) + } + + fn update(&mut self, message: Message) -> Task { + match self { + Todos::Loading => { + match message { + Message::Loaded(Ok(state)) => { + *self = Todos::Loaded(State { + input_value: state.input_value, + filter: state.filter, + tasks: state.tasks, + window_id_ctr: 1, + ..State::default() + }); + } + Message::Loaded(Err(_)) => { + *self = Todos::Loaded(State::default()); + } + _ => {} + } + + text_input::focus(INPUT_ID.clone()) + } + Todos::Loaded(state) => { + let mut saved = false; + + let command = match message { + Message::InputChanged(value) => { + state.input_value = value; + + Task::none() + } + Message::CreateTask => { + if !state.input_value.is_empty() { + state + .tasks + .push(MyTask::new(state.input_value.clone())); + state.input_value.clear(); + } + Task::none() + } + Message::FilterChanged(filter) => { + state.filter = filter; + + Task::none() + } + Message::TaskMessage(i, TaskMessage::Delete) => { + state.tasks.remove(i); + + Task::none() + } + Message::TaskMessage(i, task_message) => { + if let Some(task) = state.tasks.get_mut(i) { + let should_focus = + matches!(task_message, TaskMessage::Edit); + + task.update(task_message); + + if should_focus { + let id = MyTask::text_input_id(i); + Task::batch(vec![ + text_input::focus(INPUT_ID.clone()), + text_input::select_all(INPUT_ID.clone()), + ]) + } else { + Task::none() + } + } else { + Task::none() + } + } + Message::Saved(_) => { + state.saving = false; + saved = true; + + Task::none() + } + Message::TabPressed { shift } => { + if shift { + widget::focus_previous() + } else { + widget::focus_next() + } + } + Message::CloseRequested(_) => { + std::process::exit(0); + } + _ => Task::none(), + }; + + if !saved { + state.dirty = true; + } + + let save = if state.dirty && !state.saving { + state.dirty = false; + state.saving = true; + + Task::perform( + SavedState { + input_value: state.input_value.clone(), + filter: state.filter, + tasks: state.tasks.clone(), + } + .save(), + Message::Saved, + ) + } else { + Task::none() + }; + + Task::batch(vec![command, save]) + } + } + } + + fn view(&self, id: window::Id) -> Element { + match self { + Todos::Loading => loading_message(), + Todos::Loaded(State { + input_value, + filter, + tasks, + window_id_ctr, + .. + }) => { + let title = text("todos") + .width(Length::Fill) + .size(100) + .color([0.5, 0.5, 0.5]); + + let input = text_input("What needs to be done?", input_value) + .id(INPUT_ID.clone()) + .padding(15) + .size(30) + .on_submit(Message::CreateTask) + .on_input(Message::InputChanged) + .on_paste(Message::InputChanged); + + let controls = view_controls(tasks, *filter); + let filtered_tasks = + tasks.iter().filter(|task| filter.matches(task)); + + let tasks: Element<_> = if filtered_tasks.count() > 0 { + column( + tasks + .iter() + .enumerate() + .filter(|(_, task)| filter.matches(task)) + .map(|(i, task)| { + task.view(i).map(move |message| { + Message::TaskMessage(i, message) + }) + }) + .collect::>(), + ) + .spacing(10) + .into() + } else { + empty_message(match filter { + Filter::All => "You have not created a task yet...", + Filter::Active => "All your tasks are done! :D", + Filter::Completed => { + "You have not completed a task yet..." + } + }) + }; + + let content = column![title, input, controls, tasks] + .spacing(20) + .max_width(800); + + scrollable( + container(content) + .width(Length::Fill) + .padding(40) + .center_x(Length::Fill), + ) + .into() + } + } + } + + fn subscription(&self) -> Subscription { + listen_raw(|event, status, window| { + // dbg!(&event); + match (event, status, window) { + ( + Event::Keyboard(keyboard::Event::KeyPressed { + key: keyboard::Key::Named(Named::Tab), + modifiers, + .. + }), + event::Status::Ignored, + _, + ) => Some(Message::TabPressed { + shift: modifiers.shift(), + }), + ( + Event::PlatformSpecific(event::PlatformSpecific::Wayland( + event::wayland::Event::Window(e), + )), + _, + _, + ) => { + dbg!(&e); + None + } + _ => None, + } + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct MyTask { + description: String, + completed: bool, + + #[serde(skip)] + state: TaskState, +} + +#[derive(Debug, Clone)] +pub enum TaskState { + Idle, + Editing, +} + +impl Default for TaskState { + fn default() -> Self { + Self::Idle + } +} + +#[derive(Debug, Clone)] +pub enum TaskMessage { + Completed(bool), + Edit, + DescriptionEdited(String), + FinishEdition, + Delete, +} + +impl MyTask { + fn text_input_id(i: usize) -> text_input::Id { + text_input::Id::new(format!("task-{}", i)) + } + + fn new(description: String) -> Self { + MyTask { + description, + completed: false, + state: TaskState::Idle, + } + } + + fn update(&mut self, message: TaskMessage) { + match message { + TaskMessage::Completed(completed) => { + self.completed = completed; + } + TaskMessage::Edit => { + self.state = TaskState::Editing; + } + TaskMessage::DescriptionEdited(new_description) => { + self.description = new_description; + } + TaskMessage::FinishEdition => { + if !self.description.is_empty() { + self.state = TaskState::Idle; + } + } + TaskMessage::Delete => {} + } + } + + fn view(&self, i: usize) -> Element { + match &self.state { + TaskState::Idle => { + let checkbox = checkbox(&self.description, self.completed) + .width(Length::Fill) + .on_toggle(TaskMessage::Completed); + + row![ + checkbox, + button(edit_icon()) + .on_press(TaskMessage::Edit) + .padding(10) + .style(button::text), + ] + .spacing(20) + .align_y(Alignment::Center) + .into() + } + TaskState::Editing => { + let text_input = + text_input("Describe your task...", &self.description) + .id(Self::text_input_id(i)) + .on_submit(TaskMessage::FinishEdition) + .on_input(TaskMessage::DescriptionEdited) + .on_paste(TaskMessage::DescriptionEdited) + .padding(10); + + row![ + text_input, + button(row![delete_icon(), "Delete"].spacing(10)) + .on_press(TaskMessage::Delete) + .padding(10) + .style(button::danger) + ] + .spacing(20) + .align_y(Alignment::Center) + .into() + } + } + } +} + +fn view_controls(tasks: &[MyTask], current_filter: Filter) -> Element { + let tasks_left = tasks.iter().filter(|task| !task.completed).count(); + + let filter_button = |label, filter, current_filter| { + let label = text(label).size(16); + + let button = button(label).style(if filter == current_filter { + button::primary + } else { + button::text + }); + + button.on_press(Message::FilterChanged(filter)).padding(8) + }; + + row![ + text(format!( + "{} {} left", + tasks_left, + if tasks_left == 1 { "task" } else { "tasks" } + )) + .width(Length::Fill) + .size(16), + row![ + filter_button("All", Filter::All, current_filter), + filter_button("Active", Filter::Active, current_filter), + filter_button("Completed", Filter::Completed, current_filter,), + ] + .width(Length::Shrink) + .spacing(10) + ] + .spacing(20) + .align_y(Alignment::Center) + .into() +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Filter { + All, + Active, + Completed, +} + +impl Default for Filter { + fn default() -> Self { + Filter::All + } +} + +impl Filter { + fn matches(&self, task: &MyTask) -> bool { + match self { + Filter::All => true, + Filter::Active => !task.completed, + Filter::Completed => task.completed, + } + } +} + +fn loading_message<'a>() -> Element<'a, Message> { + container(text("Loading...").size(50)) + .width(Length::Fill) + .height(Length::Fill) + .center_y(Length::Fill) + .into() +} + +fn empty_message(message: &str) -> Element<'_, Message> { + container( + text(message) + .width(Length::Fill) + .size(25) + .color([0.7, 0.7, 0.7]), + ) + .width(Length::Fill) + .height(Length::Fixed(200.0)) + .center_y(Length::Fill) + .into() +} + +// Fonts +const ICONS: Font = Font::with_name("Iced-Todos-Icons"); + +fn icon(unicode: char) -> Text<'static> { + text(unicode.to_string()) + .font(ICONS) + .width(Length::Fixed(20.0)) + .size(20) +} + +fn edit_icon() -> Text<'static> { + icon('\u{F303}') +} + +fn delete_icon() -> Text<'static> { + icon('\u{F1F8}') +} + +// Persistence +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SavedState { + input_value: String, + filter: Filter, + tasks: Vec, +} + +#[derive(Debug, Clone)] +enum LoadError { + File, + Format, +} + +#[derive(Debug, Clone)] +enum SaveError { + File, + Write, + Format, +} + +#[cfg(not(target_arch = "wasm32"))] +impl SavedState { + fn path() -> std::path::PathBuf { + let mut path = if let Some(project_dirs) = + directories_next::ProjectDirs::from("rs", "Iced", "Todos") + { + project_dirs.data_dir().into() + } else { + std::env::current_dir().unwrap_or_default() + }; + + path.push("todos.json"); + + path + } + + async fn load() -> Result { + use async_std::prelude::*; + + let mut contents = String::new(); + + let mut file = async_std::fs::File::open(Self::path()) + .await + .map_err(|_| LoadError::File)?; + + file.read_to_string(&mut contents) + .await + .map_err(|_| LoadError::File)?; + + serde_json::from_str(&contents).map_err(|_| LoadError::Format) + } + + async fn save(self) -> Result<(), SaveError> { + use async_std::prelude::*; + + let json = serde_json::to_string_pretty(&self) + .map_err(|_| SaveError::Format)?; + + let path = Self::path(); + + if let Some(dir) = path.parent() { + async_std::fs::create_dir_all(dir) + .await + .map_err(|_| SaveError::File)?; + } + + { + let mut file = async_std::fs::File::create(path) + .await + .map_err(|_| SaveError::File)?; + + file.write_all(json.as_bytes()) + .await + .map_err(|_| SaveError::Write)?; + } + + // This is a simple way to save at most once every couple seconds + async_std::task::sleep(std::time::Duration::from_secs(2)).await; + + Ok(()) + } +} + +#[cfg(target_arch = "wasm32")] +impl SavedState { + fn storage() -> Option { + let window = web_sys::window()?; + + window.local_storage().ok()? + } + + async fn load() -> Result { + let storage = Self::storage().ok_or(LoadError::File)?; + + let contents = storage + .get_item("state") + .map_err(|_| LoadError::File)? + .ok_or(LoadError::File)?; + + serde_json::from_str(&contents).map_err(|_| LoadError::Format) + } + + async fn save(self) -> Result<(), SaveError> { + let storage = Self::storage().ok_or(SaveError::File)?; + + let json = serde_json::to_string_pretty(&self) + .map_err(|_| SaveError::Format)?; + + storage + .set_item("state", &json) + .map_err(|_| SaveError::Write)?; + + let _ = wasm_timer::Delay::new(std::time::Duration::from_secs(2)).await; + + Ok(()) + } +} diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml index 78208fb0b0..4be61237af 100644 --- a/examples/svg/Cargo.toml +++ b/examples/svg/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["svg"] +iced.features = ["svg", "winit", "tokio"] diff --git a/examples/system_information/Cargo.toml b/examples/system_information/Cargo.toml deleted file mode 100644 index 419031227e..0000000000 --- a/examples/system_information/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "system_information" -version = "0.1.0" -authors = ["Richard "] -edition = "2021" -publish = false - -[dependencies] -iced.workspace = true -iced.features = ["system"] - -bytesize = "1.1" diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs deleted file mode 100644 index 363df59000..0000000000 --- a/examples/system_information/src/main.rs +++ /dev/null @@ -1,142 +0,0 @@ -use iced::widget::{button, center, column, text}; -use iced::{system, Element, Task}; - -pub fn main() -> iced::Result { - iced::application( - "System Information - Iced", - Example::update, - Example::view, - ) - .run() -} - -#[derive(Default)] -#[allow(clippy::large_enum_variant)] -enum Example { - #[default] - Loading, - Loaded { - information: system::Information, - }, -} - -#[derive(Clone, Debug)] -#[allow(clippy::large_enum_variant)] -enum Message { - InformationReceived(system::Information), - Refresh, -} - -impl Example { - fn update(&mut self, message: Message) -> Task { - match message { - Message::Refresh => { - *self = Self::Loading; - - return system::fetch_information() - .map(Message::InformationReceived); - } - Message::InformationReceived(information) => { - *self = Self::Loaded { information }; - } - } - - Task::none() - } - - fn view(&self) -> Element { - use bytesize::ByteSize; - - let content: Element<_> = match self { - Example::Loading => text("Loading...").size(40).into(), - Example::Loaded { information } => { - let system_name = text!( - "System name: {}", - information - .system_name - .as_ref() - .unwrap_or(&"unknown".to_string()) - ); - - let system_kernel = text!( - "System kernel: {}", - information - .system_kernel - .as_ref() - .unwrap_or(&"unknown".to_string()) - ); - - let system_version = text!( - "System version: {}", - information - .system_version - .as_ref() - .unwrap_or(&"unknown".to_string()) - ); - - let system_short_version = text!( - "System short version: {}", - information - .system_short_version - .as_ref() - .unwrap_or(&"unknown".to_string()) - ); - - let cpu_brand = - text!("Processor brand: {}", information.cpu_brand); - - let cpu_cores = text!( - "Processor cores: {}", - information - .cpu_cores - .map_or("unknown".to_string(), |cores| cores - .to_string()) - ); - - let memory_readable = - ByteSize::b(information.memory_total).to_string(); - - let memory_total = text!( - "Memory (total): {} bytes ({memory_readable})", - information.memory_total, - ); - - let memory_text = if let Some(memory_used) = - information.memory_used - { - let memory_readable = ByteSize::b(memory_used).to_string(); - - format!("{memory_used} bytes ({memory_readable})") - } else { - String::from("None") - }; - - let memory_used = text!("Memory (used): {memory_text}"); - - let graphics_adapter = - text!("Graphics adapter: {}", information.graphics_adapter); - - let graphics_backend = - text!("Graphics backend: {}", information.graphics_backend); - - column![ - system_name.size(30), - system_kernel.size(30), - system_version.size(30), - system_short_version.size(30), - cpu_brand.size(30), - cpu_cores.size(30), - memory_total.size(30), - memory_used.size(30), - graphics_adapter.size(30), - graphics_backend.size(30), - button("Refresh").on_press(Message::Refresh) - ] - .spacing(10) - .into() - } - }; - - center(content).into() - } -} diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 8f6a836e50..7b19aee0bd 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -167,7 +167,9 @@ mod toast { use iced::advanced::layout::{self, Layout}; use iced::advanced::overlay; use iced::advanced::renderer; - use iced::advanced::widget::{self, Operation, Tree}; + use iced::advanced::widget::{ + self, Operation, OperationOutputWrapper, Tree, + }; use iced::advanced::{Clipboard, Shell, Widget}; use iced::event::{self, Event}; use iced::mouse; @@ -315,7 +317,7 @@ mod toast { .collect() } - fn diff(&self, tree: &mut Tree) { + fn diff(&mut self, tree: &mut Tree) { let instants = tree.state.downcast_mut::>>(); // Invalidating removed instants to None allows us to remove @@ -336,8 +338,8 @@ mod toast { } tree.diff_children( - &std::iter::once(&self.content) - .chain(self.toasts.iter()) + &mut std::iter::once(&mut self.content) + .chain(self.toasts.iter_mut()) .collect::>(), ); } diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 0d72be8695..b67dfb41c7 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -7,7 +7,19 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["async-std", "debug"] +iced_core.workspace = true +# iced.features = ["async-std", "debug", "winit", "a11y", "tiny-skia"] +# TODO(POP): Fix a11y not working with new winit +iced.features = [ + "a11y", + "tokio", + "debug", + "winit", + "tiny-skia", + "wgpu", + "svg", + "web-colors", +] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -28,6 +40,14 @@ wasm-timer.workspace = true [package.metadata.deb] assets = [ - ["target/release-opt/todos", "usr/bin/iced-todos", "755"], - ["iced-todos.desktop", "usr/share/applications/", "644"], + [ + "target/release-opt/todos", + "usr/bin/iced-todos", + "755", + ], + [ + "iced-todos.desktop", + "usr/share/applications/", + "644", + ], ] diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 25e3ead2a6..7ad83ec83e 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,10 +1,18 @@ -use iced::keyboard; +use std::{borrow::Cow, convert::Infallible}; + +use iced::alignment::{self, Alignment}; +use iced::clipboard; +use iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; +use iced::font::{self, Font}; +use iced::futures::task; +use iced::keyboard::{self, Modifiers}; use iced::widget::{ self, button, center, checkbox, column, container, keyed_column, row, scrollable, text, text_input, Text, }; use iced::window; -use iced::{Center, Element, Fill, Font, Subscription, Task as Command}; +use iced::{Center, Element, Fill, Length, Subscription, Task as Command}; +use iced_core::widget::Id; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -45,6 +53,37 @@ enum Message { TaskMessage(usize, TaskMessage), TabPressed { shift: bool }, ToggleFullscreen(window::Mode), + Dbg(Option), +} + +pub struct MyText(pub String); +impl AllowedMimeTypes for MyText { + fn allowed() -> std::borrow::Cow<'static, [String]> { + Cow::Owned(vec!["text/plain".to_string()].into()) + } +} + +impl TryFrom<(Vec, String)> for MyText { + type Error = Infallible; + + fn try_from(value: (Vec, String)) -> Result { + Ok(MyText( + String::from_utf8_lossy(value.0.as_ref()).to_string(), + )) + } +} + +impl AsMimeTypes for MyText { + fn available(&self) -> std::borrow::Cow<'static, [String]> { + Cow::Owned(vec!["text/plain".to_string()]) + } + + fn as_bytes( + &self, + mime_type: &str, + ) -> Option> { + (mime_type == "text/plain").then(|| self.0.clone().into_bytes().into()) + } } impl Todos { @@ -89,9 +128,12 @@ impl Todos { let command = match message { Message::InputChanged(value) => { - state.input_value = value; - - Command::none() + state.input_value = value.clone(); + Command::batch(vec![ + clipboard::write_data(MyText(value)), + clipboard::read_data() + .map(|s| Message::Dbg(s.map(|s: MyText| s.0))), + ]) } Message::CreateTask => { if !state.input_value.is_empty() { @@ -103,6 +145,10 @@ impl Todos { Command::none() } + Message::Dbg(s) => { + dbg!(s); + Command::none() + } Message::FilterChanged(filter) => { state.filter = filter; @@ -180,6 +226,9 @@ impl Todos { } fn view(&self) -> Element { + // row![ + // button("Press me").on_press(Message::ToggleFullscreen(window::Mode::Fullscreen)) + // ].into() match self { Todos::Loading => loading_message(), Todos::Loaded(State { @@ -232,8 +281,9 @@ impl Todos { } }) }; + let test = row![container(text("0000 0000 00000 000000000000 000000000000000 00000 0000 00000000 000000 000000000 l00000")).width(Length::Fill), container(text("a")).width(Length::Fixed(100.0))]; - let content = column![title, input, controls, tasks] + let content = column![title, input, controls, tasks, test] .spacing(20) .max_width(800); diff --git a/examples/tooltip/Cargo.toml b/examples/tooltip/Cargo.toml index 57bb0dcb44..a587cdd200 100644 --- a/examples/tooltip/Cargo.toml +++ b/examples/tooltip/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug"] +iced.features = ["debug", "winit", "tiny-skia"] diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 9e984ad124..266b4fcd9c 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["image", "debug"] +iced.features = ["image", "debug", "winit"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tracing-subscriber = "0.3" diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 8b1efb4125..1a19bd8ea1 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -11,6 +11,7 @@ pub fn main() -> iced::Result { .subscription(WebSocket::subscription) .run_with(WebSocket::new) } +use iced::id::Id; struct WebSocket { messages: Vec, diff --git a/futures/Cargo.toml b/futures/Cargo.toml index a6fcfde13b..1d09a42533 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -19,6 +19,7 @@ all-features = true [features] thread-pool = ["futures/thread-pool"] +a11y = ["iced_core/a11y"] [dependencies] iced_core.workspace = true diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index eaea1a1fd6..0caed66c9f 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -344,6 +344,7 @@ struct Map where F: Fn(A) -> B + 'static, { + id: TypeId, recipe: Box>, mapper: F, } @@ -353,7 +354,11 @@ where F: Fn(A) -> B + 'static, { fn new(recipe: Box>, mapper: F) -> Self { - Map { recipe, mapper } + Map { + id: TypeId::of::(), + recipe, + mapper, + } } } @@ -366,7 +371,7 @@ where type Output = B; fn hash(&self, state: &mut Hasher) { - TypeId::of::().hash(state); + self.id.hash(state); self.recipe.hash(state); } diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 3026bead46..d4db344377 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -136,6 +136,18 @@ pub enum SurfaceError { /// There is no more memory left to allocate a new frame. #[error("There is no more memory left to allocate a new frame")] OutOfMemory, + /// Resize Error + #[error("Resize Error")] + Resize, + /// Invalid dimensions + #[error("Invalid dimensions")] + InvalidDimensions, + /// Present Error + #[error("Present Error")] + Present(String), + /// Present Error + #[error("No damage to present")] + NoDamage, } /// Contains information about the graphics (e.g. graphics adapter, graphics backend). diff --git a/graphics/src/geometry/text.rs b/graphics/src/geometry/text.rs index 90147f8796..b3f381470e 100644 --- a/graphics/src/geometry/text.rs +++ b/graphics/src/geometry/text.rs @@ -44,7 +44,7 @@ impl Text { let mut buffer = cosmic_text::BufferLine::new( &self.content, cosmic_text::LineEnding::default(), - cosmic_text::AttrsList::new(text::to_attributes(self.font)), + cosmic_text::AttrsList::new(&text::to_attributes(self.font)), text::to_shaping(self.shaping), ); @@ -54,7 +54,7 @@ impl Text { None, cosmic_text::Wrap::None, None, - 4, + 8, ); let translation_x = match self.horizontal_alignment { @@ -174,12 +174,12 @@ impl Default for Text { content: String::new(), position: Point::ORIGIN, color: Color::BLACK, - size: Pixels(16.0), - line_height: LineHeight::Relative(1.2), + size: Pixels(14.0), + line_height: LineHeight::default(), font: Font::default(), horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, - shaping: Shaping::Basic, + shaping: Shaping::Advanced, } } } diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 67a5e0cf35..fcafb27aa0 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -4,24 +4,39 @@ pub use ::image as image_rs; use crate::core::image; use crate::core::svg; +use crate::core::Color; +use crate::core::Radians; use crate::core::Rectangle; /// A raster or vector image. #[derive(Debug, Clone, PartialEq)] pub enum Image { /// A raster image. - Raster(image::Image, Rectangle), + Raster { + /// The image handle + handle: image::Image, + /// The bounds of the image. + bounds: Rectangle, + }, /// A vector image. - Vector(svg::Svg, Rectangle), + Vector { + /// The handle of a vector image. + handle: svg::Svg, + + /// The bounds of the image. + bounds: Rectangle, + }, } impl Image { /// Returns the bounds of the [`Image`]. pub fn bounds(&self) -> Rectangle { match self { - Image::Raster(image, bounds) => bounds.rotate(image.rotation), - Image::Vector(svg, bounds) => bounds.rotate(svg.rotation), + Image::Raster { handle, bounds } => bounds.rotate(handle.rotation), + Image::Vector { handle, bounds, .. } => { + bounds.rotate(handle.rotation) + } } } } @@ -85,7 +100,9 @@ pub fn load( let (width, height, pixels) = match handle { image::Handle::Path(_, path) => { - let image = ::image::open(path)?; + let image = ::image::io::Reader::open(&path)? + .with_guessed_format()? + .decode()?; let operation = std::fs::File::open(path) .ok() diff --git a/graphics/src/settings.rs b/graphics/src/settings.rs index 2e8275c699..02a81947ac 100644 --- a/graphics/src/settings.rs +++ b/graphics/src/settings.rs @@ -9,7 +9,7 @@ pub struct Settings { /// The default size of text. /// - /// By default, it will be set to `16.0`. + /// By default, it will be set to `14.0`. pub default_text_size: Pixels, /// The antialiasing strategy that will be used for triangle primitives. @@ -22,7 +22,7 @@ impl Default for Settings { fn default() -> Settings { Settings { default_font: Font::default(), - default_text_size: Pixels(16.0), + default_text_size: Pixels(14.0), antialiasing: None, } } diff --git a/graphics/src/text.rs b/graphics/src/text.rs index feb9932a81..55c2385b6f 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -238,8 +238,15 @@ pub fn measure(buffer: &cosmic_text::Buffer) -> Size { .fold((0.0, 0.0), |(width, height), run| { (run.line_w.max(width), height + run.line_height) }); - Size::new(width, height) + + // let (max_width_opt, max_height_opt) = buffer.size(); + + // Size::new( + // width.min(max_width_opt.unwrap_or(f32::MAX)), + // (buffer.total_lines as f32 * buffer.metrics().line_height) + // .min(max_height_opt.unwrap_or(f32::MAX)), + // ) } /// Returns the attributes of the given [`Font`]. diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs index e64d93f166..7af65b9c97 100644 --- a/graphics/src/text/cache.rs +++ b/graphics/src/text/cache.rs @@ -54,7 +54,7 @@ impl Cache { buffer.set_text( font_system, key.content, - text::to_attributes(key.font), + &text::to_attributes(key.font), text::to_shaping(key.shaping), ); diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 1f1d0050c8..89bc1caee2 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -71,7 +71,7 @@ impl editor::Editor for Editor { buffer.set_text( font_system.raw(), text, - cosmic_text::Attrs::new(), + &cosmic_text::Attrs::new(), cosmic_text::Shaping::Advanced, ); @@ -166,10 +166,8 @@ impl editor::Editor for Editor { .get(cursor.line) .expect("Cursor line should be present"); - let layout = line - .layout_opt() - .as_ref() - .expect("Line layout should be cached"); + let layout = + line.layout_opt().expect("Line layout should be cached"); let mut lines = layout.iter().enumerate(); @@ -467,7 +465,7 @@ impl editor::Editor for Editor { for line in buffer.lines.iter_mut() { let _ = line.set_attrs_list(cosmic_text::AttrsList::new( - text::to_attributes(new_font), + &text::to_attributes(new_font), )); } @@ -575,7 +573,7 @@ impl editor::Editor for Editor { for line in &mut buffer_mut_from_editor(&mut internal.editor).lines [current_line..=last_visible_line] { - let mut list = cosmic_text::AttrsList::new(attributes); + let mut list = cosmic_text::AttrsList::new(&attributes); for (range, highlight) in highlighter.highlight_line(line.text()) { let format = format_highlight(&highlight); @@ -583,12 +581,12 @@ impl editor::Editor for Editor { if format.color.is_some() || format.font.is_some() { list.add_span( range, - cosmic_text::Attrs { + &cosmic_text::Attrs { color_opt: format.color.map(text::to_color), ..if let Some(font) = format.font { text::to_attributes(font) } else { - attributes + attributes.clone() // NOTE(POP-OS): Added clone due to attrslist now requiring a ref } }, ); @@ -674,11 +672,7 @@ fn highlight_line( from: usize, to: usize, ) -> impl Iterator + '_ { - let layout = line - .layout_opt() - .as_ref() - .map(Vec::as_slice) - .unwrap_or_default(); + let layout = line.layout_opt().map(Vec::as_slice).unwrap_or_default(); layout.iter().map(move |visual_line| { let start = visual_line @@ -729,9 +723,7 @@ fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 { let visual_lines_offset: usize = buffer.lines[start..] .iter() .take(end - start) - .map(|line| { - line.layout_opt().as_ref().map(Vec::len).unwrap_or_default() - }) + .map(|line| line.layout_opt().map(Vec::len).unwrap_or_default()) .sum(); visual_lines_offset as i32 * if scroll.line < line { 1 } else { -1 } diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 07ddbb821a..7fdf6d6b24 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -1,8 +1,8 @@ //! Draw paragraphs. use crate::core; use crate::core::alignment; -use crate::core::text::{Hit, Shaping, Span, Text, Wrapping}; -use crate::core::{Font, Point, Rectangle, Size}; +use crate::core::text::{Hit, LineHeight, Shaping, Span, Text, Wrapping}; +use crate::core::{Font, Pixels, Point, Rectangle, Size}; use crate::text; use std::fmt; @@ -80,10 +80,12 @@ impl core::text::Paragraph for Paragraph { Some(text.bounds.height), ); + buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping)); + buffer.set_text( font_system.raw(), text.content, - text::to_attributes(text.font), + &text::to_attributes(text.font), text::to_shaping(text.shaping), ); @@ -150,8 +152,13 @@ impl core::text::Paragraph for Paragraph { (span.text.as_ref(), attrs.metadata(i)) }), - text::to_attributes(text.font), + &text::to_attributes(text.font), text::to_shaping(text.shaping), + Some(match text.horizontal_alignment { + alignment::Horizontal::Left => cosmic_text::Align::Left, + alignment::Horizontal::Center => cosmic_text::Align::Center, + alignment::Horizontal::Right => cosmic_text::Align::Right, + }), ); let min_bounds = text::measure(&buffer); diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs index dc8e21d327..c30d8b05fb 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -10,6 +10,23 @@ pub struct Viewport { } impl Viewport { + /// Creates a new [`Viewport`] with the given logical dimensions and scale factor + pub fn with_logical_size(size: Size, scale_factor: f64) -> Viewport { + let physical_size = Size::new( + (size.width as f64 * scale_factor).ceil() as u32, + (size.height as f64 * scale_factor).ceil() as u32, + ); + Viewport { + physical_size, + logical_size: size, + scale_factor, + projection: Transformation::orthographic( + physical_size.width, + physical_size.height, + ), + } + } + /// Creates a new [`Viewport`] with the given physical dimensions and scale /// factor. pub fn with_physical_size(size: Size, scale_factor: f64) -> Viewport { diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index ac223f16e2..11f7be1d1c 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -14,6 +14,7 @@ keywords.workspace = true workspace = true [features] +default = [] wgpu = ["iced_wgpu"] tiny-skia = ["iced_tiny_skia"] image = ["iced_tiny_skia?/image", "iced_wgpu?/image"] diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/renderer/src/compositor.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 8cb18bdea0..f940f15833 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -77,11 +77,13 @@ where Font = A::Font, Paragraph = A::Paragraph, Editor = A::Editor, + Raw = A::Raw, >, { type Font = A::Font; type Paragraph = A::Paragraph; type Editor = A::Editor; + type Raw = A::Raw; const ICON_FONT: Self::Font = A::ICON_FONT; const CHECKMARK_ICON: char = A::CHECKMARK_ICON; @@ -123,6 +125,10 @@ where ); } + fn fill_raw(&mut self, raw: Self::Raw) { + delegate!(self, renderer, renderer.fill_raw(raw)); + } + fn fill_text( &mut self, text: core::Text, @@ -149,8 +155,27 @@ where delegate!(self, renderer, renderer.measure_image(handle)) } - fn draw_image(&mut self, image: Image, bounds: Rectangle) { - delegate!(self, renderer, renderer.draw_image(image, bounds)); + fn draw_image( + &mut self, + handle: Self::Handle, + filter_method: image::FilterMethod, + bounds: Rectangle, + rotation: crate::core::Radians, + opacity: f32, + border_radius: [f32; 4], + ) { + delegate!( + self, + renderer, + renderer.draw_image( + handle, + filter_method, + bounds, + rotation, + opacity, + border_radius, + ) + ); } } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 703c3ed955..c4c60c254f 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -16,12 +16,19 @@ workspace = true [features] debug = [] multi-window = [] +a11y = ["iced_accessibility", "iced_core/a11y"] +wayland = ["iced_core/wayland", "sctk"] [dependencies] bytes.workspace = true iced_core.workspace = true iced_futures.workspace = true iced_futures.features = ["thread-pool"] - +sctk.workspace = true +sctk.optional = true thiserror.workspace = true raw-window-handle.workspace = true +iced_accessibility.workspace = true +iced_accessibility.optional = true +window_clipboard.workspace = true +dnd.workspace = true diff --git a/runtime/src/clipboard.rs b/runtime/src/clipboard.rs index a02cc011cf..6c0c92db88 100644 --- a/runtime/src/clipboard.rs +++ b/runtime/src/clipboard.rs @@ -1,4 +1,6 @@ //! Access the clipboard. +use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; + use crate::core::clipboard::Kind; use crate::futures::futures::channel::oneshot; use crate::task::{self, Task}; @@ -6,9 +8,8 @@ use crate::task::{self, Task}; /// A clipboard action to be performed by some [`Task`]. /// /// [`Task`]: crate::Task -#[derive(Debug)] pub enum Action { - /// Read the clipboard and produce `T` with the result. + /// Read the clipboard and produce `String` with the result. Read { /// The clipboard target. target: Kind, @@ -23,6 +24,40 @@ pub enum Action { /// The contents to be written. contents: String, }, + + /// Write the given contents to the clipboard. + WriteData(Box, Kind), + + #[allow(clippy::type_complexity)] + /// Read the clipboard and produce `T` with the result. + ReadData( + Vec, + oneshot::Sender, String)>>, + // Box, String)>) -> T + Send + 'static>, + Kind, + ), +} + +impl std::fmt::Debug for Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Read { channel: _, target } => { + write!(f, "Action::Read{target:?}") + } + Self::Write { + contents: _, + target, + } => { + write!(f, "Action::Write({target:?})") + } + Self::WriteData(_, target) => { + write!(f, "Action::WriteData({target:?})") + } + Self::ReadData(_, _, target) => { + write!(f, "Action::ReadData({target:?})") + } + } + } } /// Read the current contents of the clipboard. @@ -60,3 +95,47 @@ pub fn write_primary(contents: String) -> Task { contents, })) } +/// Read the current contents of the clipboard. +pub fn read_data() -> Task> { + task::oneshot(|tx| { + crate::Action::Clipboard(Action::ReadData( + T::allowed().into(), + tx, + Kind::Standard, + )) + }) + .map(|d| d.and_then(|d| T::try_from(d).ok())) +} + +/// Write the given contents to the clipboard. +pub fn write_data( + contents: impl AsMimeTypes + std::marker::Sync + std::marker::Send + 'static, +) -> Task { + task::effect(crate::Action::Clipboard(Action::WriteData( + Box::new(contents), + Kind::Standard, + ))) +} + +/// Read from the primary clipboard +pub fn read_primary_data() -> Task> { + task::oneshot(|tx| { + crate::Action::Clipboard(Action::ReadData( + T::allowed().into(), + tx, + // Box::new(move |d| f(d.and_then(|d| T::try_from(d).ok()))), + Kind::Primary, + )) + }) + .map(|d| d.and_then(|d| T::try_from(d).ok())) +} + +/// Write the given contents to the clipboard. +pub fn write_primary_data( + contents: impl AsMimeTypes + std::marker::Sync + std::marker::Send + 'static, +) -> Task { + task::effect(crate::Action::Clipboard(Action::WriteData( + Box::new(contents), + Kind::Primary, + ))) +} diff --git a/runtime/src/dnd.rs b/runtime/src/dnd.rs new file mode 100644 index 0000000000..2fd3924499 --- /dev/null +++ b/runtime/src/dnd.rs @@ -0,0 +1,129 @@ +//! Access the clipboard. + +use std::any::Any; + +use bytes::buf::Take; +use dnd::{DndDestinationRectangle, DndSurface}; +use iced_core::{clipboard::DndSource, Vector}; +use iced_futures::MaybeSend; +use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; + +use crate::{oneshot, task, Action, Task}; + +/// An action to be performed on the system. +pub enum DndAction { + /// Register a Dnd destination. + RegisterDndDestination { + /// The surface to register. + surface: DndSurface, + /// The rectangles to register. + rectangles: Vec, + }, + /// Start a Dnd operation. + StartDnd { + /// Whether the Dnd operation is internal. + internal: bool, + /// The source surface of the Dnd operation. + source_surface: Option, + /// The icon surface of the Dnd operation. + icon_surface: Option>, + /// The content of the Dnd operation. + content: Box, + /// The actions of the Dnd operation. + actions: dnd::DndAction, + }, + /// End a Dnd operation. + EndDnd, + /// Peek the current Dnd operation. + PeekDnd( + String, + oneshot::Sender, String)>>, + // Box, String)>) -> T + Send + 'static>, + ), + /// Set the action of the Dnd operation. + SetAction(dnd::DndAction), +} + +impl std::fmt::Debug for DndAction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::RegisterDndDestination { + surface, + rectangles, + } => f + .debug_struct("RegisterDndDestination") + .field("surface", surface) + .field("rectangles", rectangles) + .finish(), + Self::StartDnd { + internal, + source_surface, + icon_surface, + content: _, + actions, + } => f + .debug_struct("StartDnd") + .field("internal", internal) + .field("source_surface", source_surface) + .field("icon_surface", icon_surface) + .field("actions", actions) + .finish(), + Self::EndDnd => f.write_str("EndDnd"), + Self::PeekDnd(mime, _) => { + f.debug_struct("PeekDnd").field("mime", mime).finish() + } + Self::SetAction(a) => f.debug_tuple("SetAction").field(a).finish(), + } + } +} + +/// Read the current contents of the Dnd operation. +pub fn peek_dnd() -> Task> { + task::oneshot(|tx| { + Action::Dnd(DndAction::PeekDnd( + T::allowed() + .get(0) + .map_or_else(|| String::new(), |s| s.to_string()), + tx, + )) + }) + .map(|data| data.and_then(|data| T::try_from(data).ok())) +} + +/// Register a Dnd destination. +pub fn register_dnd_destination( + surface: DndSurface, + rectangles: Vec, +) -> Task { + task::effect(Action::Dnd(DndAction::RegisterDndDestination { + surface, + rectangles, + })) +} + +/// Start a Dnd operation. +pub fn start_dnd( + internal: bool, + source_surface: Option, + icon_surface: Option>, + content: Box, + actions: dnd::DndAction, +) -> Task { + task::effect(Action::Dnd(DndAction::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + })) +} + +/// End a Dnd operation. +pub fn end_dnd() -> Task { + task::effect(Action::Dnd(DndAction::EndDnd)) +} + +/// Set the action of the Dnd operation. +pub fn set_action(a: dnd::DndAction) -> Task { + task::effect(Action::Dnd(DndAction::SetAction(a))) +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ae6d1dce82..8516561f90 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -10,9 +10,11 @@ )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod clipboard; +pub mod dnd; pub mod font; pub mod keyboard; pub mod overlay; +pub mod platform_specific; pub mod program; pub mod system; pub mod task; @@ -41,6 +43,7 @@ pub use user_interface::UserInterface; use crate::core::widget; use crate::futures::futures::channel::oneshot; +use dnd::DndAction; use std::borrow::Cow; use std::fmt; @@ -75,6 +78,11 @@ pub enum Action { /// This will normally close any application windows and /// terminate the runtime loop. Exit, + /// Run a Dnd action. + Dnd(crate::dnd::DndAction), + + /// Run a platform specific action + PlatformSpecific(crate::platform_specific::Action), } impl Action { @@ -94,6 +102,8 @@ impl Action { Action::Window(action) => Err(Action::Window(action)), Action::System(action) => Err(Action::System(action)), Action::Exit => Err(Action::Exit), + Action::Dnd(a) => Err(Action::Dnd(a)), + Action::PlatformSpecific(a) => Err(Action::PlatformSpecific(a)), } } } @@ -117,6 +127,10 @@ where Action::Window(_) => write!(f, "Action::Window"), Action::System(action) => write!(f, "Action::System({action:?})"), Action::Exit => write!(f, "Action::Exit"), + Action::PlatformSpecific(action) => { + write!(f, "Action::PlatformSpecific({:?})", action) + } + Action::Dnd(action) => write!(f, "Action::Dnd"), } } } @@ -128,3 +142,39 @@ where pub fn exit() -> Task { task::effect(Action::Exit) } + +/// The appearance of a program. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Appearance { + /// The background [`Color`] of the application. + pub background_color: iced_core::Color, + + /// The default text [`Color`] of the application. + pub text_color: iced_core::Color, + + /// The default icon [`Color`] of the application. + pub icon_color: iced_core::Color, +} + +/// The default style of a [`Program`]. +pub trait DefaultStyle { + /// Returns the default style of a [`Program`]. + fn default_style(&self) -> Appearance; +} + +impl DefaultStyle for iced_core::Theme { + fn default_style(&self) -> Appearance { + default(self) + } +} + +/// The default [`Appearance`] of a [`Program`] with the built-in [`Theme`]. +pub fn default(theme: &iced_core::Theme) -> Appearance { + let palette = theme.extended_palette(); + + Appearance { + background_color: palette.background.base.color, + text_color: palette.background.base.text, + icon_color: palette.background.base.text, + } +} diff --git a/runtime/src/multi_window/state.rs b/runtime/src/multi_window/state.rs index 0bec555f0e..4cb8d7cca3 100644 --- a/runtime/src/multi_window/state.rs +++ b/runtime/src/multi_window/state.rs @@ -231,6 +231,7 @@ where operation::Outcome::Chain(next) => { current_operation = Some(next); } + _ => {} }; } } diff --git a/runtime/src/platform_specific/mod.rs b/runtime/src/platform_specific/mod.rs new file mode 100644 index 0000000000..a8f761b75a --- /dev/null +++ b/runtime/src/platform_specific/mod.rs @@ -0,0 +1,26 @@ +//! Platform specific actions defined for wayland + +use std::{fmt, marker::PhantomData}; + +use iced_futures::MaybeSend; + +#[cfg(feature = "wayland")] +/// Platform specific actions defined for wayland +pub mod wayland; + +/// Platform specific actions defined for wayland +pub enum Action { + /// Wayland Specific Actions + #[cfg(feature = "wayland")] + Wayland(wayland::Action), +} + +impl fmt::Debug for Action { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(feature = "wayland")] + Action::Wayland(action) => action.fmt(_f), + _ => Ok(()), + } + } +} diff --git a/runtime/src/platform_specific/wayland/activation.rs b/runtime/src/platform_specific/wayland/activation.rs new file mode 100644 index 0000000000..64d4dc4dc4 --- /dev/null +++ b/runtime/src/platform_specific/wayland/activation.rs @@ -0,0 +1,42 @@ +use iced_core::window::Id; + +use std::fmt; + +use crate::oneshot; + +/// xdg-activation Actions +pub enum Action { + /// request an activation token + RequestToken { + /// application id + app_id: Option, + /// window, if provided + window: Option, + /// message generation + channel: oneshot::Sender>, + }, + /// request a window to be activated + Activate { + /// window to activate + window: Id, + /// activation token + token: String, + }, +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::RequestToken { app_id, window, .. } => write!( + f, + "Action::ActivationAction::RequestToken {{ app_id: {:?}, window: {:?} }}", + app_id, window, + ), + Action::Activate { window, token } => write!( + f, + "Action::ActivationAction::Activate {{ window: {:?}, token: {:?} }}", + window, token, + ) + } + } +} diff --git a/runtime/src/platform_specific/wayland/layer_surface.rs b/runtime/src/platform_specific/wayland/layer_surface.rs new file mode 100644 index 0000000000..1629a463bd --- /dev/null +++ b/runtime/src/platform_specific/wayland/layer_surface.rs @@ -0,0 +1,185 @@ +use std::fmt; + +use iced_core::layout::Limits; +use sctk::{ + reexports::client::protocol::wl_output::WlOutput, + shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}, +}; + +use iced_core::window::Id; + +/// output for layer surface +#[derive(Debug, Clone)] +pub enum IcedOutput { + /// show on all outputs + All, + /// show on active output + Active, + /// show on a specific output + Output(WlOutput), +} + +impl Default for IcedOutput { + fn default() -> Self { + Self::Active + } +} + +/// margins of the layer surface +#[derive(Debug, Clone, Copy, Default)] +pub struct IcedMargin { + /// top + pub top: i32, + /// right + pub right: i32, + /// bottom + pub bottom: i32, + /// left + pub left: i32, +} + +/// layer surface +#[derive(Debug, Clone)] +pub struct SctkLayerSurfaceSettings { + /// XXX id must be unique for every surface, window, and popup + pub id: Id, + /// layer + pub layer: Layer, + /// keyboard interactivity + pub keyboard_interactivity: KeyboardInteractivity, + /// pointer interactivity + pub pointer_interactivity: bool, + /// anchor, if a surface is anchored to two opposite edges, it will be stretched to fit between those edges, regardless of the specified size in that dimension. + pub anchor: Anchor, + /// output + pub output: IcedOutput, + /// namespace + pub namespace: String, + /// margin + pub margin: IcedMargin, + /// XXX size, providing None will autosize the layer surface to its contents + /// If Some size is provided, None in a given dimension lets the compositor decide for that dimension, usually this would be done with a layer surface that is anchored to left & right or top & bottom + pub size: Option<(Option, Option)>, + /// exclusive zone + pub exclusive_zone: i32, + /// Limits of the popup size + pub size_limits: Limits, +} + +impl Default for SctkLayerSurfaceSettings { + fn default() -> Self { + Self { + id: Id::unique(), + layer: Layer::Top, + keyboard_interactivity: Default::default(), + pointer_interactivity: true, + anchor: Anchor::empty(), + output: Default::default(), + namespace: Default::default(), + margin: Default::default(), + size: Default::default(), + exclusive_zone: Default::default(), + size_limits: Limits::NONE + .min_height(1.0) + .min_width(1.0) + .max_width(1920.0) + .max_height(1080.023), + } + } +} + +#[derive(Clone)] +/// LayerSurface Action +pub enum Action { + /// create a layer surface and receive a message with its Id + LayerSurface { + /// surface builder + builder: SctkLayerSurfaceSettings, + }, + /// Set size of the layer surface. + Size { + /// id of the layer surface + id: Id, + /// The new logical width of the window + width: Option, + /// The new logical height of the window + height: Option, + }, + /// Destroy the layer surface + Destroy(Id), + /// The edges which the layer surface is anchored to + Anchor { + /// id of the layer surface + id: Id, + /// anchor of the layer surface + anchor: Anchor, + }, + /// exclusive zone of the layer surface + ExclusiveZone { + /// id of the layer surface + id: Id, + /// exclusive zone of the layer surface + exclusive_zone: i32, + }, + /// margin of the layer surface, ignored for un-anchored edges + Margin { + /// id of the layer surface + id: Id, + /// margins of the layer surface + margin: IcedMargin, + }, + /// keyboard interactivity of the layer surface + KeyboardInteractivity { + /// id of the layer surface + id: Id, + /// keyboard interactivity of the layer surface + keyboard_interactivity: KeyboardInteractivity, + }, + /// layer of the layer surface + Layer { + /// id of the layer surface + id: Id, + /// layer of the layer surface + layer: Layer, + }, +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::LayerSurface { builder, .. } => write!( + f, + "Action::LayerSurfaceAction::LayerSurface {{ builder: {:?} }}", + builder + ), + Action::Size { id, width, height } => write!( + f, + "Action::LayerSurfaceAction::Size {{ id: {:#?}, width: {:?}, height: {:?} }}", id, width, height + ), + Action::Destroy(id) => write!( + f, + "Action::LayerSurfaceAction::Destroy {{ id: {:#?} }}", id + ), + Action::Anchor { id, anchor } => write!( + f, + "Action::LayerSurfaceAction::Anchor {{ id: {:#?}, anchor: {:?} }}", id, anchor + ), + Action::ExclusiveZone { id, exclusive_zone } => write!( + f, + "Action::LayerSurfaceAction::ExclusiveZone {{ id: {:#?}, exclusive_zone: {exclusive_zone} }}", id + ), + Action::Margin { id, margin } => write!( + f, + "Action::LayerSurfaceAction::Margin {{ id: {:#?}, margin: {:?} }}", id, margin + ), + Action::KeyboardInteractivity { id, keyboard_interactivity } => write!( + f, + "Action::LayerSurfaceAction::Margin {{ id: {:#?}, keyboard_interactivity: {:?} }}", id, keyboard_interactivity + ), + Action::Layer { id, layer } => write!( + f, + "Action::LayerSurfaceAction::Margin {{ id: {:#?}, layer: {:?} }}", id, layer + ), + } + } +} diff --git a/runtime/src/platform_specific/wayland/mod.rs b/runtime/src/platform_specific/wayland/mod.rs new file mode 100644 index 0000000000..c370d38592 --- /dev/null +++ b/runtime/src/platform_specific/wayland/mod.rs @@ -0,0 +1,42 @@ +//! Wayland specific actions + +use std::fmt::Debug; + +/// activation Actions +pub mod activation; + +/// layer surface actions +pub mod layer_surface; +/// popup actions +pub mod popup; +/// session locks +pub mod session_lock; + +/// Platform specific actions defined for wayland +pub enum Action { + /// LayerSurface Actions + LayerSurface(layer_surface::Action), + /// popup + Popup(popup::Action), + /// activation + Activation(activation::Action), + /// session lock + SessionLock(session_lock::Action), +} + +impl Debug for Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::LayerSurface(arg0) => { + f.debug_tuple("LayerSurface").field(arg0).finish() + } + Self::Popup(arg0) => f.debug_tuple("Popup").field(arg0).finish(), + Self::Activation(arg0) => { + f.debug_tuple("Activation").field(arg0).finish() + } + Self::SessionLock(arg0) => { + f.debug_tuple("SessionLock").field(arg0).finish() + } + } + } +} diff --git a/runtime/src/platform_specific/wayland/popup.rs b/runtime/src/platform_specific/wayland/popup.rs new file mode 100644 index 0000000000..615a6d49d7 --- /dev/null +++ b/runtime/src/platform_specific/wayland/popup.rs @@ -0,0 +1,141 @@ +use std::fmt; +use std::hash::{Hash, Hasher}; + +use iced_core::layout::Limits; +use iced_core::window::Id; +use iced_core::Rectangle; +use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{ + Anchor, Gravity, +}; +/// Popup creation details +#[derive(Debug, Clone)] +pub struct SctkPopupSettings { + /// XXX must be unique, id of the parent + pub parent: Id, + /// XXX must be unique, id of the popup + pub id: Id, + /// positioner of the popup + pub positioner: SctkPositioner, + /// optional parent size, must be correct if specified or the behavior is undefined + pub parent_size: Option<(u32, u32)>, + /// whether a grab should be requested for the popup after creation + pub grab: bool, +} + +impl Hash for SctkPopupSettings { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +/// Positioner of a popup +#[derive(Debug, Clone)] +pub struct SctkPositioner { + /// size of the popup (if it is None, the popup will be autosized) + pub size: Option<(u32, u32)>, + /// Limits of the popup size + pub size_limits: Limits, + /// the rectangle which the popup will be anchored to + pub anchor_rect: Rectangle, + /// the anchor location on the popup + pub anchor: Anchor, + /// the gravity of the popup + pub gravity: Gravity, + /// the constraint adjustment, + /// Specify how the window should be positioned if the originally intended position caused the surface to be constrained, meaning at least partially outside positioning boundaries set by the compositor. The adjustment is set by constructing a bitmask describing the adjustment to be made when the surface is constrained on that axis. + /// If no bit for one axis is set, the compositor will assume that the child surface should not change its position on that axis when constrained. + /// + /// If more than one bit for one axis is set, the order of how adjustments are applied is specified in the corresponding adjustment descriptions. + /// + /// The default adjustment is none. + pub constraint_adjustment: u32, + /// offset of the popup + pub offset: (i32, i32), + /// whether the popup is reactive + pub reactive: bool, +} + +impl Hash for SctkPositioner { + fn hash(&self, state: &mut H) { + self.size.hash(state); + self.anchor_rect.x.hash(state); + self.anchor_rect.y.hash(state); + self.anchor_rect.width.hash(state); + self.anchor_rect.height.hash(state); + self.anchor.hash(state); + self.gravity.hash(state); + self.constraint_adjustment.hash(state); + self.offset.hash(state); + self.reactive.hash(state); + } +} + +impl Default for SctkPositioner { + fn default() -> Self { + Self { + size: None, + size_limits: Limits::NONE + .min_height(1.0) + .min_width(1.0) + .max_width(300.0) + .max_height(1080.0), + anchor_rect: Rectangle { + x: 0, + y: 0, + width: 1, + height: 1, + }, + anchor: Anchor::None, + gravity: Gravity::None, + constraint_adjustment: 15, + offset: Default::default(), + reactive: true, + } + } +} + +#[derive(Clone)] +/// Window Action +pub enum Action { + /// create a window and receive a message with its Id + Popup { + /// popup + popup: SctkPopupSettings, + }, + /// destroy the popup + Destroy { + /// id of the popup + id: Id, + }, + /// set the size of the popup + Size { + /// id of the popup + id: Id, + /// width + width: u32, + /// height + height: u32, + }, +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::Popup { popup, .. } => write!( + f, + "Action::PopupAction::Popup {{ popup: {:?} }}", + popup + ), + Action::Destroy { id } => write!( + f, + "Action::PopupAction::Destroy {{ id: {:?} }}", + id + ), + Action::Size { id, width, height } => write!( + f, + "Action::PopupAction::Size {{ id: {:?}, width: {:?}, height: {:?} }}", + id, width, height + ), + } + } +} diff --git a/runtime/src/platform_specific/wayland/session_lock.rs b/runtime/src/platform_specific/wayland/session_lock.rs new file mode 100644 index 0000000000..adbc2390e4 --- /dev/null +++ b/runtime/src/platform_specific/wayland/session_lock.rs @@ -0,0 +1,45 @@ +use std::fmt; + +use iced_core::window::Id; + +use sctk::reexports::client::protocol::wl_output::WlOutput; + +/// Session lock action +#[derive(Clone)] +pub enum Action { + /// Request a session lock + Lock, + /// Destroy lock + Unlock, + /// Create lock surface for output + LockSurface { + /// unique id for surface + id: Id, + /// output + output: WlOutput, + }, + /// Destroy lock surface + DestroyLockSurface { + /// unique id for surface + id: Id, + }, +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::Lock => write!(f, "Action::SessionLock::Lock"), + Action::Unlock => write!(f, "Action::SessionLock::Unlock"), + Action::LockSurface { id, output } => write!( + f, + "Action::SessionLock::LockSurface {{ id: {:?}, output: {:?} }}", + id, output + ), + Action::DestroyLockSurface { id } => write!( + f, + "Action::SessionLock::DestroyLockSurface {{ id: {:?} }}", + id + ), + } + } +} diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index c377814a74..c0befaa2b0 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -1,3 +1,5 @@ +use iced_core::widget::operation::Outcome; + use crate::core::event::{self, Event}; use crate::core::mouse; use crate::core::renderer; @@ -27,12 +29,14 @@ where /// Creates a new [`State`] with the provided [`Program`], initializing its /// primitive with the given logical bounds and renderer. pub fn new( + id: iced_core::id::Id, mut program: P, bounds: Size, renderer: &mut P::Renderer, debug: &mut Debug, ) -> Self { let user_interface = build_user_interface( + id, &mut program, user_interface::Cache::default(), renderer, @@ -88,6 +92,7 @@ where /// after updating it, only if an update was necessary. pub fn update( &mut self, + id: iced_core::id::Id, bounds: Size, cursor: mouse::Cursor, renderer: &mut P::Renderer, @@ -97,6 +102,7 @@ where debug: &mut Debug, ) -> (Vec, Option>) { let mut user_interface = build_user_interface( + id.clone(), &mut self.program, self.cache.take().unwrap(), renderer, @@ -154,6 +160,7 @@ where })); let mut user_interface = build_user_interface( + id, &mut self.program, temp_cache, renderer, @@ -177,12 +184,14 @@ where /// Applies [`Operation`]s to the [`State`] pub fn operate( &mut self, + id: iced_core::id::Id, renderer: &mut P::Renderer, operations: impl Iterator>, bounds: Size, debug: &mut Debug, ) { let mut user_interface = build_user_interface( + id, &mut self.program, self.cache.take().unwrap(), renderer, @@ -202,6 +211,7 @@ where operation::Outcome::Chain(next) => { current_operation = Some(next); } + _ => {} }; } } @@ -211,6 +221,7 @@ where } fn build_user_interface<'a, P: Program>( + _id: iced_core::id::Id, program: &'a mut P, cache: user_interface::Cache, renderer: &mut P::Renderer, diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 8dfc97a728..f6184d144c 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -1,4 +1,9 @@ //! Implement your own event loop to drive a user interface. + +use iced_core::clipboard::DndDestinationRectangles; +use iced_core::widget::tree::NAMED; +use iced_core::widget::Operation; + use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -90,10 +95,15 @@ where cache: Cache, renderer: &mut Renderer, ) -> Self { - let root = root.into(); + let mut root = root.into(); let Cache { mut state } = cache; - state.diff(root.as_widget()); + NAMED.with(|named| { + let mut guard = named.borrow_mut(); + *guard = state.take_all_named(); + }); + + state.diff(root.as_widget_mut()); let base = root.as_widget().layout( &mut state, @@ -101,6 +111,10 @@ where &layout::Limits::new(Size::ZERO, bounds), ); + NAMED.with(|named| { + named.borrow_mut().clear(); + }); + UserInterface { root, base, @@ -609,6 +623,40 @@ where pub fn into_cache(self) -> Cache { Cache { state: self.state } } + + /// get a11y nodes + #[cfg(feature = "a11y")] + pub fn a11y_nodes( + &self, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + self.root.as_widget().a11y_nodes( + Layout::new(&self.base), + &self.state, + cursor, + ) + } + + /// Find widget with given id + pub fn find(&self, id: &widget::Id) -> Option<&widget::Tree> { + self.state.find(id) + } + + /// Get the destination rectangles for the user interface. + pub fn dnd_rectangles( + &self, + prev_capacity: usize, + renderer: &Renderer, + ) -> DndDestinationRectangles { + let mut ret = DndDestinationRectangles::with_capacity(prev_capacity); + self.root.as_widget().drag_destinations( + &self.state, + Layout::new(&self.base), + renderer, + &mut ret, + ); + ret + } } /// Reusable data of a specific [`UserInterface`]. diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 382f4518d4..48c200ec99 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -169,9 +169,31 @@ pub enum Action { /// /// In any case, this [`Subscription`] is useful to smoothly draw application-driven /// animations without missing any frames. -pub fn frames() -> Subscription { +pub fn frames() -> Subscription<(Id, Instant)> { + event::listen_raw(|event, _status, window| match event { + crate::core::Event::Window(Event::RedrawRequested(at)) => { + Some((window, at)) + } + _ => None, + }) +} +#[cfg(feature = "wayland")] +/// 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 wayland_frames() -> Subscription { event::listen_raw(|event, _status, _window| match event { - crate::core::Event::Window(Event::RedrawRequested(at)) => Some(at), + iced_core::Event::Window(Event::RedrawRequested(at)) + | iced_core::Event::PlatformSpecific( + iced_core::event::PlatformSpecific::Wayland( + iced_core::event::wayland::Event::Frame(at, _, _), + ), + ) => Some(at), _ => None, }) } diff --git a/runtime/src/window/screenshot.rs b/runtime/src/window/screenshot.rs index d9adbc010a..06eadf92e1 100644 --- a/runtime/src/window/screenshot.rs +++ b/runtime/src/window/screenshot.rs @@ -6,7 +6,7 @@ use std::fmt::{Debug, Formatter}; /// Data of a screenshot, captured with `window::screenshot()`. /// -/// The `bytes` of this screenshot will always be ordered as `RGBA` in the `sRGB` color space. +/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space. #[derive(Clone)] pub struct Screenshot { /// The bytes of the [`Screenshot`]. diff --git a/src/application.rs b/src/application.rs index 2ba764bed6..acd38062b3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -31,6 +31,10 @@ //! } //! ``` use crate::program::{self, Program}; +#[cfg(not(feature = "winit"))] +use crate::runtime::{Appearance, DefaultStyle}; +#[cfg(feature = "winit")] +pub use crate::shell::program::{Appearance, DefaultStyle}; use crate::window; use crate::{ Element, Executor, Font, Result, Settings, Size, Subscription, Task, @@ -38,8 +42,6 @@ use crate::{ use std::borrow::Cow; -pub use crate::shell::program::{Appearance, DefaultStyle}; - /// Creates an iced [`Application`] given its title, update, and view logic. /// /// # Example @@ -152,6 +154,7 @@ pub struct Application { } impl Application

{ + #[cfg(any(feature = "winit", feature = "wayland"))] /// Runs the [`Application`]. /// /// The state of the [`Application`] must implement [`Default`]. @@ -167,6 +170,7 @@ impl Application

{ self.raw.run(self.settings, Some(self.window)) } + #[cfg(any(feature = "winit", feature = "wayland"))] /// Runs the [`Application`] with a closure that creates the initial state. pub fn run_with(self, initialize: I) -> Result where diff --git a/src/daemon.rs b/src/daemon.rs index 81254bf947..689b5222a4 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,12 +1,15 @@ //! Create and run daemons that run in the background. use crate::application; use crate::program::{self, Program}; +#[cfg(any(feature = "winit", feature = "wayland"))] +pub use crate::shell::program::{Appearance, DefaultStyle}; use crate::window; use crate::{Element, Executor, Font, Result, Settings, Subscription, Task}; -use std::borrow::Cow; +#[cfg(not(any(feature = "winit", feature = "wayland")))] +use crate::runtime::{Appearance, DefaultStyle}; -pub use crate::shell::program::{Appearance, DefaultStyle}; +use std::borrow::Cow; /// Creates an iced [`Daemon`] given its title, update, and view logic. /// @@ -100,6 +103,7 @@ pub struct Daemon { } impl Daemon

{ + #[cfg(any(feature = "winit", feature = "wayland"))] /// Runs the [`Daemon`]. /// /// The state of the [`Daemon`] must implement [`Default`]. @@ -115,6 +119,7 @@ impl Daemon

{ self.raw.run(self.settings, None) } + #[cfg(any(feature = "winit", feature = "wayland"))] /// Runs the [`Daemon`] with a closure that creates the initial state. pub fn run_with(self, initialize: I) -> Result where diff --git a/src/error.rs b/src/error.rs index 111bedf245..3285592f85 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ use crate::futures; use crate::graphics; +#[cfg(any(feature = "winit", feature = "wayland"))] use crate::shell; /// An error that occurred while running an application. @@ -18,12 +19,18 @@ pub enum Error { GraphicsCreationFailed(graphics::Error), } +#[cfg(any(feature = "winit", feature = "wayland"))] impl From for Error { fn from(error: shell::Error) -> Error { match error { shell::Error::ExecutorCreationFailed(error) => { Error::ExecutorCreationFailed(error) } + #[cfg(feature = "winit")] + shell::Error::WindowCreationFailed(error) => { + Error::WindowCreationFailed(Box::new(error)) + } + #[cfg(feature = "wayland")] shell::Error::WindowCreationFailed(error) => { Error::WindowCreationFailed(Box::new(error)) } diff --git a/src/lib.rs b/src/lib.rs index 8f526cfde1..5a0fa80a3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -471,14 +471,19 @@ )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))] -use iced_widget::graphics; -use iced_widget::renderer; -use iced_winit as shell; -use iced_winit::core; -use iced_winit::runtime; + +// #[cfg(all(feature = "wayland", feature = "winit"))] +// compile_error!("cannot use `wayland` feature with `winit"); pub use iced_futures::futures; pub use iced_futures::stream; +pub use iced_widget::core; +use iced_widget::graphics; +use iced_widget::renderer; +pub use iced_widget::runtime; + +#[cfg(feature = "winit")] +use iced_winit as shell; #[cfg(feature = "highlighter")] pub use iced_highlighter as highlighter; @@ -487,7 +492,7 @@ pub use iced_highlighter as highlighter; pub use iced_renderer::wgpu::wgpu; mod error; -mod program; +pub mod program; pub mod application; pub mod daemon; @@ -495,19 +500,44 @@ pub mod settings; pub mod time; pub mod window; +#[cfg(feature = "winit")] +pub mod platform_specific { + pub use iced_winit::{ + platform_specific as shell, runtime::platform_specific as runtime, + }; +} + +#[cfg(feature = "winit")] +pub use application::Application; +#[cfg(feature = "winit")] +pub use program::Program; + +// wayland application +// #[cfg(feature = "wayland")] +// pub mod wayland; +// #[cfg(feature = "wayland")] +// pub use wayland::application; +// #[cfg(feature = "wayland")] +// pub use wayland::application::Application; +// #[cfg(feature = "wayland")] +// pub use wayland::program; +// #[doc(inline)] +// #[cfg(feature = "wayland")] +// pub use wayland::program::Program; + #[cfg(feature = "advanced")] pub mod advanced; pub use crate::core::alignment; -pub use crate::core::border; +pub use crate::core::border::{self, Radius}; pub use crate::core::color; pub use crate::core::gradient; pub use crate::core::padding; pub use crate::core::theme; pub use crate::core::{ - Alignment, Background, Border, Color, ContentFit, Degrees, Gradient, - Length, Padding, Pixels, Point, Radians, Rectangle, Rotation, Shadow, Size, - Theme, Transformation, Vector, + id, layout::Limits, Alignment, Background, Border, Color, ContentFit, + Degrees, Gradient, Length, Padding, Pixels, Point, Radians, Rectangle, + Rotation, Shadow, Size, Theme, Transformation, Vector, }; pub use crate::runtime::exit; pub use iced_futures::Subscription; @@ -525,8 +555,11 @@ pub mod task { pub mod clipboard { //! Access the clipboard. pub use crate::runtime::clipboard::{ - read, read_primary, write, write_primary, + read, read_data, read_primary, read_primary_data, write, write_data, + write_primary, write_primary_data, }; + pub use dnd; + pub use mime; } pub mod executor { @@ -554,6 +587,9 @@ pub mod font { pub mod event { //! Handle events of a user interface. + #[cfg(feature = "wayland")] + pub use crate::core::event::wayland; + pub use crate::core::event::PlatformSpecific; pub use crate::core::event::{Event, Status}; pub use iced_futures::event::{ listen, listen_raw, listen_url, listen_with, @@ -578,6 +614,7 @@ pub mod mouse { pub mod system { //! Retrieve system information. pub use crate::runtime::system::Information; + #[cfg(any(feature = "winit", feature = "wayland"))] pub use crate::shell::system::*; } @@ -618,8 +655,8 @@ pub mod widget { mod runtime {} } -pub use application::Application; -pub use daemon::Daemon; +pub use application::application; +pub use daemon::{daemon, Daemon}; pub use error::Error; pub use event::Event; pub use executor::Executor; @@ -628,11 +665,6 @@ pub use renderer::Renderer; pub use settings::Settings; pub use task::Task; -#[doc(inline)] -pub use application::application; -#[doc(inline)] -pub use daemon::daemon; - /// A generic widget. /// /// This is an alias of an `iced_native` element with a default `Renderer`. @@ -646,6 +678,7 @@ pub type Element< /// The result of running an iced program. pub type Result = std::result::Result<(), Error>; +#[cfg(any(feature = "winit"))] /// Runs a basic iced application with default [`Settings`] given its title, /// update, and view logic. /// diff --git a/src/program.rs b/src/program.rs index 94cb9a7d33..bac6ddfc39 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,10 +1,14 @@ use crate::core::text; use crate::graphics::compositor; +#[cfg(any(feature = "winit", feature = "wayland"))] use crate::shell; +#[cfg(any(feature = "winit", feature = "wayland"))] +pub use crate::shell::program::{Appearance, DefaultStyle}; use crate::window; use crate::{Element, Executor, Result, Settings, Subscription, Task}; -pub use crate::shell::program::{Appearance, DefaultStyle}; +#[cfg(not(any(feature = "winit", feature = "wayland")))] +pub use crate::runtime::{Appearance, DefaultStyle}; /// The internal definition of a [`Program`]. /// @@ -62,6 +66,7 @@ pub trait Program: Sized { 1.0 } + #[cfg(any(feature = "winit", feature = "wayland"))] /// Runs the [`Program`]. /// /// The state of the [`Program`] must implement [`Default`]. @@ -83,6 +88,7 @@ pub trait Program: Sized { }) } + #[cfg(any(feature = "winit", feature = "wayland"))] /// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state. fn run_with( self, @@ -184,6 +190,7 @@ pub trait Program: Sized { default_font: settings.default_font, default_text_size: settings.default_text_size, antialiasing: settings.antialiasing, + exit_on_close_request: settings.exit_on_close_request, } .into(), renderer_settings, diff --git a/src/settings.rs b/src/settings.rs index ebac7a86de..3b48ca1e31 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -28,13 +28,13 @@ pub struct Settings { /// If set to true, the renderer will try to perform antialiasing for some /// primitives. /// - /// Enabling it can produce a smoother result in some widgets, like the - /// [`Canvas`], at a performance cost. + /// Enabling it can produce a smoother result in some widgets /// /// By default, it is disabled. - /// - /// [`Canvas`]: crate::widget::Canvas pub antialiasing: bool, + + /// If set to true the application will exit when the main window is closed. + pub exit_on_close_request: bool, } impl Default for Settings { @@ -43,12 +43,14 @@ impl Default for Settings { id: None, fonts: Vec::new(), default_font: Font::default(), - default_text_size: Pixels(16.0), + default_text_size: Pixels(14.0), antialiasing: false, + exit_on_close_request: false, } } } +#[cfg(feature = "winit")] impl From for iced_winit::Settings { fn from(settings: Settings) -> iced_winit::Settings { iced_winit::Settings { diff --git a/src/wayland/application.rs b/src/wayland/application.rs new file mode 100644 index 0000000000..97bd0313ac --- /dev/null +++ b/src/wayland/application.rs @@ -0,0 +1,211 @@ +use crate::{Element, Executor, Settings as Settings_, Subscription}; +use iced_core::window::Id; + +use crate::core::text; +pub use crate::{platform_specific::wayland as actions, Task}; +use iced_renderer::graphics::{compositor, Antialiasing}; +pub use iced_sctk::application::Appearance; +pub use iced_sctk::{ + application::{DefaultStyle, SurfaceIdWrapper}, + commands::*, + settings::*, +}; + +/// A pure version of [`Application`]. +/// +/// Unlike the impure version, the `view` method of this trait takes an +/// immutable reference to `self` and returns a pure [`Element`]. +pub trait Application: Sized { + /// The [`Executor`] that will run commands and subscriptions. + /// + /// The [default executor] can be a good starting point! + /// + /// [`Executor`]: Self::Executor + /// [default executor]: crate::executor::Default + type Executor: Executor; + + /// The type of __messages__ your [`Application`] will produce. + type Message: std::fmt::Debug + Send; + + /// The theme of your [`Application`]. + type Theme: Default + DefaultStyle; + + /// The renderer of your [`Application`]. + type Renderer: text::Renderer + compositor::Default; + + /// The data needed to initialize your [`Application`]. + type Flags; + + /// Initializes the [`Application`] with the flags provided to + /// [`run`] as part of the [`Settings`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Task`] if you need to perform some + /// async action in the background on startup. This is useful if you want to + /// load state from a file, perform an initial HTTP request, etc. + /// + /// [`run`]: Self::run + fn new(flags: Self::Flags) -> (Self, Task); + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + fn title(&self, id: Id) -> String; + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Task`] returned will be executed immediately in the background. + fn update(&mut self, message: Self::Message) -> Task; + + /// Returns the current [`Theme`] of the [`Application`]. + /// + /// [`Theme`]: Self::Theme + fn theme(&self, _id: Id) -> Self::Theme { + Self::Theme::default() + } + + /// Returns the current Style of the Theme. + fn style(&self, theme: &Self::Theme) -> Appearance { + theme.default_style() + } + + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view( + &self, + id: Id, + ) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>; + + /// Returns the scale factor of the [`Application`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + fn scale_factor(&self, _id: Id) -> f64 { + 1.0 + } + + /// Runs the [`Application`]. + /// + /// On native platforms, this method will take control of the current thread + /// until the [`Application`] exits. + /// + /// On the web platform, this method __will NOT return__ unless there is an + /// [`Error`] during startup. + /// + /// [`Error`]: crate::Error + fn run(settings: Settings_) -> crate::Result + where + Self: 'static, + { + #[allow(clippy::needless_update)] + let renderer_settings = crate::graphics::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: if settings.antialiasing { + Some(Antialiasing::MSAAx4) + } else { + None + }, + ..crate::graphics::Settings::default() + }; + + let run = crate::shell::application::run::< + Instance, + Self::Executor, + ::Compositor, + >(settings.into(), renderer_settings); + #[cfg(target_arch = "wasm32")] + { + use crate::futures::FutureExt; + use iced_futures::backend::wasm::wasm_bindgen::Executor; + + Executor::new() + .expect("Create Wasm executor") + .spawn(run.map(|_| ())); + + Ok(()) + } + + #[cfg(not(target_arch = "wasm32"))] + Ok(crate::futures::executor::block_on(run)?) + } +} + +struct Instance(A); + +impl crate::runtime::multi_window::Program for Instance +where + A: Application, +{ + type Theme = A::Theme; + type Renderer = A::Renderer; + type Message = A::Message; + + fn update(&mut self, message: Self::Message) -> Task { + self.0.update(message) + } + + fn view( + &self, + id: Id, + ) -> Element<'_, Self::Message, Self::Theme, Self::Renderer> { + self.0.view(id) + } +} + +impl crate::shell::Application for Instance +where + A: Application, +{ + type Flags = A::Flags; + + fn new(flags: Self::Flags) -> (Self, Task) { + let (app, command) = A::new(flags); + + (Instance(app), command) + } + + fn title(&self, window: Id) -> String { + self.0.title(window) + } + + fn theme(&self, id: Id) -> A::Theme { + self.0.theme(id) + } + + fn style(&self, theme: &A::Theme) -> Appearance { + self.0.style(theme) + } + + fn subscription(&self) -> Subscription { + self.0.subscription() + } + + fn scale_factor(&self, window: Id) -> f64 { + self.0.scale_factor(window) + } +} diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs new file mode 100644 index 0000000000..1e0107a02d --- /dev/null +++ b/src/wayland/mod.rs @@ -0,0 +1,4 @@ +/// wayland application +pub mod application; +/// wayland program +pub mod program; diff --git a/src/wayland/program.rs b/src/wayland/program.rs new file mode 100644 index 0000000000..c701a6c2c0 --- /dev/null +++ b/src/wayland/program.rs @@ -0,0 +1,833 @@ +//! Create and run iced applications step by step. +//! +//! # Example +//! ```no_run +//! use iced::widget::{button, column, text, Column}; +//! use iced::Theme; +//! +//! pub fn main() -> iced::Result { +//! iced::program("A counter", update, view) +//! .theme(|_| Theme::Dark) +//! .centered() +//! .run() +//! } +//! +//! #[derive(Debug, Clone)] +//! enum Message { +//! Increment, +//! } +//! +//! fn update(value: &mut u64, message: Message) { +//! match message { +//! Message::Increment => *value += 1, +//! } +//! } +//! +//! fn view(value: &u64) -> Column { +//! column![ +//! text(value), +//! button("+").on_press(Message::Increment), +//! ] +//! } +//! ``` +use crate::executor::{self, Executor}; +use crate::window; +use crate::Result; +use crate::Task; + +use iced_core::window::Id; +pub use iced_sctk::application::{Appearance, DefaultStyle}; + +use crate::theme::{self, Theme}; +use crate::{ + wayland::application::Application, Element, Error, Font, Settings, + Subscription, +}; + +use crate::core::text; +use crate::graphics::compositor; + +use std::borrow::Cow; + +/// Creates an iced [`Program`] given its title, update, and view logic. +/// +/// # Example +/// ```no_run +/// use iced::widget::{button, column, text, Column}; +/// +/// pub fn main() -> iced::Result { +/// iced::program("A counter", update, view).run() +/// } +/// +/// #[derive(Debug, Clone)] +/// enum Message { +/// Increment, +/// } +/// +/// fn update(value: &mut u64, message: Message) { +/// match message { +/// Message::Increment => *value += 1, +/// } +/// } +/// +/// fn view(value: &u64) -> Column { +/// column![ +/// text(value), +/// button("+").on_press(Message::Increment), +/// ] +/// } +/// ``` +pub fn program( + title: impl Title, + update: impl Update, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, +) -> Program> +where + State: 'static, + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: self::Renderer, +{ + use std::marker::PhantomData; + + struct Application { + update: Update, + view: View, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + } + + impl Definition + for Application + where + Message: Send + std::fmt::Debug + 'static, + Theme: Default + DefaultStyle, + Renderer: self::Renderer, + Update: self::Update, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, + { + type State = State; + type Message = Message; + type Theme = Theme; + type Renderer = Renderer; + type Executor = executor::Default; + + fn load(&self) -> Task { + Task::none() + } + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task { + self.update.update(state, message).into() + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.view.view(state).into() + } + } + + Program { + raw: Application { + update, + view, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + }, + settings: Settings::default(), + } + .title(title) +} + +/// The underlying definition and configuration of an iced application. +/// +/// You can use this API to create and run iced applications +/// step by step—without coupling your logic to a trait +/// or a specific type. +/// +/// You can create a [`Program`] with the [`program`] helper. +/// +/// [`run`]: Program::run +#[derive(Debug)] +pub struct Program { + raw: P, + settings: Settings, +} + +impl Program

{ + /// Runs the underlying [`Application`] of the [`Program`]. + /// + /// The state of the [`Program`] must implement [`Default`]. + /// If your state does not implement [`Default`], use [`run_with`] + /// instead. + /// + /// [`run_with`]: Self::run_with + pub fn run(self) -> Result + where + Self: 'static, + P::State: Default, + { + self.run_with(P::State::default) + } + + /// Runs the underlying [`Application`] of the [`Program`] with a + /// closure that creates the initial state. + pub fn run_with( + self, + initialize: impl Fn() -> P::State + Clone + 'static, + ) -> Result + where + Self: 'static, + { + use std::marker::PhantomData; + + struct Instance { + program: P, + state: P::State, + _initialize: PhantomData, + } + + impl P::State> Application for Instance { + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Flags = (P, I); + type Executor = P::Executor; + + fn new( + (program, initialize): Self::Flags, + ) -> (Self, Task) { + let state = initialize(); + let command = program.load(); + + ( + Self { + program, + state, + _initialize: PhantomData, + }, + command, + ) + } + + fn title(&self, _id: Id) -> String { + self.program.title(&self.state) + } + + fn update( + &mut self, + message: Self::Message, + ) -> Task { + self.program.update(&mut self.state, message) + } + + fn view( + &self, + _id: Id, + ) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer> + { + self.program.view(&self.state) + } + + fn subscription(&self) -> Subscription { + self.program.subscription(&self.state) + } + + fn theme(&self, _id: Id) -> Self::Theme { + self.program.theme(&self.state) + } + + fn style(&self, theme: &Self::Theme) -> Appearance { + self.program.style(&self.state, theme) + } + } + + let Self { raw, settings } = self; + + Instance::run(Settings { + flags: (raw, initialize), + id: settings.id, + initial_surface: settings.initial_surface, + fonts: settings.fonts, + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + exit_on_close_request: settings.exit_on_close_request, + }) + } + + /// Sets the [`Settings`] that will be used to run the [`Program`]. + pub fn settings(self, settings: Settings) -> Self { + Self { settings, ..self } + } + + /// Sets the [`Settings::antialiasing`] of the [`Program`]. + pub fn antialiasing(self, antialiasing: bool) -> Self { + Self { + settings: Settings { + antialiasing, + ..self.settings + }, + ..self + } + } + + /// Sets the default [`Font`] of the [`Program`]. + pub fn default_font(self, default_font: Font) -> Self { + Self { + settings: Settings { + default_font, + ..self.settings + }, + ..self + } + } + + /// Adds a font to the list of fonts that will be loaded at the start of the [`Program`]. + pub fn font(mut self, font: impl Into>) -> Self { + self.settings.fonts.push(font.into()); + self + } + + /// Sets the [`Title`] of the [`Program`]. + pub(crate) fn title( + self, + title: impl Title, + ) -> Program< + impl Definition, + > { + Program { + raw: with_title(self.raw, title), + settings: self.settings, + } + } + + /// Runs the [`Task`] produced by the closure at startup. + pub fn load( + self, + f: impl Fn() -> Task, + ) -> Program< + impl Definition, + > { + Program { + raw: with_load(self.raw, f), + settings: self.settings, + } + } + + /// Sets the subscription logic of the [`Program`]. + pub fn subscription( + self, + f: impl Fn(&P::State) -> Subscription, + ) -> Program< + impl Definition, + > { + Program { + raw: with_subscription(self.raw, f), + settings: self.settings, + } + } + + /// Sets the theme logic of the [`Program`]. + pub fn theme( + self, + f: impl Fn(&P::State) -> P::Theme, + ) -> Program< + impl Definition, + > { + Program { + raw: with_theme(self.raw, f), + settings: self.settings, + } + } + + /// Sets the style logic of the [`Program`]. + pub fn style( + self, + f: impl Fn(&P::State, &P::Theme) -> Appearance, + ) -> Program< + impl Definition, + > { + Program { + raw: with_style(self.raw, f), + settings: self.settings, + } + } +} + +/// The internal definition of a [`Program`]. +/// +/// You should not need to implement this trait directly. Instead, use the +/// methods available in the [`Program`] struct. +#[allow(missing_docs)] +pub trait Definition: Sized { + /// The state of the program. + type State; + + /// The message of the program. + type Message: Send + std::fmt::Debug + 'static; + + /// The theme of the program. + type Theme: Default + DefaultStyle; + + /// The renderer of the program. + type Renderer: Renderer; + + /// The executor of the program. + type Executor: Executor; + + fn load(&self) -> Task; + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task; + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; + + fn title(&self, _state: &Self::State) -> String { + String::from("A cool iced application!") + } + + fn subscription( + &self, + _state: &Self::State, + ) -> Subscription { + Subscription::none() + } + + fn theme(&self, _state: &Self::State) -> Self::Theme { + Self::Theme::default() + } + + fn style(&self, _state: &Self::State, theme: &Self::Theme) -> Appearance { + DefaultStyle::default_style(theme) + } +} + +fn with_title( + program: P, + title: impl Title, +) -> impl Definition { + struct WithTitle { + program: P, + title: Title, + } + + impl Definition for WithTitle + where + P: Definition, + Title: self::Title, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + + fn load(&self) -> Task { + self.program.load() + } + + fn title(&self, state: &Self::State) -> String { + self.title.title(state) + } + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn subscription( + &self, + state: &Self::State, + ) -> Subscription { + self.program.subscription(state) + } + + fn style( + &self, + state: &Self::State, + theme: &Self::Theme, + ) -> Appearance { + self.program.style(state, theme) + } + } + + WithTitle { program, title } +} + +fn with_load( + program: P, + f: impl Fn() -> Task, +) -> impl Definition { + struct WithLoad { + program: P, + load: F, + } + + impl Definition for WithLoad + where + F: Fn() -> Task, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = executor::Default; + + fn load(&self) -> Task { + Task::batch([self.program.load(), (self.load)()]) + } + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn title(&self, state: &Self::State) -> String { + self.program.title(state) + } + + fn subscription( + &self, + state: &Self::State, + ) -> Subscription { + self.program.subscription(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn style( + &self, + state: &Self::State, + theme: &Self::Theme, + ) -> Appearance { + self.program.style(state, theme) + } + } + + WithLoad { program, load: f } +} + +fn with_subscription( + program: P, + f: impl Fn(&P::State) -> Subscription, +) -> impl Definition { + struct WithSubscription { + program: P, + subscription: F, + } + + impl Definition for WithSubscription + where + F: Fn(&P::State) -> Subscription, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = executor::Default; + + fn subscription( + &self, + state: &Self::State, + ) -> Subscription { + (self.subscription)(state) + } + + fn load(&self) -> Task { + self.program.load() + } + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn title(&self, state: &Self::State) -> String { + self.program.title(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn style( + &self, + state: &Self::State, + theme: &Self::Theme, + ) -> Appearance { + self.program.style(state, theme) + } + } + + WithSubscription { + program, + subscription: f, + } +} + +fn with_theme( + program: P, + f: impl Fn(&P::State) -> P::Theme, +) -> impl Definition { + struct WithTheme { + program: P, + theme: F, + } + + impl Definition for WithTheme + where + F: Fn(&P::State) -> P::Theme, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + + fn theme(&self, state: &Self::State) -> Self::Theme { + (self.theme)(state) + } + + fn load(&self) -> Task { + self.program.load() + } + + fn title(&self, state: &Self::State) -> String { + self.program.title(state) + } + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn subscription( + &self, + state: &Self::State, + ) -> Subscription { + self.program.subscription(state) + } + + fn style( + &self, + state: &Self::State, + theme: &Self::Theme, + ) -> Appearance { + self.program.style(state, theme) + } + } + + WithTheme { program, theme: f } +} + +fn with_style( + program: P, + f: impl Fn(&P::State, &P::Theme) -> Appearance, +) -> impl Definition { + struct WithStyle { + program: P, + style: F, + } + + impl Definition for WithStyle + where + F: Fn(&P::State, &P::Theme) -> Appearance, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + + fn style( + &self, + state: &Self::State, + theme: &Self::Theme, + ) -> Appearance { + (self.style)(state, theme) + } + + fn load(&self) -> Task { + self.program.load() + } + + fn title(&self, state: &Self::State) -> String { + self.program.title(state) + } + + fn update( + &self, + state: &mut Self::State, + message: Self::Message, + ) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn subscription( + &self, + state: &Self::State, + ) -> Subscription { + self.program.subscription(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + } + + WithStyle { program, style: f } +} + +/// The title logic of some [`Program`]. +/// +/// This trait is implemented both for `&static str` and +/// any closure `Fn(&State) -> String`. +/// +/// This trait allows the [`program`] builder to take any of them. +pub trait Title { + /// Produces the title of the [`Program`]. + fn title(&self, state: &State) -> String; +} + +impl Title for &'static str { + fn title(&self, _state: &State) -> String { + self.to_string() + } +} + +impl Title for T +where + T: Fn(&State) -> String, +{ + fn title(&self, state: &State) -> String { + self(state) + } +} + +/// The update logic of some [`Program`]. +/// +/// This trait allows the [`program`] builder to take any closure that +/// returns any `Into>`. +pub trait Update { + /// Processes the message and updates the state of the [`Program`]. + fn update( + &self, + state: &mut State, + message: Message, + ) -> impl Into>; +} + +impl Update for T +where + T: Fn(&mut State, Message) -> C, + C: Into>, +{ + fn update( + &self, + state: &mut State, + message: Message, + ) -> impl Into> { + self(state, message) + } +} + +/// The view logic of some [`Program`]. +/// +/// This trait allows the [`program`] builder to take any closure that +/// returns any `Into>`. +pub trait View<'a, State, Message, Theme, Renderer> { + /// Produces the widget of the [`Program`]. + fn view( + &self, + state: &'a State, + ) -> impl Into>; +} + +impl<'a, T, State, Message, Theme, Renderer, Widget> + View<'a, State, Message, Theme, Renderer> for T +where + T: Fn(&'a State) -> Widget, + State: 'static, + Widget: Into>, +{ + fn view( + &self, + state: &'a State, + ) -> impl Into> { + self(state) + } +} + +/// The renderer of some [`Program`]. +pub trait Renderer: text::Renderer + compositor::Default {} + +impl Renderer for T where T: text::Renderer + compositor::Default {} diff --git a/src/window.rs b/src/window.rs index 9f96da5245..1ceb3f9094 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,8 +1,13 @@ //! Configure the window of your application in native platforms. +#[cfg(feature = "winit")] pub mod icon; +#[cfg(feature = "winit")] pub use icon::Icon; +#[cfg(feature = "winit")] +pub use settings::{PlatformSpecific, Settings}; + pub use crate::core::window::*; pub use crate::runtime::window::*; diff --git a/src/window/icon.rs b/src/window/icon.rs index 7fe4ca7bd1..0856dcb969 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -13,7 +13,10 @@ use std::path::Path; /// This will return an error in case the file is missing at run-time. You may prefer [`from_file_data`] instead. #[cfg(feature = "image")] pub fn from_file>(icon_path: P) -> Result { - let icon = image::io::Reader::open(icon_path)?.decode()?.to_rgba8(); + let icon = ::image::io::Reader::open(icon_path)? + .with_guessed_format()? + .decode()? + .to_rgba8(); Ok(icon::from_rgba(icon.to_vec(), icon.width(), icon.height())?) } diff --git a/tiny_skia/fonts/Iced-Icons.ttf b/tiny_skia/fonts/Iced-Icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e3273141c45fff98c2f411b421e2e3fd225668aa GIT binary patch literal 5108 zcmds5Uu;`f8UN1ppQLdbdrRt;xn;+@Nt(vjPU^O4)@GgOKk?dh%t`;?+X3^9?fmJI z*f`#35f2Cisw_Z*2LwFufT%PHgpQLTm5zr>yub@D2#J4UlMvE235kaxjY+KhzI(6T zrAwK#N1WXI{l4$~&Ue1^o$s9MYZ62h!p%!GJA3h&AKd-wYea*8fc0QLo13M>bO8D- z=bKS{>-+tL7@}!6Y z^<`_b4togx6lCx6+V!RUTfhAh_!a1PR;pIz(0}$C@YN4}Y6S-Wzdc3hS?I$n>&~yQJ74+B_c7!!VwkVjHk)s~k}Y8T zYiOUYH>&l&KNb5W(E!Kub>dv$I{CxbUVT0|^DlbT^Cm3xr#o+7X8A_tbsk4}Zff6y z>?Mt@_@Iwx;qF!3ZNBHG?a66#?mpY%qp;hekso*yjR`trQ-tRs@iO^{v@dFih`!-S z%=Df%G;Nx)?U1aB8iUEzq?+XRNoL+D z6eWUaSyJ^CiX~VixAifaVl-8XluXl%C=oMFT~VP}HO;v47&0f7cf>+KzD%L0e0o~> z^)$jal__p7f1iTJyzHA!03Ov7s5uTJga$NZg)su;Bp_uH{_@U})9Vyn+meLlNlqkc$@{Pw;k0Ck8K&E`lON*c& z%v8x_N-1jx*=ua|1SpeBk4L-7-ec@cexKt-#1KLTGcL(oOSgC->`YOFv#DeRL3W~4 zo)O*3Izt2=&|M9q7dm&R_Q6j5Tr+xlE70f36{8V7YK}+aYOm4Ow4AC~*|-`oFaar5 zf94#|3qVhsYA9Smaqth5ToxJXgD>2^Qg_Tr{Q7%Sl!DHPjW zxky&Jh2`a8$D=yBb#TOK=PsyyZZ$EJJmSxnu!uXB>H!Pb#v!dE#Ti9swuBINH4GZm zCM|cdsDgT0=2QSh*@uBkX<5SXKM&zSHbCigtJHqjA5)E3}`H0c>vy2$+9+n4EM zR53%1$J98si-Ahbqoj7-FS<3Y^I7#j8K{@Iig(vQgq1O_#J)*8%ZU$WWoOfbAy3$G zPJ$zHAKtDw1$GUK?G%31esW~b=+o^9u@5sjig_c@UAvS}wbpc8C5^=XnYcRkQB+h| z8T8{=3JQlc0z*8)qK2>oFnr4PpEfI6}gxe*D`MwI>s+wi3FO zGcEXO(k&$g})bog$<^!%5MHJQ=HkGe*l;5#W-Ejd_0`3nAgu)n~2gh>j6vF)s*v6!_a^5Cp;}p%=Rw zx%Z)vaBklh2|QZpJYoldJ02r^gxwnpRdSMZAThdc)XjlDgVaphX4Q+`E9I=7=fQZj z9=B7*6L2$17mEowg9jf+>>AchoV(%qFojB$0!jUdxmni4;I z@1J64DNh@^OmNm_aq7;PiFQm3VdKqo%~;KQ<|n#lo89TUgLB4ECt{tJ9ZsJ#)Ksj6 z8scJUVLN_kU@S{Q#V~%#P8SzoC%ar+I(9_@o5YFnDI69!pu3gYneRuJVt%*6_&C+a zdE;|sMxTyEKd|0W(~U9B$>=0C!}E`F<~c-n9^ENtG39;E=ES2M`*1L!j$$w79$=rv zSu2JHRT7-Mp^ig5%~{JKuW}a8tWKWJ8eER*X{7a>u|@P0z#M>JaNgJww&fWB+wv*q zq|x*&a~Kqu!(g5{3>KJk2H*m77+hoygGPJkJ~kCFU@&n8RSvP$#=-F0-Oe!(6e^3{cgsCzzjrvSg^~E_<03 zo4sNqX0O_asaFm4c$fMDD>ik_Moe9|5mPq|b*f9Pv0_u}He%}MZN$`uvAxHub-o#; zW2(2RJi~=+{HTc&zGVM;0bfAGw{R#sU#shF@})6*8fUQGr-@z%4Umoe>D$^JH1C16 zCw-ez4)##CJuGg_<6Gm6V%t;6wntgJ-TP${-qejUzB++|FURI~zb5*}Z;88KQ{Hdl zLBVfO_kRxHJsE%e4(7ZIx&(eddL-!h(}Y&*Ivv_S z|!tViL#4*6cEl^Hosx^iC0{EAH}qQ zivu*MJ-5FDw%Bm7ANKFKxQ{N2 zZ5Ib`DJO*xi1B9oKJNx67k)?ixQu-2sYVsoX|*pw-` zRNH7)*Vbx@at+rsToU0-LY~6aaqJ++vD0!KgC*^Y6L1W!5YcR{v0RnO#H2hWd3f9~Je1?{WE`^r zZ8wj0s16su8X<@V;$O8xlXTP$hb~teo2#`Ac{DM3&&dPln}?GP+mU6bxq`Vk3$#fu zVV+4?n^*>3iiVwf+08{~Q6v_f+pfY-8SDg6zPeFuSWQd}tFrmha}lcYt<=7- r5wEN^wAu@>3H%}xw08^tN*2PY!@j(@SPJ)Y5WW7#+sxYW8`ggTroxeh literal 0 HcmV?d00001 diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs index 196c36cf16..7e5b517a6c 100644 --- a/tiny_skia/src/engine.rs +++ b/tiny_skia/src/engine.rs @@ -1,3 +1,5 @@ +use tiny_skia::Transform; + use crate::core::renderer::Quad; use crate::core::{ Background, Color, Gradient, Rectangle, Size, Transformation, Vector, @@ -51,7 +53,7 @@ impl Engine { return; } - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + let clip_mask = (!physical_bounds.is_within_strict(&clip_bounds)) .then_some(clip_mask as &_); let transform = into_transform(transformation); @@ -64,18 +66,27 @@ impl Engine { .min(quad.bounds.height / 2.0); let mut fill_border_radius = <[f32; 4]>::from(quad.border.radius); - + // Offset the fill by the border width + let path_bounds = Rectangle { + x: quad.bounds.x + border_width, + y: quad.bounds.y + border_width, + width: quad.bounds.width - 2.0 * border_width, + height: quad.bounds.height - 2.0 * border_width, + }; + // fill border radius is the border radius minus the border width for radius in &mut fill_border_radius { - *radius = (*radius) - .min(quad.bounds.width / 2.0) - .min(quad.bounds.height / 2.0); + *radius = (*radius - border_width / 2.0) + .min(path_bounds.width / 2.0) + .min(path_bounds.height / 2.0); } - let path = rounded_rectangle(quad.bounds, fill_border_radius); + let path = rounded_rectangle(path_bounds, fill_border_radius); let shadow = quad.shadow; - - if shadow.color.a > 0.0 { + // TODO: Disabled due to graphical glitches + // TODO(POP): This TODO existed in the pop fork, and if false was used. Evaluate if still needed + // if shadow.color.a > 0.0 { + if false { let shadow_bounds = Rectangle { x: quad.bounds.x + shadow.offset.x - shadow.blur_radius, y: quad.bounds.y + shadow.offset.y - shadow.blur_radius, @@ -262,22 +273,22 @@ impl Engine { // Draw corners that have too small border radii as having no border radius, // but mask them with the rounded rectangle with the correct border radius. let mut temp_pixmap = tiny_skia::Pixmap::new( - quad.bounds.width as u32, - quad.bounds.height as u32, + path_bounds.width as u32, + path_bounds.height as u32, ) .unwrap(); let mut quad_mask = tiny_skia::Mask::new( - quad.bounds.width as u32, - quad.bounds.height as u32, + path_bounds.width as u32, + path_bounds.height as u32, ) .unwrap(); let zero_bounds = Rectangle { x: 0.0, y: 0.0, - width: quad.bounds.width, - height: quad.bounds.height, + width: path_bounds.width, + height: path_bounds.height, }; let path = rounded_rectangle(zero_bounds, fill_border_radius); @@ -288,10 +299,10 @@ impl Engine { transform, ); let path_bounds = Rectangle { - x: border_width / 2.0, - y: border_width / 2.0, - width: quad.bounds.width - border_width, - height: quad.bounds.height - border_width, + x: (border_width / 2.0), + y: (border_width / 2.0), + width: path_bounds.width - border_width, + height: path_bounds.height - border_width, }; let border_radius_path = @@ -315,8 +326,8 @@ impl Engine { ); pixels.draw_pixmap( - quad.bounds.x as i32, - quad.bounds.y as i32, + (quad.bounds.x) as i32, + (quad.bounds.y) as i32, temp_pixmap.as_ref(), &tiny_skia::PixmapPaint::default(), transform, @@ -352,8 +363,9 @@ impl Engine { return; } - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); + let clip_mask = (!physical_bounds + .is_within_strict(&clip_bounds)) + .then_some(clip_mask as &_); self.text_pipeline.draw_paragraph( paragraph, @@ -380,8 +392,9 @@ impl Engine { return; } - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); + let clip_mask = (!physical_bounds + .is_within_strict(&clip_bounds)) + .then_some(clip_mask as &_); self.text_pipeline.draw_editor( editor, @@ -410,8 +423,9 @@ impl Engine { return; } - let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then_some(clip_mask as &_); + let clip_mask = (!physical_bounds + .is_within_strict(&clip_bounds)) + .then_some(clip_mask as &_); self.text_pipeline.draw_cached( content, @@ -437,13 +451,13 @@ impl Engine { }; let transformation = transformation * *local_transformation; - let (width, height) = buffer.size(); + let (width_opt, height_opt) = buffer.size(); let physical_bounds = Rectangle::new( raw.position, Size::new( - width.unwrap_or(clip_bounds.width), - height.unwrap_or(clip_bounds.height), + width_opt.unwrap_or(clip_bounds.width), + height_opt.unwrap_or(clip_bounds.height), ), ) * transformation; @@ -550,7 +564,7 @@ impl Engine { ) { match image { #[cfg(feature = "image")] - Image::Raster(raster, bounds) => { + Image::Raster { handle, bounds } => { let physical_bounds = *bounds * _transformation; if !_clip_bounds.intersects(&physical_bounds) { @@ -561,26 +575,27 @@ impl Engine { .then_some(_clip_mask as &_); let center = physical_bounds.center(); - let radians = f32::from(raster.rotation); + let radians = f32::from(handle.rotation); - let transform = into_transform(_transformation).post_rotate_at( + let transform = Transform::default().post_rotate_at( radians.to_degrees(), center.x, center.y, ); self.raster_pipeline.draw( - &raster.handle, - raster.filter_method, - *bounds, - raster.opacity, + &handle.handle, + handle.filter_method, + physical_bounds, + handle.opacity, _pixels, transform, clip_mask, + handle.border_radius, ); } #[cfg(feature = "svg")] - Image::Vector(svg, bounds) => { + Image::Vector { handle, bounds } => { let physical_bounds = *bounds * _transformation; if !_clip_bounds.intersects(&physical_bounds) { @@ -591,19 +606,19 @@ impl Engine { .then_some(_clip_mask as &_); let center = physical_bounds.center(); - let radians = f32::from(svg.rotation); + let radians = f32::from(handle.rotation); - let transform = into_transform(_transformation).post_rotate_at( + let transform = Transform::default().post_rotate_at( radians.to_degrees(), center.x, center.y, ); self.vector_pipeline.draw( - &svg.handle, - svg.color, + &handle.handle, + handle.color, physical_bounds, - svg.opacity, + handle.opacity, _pixels, transform, clip_mask, diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 681bf25d3c..d1f1c7816e 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -299,7 +299,10 @@ impl geometry::frame::Backend for Frame { image.rotation += external_rotation; - self.images.push(graphics::Image::Raster(image, bounds)); + self.images.push(graphics::Image::Raster { + handle: image, + bounds, + }); } fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into) { @@ -310,7 +313,10 @@ impl geometry::frame::Backend for Frame { svg.rotation += external_rotation; - self.images.push(Image::Vector(svg, bounds)); + self.images.push(Image::Vector { + handle: svg, + bounds, + }); } } diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index bdfd4d388a..30ee93bcad 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -1,6 +1,6 @@ use crate::core::renderer::Quad; use crate::core::{ - self, Background, Color, Point, Rectangle, Svg, Transformation, + self, Background, Color, Point, Radians, Rectangle, Svg, Transformation, }; use crate::graphics::damage; use crate::graphics::layer; @@ -117,11 +117,11 @@ impl Layer { pub fn draw_image(&mut self, image: Image, transformation: Transformation) { match image { - Image::Raster(raster, bounds) => { - self.draw_raster(raster, bounds, transformation); + Image::Raster { handle, bounds } => { + self.draw_raster(handle, bounds, transformation) } - Image::Vector(svg, bounds) => { - self.draw_svg(svg, bounds, transformation); + Image::Vector { handle, bounds } => { + self.draw_svg(handle, bounds, transformation) } } } @@ -132,7 +132,17 @@ impl Layer { bounds: Rectangle, transformation: Transformation, ) { - let image = Image::Raster(image, bounds * transformation); + let image = Image::Raster { + handle: crate::core::Image { + handle: image.handle, + filter_method: image.filter_method, + rotation: image.rotation, + opacity: image.opacity, + snap: image.snap, + border_radius: image.border_radius, + }, + bounds: bounds * transformation, + }; self.images.push(image); } @@ -143,7 +153,10 @@ impl Layer { bounds: Rectangle, transformation: Transformation, ) { - let svg = Image::Vector(svg, bounds * transformation); + let svg = Image::Vector { + handle: svg, + bounds: bounds * transformation, + }; self.images.push(svg); } diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 758921d44f..9085045790 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -33,7 +33,7 @@ use crate::core::{ }; use crate::engine::Engine; use crate::graphics::compositor; -use crate::graphics::text::{Editor, Paragraph}; +use crate::graphics::text::{Editor, Paragraph, Raw}; use crate::graphics::Viewport; /// A [`tiny-skia`] graphics renderer for [`iced`]. @@ -261,6 +261,7 @@ impl core::text::Renderer for Renderer { type Font = Font; type Paragraph = Paragraph; type Editor = Editor; + type Raw = Raw; const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; @@ -282,7 +283,6 @@ impl core::text::Renderer for Renderer { clip_bounds: Rectangle, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_paragraph( text, position, @@ -313,6 +313,8 @@ impl core::text::Renderer for Renderer { let (layer, transformation) = self.layers.current_mut(); layer.draw_text(text, position, color, clip_bounds, transformation); } + + fn fill_raw(&mut self, _raw: Self::Raw) {} } #[cfg(feature = "geometry")] @@ -381,9 +383,28 @@ impl core::image::Renderer for Renderer { self.engine.raster_pipeline.dimensions(handle) } - fn draw_image(&mut self, image: core::Image, bounds: Rectangle) { + fn draw_image( + &mut self, + handle: Self::Handle, + filter_method: core::image::FilterMethod, + bounds: Rectangle, + rotation: core::Radians, + opacity: f32, + border_radius: [f32; 4], + ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_raster(image, bounds, transformation); + layer.draw_raster( + crate::core::Image { + handle, + filter_method, + rotation, + opacity, + snap: true, + border_radius, + }, + bounds, + transformation, + ); } } @@ -396,7 +417,7 @@ impl core::svg::Renderer for Renderer { self.engine.vector_pipeline.viewport_dimensions(handle) } - fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) { + fn draw_svg(&mut self, svg: core::svg::Svg, bounds: Rectangle) { let (layer, transformation) = self.layers.current_mut(); layer.draw_svg(svg, bounds, transformation); } diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index c40f55b2ff..96d8c98ea6 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -35,8 +35,9 @@ impl Pipeline { pixels: &mut tiny_skia::PixmapMut<'_>, transform: tiny_skia::Transform, clip_mask: Option<&tiny_skia::Mask>, + border_radius: [f32; 4], ) { - if let Some(image) = self.cache.borrow_mut().allocate(handle) { + if let Some(mut 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; @@ -50,6 +51,24 @@ impl Pipeline { tiny_skia::FilterQuality::Nearest } }; + let mut scratch; + + // Round the borders if a border radius is defined + if border_radius.iter().any(|&corner| corner != 0.0) { + scratch = image.to_owned(); + round(&mut scratch.as_mut(), { + let [a, b, c, d] = border_radius; + let scale_by = width_scale.min(height_scale); + let max_radius = image.width().min(image.height()) / 2; + [ + ((a / scale_by) as u32).max(1).min(max_radius), + ((b / scale_by) as u32).max(1).min(max_radius), + ((c / scale_by) as u32).max(1).min(max_radius), + ((d / scale_by) as u32).max(1).min(max_radius), + ] + }); + image = scratch.as_ref(); + } pixels.draw_pixmap( (bounds.x / width_scale) as i32, @@ -128,3 +147,131 @@ struct Entry { height: u32, pixels: Vec, } + +// https://users.rust-lang.org/t/how-to-trim-image-to-circle-image-without-jaggy/70374/2 +fn round(img: &mut tiny_skia::PixmapMut<'_>, radius: [u32; 4]) { + let (width, height) = (img.width(), img.height()); + assert!(radius[0] + radius[1] <= width); + assert!(radius[3] + radius[2] <= width); + assert!(radius[0] + radius[3] <= height); + assert!(radius[1] + radius[2] <= height); + + // top left + border_radius(img, radius[0], |x, y| (x - 1, y - 1)); + // top right + border_radius(img, radius[1], |x, y| (width - x, y - 1)); + // bottom right + border_radius(img, radius[2], |x, y| (width - x, height - y)); + // bottom left + border_radius(img, radius[3], |x, y| (x - 1, height - y)); +} + +fn border_radius( + img: &mut tiny_skia::PixmapMut<'_>, + r: u32, + coordinates: impl Fn(u32, u32) -> (u32, u32), +) { + if r == 0 { + return; + } + let r0 = r; + + // 16x antialiasing: 16x16 grid creates 256 possible shades, great for u8! + let r = 16 * r; + + let mut x = 0; + let mut y = r - 1; + let mut p: i32 = 2 - r as i32; + + // ... + + let mut alpha: u16 = 0; + let mut skip_draw = true; + + fn pixel_id(width: u32, (x, y): (u32, u32)) -> usize { + ((width as usize * y as usize) + x as usize) * 4 + } + + let clear_pixel = |img: &mut tiny_skia::PixmapMut<'_>, + (x, y): (u32, u32)| { + let pixel = pixel_id(img.width(), (x, y)); + img.data_mut()[pixel..pixel + 4].copy_from_slice(&[0; 4]); + }; + + let draw = |img: &mut tiny_skia::PixmapMut<'_>, alpha, x, y| { + debug_assert!((1..=256).contains(&alpha)); + let pixel = pixel_id(img.width(), coordinates(r0 - x, r0 - y)); + let pixel_alpha = &mut img.data_mut()[pixel + 3]; + *pixel_alpha = ((alpha * *pixel_alpha as u16 + 128) / 256) as u8; + }; + + 'l: loop { + // (comments for bottom_right case:) + // remove contents below current position + { + let i = x / 16; + for j in y / 16 + 1..r0 { + clear_pixel(img, coordinates(r0 - i, r0 - j)); + } + } + // remove contents right of current position mirrored + { + let j = x / 16; + for i in y / 16 + 1..r0 { + clear_pixel(img, coordinates(r0 - i, r0 - j)); + } + } + + // draw when moving to next pixel in x-direction + if !skip_draw { + draw(img, alpha, x / 16 - 1, y / 16); + draw(img, alpha, y / 16, x / 16 - 1); + alpha = 0; + } + + for _ in 0..16 { + skip_draw = false; + + if x >= y { + break 'l; + } + + alpha += y as u16 % 16 + 1; + if p < 0 { + x += 1; + p += (2 * x + 2) as i32; + } else { + // draw when moving to next pixel in y-direction + if y % 16 == 0 { + draw(img, alpha, x / 16, y / 16); + draw(img, alpha, y / 16, x / 16); + skip_draw = true; + alpha = (x + 1) as u16 % 16 * 16; + } + + x += 1; + p -= (2 * (y - x) + 2) as i32; + y -= 1; + } + } + } + + // one corner pixel left + if x / 16 == y / 16 { + // column under current position possibly not yet accounted + if x == y { + alpha += y as u16 % 16 + 1; + } + let s = y as u16 % 16 + 1; + let alpha = 2 * alpha - s * s; + draw(img, alpha, x / 16, y / 16); + } + + // remove remaining square of content in the corner + let range = y / 16 + 1..r0; + for i in range.clone() { + for j in range.clone() { + clear_pixel(img, coordinates(r0 - i, r0 - j)); + } + } +} diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs index 672c49f32d..03361d21ce 100644 --- a/tiny_skia/src/settings.rs +++ b/tiny_skia/src/settings.rs @@ -19,7 +19,7 @@ impl Default for Settings { fn default() -> Settings { Settings { default_font: Font::default(), - default_text_size: Pixels(16.0), + default_text_size: Pixels(14.0), } } } diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 0fc3d1f71b..4924d1f3bc 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -163,7 +163,7 @@ impl Pipeline { ) { let mut font_system = font_system().write().expect("Write font system"); - let (width, height) = buffer.size(); + let (width_opt, height_opt) = buffer.size(); draw( font_system.raw(), @@ -172,8 +172,8 @@ impl Pipeline { Rectangle::new( position, Size::new( - width.unwrap_or(pixels.width() as f32), - height.unwrap_or(pixels.height() as f32), + width_opt.unwrap_or(pixels.width() as f32), + height_opt.unwrap_or(pixels.height() as f32), ), ), color, diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index ea7de2150b..4eb9add55a 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -50,6 +50,7 @@ impl Pipeline { image, &tiny_skia::PixmapPaint { opacity, + quality: tiny_skia::FilterQuality::Bicubic, ..tiny_skia::PixmapPaint::default() }, transform, diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 153af6d562..cc887d04bb 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -72,7 +72,7 @@ impl crate::graphics::Compositor for Compositor { clip_mask: tiny_skia::Mask::new(width, height) .expect("Create clip mask"), layer_stack: VecDeque::new(), - background_color: Color::BLACK, + background_color: Color::TRANSPARENT, max_age: 0, }; @@ -181,11 +181,15 @@ pub fn present>( }) .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]); - if damage.is_empty() { - return Ok(()); - } - + // TODO(POP): I tried to adapt this to what I saw in the diff, which was essentially making sure this is called + // before the damage.is_empty() check surface.layer_stack.push_front(renderer.layers().to_vec()); + + // TODO better handling of no damage. As it is, the winit shell does not handle a skipped present well. + // if damage.is_empty() { + // return Err(compositor::SurfaceError::NoDamage); + // } + surface.background_color = background_color; let damage = @@ -198,6 +202,9 @@ pub fn present>( ) .expect("Create pixel map"); + // let damage = damage::group(damage, scale_factor, physical_size); + // TODO(POP): Is this something that needs to be adapted? + renderer.draw( &mut pixels, &mut surface.clip_mask, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index a8ebf3aa85..35af5870be 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -45,3 +45,20 @@ lyon.optional = true resvg.workspace = true resvg.optional = true + +[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] +rustix = { version = "0.38" } +raw-window-handle.workspace = true +sctk.workspace = true +wayland-protocols.workspace = true +wayland-backend = { version = "0.3.3", features = ["client_system"] } +wayland-client = { version = "0.31.2" } +wayland-sys = { version = "0.31.1", features = ["dlopen"] } +as-raw-xcb-connection = "1.0.1" +tiny-xlib = "0.2.3" +x11rb = { version = "0.13.1", features = [ + "allow-unsafe-code", + "dl-libxcb", + "dri3", + "randr", +] } diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index d2ffda53e1..8ad4c758e2 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -422,7 +422,10 @@ impl geometry::frame::Backend for Frame { image.rotation += external_rotation; - self.images.push(Image::Raster(image, bounds)); + self.images.push(Image::Raster { + handle: image, + bounds, + }); } fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into) { @@ -433,7 +436,10 @@ impl geometry::frame::Backend for Frame { svg.rotation += external_rotation; - self.images.push(Image::Vector(svg, bounds)); + self.images.push(Image::Vector { + handle: svg, + bounds, + }); } } diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index caac081382..b0d02ebae1 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -225,18 +225,18 @@ impl Pipeline { for image in images { match &image { #[cfg(feature = "image")] - Image::Raster(image, bounds) => { + Image::Raster { handle, bounds } => { if let Some(atlas_entry) = - cache.upload_raster(device, encoder, &image.handle) + cache.upload_raster(device, encoder, &handle.handle) { add_instances( [bounds.x, bounds.y], [bounds.width, bounds.height], - f32::from(image.rotation), - image.opacity, - image.snap, + f32::from(handle.rotation), + handle.opacity, + handle.snap, atlas_entry, - match image.filter_method { + match handle.filter_method { crate::core::image::FilterMethod::Nearest => { nearest_instances } @@ -251,22 +251,22 @@ impl Pipeline { Image::Raster { .. } => {} #[cfg(feature = "svg")] - Image::Vector(svg, bounds) => { + Image::Vector { handle, bounds } => { let size = [bounds.width, bounds.height]; if let Some(atlas_entry) = cache.upload_vector( device, encoder, - &svg.handle, - svg.color, + &handle.handle, + handle.color, size, scale, ) { add_instances( [bounds.x, bounds.y], size, - f32::from(svg.rotation), - svg.opacity, + f32::from(handle.rotation), + handle.opacity, true, atlas_entry, nearest_instances, @@ -595,12 +595,12 @@ fn add_instance( _rotation: rotation, _opacity: opacity, _position_in_atlas: [ - (x as f32 + 0.5) / atlas::SIZE as f32, - (y as f32 + 0.5) / atlas::SIZE as f32, + x as f32 / atlas::SIZE as f32, + y as f32 / atlas::SIZE as f32, ], _size_in_atlas: [ - (width as f32 - 1.0) / atlas::SIZE as f32, - (height as f32 - 1.0) / atlas::SIZE as f32, + width as f32 / atlas::SIZE as f32, + height as f32 / atlas::SIZE as f32, ], _layer: layer as u32, _snap: snap as u32, diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 68d5a015bf..5340a66b90 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,3 +1,4 @@ +use crate::core::Radians; use crate::core::{ self, renderer, Background, Color, Point, Rectangle, Svg, Transformation, }; @@ -114,11 +115,11 @@ impl Layer { pub fn draw_image(&mut self, image: Image, transformation: Transformation) { match image { - Image::Raster(image, bounds) => { - self.draw_raster(image, bounds, transformation); + Image::Raster { handle, bounds } => { + self.draw_raster(handle, bounds, transformation); } - Image::Vector(svg, bounds) => { - self.draw_svg(svg, bounds, transformation); + Image::Vector { handle, bounds } => { + self.draw_svg(handle, bounds, transformation); } } } @@ -129,7 +130,10 @@ impl Layer { bounds: Rectangle, transformation: Transformation, ) { - let image = Image::Raster(image, bounds * transformation); + let image = Image::Raster { + handle: image, + bounds: bounds * transformation, + }; self.images.push(image); } @@ -140,7 +144,10 @@ impl Layer { bounds: Rectangle, transformation: Transformation, ) { - let svg = Image::Vector(svg, bounds * transformation); + let svg = Image::Vector { + handle: svg, + bounds: (bounds * transformation), + }; self.images.push(svg); } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index d79f0dc85f..c0703996c1 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -49,6 +49,7 @@ use buffer::Buffer; pub use iced_graphics as graphics; pub use iced_graphics::core; +use iced_graphics::text::Raw; pub use wgpu; @@ -61,8 +62,8 @@ pub use settings::Settings; pub use geometry::Geometry; use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, - Vector, + image::FilterMethod, Background, Color, Font, Pixels, Point, Radians, + Rectangle, Size, Transformation, Vector, }; use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Viewport; @@ -407,7 +408,7 @@ impl Renderer { font: Font::MONOSPACE, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, - shaping: core::text::Shaping::Basic, + shaping: core::text::Shaping::Advanced, wrapping: core::text::Wrapping::Word, }; @@ -466,6 +467,7 @@ impl core::text::Renderer for Renderer { type Font = Font; type Paragraph = Paragraph; type Editor = Editor; + type Raw = Raw; const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; @@ -518,6 +520,10 @@ impl core::text::Renderer for Renderer { let (layer, transformation) = self.layers.current_mut(); layer.draw_text(text, position, color, clip_bounds, transformation); } + + fn fill_raw(&mut self, raw: Self::Raw) { + // TODO + } } #[cfg(feature = "image")] @@ -528,9 +534,28 @@ impl core::image::Renderer for Renderer { self.image_cache.borrow_mut().measure_image(handle) } - fn draw_image(&mut self, image: core::Image, bounds: Rectangle) { + fn draw_image( + &mut self, + handle: Self::Handle, + filter_method: FilterMethod, + bounds: Rectangle, + rotation: Radians, + opacity: f32, + border_radius: [f32; 4], + ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_raster(image, bounds, transformation); + layer.draw_raster( + crate::core::Image { + handle, + filter_method, + rotation, + opacity, + snap: true, + border_radius, + }, + bounds, + transformation, + ); } } @@ -540,9 +565,9 @@ impl core::svg::Renderer for Renderer { self.image_cache.borrow_mut().measure_svg(handle) } - fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) { + fn draw_svg(&mut self, handle: core::svg::Svg, bounds: Rectangle) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg(svg, bounds, transformation); + layer.draw_svg(handle, bounds, transformation); } } diff --git a/wgpu/src/offscreen.rs b/wgpu/src/offscreen.rs new file mode 100644 index 0000000000..29913d0244 --- /dev/null +++ b/wgpu/src/offscreen.rs @@ -0,0 +1,102 @@ +use std::borrow::Cow; + +/// A simple compute pipeline to convert any texture to Rgba8UnormSrgb. +#[derive(Debug)] +pub struct Pipeline { + pipeline: wgpu::ComputePipeline, + layout: wgpu::BindGroupLayout, +} + +impl Pipeline { + pub fn new(device: &wgpu::Device) -> Self { + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu.offscreen.blit.shader"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( + "shader/offscreen_blit.wgsl" + ))), + }); + + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("iced_wgpu.offscreen.blit.bind_group_layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: false, + }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::WriteOnly, + format: wgpu::TextureFormat::Rgba8Unorm, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + ], + }); + + let pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let pipeline = + device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("iced_wgpu.offscreen.blit.pipeline"), + layout: Some(&pipeline_layout), + module: &shader, + entry_point: "main", + }); + + Self { + pipeline, + layout: bind_group_layout, + } + } + + pub fn convert( + &self, + device: &wgpu::Device, + extent: wgpu::Extent3d, + frame: &wgpu::TextureView, + view: &wgpu::TextureView, + encoder: &mut wgpu::CommandEncoder, + ) { + let bind = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu.offscreen.blit.bind_group"), + layout: &self.layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(frame), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(view), + }, + ], + }); + + let mut compute_pass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("iced_wgpu.offscreen.blit.compute_pass"), + }); + + compute_pass.set_pipeline(&self.pipeline); + compute_pass.set_bind_group(0, &bind, &[]); + compute_pass.dispatch_workgroups(extent.width, extent.height, 1); + } +} diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index b3c3cf6ad6..b4f97a7d60 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -20,7 +20,7 @@ pub struct Settings { /// The default size of text. /// - /// By default, it will be set to `16.0`. + /// By default, it will be set to `14.0`. pub default_text_size: Pixels, /// The antialiasing strategy that will be used for triangle primitives. @@ -35,7 +35,7 @@ impl Default for Settings { present_mode: wgpu::PresentMode::AutoVsync, backends: wgpu::Backends::all(), default_font: Font::default(), - default_text_size: Pixels(16.0), + default_text_size: Pixels(14.0), antialiasing: None, } } diff --git a/wgpu/src/shader/offscreen_blit.wgsl b/wgpu/src/shader/offscreen_blit.wgsl new file mode 100644 index 0000000000..9c764c36dc --- /dev/null +++ b/wgpu/src/shader/offscreen_blit.wgsl @@ -0,0 +1,22 @@ +@group(0) @binding(0) var u_texture: texture_2d; +@group(0) @binding(1) var out_texture: texture_storage_2d; + +fn srgb(color: f32) -> f32 { + if (color <= 0.0031308) { + return 12.92 * color; + } else { + return (1.055 * (pow(color, (1.0/2.4)))) - 0.055; + } +} + +@compute @workgroup_size(1) +fn main(@builtin(global_invocation_id) id: vec3) { + // texture coord must be i32 due to a naga bug: + // https://github.com/gfx-rs/naga/issues/1997 + let coords = vec2(i32(id.x), i32(id.y)); + + let src: vec4 = textureLoad(u_texture, coords, 0); + let srgb_color: vec4 = vec4(srgb(src.x), srgb(src.y), srgb(src.z), src.w); + + textureStore(out_texture, coords, srgb_color); +} diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index 9545a14e5b..92f1687372 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -1,5 +1,41 @@ //! Display rendering results on windows. pub mod compositor; +#[cfg(all(unix, not(target_os = "macos")))] +mod wayland; +#[cfg(all(unix, not(target_os = "macos")))] +mod x11; pub use compositor::Compositor; pub use wgpu::Surface; + +#[cfg(all(unix, not(target_os = "macos")))] +use rustix::fs::{major, minor}; +#[cfg(all(unix, not(target_os = "macos")))] +use std::{fs::File, io::Read, path::PathBuf}; + +#[cfg(all(unix, not(target_os = "macos")))] +fn ids_from_dev(dev: u64) -> Option<(u16, u16)> { + let path = PathBuf::from(format!( + "/sys/dev/char/{}:{}/device", + major(dev), + minor(dev) + )); + let vendor = { + let path = path.join("vendor"); + let mut file = File::open(&path).ok()?; + let mut contents = String::new(); + let _ = file.read_to_string(&mut contents).ok()?; + u16::from_str_radix(contents.trim().trim_start_matches("0x"), 16) + .ok()? + }; + let device = { + let path = path.join("device"); + let mut file = File::open(&path).ok()?; + let mut contents = String::new(); + let _ = file.read_to_string(&mut contents).ok()?; + u16::from_str_radix(contents.trim().trim_start_matches("0x"), 16) + .ok()? + }; + + Some((vendor, device)) +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 56f33b5013..260f0171ae 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -7,6 +7,12 @@ use crate::graphics::{self, Viewport}; use crate::settings::{self, Settings}; use crate::{Engine, Renderer}; +#[cfg(all(unix, not(target_os = "macos")))] +use super::wayland::get_wayland_device_ids; +#[cfg(all(unix, not(target_os = "macos")))] +use super::x11::get_x11_device_ids; +use std::future::Future; + /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] pub struct Compositor { @@ -54,6 +60,26 @@ impl Compositor { settings: Settings, compatible_window: Option, ) -> Result { + #[cfg(all(unix, not(target_os = "macos")))] + let ids = compatible_window.as_ref().and_then(|window| { + get_wayland_device_ids(window) + .or_else(|| get_x11_device_ids(window)) + }); + + // HACK: + // 1. If we specifically didn't select an nvidia gpu + // 2. and nobody set an adapter name, + // 3. and the user didn't request the high power pref + // => don't load the nvidia icd, as it might power on the gpu in hybrid setups causing severe delays + #[cfg(all(unix, not(target_os = "macos")))] + if !matches!(ids, Some((0x10de, _))) + && std::env::var_os("WGPU_ADAPTER_NAME").is_none() + && std::env::var("WGPU_POWER_PREF").as_deref() != Ok("high") + { + std::env::set_var("VK_LOADER_DRIVERS_DISABLE", "nvidia*"); + } + + // only load the instance after setting environment variables, this initializes the vulkan loader let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: settings.backends, flags: if cfg!(feature = "strict-assertions") { @@ -66,6 +92,10 @@ impl Compositor { log::info!("{settings:#?}"); + let available_adapters = instance.enumerate_adapters(settings.backends); + + std::env::remove_var("VK_LOADER_DRIVERS_DISABLE"); + #[cfg(not(target_arch = "wasm32"))] if log::max_level() >= log::LevelFilter::Info { let available_adapters: Vec<_> = instance @@ -95,7 +125,63 @@ impl Compositor { .request_adapter(&adapter_options) .await .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?; - + // start pop + // let mut adapter = None; + // #[cfg_attr(not(unix), allow(dead_code))] + // if std::env::var_os("WGPU_ADAPTER_NAME").is_none() { + // #[cfg(all(unix, not(target_os = "macos")))] + // if let Some((vendor_id, device_id)) = ids { + // adapter = available_adapters + // .into_iter() + // .filter(|adapter| { + // let info = adapter.get_info(); + // info.device == device_id as u32 + // && info.vendor == vendor_id as u32 + // }) + // .find(|adapter| { + // if let Some(surface) = compatible_surface.as_ref() { + // adapter.is_surface_supported(surface) + // } else { + // true + // } + // }); + // } + // } else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") { + // adapter = available_adapters + // .into_iter() + // .filter(|adapter| { + // let info = adapter.get_info(); + // info.name == name + // }) + // .find(|adapter| { + // if let Some(surface) = compatible_surface.as_ref() { + // adapter.is_surface_supported(surface) + // } else { + // true + // } + // }); + // } + + // let adapter = + // match adapter { + // Some(adapter) => adapter, + // None => instance + // .request_adapter(&wgpu::RequestAdapterOptions { + // power_preference: + // wgpu::util::power_preference_from_env().unwrap_or( + // if settings.antialiasing.is_none() { + // wgpu::PowerPreference::LowPower + // } else { + // wgpu::PowerPreference::HighPerformance + // }, + // ), + // compatible_surface: compatible_surface.as_ref(), + // force_fallback_adapter: false, + // }) + // .await?, + // }; + // end pop + // TODO(POP): Merge conflict ensued with above stuff, is your code still needed? log::info!("Selected: {:#?}", adapter.get_info()); let (format, alpha_mode) = compatible_surface @@ -332,6 +418,21 @@ impl graphics::Compositor for Compositor { width: u32, height: u32, ) { + let caps = surface.get_capabilities(&self.adapter); + let alpha_mode = if caps + .alpha_modes + .contains(&wgpu::CompositeAlphaMode::PostMultiplied) + { + wgpu::CompositeAlphaMode::PostMultiplied + } else if caps + .alpha_modes + .contains(&wgpu::CompositeAlphaMode::PreMultiplied) + { + wgpu::CompositeAlphaMode::PreMultiplied + } else { + wgpu::CompositeAlphaMode::Auto + }; + surface.configure( &self.device, &wgpu::SurfaceConfiguration { @@ -340,7 +441,7 @@ impl graphics::Compositor for Compositor { present_mode: self.settings.present_mode, width, height, - alpha_mode: self.alpha_mode, + alpha_mode, view_formats: vec![], desired_maximum_frame_latency: 1, }, diff --git a/wgpu/src/window/wayland.rs b/wgpu/src/window/wayland.rs new file mode 100644 index 0000000000..b50f5d6909 --- /dev/null +++ b/wgpu/src/window/wayland.rs @@ -0,0 +1,115 @@ +use crate::graphics::compositor::Window; +use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle}; +use sctk::{ + dmabuf::{DmabufFeedback, DmabufHandler, DmabufState}, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, +}; +use wayland_client::{ + backend::Backend, globals::registry_queue_init, protocol::wl_buffer, + Connection, QueueHandle, +}; +use wayland_protocols::wp::linux_dmabuf::zv1::client::{ + zwp_linux_buffer_params_v1, zwp_linux_dmabuf_feedback_v1, +}; + +struct AppData { + registry_state: RegistryState, + dmabuf_state: DmabufState, + feedback: Option, +} + +impl DmabufHandler for AppData { + fn dmabuf_state(&mut self) -> &mut DmabufState { + &mut self.dmabuf_state + } + + fn dmabuf_feedback( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, + feedback: DmabufFeedback, + ) { + self.feedback = Some(feedback); + } + + fn created( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, + _buffer: wl_buffer::WlBuffer, + ) { + } + + fn failed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, + ) { + } + + fn released( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _buffer: &wl_buffer::WlBuffer, + ) { + } +} + +impl ProvidesRegistryState for AppData { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + registry_handlers![,]; +} + +pub fn get_wayland_device_ids(window: &W) -> Option<(u16, u16)> { + if !wayland_sys::client::is_lib_available() { + return None; + } + + let conn = match window.display_handle().map(|handle| handle.as_raw()) { + #[allow(unsafe_code)] + Ok(RawDisplayHandle::Wayland(WaylandDisplayHandle { + display, .. + })) => Connection::from_backend(unsafe { + Backend::from_foreign_display(display.as_ptr() as *mut _) + }), + _ => { + return None; + } + }; + + let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); + let qh = event_queue.handle(); + + let mut app_data = AppData { + registry_state: RegistryState::new(&globals), + dmabuf_state: DmabufState::new(&globals, &qh), + feedback: None, + }; + + match app_data.dmabuf_state.version() { + Some(4..) => { + let _ = app_data.dmabuf_state.get_default_feedback(&qh).unwrap(); + + let feedback = loop { + let _ = event_queue.blocking_dispatch(&mut app_data).ok()?; + if let Some(feedback) = app_data.feedback.as_ref() { + break feedback; + } + }; + + let dev = feedback.main_device(); + super::ids_from_dev(dev) + } + _ => None, + } +} + +sctk::delegate_dmabuf!(AppData); +sctk::delegate_registry!(AppData); diff --git a/wgpu/src/window/x11.rs b/wgpu/src/window/x11.rs new file mode 100644 index 0000000000..58da401a2e --- /dev/null +++ b/wgpu/src/window/x11.rs @@ -0,0 +1,171 @@ +use std::{ + fs, + io::{BufRead, BufReader}, + path::Path, +}; + +use crate::graphics::compositor::Window; + +use as_raw_xcb_connection::AsRawXcbConnection; +use raw_window_handle::{ + RawDisplayHandle, XcbDisplayHandle, XlibDisplayHandle, +}; +use rustix::fs::{fstat, stat}; +use tiny_xlib::Display; +use x11rb::{ + connection::{Connection, RequestConnection}, + protocol::{ + dri3::{ConnectionExt as _, X11_EXTENSION_NAME as DRI3_NAME}, + randr::{ + ConnectionExt as _, ProviderCapability, + X11_EXTENSION_NAME as RANDR_NAME, + }, + }, + xcb_ffi::XCBConnection, +}; + +pub fn get_x11_device_ids(window: &W) -> Option<(u16, u16)> { + x11rb::xcb_ffi::load_libxcb().ok()?; + + #[allow(unsafe_code)] + let (conn, screen) = match window + .display_handle() + .map(|handle| handle.as_raw()) + { + #[allow(unsafe_code)] + Ok(RawDisplayHandle::Xlib(XlibDisplayHandle { + display, + screen, + .. + })) => match display { + Some(ptr) => unsafe { + let xlib_display = Display::from_ptr(ptr.as_ptr()); + let conn = XCBConnection::from_raw_xcb_connection( + xlib_display.as_raw_xcb_connection() as *mut _, + false, + ) + .ok(); + // intentially leak the display, we don't want to close the connection + + (conn?, screen) + }, + None => (XCBConnection::connect(None).ok()?.0, screen), + }, + Ok(RawDisplayHandle::Xcb(XcbDisplayHandle { + connection, + screen, + .. + })) => match connection { + Some(ptr) => ( + unsafe { + XCBConnection::from_raw_xcb_connection(ptr.as_ptr(), false) + .ok()? + }, + screen, + ), + None => (XCBConnection::connect(None).ok()?.0, screen), + }, + _ => { + return None; + } + }; + let root = conn.setup().roots[screen as usize].root; + + // The nvidia xorg driver advertises DRI2 and DRI3, + // but doesn't really return any useful data for either of them. + // We also can't query EGL, as a display created from an X11 display + // running on the properietary driver won't return an EGLDevice. + // + // So we have to resort to hacks. + + // check for randr + let _ = conn.extension_information(RANDR_NAME).ok()??; + // check version, because we need providers to exist + let version = conn.randr_query_version(1, 4).ok()?.reply().ok()?; + if version.major_version < 1 + || (version.major_version == 1 && version.minor_version < 4) + { + return None; + } + + // get the name of the first Source Output provider, that will be our main device + let randr = conn.randr_get_providers(root).ok()?.reply().ok()?; + let mut name = None; + for provider in randr.providers { + let info = conn + .randr_get_provider_info(provider, randr.timestamp) + .ok()? + .reply() + .ok()?; + if info + .capabilities + .contains(ProviderCapability::SOURCE_OUTPUT) + || name.is_none() + { + name = std::str::from_utf8(&info.name) + .ok() + .map(ToString::to_string); + } + } + + // if that name is formatted `NVIDIA-x`, then x represents the /dev/nvidiaX number, which we can relate to /dev/dri + if let Some(number) = name.and_then(|name| { + name.trim().strip_prefix("NVIDIA-")?.parse::().ok() + }) { + // let it be known, that I hate this "interface"... + for busid in fs::read_dir("/proc/driver/nvidia/gpus") + .ok()? + .map(Result::ok) + .flatten() + { + for line in BufReader::new( + fs::File::open(busid.path().join("information")).ok()?, + ) + .lines() + { + if let Ok(line) = line { + if line.starts_with("Device Minor") { + if let Some((_, num)) = line.split_once(":") { + let minor = num.trim().parse::().ok()?; + if minor == number { + // we found the device + for device in fs::read_dir( + Path::new("/sys/module/nvidia/drivers/pci:nvidia/") + .join(busid.file_name()) + .join("drm"), + ) + .ok()? + .map(Result::ok) + .flatten() + { + let device = device.file_name(); + if device.to_string_lossy().starts_with("card") + || device.to_string_lossy().starts_with("render") + { + let stat = + stat(Path::new("/dev/dri").join(device)).ok()?; + let dev = stat.st_rdev; + return super::ids_from_dev(dev); + } + } + } + } + } + } + } + } + + None + } else { + // check via DRI3 + let _ = conn.extension_information(DRI3_NAME).ok()??; + // we have dri3, dri3_open exists on any version, so skip version checks. + + // provider being NONE tells the X server to use the RandR provider. + let dri3 = conn.dri3_open(root, x11rb::NONE).ok()?.reply().ok()?; + let device_fd = dri3.device_fd; + let stat = fstat(device_fd).ok()?; + let dev = stat.st_rdev; + super::ids_from_dev(dev) + } +} diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 98a8114582..1aec15e56d 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -27,16 +27,23 @@ wgpu = ["iced_renderer/wgpu"] markdown = ["dep:pulldown-cmark", "dep:url"] highlighter = ["dep:iced_highlighter"] advanced = [] +a11y = ["iced_accessibility"] +wayland = ["sctk", "iced_runtime/wayland"] [dependencies] iced_renderer.workspace = true iced_runtime.workspace = true - +iced_accessibility.workspace = true +iced_accessibility.optional = true +sctk.workspace = true +sctk.optional = true num-traits.workspace = true once_cell.workspace = true rustc-hash.workspace = true thiserror.workspace = true unicode-segmentation.workspace = true +window_clipboard.workspace = true +dnd.workspace = true ouroboros.workspace = true ouroboros.optional = true diff --git a/widget/src/button.rs b/widget/src/button.rs index a3394a013a..5d4e580a4e 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -16,6 +16,13 @@ //! button("Press me!").on_press(Message::ButtonPressed).into() //! } //! ``` +//! Allow your users to perform actions by pressing a button. +use iced_runtime::core::border::Radius; +use iced_runtime::core::widget::Id; +use iced_runtime::{keyboard, task, Task}; +#[cfg(feature = "a11y")] +use std::borrow::Cow; + use crate::core::border::{self, Border}; use crate::core::event::{self, Event}; use crate::core::layout; @@ -31,6 +38,8 @@ use crate::core::{ Shadow, Shell, Size, Theme, Vector, Widget, }; +use iced_renderer::core::widget::operation; + /// A generic widget that produces a message when pressed. /// /// # Example @@ -76,6 +85,13 @@ where { content: Element<'a, Message, Theme, Renderer>, on_press: Option>, + id: Id, + #[cfg(feature = "a11y")] + name: Option>, + #[cfg(feature = "a11y")] + description: Option>, + #[cfg(feature = "a11y")] + label: Option>, width: Length, height: Length, padding: Padding, @@ -111,6 +127,13 @@ where Button { content, + id: Id::unique(), + #[cfg(feature = "a11y")] + name: None, + #[cfg(feature = "a11y")] + description: None, + #[cfg(feature = "a11y")] + label: None, on_press: None, width: size.width.fluid(), height: size.height.fluid(), @@ -195,11 +218,54 @@ where self.class = class.into(); self } + + /// Sets the [`Id`] of the [`Button`]. + pub fn id(mut self, id: Id) -> Self { + self.id = id; + self + } + + #[cfg(feature = "a11y")] + /// Sets the name of the [`Button`]. + pub fn name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description_widget( + mut self, + description: &T, + ) -> Self { + self.description = Some(iced_accessibility::Description::Id( + description.description(), + )); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description(mut self, description: impl Into>) -> Self { + self.description = + Some(iced_accessibility::Description::Text(description.into())); + self + } + + #[cfg(feature = "a11y")] + /// Sets the label of the [`Button`]. + pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { + self.label = + Some(label.label().into_iter().map(|l| l.into()).collect()); + self + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] struct State { + is_hovered: bool, is_pressed: bool, + is_focused: bool, } impl<'a, Message, Theme, Renderer> Widget @@ -221,8 +287,8 @@ where vec![Tree::new(&self.content)] } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)); + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(std::slice::from_mut(&mut self.content)) } fn size(&self) -> Size { @@ -328,9 +394,43 @@ where } } } - Event::Touch(touch::Event::FingerLost { .. }) => { + #[cfg(feature = "a11y")] + Event::A11y( + event_id, + iced_accessibility::accesskit::ActionRequest { action, .. }, + ) => { let state = tree.state.downcast_mut::(); - + if let Some(Some(on_press)) = (self.id == event_id + && matches!( + action, + iced_accessibility::accesskit::Action::Default + )) + .then(|| self.on_press.as_ref()) + { + state.is_pressed = false; + shell.publish(on_press.get()); + } + return event::Status::Captured; + } + Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { + if let Some(on_press) = self.on_press.as_ref() { + let state = tree.state.downcast_mut::(); + if state.is_focused + && matches!( + key, + keyboard::Key::Named(keyboard::key::Named::Enter) + ) + { + state.is_pressed = true; + shell.publish(on_press.get()); + return event::Status::Captured; + } + } + } + Event::Touch(touch::Event::FingerLost { .. }) + | Event::Mouse(mouse::Event::CursorLeft) => { + let state = tree.state.downcast_mut::(); + state.is_hovered = false; state.is_pressed = false; } _ => {} @@ -344,7 +444,7 @@ where tree: &Tree, renderer: &mut Renderer, theme: &Theme, - _style: &renderer::Style, + renderer_style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, @@ -397,6 +497,10 @@ where theme, &renderer::Style { text_color: style.text_color, + icon_color: style + .icon_color + .unwrap_or(renderer_style.icon_color), + scale_factor: renderer_style.scale_factor, }, content_layout, cursor, @@ -435,6 +539,90 @@ where translation, ) } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + p: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::{ + accesskit::{ + Action, DefaultActionVerb, NodeBuilder, NodeId, Rect, Role, + }, + A11yNode, A11yTree, + }; + + let child_layout = layout.children().next().unwrap(); + let child_tree = &state.children[0]; + let child_tree = + self.content + .as_widget() + .a11y_nodes(child_layout, child_tree, p); + + let Rectangle { + x, + y, + width, + height, + } = layout.bounds(); + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, + ); + let is_hovered = state.state.downcast_ref::().is_hovered; + + let mut node = NodeBuilder::new(Role::Button); + node.add_action(Action::Focus); + node.add_action(Action::Default); + node.set_bounds(bounds); + if let Some(name) = self.name.as_ref() { + node.set_name(name.clone()); + } + match self.description.as_ref() { + Some(iced_accessibility::Description::Id(id)) => { + node.set_described_by( + id.iter() + .cloned() + .map(|id| NodeId::from(id)) + .collect::>(), + ); + } + Some(iced_accessibility::Description::Text(text)) => { + node.set_description(text.clone()); + } + None => {} + } + + if let Some(label) = self.label.as_ref() { + node.set_labelled_by(label.clone()); + } + + if self.on_press.is_none() { + node.set_disabled() + } + if is_hovered { + node.set_hovered() + } + node.set_default_action_verb(DefaultActionVerb::Click); + + A11yTree::node_with_child_tree( + A11yNode::new(node, self.id.clone()), + child_tree, + ) + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } } impl<'a, Message, Theme, Renderer> From> @@ -478,6 +666,14 @@ pub enum Status { pub struct Style { /// The [`Background`] of the button. pub background: Option, + /// The border radius of the button. + pub border_radius: Radius, + /// The border width of the button. + pub border_width: f32, + /// The border [`Color`] of the button. + pub border_color: Color, + /// The icon [`Color`] of the button. + pub icon_color: Option, /// The text [`Color`] of the button. pub text_color: Color, /// The [`Border`] of the button. @@ -494,12 +690,36 @@ impl Style { ..self } } + + // /// Returns whether the [`Button`] is currently focused or not. + // pub fn is_focused(&self) -> bool { + // self.is_focused + // } + + // /// Returns whether the [`Button`] is currently hovered or not. + // pub fn is_hovered(&self) -> bool { + // self.is_hovered + // } + + // /// Focuses the [`Button`]. + // pub fn focus(&mut self) { + // self.is_focused = true; + // } + + // /// Unfocuses the [`Button`]. + // pub fn unfocus(&mut self) { + // self.is_focused = false; + // } } impl Default for Style { fn default() -> Self { Self { background: None, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + icon_color: None, text_color: Color::BLACK, border: Border::default(), shadow: Shadow::default(), @@ -679,3 +899,22 @@ fn disabled(style: Style) -> Style { ..style } } + +/// Produces a [`Command`] that focuses the [`Button`] with the given [`Id`]. +pub fn focus(id: Id) -> Task { + task::widget(operation::focusable::focus(id)) +} + +impl operation::Focusable for State { + fn is_focused(&self) -> bool { + self.is_focused + } + + fn focus(&mut self) { + self.is_focused = true; + } + + fn unfocus(&mut self) { + self.is_focused = false; + } +} diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 9fbccf824e..6bffccb730 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -233,6 +233,7 @@ where Some(Event::Keyboard(keyboard_event)) } core::Event::Window(_) => None, + _ => None, }; if let Some(canvas_event) = canvas_event { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 819f0d9da1..3956561cec 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -30,6 +30,11 @@ //! } //! ``` //! ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true) +//! Show toggle controls using checkboxes. +use iced_runtime::core::widget::Id; +#[cfg(feature = "a11y")] +use std::borrow::Cow; + use crate::core::alignment; use crate::core::event::{self, Event}; use crate::core::layout; @@ -41,8 +46,8 @@ use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, - Rectangle, Shell, Size, Theme, Widget, + id::Internal, Background, Border, Clipboard, Color, Element, Layout, + Length, Pixels, Rectangle, Shell, Size, Theme, Widget, }; /// A box that can be checked. @@ -87,6 +92,12 @@ pub struct Checkbox< Renderer: text::Renderer, Theme: Catalog, { + id: Id, + label_id: Id, + #[cfg(feature = "a11y")] + name: Option>, + #[cfg(feature = "a11y")] + description: Option>, is_checked: bool, on_toggle: Option Message + 'a>>, label: String, @@ -120,6 +131,12 @@ where /// * a boolean describing whether the [`Checkbox`] is checked or not pub fn new(label: impl Into, is_checked: bool) -> Self { Checkbox { + id: Id::unique(), + label_id: Id::unique(), + #[cfg(feature = "a11y")] + name: None, + #[cfg(feature = "a11y")] + description: None, is_checked, on_toggle: None, label: label.into(), @@ -136,7 +153,8 @@ where code_point: Renderer::CHECKMARK_ICON, size: None, line_height: text::LineHeight::default(), - shaping: text::Shaping::Basic, + shaping: text::Shaping::Advanced, + wrap: text::Wrapping::default(), }, class: Theme::default(), } @@ -243,6 +261,33 @@ where self.class = class.into(); self } + + #[cfg(feature = "a11y")] + /// Sets the name of the [`Button`]. + pub fn name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description_widget( + mut self, + description: &T, + ) -> Self { + self.description = Some(iced_accessibility::Description::Id( + description.description(), + )); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description(mut self, description: impl Into>) -> Self { + self.description = + Some(iced_accessibility::Description::Text(description.into())); + self + } } impl<'a, Message, Theme, Renderer> Widget @@ -275,7 +320,7 @@ where layout::next_to_each_other( &limits.width(self.width), self.spacing, - |_| layout::Node::new(Size::new(self.size, self.size)), + |_| layout::Node::new(crate::core::Size::new(self.size, self.size)), |limits| { let state = tree .state @@ -389,6 +434,7 @@ where size, line_height, shaping, + wrap, } = &self.icon; let size = size.unwrap_or(Pixels(bounds.height * 0.7)); @@ -429,6 +475,87 @@ where ); } } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + _state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::{ + accesskit::{Action, NodeBuilder, NodeId, Rect, Role}, + A11yNode, A11yTree, + }; + + let bounds = layout.bounds(); + let is_hovered = cursor.is_over(bounds); + let Rectangle { + x, + y, + width, + height, + } = bounds; + + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, + ); + + let mut node = NodeBuilder::new(Role::CheckBox); + node.add_action(Action::Focus); + node.add_action(Action::Default); + node.set_bounds(bounds); + if let Some(name) = self.name.as_ref() { + node.set_name(name.clone()); + } + match self.description.as_ref() { + Some(iced_accessibility::Description::Id(id)) => { + node.set_described_by( + id.iter() + .cloned() + .map(|id| NodeId::from(id)) + .collect::>(), + ); + } + Some(iced_accessibility::Description::Text(text)) => { + node.set_description(text.clone()); + } + None => {} + } + node.set_selected(self.is_checked); + if is_hovered { + node.set_hovered(); + } + node.add_action(Action::Default); + let mut label_node = NodeBuilder::new(Role::Label); + label_node.set_name(self.label.clone()); + // TODO proper label bounds + label_node.set_bounds(bounds); + + A11yTree::node_with_child_tree( + A11yNode::new(node, self.id.clone()), + A11yTree::leaf(label_node, self.label_id.clone()), + ) + } + fn id(&self) -> Option { + Some(Id(Internal::Set(vec![ + self.id.0.clone(), + self.label_id.0.clone(), + ]))) + } + + fn set_id(&mut self, id: Id) { + if let Id(Internal::Set(list)) = id { + if list.len() == 2 { + self.id.0 = list[0].clone(); + self.label_id.0 = list[1].clone(); + } + } + } } impl<'a, Message, Theme, Renderer> From> @@ -458,6 +585,8 @@ pub struct Icon { pub line_height: text::LineHeight, /// The shaping strategy of the icon. pub shaping: text::Shaping, + /// The wrap mode of the icon. + pub wrap: text::Wrapping, } /// The possible status of a [`Checkbox`]. diff --git a/widget/src/column.rs b/widget/src/column.rs index 213f68fc1a..ec0c059c28 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -205,8 +205,8 @@ where self.children.iter().map(Tree::new).collect() } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(&self.children); + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(self.children.as_mut_slice()); } fn size(&self) -> Size { @@ -355,6 +355,48 @@ where translation, ) } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::A11yTree; + A11yTree::join( + self.children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + .map(|((c, c_layout), state)| { + c.as_widget().a11y_nodes(c_layout, state, cursor) + }), + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, + ) { + for ((e, layout), state) in self + .children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + { + e.as_widget().drag_destinations( + state, + layout, + renderer, + dnd_rectangles, + ); + } + } } impl<'a, Message, Theme, Renderer> From> diff --git a/widget/src/container.rs b/widget/src/container.rs index f4993ac935..9fe70d7c73 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -28,7 +28,7 @@ use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; -use crate::core::widget::{self, Operation}; +use crate::core::widget::{self, Id, Operation}; use crate::core::{ self, color, Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector, @@ -67,7 +67,6 @@ pub struct Container< Theme: Catalog, Renderer: core::Renderer, { - id: Option, padding: Padding, width: Length, height: Length, @@ -93,7 +92,6 @@ where let size = content.as_widget().size_hint(); Container { - id: None, padding: Padding::ZERO, width: size.width.fluid(), height: size.height.fluid(), @@ -107,12 +105,6 @@ where } } - /// Sets the [`Id`] of the [`Container`]. - pub fn id(mut self, id: Id) -> Self { - self.id = Some(id); - self - } - /// Sets the [`Padding`] of the [`Container`]. pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); @@ -247,8 +239,8 @@ where self.content.as_widget().children() } - fn diff(&self, tree: &mut Tree) { - self.content.as_widget().diff(tree); + fn diff(&mut self, tree: &mut Tree) { + self.content.as_widget_mut().diff(tree); } fn size(&self) -> Size { @@ -285,7 +277,7 @@ where operation: &mut dyn Operation, ) { operation.container( - self.id.as_ref().map(|id| &id.0), + self.content.as_widget().id().as_ref(), layout.bounds(), &mut |operation| { self.content.as_widget().operate( @@ -359,9 +351,13 @@ where renderer, theme, &renderer::Style { + icon_color: style + .icon_color + .unwrap_or(renderer_style.icon_color), text_color: style .text_color .unwrap_or(renderer_style.text_color), + scale_factor: renderer_style.scale_factor, }, layout.children().next().unwrap(), cursor, @@ -388,6 +384,49 @@ where translation, ) } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_layout = layout.children().next().unwrap(); + let c_state = state.children.get(0); + + self.content.as_widget().a11y_nodes( + c_layout, + c_state.unwrap_or(&Tree::empty()), + cursor, + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, + ) { + if let Some(l) = layout.children().next() { + self.content.as_widget().drag_destinations( + state, + l, + renderer, + dnd_rectangles, + ); + } + } + + fn id(&self) -> Option { + self.content.as_widget().id().clone() + } + + fn set_id(&mut self, id: Id) { + self.content.as_widget_mut().set_id(id); + } } impl<'a, Message, Theme, Renderer> From> @@ -457,30 +496,6 @@ pub fn draw_background( } } -/// The identifier of a [`Container`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(widget::Id); - -impl Id { - /// Creates a custom [`Id`]. - pub fn new(id: impl Into>) -> Self { - Self(widget::Id::new(id)) - } - - /// Creates a unique [`Id`]. - /// - /// This function produces a different [`Id`] every time it is called. - pub fn unique() -> Self { - Self(widget::Id::unique()) - } -} - -impl From for widget::Id { - fn from(id: Id) -> Self { - id.0 - } -} - /// Produces a [`Task`] that queries the visible screen bounds of the /// [`Container`] with the given [`Id`]. pub fn visible_bounds(id: Id) -> Task> { @@ -574,6 +589,8 @@ pub fn visible_bounds(id: Id) -> Task> { /// The appearance of a container. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Style { + /// The icon [`Color`] of the container. + pub icon_color: Option, /// The text [`Color`] of the container. pub text_color: Option, /// The [`Background`] of the container. @@ -605,6 +622,8 @@ impl Style { pub fn background(self, background: impl Into) -> Self { Self { background: Some(background.into()), + icon_color: None, + text_color: None, ..self } } @@ -684,6 +703,7 @@ pub fn rounded_box(theme: &Theme) -> Style { let palette = theme.extended_palette(); Style { + icon_color: None, background: Some(palette.background.weak.color.into()), border: border::rounded(2), ..Style::default() diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 52290a54d5..e1f41ec585 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -27,6 +27,7 @@ use crate::vertical_slider::{self, VerticalSlider}; use crate::{Column, MouseArea, Row, Space, Stack, Themer}; use std::borrow::Borrow; + use std::ops::RangeInclusive; /// Creates a [`Column`] with the given children. @@ -391,8 +392,8 @@ where self.content.as_widget().children() } - fn diff(&self, tree: &mut Tree) { - self.content.as_widget().diff(tree); + fn diff(&mut self, tree: &mut Tree) { + self.content.as_widget_mut().diff(tree); } fn size(&self) -> Size { @@ -558,8 +559,8 @@ where vec![Tree::new(&self.base), Tree::new(&self.top)] } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(&[&self.base, &self.top]); + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(&mut [&mut self.base, &mut self.top]); } fn size(&self) -> Size { @@ -1624,7 +1625,10 @@ where /// ``` /// #[cfg(feature = "image")] -pub fn image(handle: impl Into) -> crate::Image { +#[cfg_attr(docsrs, doc(cfg(feature = "image")))] +pub fn image<'a, Handle>( + handle: impl Into, +) -> crate::Image<'a, Handle> { crate::Image::new(handle.into()) } diff --git a/widget/src/image.rs b/widget/src/image.rs index c8f2a620f6..2e80094078 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -17,6 +17,7 @@ //! ``` //! pub mod viewer; +use iced_runtime::core::widget::Id; pub use viewer::Viewer; use crate::core::image; @@ -31,6 +32,9 @@ use crate::core::{ pub use image::{FilterMethod, Handle}; +#[cfg(feature = "a11y")] +use std::borrow::Cow; + /// Creates a new [`Viewer`] with the given image `Handle`. pub fn viewer(handle: Handle) -> Viewer { Viewer::new(handle) @@ -55,7 +59,14 @@ pub fn viewer(handle: Handle) -> Viewer { /// ``` /// #[derive(Debug)] -pub struct Image { +pub struct Image<'a, Handle = image::Handle> { + id: Id, + #[cfg(feature = "a11y")] + name: Option>, + #[cfg(feature = "a11y")] + description: Option>, + #[cfg(feature = "a11y")] + label: Option>, handle: Handle, width: Length, height: Length, @@ -63,12 +74,21 @@ pub struct Image { filter_method: FilterMethod, rotation: Rotation, opacity: f32, + border_radius: [f32; 4], + phantom_data: std::marker::PhantomData<&'a ()>, } -impl Image { +impl<'a, Handle> Image<'a, Handle> { /// Creates a new [`Image`] with the given path. pub fn new(handle: impl Into) -> Self { Image { + id: Id::unique(), + #[cfg(feature = "a11y")] + name: None, + #[cfg(feature = "a11y")] + description: None, + #[cfg(feature = "a11y")] + label: None, handle: handle.into(), width: Length::Shrink, height: Length::Shrink, @@ -76,9 +96,17 @@ impl Image { filter_method: FilterMethod::default(), rotation: Rotation::default(), opacity: 1.0, + border_radius: [0.0; 4], + phantom_data: std::marker::PhantomData, } } + /// Sets the border radius of the image. + pub fn border_radius(mut self, border_radius: [f32; 4]) -> Self { + self.border_radius = border_radius; + self + } + /// Sets the width of the [`Image`] boundaries. pub fn width(mut self, width: impl Into) -> Self { self.width = width.into(); @@ -119,6 +147,41 @@ impl Image { self.opacity = opacity.into(); self } + + #[cfg(feature = "a11y")] + /// Sets the name of the [`Button`]. + pub fn name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description_widget( + mut self, + description: &T, + ) -> Self { + self.description = Some(iced_accessibility::Description::Id( + description.description(), + )); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description(mut self, description: impl Into>) -> Self { + self.description = + Some(iced_accessibility::Description::Text(description.into())); + self + } + + #[cfg(feature = "a11y")] + /// Sets the label of the [`Button`]. + pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { + self.label = + Some(label.label().into_iter().map(|l| l.into()).collect()); + self + } } /// Computes the layout of an [`Image`]. @@ -130,6 +193,7 @@ pub fn layout( height: Length, content_fit: ContentFit, rotation: Rotation, + _border_radius: [f32; 4], ) -> layout::Node where Renderer: image::Renderer, @@ -172,6 +236,7 @@ pub fn draw( filter_method: FilterMethod, rotation: Rotation, opacity: f32, + border_radius: [f32; 4], ) where Renderer: image::Renderer, Handle: Clone, @@ -203,16 +268,19 @@ pub fn draw( let drawing_bounds = Rectangle::new(position, final_size); + let offset = Vector::new( + (bounds.width - adjusted_fit.width).max(0.0) / 2.0, + (bounds.height - adjusted_fit.height).max(0.0) / 2.0, + ); + let render = |renderer: &mut Renderer| { renderer.draw_image( - image::Image { - handle: handle.clone(), - filter_method, - rotation: rotation.radians(), - opacity, - snap: true, - }, - drawing_bounds, + handle.clone(), + filter_method, + drawing_bounds + offset, + rotation.radians(), + opacity, + border_radius, ); }; @@ -224,8 +292,8 @@ pub fn draw( } } -impl Widget - for Image +impl<'a, Message, Theme, Renderer, Handle> Widget + for Image<'a, Handle> where Renderer: image::Renderer, Handle: Clone, @@ -251,6 +319,7 @@ where self.height, self.content_fit, self.rotation, + self.border_radius, ) } @@ -272,17 +341,78 @@ where self.filter_method, self.rotation, self.opacity, + self.border_radius, ); } + + #[cfg(feature = "a11y")] + fn a11y_nodes( + &self, + layout: Layout<'_>, + _state: &Tree, + _cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::{ + accesskit::{NodeBuilder, NodeId, Rect, Role}, + A11yTree, + }; + + let bounds = layout.bounds(); + let Rectangle { + x, + y, + width, + height, + } = bounds; + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, + ); + let mut node = NodeBuilder::new(Role::Image); + node.set_bounds(bounds); + if let Some(name) = self.name.as_ref() { + node.set_name(name.clone()); + } + match self.description.as_ref() { + Some(iced_accessibility::Description::Id(id)) => { + node.set_described_by( + id.iter() + .cloned() + .map(|id| NodeId::from(id)) + .collect::>(), + ); + } + Some(iced_accessibility::Description::Text(text)) => { + node.set_description(text.clone()); + } + None => {} + } + + if let Some(label) = self.label.as_ref() { + node.set_labelled_by(label.clone()); + } + + A11yTree::leaf(node, self.id.clone()) + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } } -impl<'a, Message, Theme, Renderer, Handle> From> +impl<'a, Message, Theme, Renderer, Handle> From> for Element<'a, Message, Theme, Renderer> where Renderer: image::Renderer, Handle: Clone + 'a, { - fn from(image: Image) -> Element<'a, Message, Theme, Renderer> { + fn from(image: Image<'a, Handle>) -> Element<'a, Message, Theme, Renderer> { Element::new(image) } } diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index b1aad22c79..d378315822 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -349,14 +349,12 @@ where let render = |renderer: &mut Renderer| { renderer.with_translation(translation, |renderer| { renderer.draw_image( - Image { - handle: self.handle.clone(), - filter_method: self.filter_method, - rotation: Radians(0.0), - opacity: 1.0, - snap: true, - }, + self.handle.clone(), + self.filter_method, drawing_bounds, + Radians(0.0), + 1.0, + [0.0; 4], ); }); }; diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 5852ede19d..6ec5ca4cf3 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -1,4 +1,6 @@ //! Keyed columns distribute content vertically while keeping continuity. +//! Distribute content vertically. + use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -223,7 +225,7 @@ where self.children.iter().map(Tree::new).collect() } - fn diff(&self, tree: &mut Tree) { + fn diff(&mut self, tree: &mut Tree) { let Tree { state, children, .. } = tree; @@ -232,11 +234,11 @@ where tree::diff_children_custom_with_search( children, - &self.children, - |tree, child| child.as_widget().diff(tree), + &mut self.children, + |tree, child| child.as_widget_mut().diff(tree), |index| { self.keys.get(index).or_else(|| self.keys.last()).copied() - != Some(state.keys[index]) + != state.keys.get(index).copied() }, |child| Tree::new(child.as_widget()), ); diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 232f254c2c..430a99a765 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -6,6 +6,7 @@ pub mod responsive; #[allow(deprecated)] pub use component::Component; +use iced_renderer::core::widget::Operation; pub use responsive::Responsive; mod cache; @@ -16,7 +17,7 @@ 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::widget::Widget; use crate::core::Element; use crate::core::{ self, Clipboard, Length, Point, Rectangle, Shell, Size, Vector, @@ -128,7 +129,7 @@ where self.with_element(|element| vec![Tree::new(element.as_widget())]) } - fn diff(&self, tree: &mut Tree) { + fn diff(&mut self, tree: &mut Tree) { let current = tree .state .downcast_mut::>(); @@ -147,8 +148,10 @@ where current.element = Rc::new(RefCell::new(Some(element))); (*self.element.borrow_mut()) = Some(current.element.clone()); - self.with_element(|element| { - tree.diff_children(std::slice::from_ref(&element.as_widget())); + self.with_element_mut(|element| { + tree.diff_children(std::slice::from_mut( + &mut element.as_widget_mut(), + )) }); } else { (*self.element.borrow_mut()) = Some(current.element.clone()); @@ -184,7 +187,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn crate::core::widget::Operation, ) { self.with_element(|element| { element.as_widget().operate( @@ -305,6 +308,40 @@ where None } } + + fn set_id(&mut self, _id: iced_runtime::core::id::Id) { + if let Some(e) = self.element.borrow_mut().as_mut() { + if let Some(e) = e.borrow_mut().as_mut() { + e.as_widget_mut().set_id(_id); + } + } + } + + fn id(&self) -> Option { + if let Some(e) = self.element.borrow().as_ref() { + if let Some(e) = e.borrow().as_ref() { + return e.as_widget().id(); + } + } + None + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut core::clipboard::DndDestinationRectangles, + ) { + self.with_element(|element| { + element.as_widget().drag_destinations( + &state.children[0], + layout, + renderer, + dnd_rectangles, + ); + }); + } } #[self_referencing] diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index c7bc1264c7..cf366a3015 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -13,6 +13,7 @@ use crate::core::{ }; use crate::runtime::overlay::Nested; +use iced_renderer::core::widget::Operation; use ouroboros::self_referencing; use std::cell::RefCell; use std::marker::PhantomData; @@ -148,13 +149,13 @@ where Renderer: renderer::Renderer, { fn diff_self(&self) { - self.with_element(|element| { + self.with_element_mut(|element| { self.tree .borrow_mut() .borrow_mut() .as_mut() .unwrap() - .diff_children(std::slice::from_ref(&element)); + .diff_children(std::slice::from_mut(element)); }); } @@ -263,6 +264,7 @@ where fn state(&self) -> tree::State { let state = Rc::new(RefCell::new(Some(Tree { + id: None, tag: tree::Tag::of::>(), state: tree::State::new(S::default()), children: vec![Tree::empty()], @@ -275,7 +277,7 @@ where vec![] } - fn diff(&self, tree: &mut Tree) { + fn diff(&mut self, tree: &mut Tree) { let tree = tree.state.downcast_ref::>>>(); *self.tree.borrow_mut() = tree.clone(); self.rebuild_element_if_necessary(); @@ -489,6 +491,49 @@ where None } } + + #[cfg(feature = "a11y")] + fn a11y_nodes( + &self, + layout: Layout<'_>, + tree: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let tree = tree.state.downcast_ref::>>>(); + self.with_element(|element| { + if let Some(tree) = tree.borrow().as_ref() { + element.as_widget().a11y_nodes( + layout, + &tree.children[0], + cursor, + ) + } else { + iced_accessibility::A11yTree::default() + } + }) + } + + fn drag_destinations( + &self, + tree: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut core::clipboard::DndDestinationRectangles, + ) { + let mut tree = tree + .state + .downcast_ref::>>>() + .borrow_mut(); + let mut tree = tree.as_ref().unwrap(); + self.with_element(|element| { + element.as_widget().drag_destinations( + &tree.children[0], + layout, + renderer, + dnd_rectangles, + ) + }); + } } struct Overlay<'a, 'b, Message, Theme, Renderer, Event, S>( diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index a6c40ab0b9..1b2d04eb74 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -3,7 +3,6 @@ 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, Vector, @@ -12,6 +11,7 @@ use crate::core::{ use crate::horizontal_space; use crate::runtime::overlay::Nested; +use iced_renderer::core::widget::Operation; use ouroboros::self_referencing; use std::cell::{RefCell, RefMut}; use std::marker::PhantomData; @@ -94,7 +94,7 @@ where self.size = new_size; self.layout = None; - tree.diff(&self.element); + tree.diff(&mut self.element); } fn resolve( @@ -165,7 +165,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn crate::core::widget::Operation, ) { let state = tree.state.downcast_mut::(); let mut content = self.content.borrow_mut(); @@ -329,6 +329,63 @@ where None } } + + #[cfg(feature = "a11y")] + fn a11y_nodes( + &self, + layout: Layout<'_>, + tree: &Tree, + cursor_position: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use std::rc::Rc; + + let tree = tree.state.downcast_ref::>>>(); + if let Some(tree) = tree.borrow().as_ref() { + self.content.borrow().element.as_widget().a11y_nodes( + layout, + &tree.children[0], + cursor_position, + ) + } else { + iced_accessibility::A11yTree::default() + } + } + + fn id(&self) -> Option { + self.content.borrow().element.as_widget().id() + } + + fn set_id(&mut self, _id: iced_runtime::core::id::Id) { + self.content + .borrow_mut() + .element + .as_widget_mut() + .set_id(_id); + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut core::clipboard::DndDestinationRectangles, + ) { + let ret = self.content.borrow_mut().resolve( + &mut state.state.downcast_ref::().tree.borrow_mut(), + renderer, + layout, + &self.view, + |tree, r, layout, element| { + element.as_widget().drag_destinations( + tree, + layout, + r, + dnd_rectangles, + ); + }, + ); + ret + } } impl<'a, Message, Theme, Renderer> @@ -465,7 +522,7 @@ where &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn Operation, ) { let _ = self.with_overlay_mut_maybe(|overlay| { overlay.operate(layout, renderer, operation); diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index c5a37ae3f1..0eaddf5781 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -1,4 +1,7 @@ //! A container for capturing mouse events. + +use iced_renderer::core::mouse::Click; + use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -20,7 +23,9 @@ pub struct MouseArea< Renderer = crate::Renderer, > { content: Element<'a, Message, Theme, Renderer>, + on_drag: Option, on_press: Option, + on_double_press: Option, on_release: Option, on_double_click: Option, on_right_press: Option, @@ -35,12 +40,25 @@ pub struct MouseArea< } impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> { + /// The message to emit when a drag is initiated. + #[must_use] + pub fn on_drag(mut self, message: Message) -> Self { + self.on_drag = Some(message); + self + } + /// The message to emit on a left button press. #[must_use] pub fn on_press(mut self, message: Message) -> Self { self.on_press = Some(message); self } + /// The message to emit on a left double button press. + #[must_use] + pub fn on_double_press(mut self, message: Message) -> Self { + self.on_double_press = Some(message); + self + } /// The message to emit on a left button release. #[must_use] @@ -133,12 +151,28 @@ impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> { } /// Local state of the [`MouseArea`]. -#[derive(Default)] struct State { is_hovered: bool, bounds: Rectangle, cursor_position: Option, previous_click: Option, + // TODO: Support on_enter and on_exit + drag_initiated: Option, + is_out_of_bounds: bool, + last_click: Option, +} +impl Default for State { + fn default() -> Self { + Self { + is_hovered: Default::default(), + drag_initiated: Default::default(), + is_out_of_bounds: true, + last_click: Default::default(), + cursor_position: None, + bounds: Default::default(), + previous_click: None, + } + } } impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> { @@ -148,7 +182,9 @@ impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> { ) -> Self { MouseArea { content: content.into(), + on_drag: None, on_press: None, + on_double_press: None, on_release: None, on_double_click: None, on_right_press: None, @@ -182,8 +218,8 @@ where vec![Tree::new(&self.content)] } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)); + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(std::slice::from_mut(&mut self.content)); } fn size(&self) -> Size { @@ -289,7 +325,6 @@ where viewport, ); } - fn overlay<'b>( &'b mut self, tree: &'b mut Tree, @@ -304,6 +339,22 @@ where translation, ) } + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, + ) { + if let Some(state) = state.children.iter().next() { + self.content.as_widget().drag_destinations( + state, + layout, + renderer, + dnd_rectangles, + ); + } + } } impl<'a, Message, Theme, Renderer> From> @@ -331,14 +382,15 @@ fn update( shell: &mut Shell<'_, Message>, ) -> event::Status { let state: &mut State = tree.state.downcast_mut(); - let cursor_position = cursor.position(); - let bounds = layout.bounds(); - if state.cursor_position != cursor_position || state.bounds != bounds { + if let Event::Mouse(mouse::Event::CursorMoved { .. }) + | Event::Touch(touch::Event::FingerMoved { .. }) = event + { let was_hovered = state.is_hovered; + let bounds = layout.bounds(); - state.is_hovered = cursor.is_over(layout.bounds()); + state.is_hovered = cursor.is_over(bounds); state.cursor_position = cursor_position; state.bounds = bounds; @@ -363,9 +415,45 @@ fn update( } if !cursor.is_over(layout.bounds()) { + if !state.is_out_of_bounds { + if widget + .on_enter + .as_ref() + .or(widget.on_exit.as_ref()) + .is_some() + { + if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { + state.is_out_of_bounds = true; + if let Some(message) = widget.on_exit.as_ref() { + shell.publish(message.clone()); + } + return event::Status::Captured; + } + } + } + return event::Status::Ignored; } + if let Some(message) = widget.on_double_press.as_ref() { + if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) = + event + { + if let Some(cursor_position) = cursor.position() { + let click = mouse::Click::new( + cursor_position, + mouse::Button::Left, + state.last_click, + ); + state.last_click = Some(click); + if let mouse::click::Kind::Double = click.kind() { + shell.publish(message.clone()); + return event::Status::Captured; + } + } + } + } + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) = event { @@ -405,6 +493,7 @@ fn update( if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) = event { + state.drag_initiated = None; shell.publish(message.clone()); return event::Status::Captured; @@ -462,5 +551,37 @@ fn update( } } + if let Some(message) = widget.on_enter.as_ref().or(widget.on_exit.as_ref()) + { + if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { + if state.is_out_of_bounds { + state.is_out_of_bounds = false; + if widget.on_enter.is_some() { + shell.publish(message.clone()); + } + return event::Status::Captured; + } + } + } + + if state.drag_initiated.is_none() && widget.on_drag.is_some() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) = event + { + state.drag_initiated = cursor.position(); + } + } else if let Some((message, drag_source)) = + widget.on_drag.as_ref().zip(state.drag_initiated) + { + if let Some(position) = cursor.position() { + if position.distance(drag_source) > 1.0 { + state.drag_initiated = None; + shell.publish(message.clone()); + + return event::Status::Captured; + } + } + } + event::Status::Ignored } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b641e8f516..1c86791ecc 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -40,6 +40,7 @@ pub struct Menu< text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrapping, font: Option, class: &'a ::Class<'b>, } @@ -73,7 +74,8 @@ where padding: Padding::ZERO, text_size: None, text_line_height: text::LineHeight::default(), - text_shaping: text::Shaping::Basic, + text_shaping: text::Shaping::Advanced, + text_wrap: text::Wrapping::default(), font: None, class, } @@ -112,6 +114,12 @@ where self } + /// Sets the [`text::Wrap`] mode of the [`Menu`]. + pub fn text_wrap(mut self, wrap: text::Wrapping) -> Self { + self.text_wrap = wrap; + self + } + /// Sets the font of the [`Menu`]. pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); @@ -198,10 +206,11 @@ where text_size, text_line_height, text_shaping, + text_wrap, class, } = menu; - let list = Scrollable::new(List { + let mut list = Scrollable::new(List { options, hovered_option, on_selected, @@ -209,12 +218,13 @@ where font, text_size, text_line_height, + text_wrap, text_shaping, padding, class, }); - state.tree.diff(&list as &dyn Widget<_, _, _>); + state.tree.diff(&mut list as &mut dyn Widget<_, _, _>); Self { position, @@ -330,6 +340,7 @@ where text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrapping, font: Option, class: &'a ::Class<'b>, } diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index b4ed4b6484..383957c4cc 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -85,7 +85,6 @@ 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::{ self, Background, Border, Clipboard, Color, Element, Layout, Length, @@ -165,6 +164,7 @@ pub struct PaneGrid< spacing: f32, on_click: Option Message + 'a>>, on_drag: Option Message + 'a>>, + #[allow(clippy::type_complexity)] on_resize: Option<(f32, Box Message + 'a>)>, class: ::Class<'a>, } @@ -319,7 +319,7 @@ where self.contents.iter().map(Content::state).collect() } - fn diff(&self, tree: &mut Tree) { + fn diff(&mut self, tree: &mut Tree) { let Memory { order, .. } = tree.state.downcast_ref(); // `Pane` always increments and is iterated by Ord so new @@ -341,8 +341,10 @@ where retain }); + let ids = self.contents.iter().map(|_| None).collect(); // TODO tree.diff_children_custom( - &self.contents, + &mut self.contents, + ids, |state, content| content.diff(state), Content::state, ); @@ -403,7 +405,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn crate::core::widget::Operation, ) { operation.container(None, layout.bounds(), &mut |operation| { self.panes diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index ec0676b188..ff6ce9ea34 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -1,3 +1,5 @@ +use iced_renderer::core::widget::Operation; + use crate::container; use crate::core::event::{self, Event}; use crate::core::layout; @@ -91,13 +93,13 @@ where } } - pub(super) fn diff(&self, tree: &mut Tree) { + pub(super) fn diff(&mut self, tree: &mut Tree) { if tree.children.len() == 2 { - if let Some(title_bar) = self.title_bar.as_ref() { + if let Some(title_bar) = self.title_bar.as_mut() { title_bar.diff(&mut tree.children[1]); } - tree.children[0].diff(&self.body); + tree.children[0].diff(&mut self.body); } else { *tree = self.state(); } diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 5002b4f73c..1495a575f2 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -1,10 +1,12 @@ +use iced_renderer::core::widget::Operation; + 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::widget::Tree; use crate::core::{ self, Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, Vector, @@ -129,17 +131,17 @@ where } } - pub(super) fn diff(&self, tree: &mut Tree) { + pub(super) fn diff(&mut self, tree: &mut Tree) { if tree.children.len() == 3 { - if let Some(controls) = self.controls.as_ref() { - if let Some(compact) = controls.compact.as_ref() { + if let Some(controls) = self.controls.as_mut() { + if let Some(compact) = controls.compact.as_mut() { tree.children[2].diff(compact); } - tree.children[1].diff(&controls.full); + tree.children[1].diff(&mut controls.full); } - tree.children[0].diff(&self.content); + tree.children[0].diff(&mut self.content); } else { *tree = self.state(); } @@ -163,7 +165,9 @@ where let style = theme.style(&self.class); let inherited_style = renderer::Style { + icon_color: style.icon_color.unwrap_or(inherited_style.icon_color), text_color: style.text_color.unwrap_or(inherited_style.text_color), + scale_factor: inherited_style.scale_factor, }; container::draw_background(renderer, &style, bounds); @@ -374,7 +378,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn widget::Operation, + operation: &mut dyn crate::core::widget::Operation, ) { let mut children = layout.children(); let padded = children.next().unwrap(); diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 4f1e9da9cd..042d32f1f0 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -60,6 +60,8 @@ //! } //! } //! ``` +//! Display a dropdown list of selectable values. + use crate::core::alignment; use crate::core::event::{self, Event}; use crate::core::keyboard; @@ -169,6 +171,7 @@ pub struct PickList< text_size: Option, text_line_height: text::LineHeight, text_shaping: text::Shaping, + text_wrap: text::Wrapping, font: Option, handle: Handle, class: ::Class<'a>, @@ -203,7 +206,8 @@ where padding: crate::button::DEFAULT_PADDING, text_size: None, text_line_height: text::LineHeight::default(), - text_shaping: text::Shaping::default(), + text_shaping: text::Shaping::Advanced, + text_wrap: text::Wrapping::default(), font: None, handle: Handle::default(), class: ::default(), @@ -250,6 +254,12 @@ where self } + /// Sets the [`text::Wrap`] mode of the [`PickList`]. + pub fn text_wrap(mut self, wrap: text::Wrapping) -> Self { + self.text_wrap = wrap; + self + } + /// Sets the font of the [`PickList`]. pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); @@ -372,7 +382,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: self.text_shaping, - wrapping: text::Wrapping::default(), + wrapping: self.text_wrap, }; for (option, paragraph) in options.iter().zip(state.options.iter_mut()) @@ -592,6 +602,7 @@ where *size, text::LineHeight::default(), text::Shaping::Basic, + text::Wrapping::default(), )), Handle::Static(Icon { font, @@ -599,7 +610,10 @@ where size, line_height, shaping, - }) => Some((*font, *code_point, *size, *line_height, *shaping)), + wrap, + }) => { + Some((*font, *code_point, *size, *line_height, *shaping, *wrap)) + } Handle::Dynamic { open, closed } => { if state.is_open { Some(( @@ -608,6 +622,7 @@ where open.size, open.line_height, open.shaping, + open.wrap, )) } else { Some(( @@ -616,13 +631,16 @@ where closed.size, closed.line_height, closed.shaping, + closed.wrap, )) } } Handle::None => None, }; - if let Some((font, code_point, size, line_height, shaping)) = handle { + if let Some((font, code_point, size, line_height, shaping, wrap)) = + handle + { let size = size.unwrap_or_else(|| renderer.default_size()); renderer.fill_text( @@ -638,7 +656,7 @@ where horizontal_alignment: alignment::Horizontal::Right, vertical_alignment: alignment::Vertical::Center, shaping, - wrapping: text::Wrapping::default(), + wrapping: wrap, }, Point::new( bounds.x + bounds.width - self.padding.right, @@ -668,7 +686,7 @@ where horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: self.text_shaping, - wrapping: text::Wrapping::default(), + wrapping: self.text_wrap, }, Point::new(bounds.x + self.padding.left, bounds.center_y()), if is_selected { @@ -814,6 +832,8 @@ pub struct Icon { pub line_height: text::LineHeight, /// The shaping strategy of the icon. pub shaping: text::Shaping, + /// The wrap mode of the icon. + pub wrap: text::Wrapping, } /// The possible status of a [`PickList`]. diff --git a/widget/src/radio.rs b/widget/src/radio.rs index d2a3bd6a37..66cad3150c 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -188,7 +188,7 @@ where spacing: Self::DEFAULT_SPACING, text_size: None, text_line_height: text::LineHeight::default(), - text_shaping: text::Shaping::default(), + text_shaping: text::Shaping::Advanced, text_wrapping: text::Wrapping::default(), font: None, class: Theme::default(), diff --git a/widget/src/row.rs b/widget/src/row.rs index 9c0fa97e11..575dd24cd9 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -203,8 +203,8 @@ where self.children.iter().map(Tree::new).collect() } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(&self.children); + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(&mut self.children) } fn size(&self) -> Size { @@ -351,6 +351,48 @@ where translation, ) } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::A11yTree; + A11yTree::join( + self.children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + .map(|((c, c_layout), state)| { + c.as_widget().a11y_nodes(c_layout, state, cursor) + }), + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, + ) { + for ((e, layout), state) in self + .children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + { + e.as_widget().drag_destinations( + state, + layout, + renderer, + dnd_rectangles, + ); + } + } } impl<'a, Message, Theme, Renderer> From> @@ -390,7 +432,7 @@ where self.row.children() } - fn diff(&self, tree: &mut Tree) { + fn diff(&mut self, tree: &mut Tree) { self.row.diff(tree); } diff --git a/widget/src/rule.rs b/widget/src/rule.rs index 2457768367..d4b68c807e 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -79,6 +79,24 @@ where } } + /// Set the width of the rule + /// Will not be applied if it is vertical + pub fn width(mut self, width: impl Into) -> Self { + if self.is_horizontal { + self.width = width.into(); + } + self + } + + /// Set the height of the rule + /// Will not be applied if it is horizontal + pub fn height(mut self, height: impl Into) -> Self { + if !self.is_horizontal { + self.height = height.into(); + } + self + } + /// Sets the style of the [`Rule`]. #[must_use] pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 528d63c1da..6841d19c80 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -21,6 +21,11 @@ //! ``` use crate::container; use crate::core::border::{self, Border}; +use crate::core::clipboard::DndDestinationRectangles; +use iced_runtime::core::widget::Id; +#[cfg(feature = "a11y")] +use std::borrow::Cow; + use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::layout; @@ -29,13 +34,12 @@ use crate::core::overlay; use crate::core::renderer; 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::{ - self, Background, Clipboard, Color, Element, Layout, Length, Padding, - Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, + self, id::Internal, Background, Clipboard, Color, Element, Layout, Length, + Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -74,7 +78,14 @@ pub struct Scrollable< Theme: Catalog, Renderer: core::Renderer, { - id: Option, + id: Id, + scrollbar_id: Id, + #[cfg(feature = "a11y")] + name: Option>, + #[cfg(feature = "a11y")] + description: Option>, + #[cfg(feature = "a11y")] + label: Option>, width: Length, height: Length, direction: Direction, @@ -101,7 +112,14 @@ where direction: impl Into, ) -> Self { Scrollable { - id: None, + id: Id::unique(), + scrollbar_id: Id::unique(), + #[cfg(feature = "a11y")] + name: None, + #[cfg(feature = "a11y")] + description: None, + #[cfg(feature = "a11y")] + label: None, width: Length::Shrink, height: Length::Shrink, direction: direction.into(), @@ -144,7 +162,7 @@ where /// Sets the [`Id`] of the [`Scrollable`]. pub fn id(mut self, id: Id) -> Self { - self.id = Some(id); + self.id = id; self } @@ -248,6 +266,41 @@ where self.class = class.into(); self } + + #[cfg(feature = "a11y")] + /// Sets the name of the [`Button`]. + pub fn name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description_widget( + mut self, + description: &impl iced_accessibility::Describes, + ) -> Self { + self.description = Some(iced_accessibility::Description::Id( + description.description(), + )); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description(mut self, description: impl Into>) -> Self { + self.description = + Some(iced_accessibility::Description::Text(description.into())); + self + } + + #[cfg(feature = "a11y")] + /// Sets the label of the [`Button`]. + pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { + self.label = + Some(label.label().into_iter().map(|l| l.into()).collect()); + self + } } /// The direction of [`Scrollable`]. @@ -402,8 +455,8 @@ where vec![Tree::new(&self.content)] } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(std::slice::from_ref(&self.content)); + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(std::slice::from_mut(&mut self.content)) } fn size(&self) -> Size { @@ -487,24 +540,20 @@ where operation.scrollable( state, - self.id.as_ref().map(|id| &id.0), + Some(&self.id), bounds, content_bounds, translation, ); - operation.container( - self.id.as_ref().map(|id| &id.0), - bounds, - &mut |operation| { - self.content.as_widget().operate( - &mut tree.children[0], - layout.children().next().unwrap(), - renderer, - operation, - ); - }, - ); + operation.container(Some(&self.id), bounds, &mut |operation| { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + operation, + ); + }); } fn on_event( @@ -764,36 +813,36 @@ where return event::Status::Ignored; } - let delta = match delta { + let Vector { x, y } = match delta { mouse::ScrollDelta::Lines { x, y } => { - let is_shift_pressed = state.keyboard_modifiers.shift(); - - // macOS automatically inverts the axes when Shift is pressed - let (x, y) = - if cfg!(target_os = "macos") && is_shift_pressed { - (y, x) - } else { - (x, y) - }; - - let is_vertical = match self.direction { - Direction::Vertical(_) => true, - Direction::Horizontal(_) => false, - Direction::Both { .. } => !is_shift_pressed, - }; - - let movement = if is_vertical { - Vector::new(x, y) - } else { - Vector::new(y, x) - }; - // TODO: Configurable speed/friction (?) - -movement * 60.0 + Vector::new(x, y) * 60. } mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y), }; + let is_shift_pressed = state.keyboard_modifiers.shift(); + + // macOS automatically inverts the axes when Shift is pressed + let (x, y) = if cfg!(target_os = "macos") && is_shift_pressed { + (y, x) + } else { + (x, y) + }; + + let is_vertical = match self.direction { + Direction::Vertical(_) => true, + Direction::Horizontal(_) => false, + Direction::Both { .. } => !is_shift_pressed, + }; + + let movement = if is_vertical { + Vector::new(x, y) + } else { + Vector::new(y, x) + }; + let delta = movement * -1.; + state.scroll( self.direction.align(delta), bounds, @@ -1146,6 +1195,181 @@ where translation - offset, ) } + + #[cfg(feature = "a11y")] + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::{ + accesskit::{NodeBuilder, NodeId, Rect, Role}, + A11yId, A11yNode, A11yTree, + }; + + let child_layout = layout.children().next().unwrap(); + let child_tree = &state.children[0]; + let child_tree = self.content.as_widget().a11y_nodes( + child_layout, + &child_tree, + cursor, + ); + + let window = layout.bounds(); + let is_hovered = cursor.is_over(window); + let Rectangle { + x, + y, + width, + height, + } = window; + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, + ); + let mut node = NodeBuilder::new(Role::ScrollView); + node.set_bounds(bounds); + if let Some(name) = self.name.as_ref() { + node.set_name(name.clone()); + } + match self.description.as_ref() { + Some(iced_accessibility::Description::Id(id)) => { + node.set_described_by( + id.iter() + .cloned() + .map(|id| NodeId::from(id)) + .collect::>(), + ); + } + Some(iced_accessibility::Description::Text(text)) => { + node.set_description(text.clone()); + } + None => {} + } + + if is_hovered { + node.set_hovered(); + } + + if let Some(label) = self.label.as_ref() { + node.set_labelled_by(label.clone()); + } + + let content = layout.children().next().unwrap(); + let content_bounds = content.bounds(); + + let mut scrollbar_node = NodeBuilder::new(Role::ScrollBar); + if matches!(state.state, tree::State::Some(_)) { + let state = state.state.downcast_ref::(); + let scrollbars = Scrollbars::new( + state, + self.direction, + content_bounds, + content_bounds, + ); + for (window, content, offset, scrollbar) in scrollbars + .x + .iter() + .map(|s| { + (window.width, content_bounds.width, state.offset_x, s) + }) + .chain(scrollbars.y.iter().map(|s| { + (window.height, content_bounds.height, state.offset_y, s) + })) + { + let scrollbar_bounds = scrollbar.total_bounds; + let is_hovered = cursor.is_over(scrollbar_bounds); + let Rectangle { + x, + y, + width, + height, + } = scrollbar_bounds; + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, + ); + scrollbar_node.set_bounds(bounds); + if is_hovered { + scrollbar_node.set_hovered(); + } + scrollbar_node + .set_controls(vec![A11yId::Widget(self.id.clone()).into()]); + scrollbar_node.set_numeric_value( + 100.0 * offset.absolute(window, content) as f64 + / scrollbar_bounds.height as f64, + ); + } + } + + let child_tree = A11yTree::join( + [ + child_tree, + A11yTree::leaf(scrollbar_node, self.scrollbar_id.clone()), + ] + .into_iter(), + ); + A11yTree::node_with_child_tree( + A11yNode::new(node, self.id.clone()), + child_tree, + ) + } + + fn id(&self) -> Option { + Some(Id(Internal::Set(vec![ + self.id.0.clone(), + self.scrollbar_id.0.clone(), + ]))) + } + + fn set_id(&mut self, id: Id) { + if let Id(Internal::Set(list)) = id { + if list.len() == 2 { + self.id.0 = list[0].clone(); + self.scrollbar_id.0 = list[1].clone(); + } + } + } + + fn drag_destinations( + &self, + tree: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, + ) { + let my_state = tree.state.downcast_ref::(); + if let Some((c_layout, c_state)) = + layout.children().zip(tree.children.iter()).next() + { + let mut my_dnd_rectangles = DndDestinationRectangles::new(); + self.content.as_widget().drag_destinations( + c_state, + c_layout, + renderer, + &mut my_dnd_rectangles, + ); + let mut my_dnd_rectangles = my_dnd_rectangles.into_rectangles(); + + let bounds = layout.bounds(); + let content_bounds = c_layout.bounds(); + for r in &mut my_dnd_rectangles { + let translation = my_state.translation( + self.direction, + bounds, + content_bounds, + ); + r.rectangle.x -= translation.x as f64; + r.rectangle.y -= translation.y as f64; + } + dnd_rectangles.append(&mut my_dnd_rectangles); + } + } } impl<'a, Message, Theme, Renderer> @@ -1163,50 +1387,22 @@ where } } -/// The identifier of a [`Scrollable`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(widget::Id); - -impl Id { - /// Creates a custom [`Id`]. - pub fn new(id: impl Into>) -> Self { - Self(widget::Id::new(id)) - } - - /// Creates a unique [`Id`]. - /// - /// This function produces a different [`Id`] every time it is called. - pub fn unique() -> Self { - Self(widget::Id::unique()) - } -} - -impl From for widget::Id { - fn from(id: Id) -> Self { - id.0 - } -} - /// Produces a [`Task`] that snaps the [`Scrollable`] with the given [`Id`] /// to the provided [`RelativeOffset`]. pub fn snap_to(id: Id, offset: RelativeOffset) -> Task { - task::effect(Action::widget(operation::scrollable::snap_to(id.0, offset))) + task::effect(Action::widget(operation::scrollable::snap_to(id, offset))) } /// Produces a [`Task`] that scrolls the [`Scrollable`] with the given [`Id`] /// to the provided [`AbsoluteOffset`]. pub fn scroll_to(id: Id, offset: AbsoluteOffset) -> Task { - task::effect(Action::widget(operation::scrollable::scroll_to( - id.0, offset, - ))) + task::effect(Action::widget(operation::scrollable::scroll_to(id, offset))) } /// Produces a [`Task`] that scrolls the [`Scrollable`] with the given [`Id`] /// by the provided [`AbsoluteOffset`]. pub fn scroll_by(id: Id, offset: AbsoluteOffset) -> Task { - task::effect(Action::widget(operation::scrollable::scroll_by( - id.0, offset, - ))) + task::effect(Action::widget(operation::scrollable::scroll_by(id, offset))) } fn notify_scroll( diff --git a/widget/src/shader.rs b/widget/src/shader.rs index fa6923360b..80fa62d549 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -110,6 +110,10 @@ where Some(Event::RedrawRequested(instant)) } core::Event::Window(_) => None, + #[cfg(feature = "a11y")] + core::Event::A11y(_, _) => None, + core::Event::Dnd(_) => None, + core::Event::PlatformSpecific(_) => None, }; if let Some(custom_shader_event) = custom_shader_event { diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 31aa0e0cba..f770dfadd4 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -37,6 +37,7 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::Id; use crate::core::{ self, Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Theme, Widget, @@ -44,6 +45,12 @@ use crate::core::{ use std::ops::RangeInclusive; +use iced_renderer::core::border::Radius; +use iced_runtime::core::gradient::Linear; + +#[cfg(feature = "a11y")] +use std::borrow::Cow; + /// An horizontal bar and a handle that selects a single value from a range of /// values. /// @@ -85,11 +92,19 @@ pub struct Slider<'a, T, Message, Theme = crate::Theme> where Theme: Catalog, { + id: Id, + #[cfg(feature = "a11y")] + name: Option>, + #[cfg(feature = "a11y")] + description: Option>, + #[cfg(feature = "a11y")] + label: Option>, range: RangeInclusive, step: T, shift_step: Option, value: T, default: Option, + breakpoints: &'a [T], on_change: Box Message + 'a>, on_release: Option, width: Length, @@ -131,11 +146,19 @@ where }; Slider { + id: Id::unique(), + #[cfg(feature = "a11y")] + name: None, + #[cfg(feature = "a11y")] + description: None, + #[cfg(feature = "a11y")] + label: None, value, default: None, range, step: T::from(1), shift_step: None, + breakpoints: &[], on_change: Box::new(on_change), on_release: None, width: Length::Fill, @@ -152,12 +175,20 @@ where self } + /// Defines breakpoints to visibly mark on the slider. + /// + /// The slider will gravitate towards a breakpoint when near it. + pub fn breakpoints(mut self, breakpoints: &'a [T]) -> Self { + self.breakpoints = breakpoints; + self + } + /// Sets the release message of the [`Slider`]. /// This is called when the mouse is released from the slider. /// /// Typically, the user's interaction with the slider is finished when this message is produced. /// This is useful if you need to spawn a long-running task from the slider's result, where - /// the default on_change message could create too many events. + /// the default `on_change` message could create too many events. pub fn on_release(mut self, on_release: Message) -> Self { self.on_release = Some(on_release); self @@ -206,6 +237,41 @@ where self.class = class.into(); self } + + #[cfg(feature = "a11y")] + /// Sets the name of the [`Button`]. + pub fn name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description_widget( + mut self, + description: &impl iced_accessibility::Describes, + ) -> Self { + self.description = Some(iced_accessibility::Description::Id( + description.description(), + )); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description(mut self, description: impl Into>) -> Self { + self.description = + Some(iced_accessibility::Description::Text(description.into())); + self + } + + #[cfg(feature = "a11y")] + /// Sets the label of the [`Button`]. + pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { + self.label = + Some(label.label().into_iter().map(|l| l.into()).collect()); + self + } } impl<'a, T, Message, Theme, Renderer> Widget @@ -433,15 +499,51 @@ where }, ); + let border_width = style + .handle + .border_width + .min(bounds.height / 2.0) + .min(bounds.width / 2.0); + let (handle_width, handle_height, handle_border_radius) = match style.handle.shape { HandleShape::Circle { radius } => { - (radius * 2.0, radius * 2.0, radius.into()) + let radius = (radius) + .max(2.0 * border_width) + .min(bounds.height / 2.0) + .min(bounds.width / 2.0); + (radius * 2.0, radius * 2.0, Radius::from(radius)) } HandleShape::Rectangle { + height, width, border_radius, - } => (f32::from(width), bounds.height, border_radius), + } => { + let width = (f32::from(width)) + .max(2.0 * border_width) + .min(bounds.width); + let height = (f32::from(height)) + .max(2.0 * border_width) + .min(bounds.height); + let mut border_radius: [f32; 4] = border_radius.into(); + for r in &mut border_radius { + *r = (*r) + .min(height / 2.0) + .min(width / 2.0) + .max(*r * (width + border_width * 2.0) / width); + } + + ( + width, + height, + Radius { + top_left: border_radius[0], + top_right: border_radius[1], + bottom_right: border_radius[2], + bottom_left: border_radius[3], + }, + ) + } }; let value = self.value.into() as f32; @@ -460,6 +562,52 @@ where let rail_y = bounds.y + bounds.height / 2.0; + // Draw the breakpoint indicators beneath the slider. + const BREAKPOINT_WIDTH: f32 = 2.0; + for &value in self.breakpoints { + let value: f64 = value.into(); + let offset = if range_start >= range_end { + 0.0 + } else { + (bounds.width - BREAKPOINT_WIDTH) * (value as f32 - range_start) + / (range_end - range_start) + }; + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + offset, + y: rail_y + 6.0, + width: BREAKPOINT_WIDTH, + height: 8.0, + }, + border: Border { + radius: 0.0.into(), + width: 0.0, + color: Color::TRANSPARENT, + }, + ..renderer::Quad::default() + }, + crate::core::Background::Color(style.breakpoint.color), + ); + } + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y - style.rail.width / 2.0, + width: offset + handle_width / 2.0, + height: style.rail.width, + }, + border: style.rail.border, + ..renderer::Quad::default() + }, + style.rail.backgrounds.0, + ); + + // TODO align the angle of the gradient for the slider? + renderer.fill_quad( renderer::Quad { bounds: Rectangle { @@ -477,9 +625,9 @@ where renderer.fill_quad( renderer::Quad { bounds: Rectangle { - x: bounds.x + offset + handle_width / 2.0, + x: bounds.x, y: rail_y - style.rail.width / 2.0, - width: bounds.width - offset - handle_width / 2.0, + width: offset + handle_width / 2.0, height: style.rail.width, }, border: style.rail.border, @@ -488,11 +636,12 @@ where style.rail.backgrounds.1, ); + // handle renderer.fill_quad( renderer::Quad { bounds: Rectangle { x: bounds.x + offset, - y: rail_y - handle_height / 2.0, + y: rail_y - (handle_height / 2.0), width: handle_width, height: handle_height, }, @@ -527,6 +676,87 @@ where mouse::Interaction::default() } } + + #[cfg(feature = "a11y")] + fn a11y_nodes( + &self, + layout: Layout<'_>, + _state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::{ + accesskit::{NodeBuilder, NodeId, Rect, Role}, + A11yTree, + }; + + let bounds = layout.bounds(); + let is_hovered = cursor.is_over(bounds); + let Rectangle { + x, + y, + width, + height, + } = bounds; + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, + ); + let mut node = NodeBuilder::new(Role::Slider); + node.set_bounds(bounds); + if let Some(name) = self.name.as_ref() { + node.set_name(name.clone()); + } + match self.description.as_ref() { + Some(iced_accessibility::Description::Id(id)) => { + node.set_described_by( + id.iter() + .cloned() + .map(|id| NodeId::from(id)) + .collect::>(), + ); + } + Some(iced_accessibility::Description::Text(text)) => { + node.set_description(text.clone()); + } + None => {} + } + + if is_hovered { + node.set_hovered(); + } + + if let Some(label) = self.label.as_ref() { + node.set_labelled_by(label.clone()); + } + + if let Ok(min) = self.range.start().clone().try_into() { + node.set_min_numeric_value(min); + } + if let Ok(max) = self.range.end().clone().try_into() { + node.set_max_numeric_value(max); + } + if let Ok(value) = self.value.clone().try_into() { + node.set_numeric_value(value); + } + if let Ok(step) = self.step.clone().try_into() { + node.set_numeric_value_step(step); + } + + // TODO: This could be a setting on the slider + node.set_live(iced_accessibility::accesskit::Live::Polite); + + A11yTree::leaf(node, self.id.clone()) + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } } impl<'a, T, Message, Theme, Renderer> From> @@ -568,6 +798,15 @@ pub struct Style { pub rail: Rail, /// The appearance of the [`Handle`] of the slider. pub handle: Handle, + /// The appearance of breakpoints. + pub breakpoint: Breakpoint, +} + +/// The appearance of slider breakpoints. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Breakpoint { + /// The color of the slider breakpoint. + pub color: Color, } impl Style { @@ -592,6 +831,21 @@ pub struct Rail { pub border: Border, } +/// The background color of the rail +#[derive(Debug, Clone, Copy)] +pub enum RailBackground { + /// Start and end colors of the rail + Pair(Color, Color), + /// Linear gradient for the background of the rail + /// includes an option for auto-selecting the angle + Gradient { + /// the linear gradient of the slider + gradient: Linear, + /// Let the widget determin the angle of the gradient + auto_angle: bool, + }, +} + /// The appearance of the handle of a slider. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Handle { @@ -617,6 +871,8 @@ pub enum HandleShape { Rectangle { /// The width of the rectangle. width: u16, + /// The height of the rectangle. + height: u16, /// The border radius of the corners of the rectangle. border_radius: border::Radius, }, @@ -675,5 +931,8 @@ pub fn default(theme: &Theme, status: Status) -> Style { border_color: Color::TRANSPARENT, border_width: 0.0, }, + breakpoint: Breakpoint { + color: palette.background.weak.text, + }, } } diff --git a/widget/src/stack.rs b/widget/src/stack.rs index 6a44c32830..32d1617522 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -1,4 +1,5 @@ //! Display content on top of other content. + use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -134,8 +135,8 @@ where self.children.iter().map(Tree::new).collect() } - fn diff(&self, tree: &mut Tree) { - tree.diff_children(&self.children); + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(&mut self.children); } fn size(&self) -> Size { diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 8d57265a7a..7cb1afb9d3 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -15,6 +15,9 @@ //! svg("tiger.svg").into() //! } //! ``` +//! Display vector graphics in your application. +use iced_runtime::core::widget::Id; + use crate::core::layout; use crate::core::mouse; use crate::core::renderer; @@ -25,6 +28,9 @@ use crate::core::{ Size, Theme, Vector, Widget, }; +#[cfg(feature = "a11y")] +use std::borrow::Cow; +use std::marker::PhantomData; use std::path::PathBuf; pub use crate::core::svg::Handle; @@ -56,6 +62,13 @@ pub struct Svg<'a, Theme = crate::Theme> where Theme: Catalog, { + id: Id, + #[cfg(feature = "a11y")] + name: Option>, + #[cfg(feature = "a11y")] + description: Option>, + #[cfg(feature = "a11y")] + label: Option>, handle: Handle, width: Length, height: Length, @@ -63,6 +76,8 @@ where class: Theme::Class<'a>, rotation: Rotation, opacity: f32, + symbolic: bool, + _phantom_data: PhantomData<&'a ()>, } impl<'a, Theme> Svg<'a, Theme> @@ -72,6 +87,13 @@ where /// Creates a new [`Svg`] from the given [`Handle`]. pub fn new(handle: impl Into) -> Self { Svg { + id: Id::unique(), + #[cfg(feature = "a11y")] + name: None, + #[cfg(feature = "a11y")] + description: None, + #[cfg(feature = "a11y")] + label: None, handle: handle.into(), width: Length::Fill, height: Length::Shrink, @@ -79,6 +101,8 @@ where class: Theme::default(), rotation: Rotation::default(), opacity: 1.0, + symbolic: false, + _phantom_data: PhantomData::default(), } } @@ -114,6 +138,13 @@ where } } + /// Symbolic icons inherit their color from the renderer if a color is not defined. + #[must_use] + pub fn symbolic(mut self, symbolic: bool) -> Self { + self.symbolic = symbolic; + self + } + /// Sets the style of the [`Svg`]. #[must_use] pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self @@ -146,6 +177,41 @@ where self.opacity = opacity.into(); self } + + #[cfg(feature = "a11y")] + /// Sets the name of the [`Button`]. + pub fn name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description_widget( + mut self, + description: &T, + ) -> Self { + self.description = Some(iced_accessibility::Description::Id( + description.description(), + )); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description(mut self, description: impl Into>) -> Self { + self.description = + Some(iced_accessibility::Description::Text(description.into())); + self + } + + #[cfg(feature = "a11y")] + /// Sets the label of the [`Button`]. + pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { + self.label = + Some(label.label().into_iter().map(|l| l.into()).collect()); + self + } } impl<'a, Message, Theme, Renderer> Widget @@ -200,7 +266,7 @@ where _state: &Tree, renderer: &mut Renderer, theme: &Theme, - _style: &renderer::Style, + renderer_style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, _viewport: &Rectangle, @@ -239,7 +305,10 @@ where Status::Idle }; - let style = theme.style(&self.class, status); + let mut style = theme.style(&self.class, status); + if self.symbolic && style.color.is_none() { + style.color = Some(renderer_style.icon_color); + } let render = |renderer: &mut Renderer| { renderer.draw_svg( @@ -248,6 +317,7 @@ where color: style.color, rotation: self.rotation.radians(), opacity: self.opacity, + border_radius: [0.0; 4], }, drawing_bounds, ); @@ -261,6 +331,66 @@ where render(renderer); } } + + #[cfg(feature = "a11y")] + fn a11y_nodes( + &self, + layout: Layout<'_>, + _state: &Tree, + _cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::{ + accesskit::{NodeBuilder, NodeId, Rect, Role}, + A11yTree, + }; + + let bounds = layout.bounds(); + let Rectangle { + x, + y, + width, + height, + } = bounds; + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, + ); + let mut node = NodeBuilder::new(Role::Image); + node.set_bounds(bounds); + if let Some(name) = self.name.as_ref() { + node.set_name(name.clone()); + } + match self.description.as_ref() { + Some(iced_accessibility::Description::Id(id)) => { + node.set_described_by( + id.iter() + .cloned() + .map(|id| NodeId::from(id)) + .collect::>(), + ); + } + Some(iced_accessibility::Description::Text(text)) => { + node.set_description(text.clone()); + } + None => {} + } + + if let Some(label) = self.label.as_ref() { + node.set_labelled_by(label.clone()); + } + + A11yTree::leaf(node, self.id.clone()) + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } } impl<'a, Message, Theme, Renderer> From> diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ff41377946..3c0842e720 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -599,7 +599,7 @@ where tree::State::new(State::::new()) } - fn diff(&self, tree: &mut Tree) { + fn diff(&mut self, tree: &mut Tree) { let state = tree.state.downcast_mut::>(); // Stop pasting if input becomes disabled diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 499a9fe8e8..384c04cfa3 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -1,4 +1,5 @@ use crate::container; + use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -82,8 +83,8 @@ where self.content.as_widget().children() } - fn diff(&self, tree: &mut Tree) { - self.content.as_widget().diff(tree); + fn diff(&mut self, tree: &mut Tree) { + self.content.as_widget_mut().diff(tree); } fn size(&self) -> Size { @@ -166,6 +167,8 @@ where let style = if let Some(text_color) = self.text_color { renderer::Style { text_color: text_color(&theme), + icon_color: style.icon_color, // TODO(POP): Is this correct? + scale_factor: style.scale_factor, // TODO(POP): Is this correct? } } else { *style diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index fdd2e68c31..08aa7fe8ca 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -30,6 +30,12 @@ //! } //! } //! ``` +//! Show toggle controls using togglers. +#[cfg(feature = "a11y")] +use std::borrow::Cow; + +use iced_runtime::core::border::Radius; + use crate::core::alignment; use crate::core::event; use crate::core::layout; @@ -37,10 +43,10 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::text; use crate::core::touch; -use crate::core::widget; use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::{self, Id}; use crate::core::{ - Border, Clipboard, Color, Element, Event, Layout, Length, Pixels, + id, Border, Clipboard, Color, Element, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Theme, Widget, }; @@ -86,6 +92,14 @@ pub struct Toggler< Theme: Catalog, Renderer: text::Renderer, { + id: Id, + label_id: Option, + #[cfg(feature = "a11y")] + name: Option>, + #[cfg(feature = "a11y")] + description: Option>, + #[cfg(feature = "a11y")] + labeled_by_widget: Option>, is_toggled: bool, on_toggle: Option Message + 'a>>, label: Option>, @@ -114,11 +128,16 @@ where /// It expects: /// * a boolean describing whether the [`Toggler`] is checked or not /// * An optional label for the [`Toggler`] - /// * a function that will be called when the [`Toggler`] is toggled. It - /// will receive the new state of the [`Toggler`] and must produce a - /// `Message`. pub fn new(is_toggled: bool) -> Self { Toggler { + id: Id::unique(), + label_id: None, + #[cfg(feature = "a11y")] + name: None, + #[cfg(feature = "a11y")] + description: None, + #[cfg(feature = "a11y")] + labeled_by_widget: None, is_toggled, on_toggle: None, label: None, @@ -127,9 +146,9 @@ where text_size: None, text_line_height: text::LineHeight::default(), text_alignment: alignment::Horizontal::Left, - text_shaping: text::Shaping::default(), text_wrapping: text::Wrapping::default(), spacing: Self::DEFAULT_SIZE / 2.0, + text_shaping: text::Shaping::Advanced, font: None, class: Theme::default(), } @@ -138,6 +157,7 @@ where /// Sets the label of the [`Toggler`]. pub fn label(mut self, label: impl text::IntoFragment<'a>) -> Self { self.label = Some(label.into_fragment()); + self.label_id = Some(Id::unique()); self } @@ -241,6 +261,44 @@ where self.class = class.into(); self } + + #[cfg(feature = "a11y")] + /// Sets the name of the [`Button`]. + pub fn name(mut self, name: impl Into>) -> Self { + self.name = Some(name.into()); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description_widget( + mut self, + description: &T, + ) -> Self { + self.description = Some(iced_accessibility::Description::Id( + description.description(), + )); + self + } + + #[cfg(feature = "a11y")] + /// Sets the description of the [`Button`]. + pub fn description(mut self, description: impl Into>) -> Self { + self.description = + Some(iced_accessibility::Description::Text(description.into())); + self + } + + #[cfg(feature = "a11y")] + /// Sets the label of the [`Button`] using another widget. + pub fn labeled_by_widget( + mut self, + label: &dyn iced_accessibility::Labels, + ) -> Self { + self.labeled_by_widget = + Some(label.label().into_iter().map(|l| l.into()).collect()); + self + } } impl<'a, Message, Theme, Renderer> Widget @@ -275,7 +333,7 @@ where layout::next_to_each_other( &limits, self.spacing, - |_| layout::Node::new(Size::new(2.0 * self.size, self.size)), + |_| layout::Node::new(crate::core::Size::new(48., 24.)), |limits| { if let Some(label) = self.label.as_deref() { let state = tree @@ -298,7 +356,7 @@ where self.text_wrapping, ) } else { - layout::Node::new(Size::ZERO) + layout::Node::new(crate::core::Size::ZERO) } }, ) @@ -365,13 +423,6 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - /// Makes sure that the border radius of the toggler looks good at every size. - const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0; - - /// The space ratio between the background Quad and the Toggler bounds, and - /// between the background Quad and foreground Quad. - const SPACE_RATIO: f32 = 0.05; - let mut children = layout.children(); let toggler_layout = children.next().unwrap(); @@ -407,21 +458,20 @@ where let style = theme.style(&self.class, status); - let border_radius = bounds.height / BORDER_RADIUS_RATIO; - let space = SPACE_RATIO * bounds.height; + let space = style.handle_margin; let toggler_background_bounds = Rectangle { - x: bounds.x + space, - y: bounds.y + space, - width: bounds.width - (2.0 * space), - height: bounds.height - (2.0 * space), + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height, }; renderer.fill_quad( renderer::Quad { bounds: toggler_background_bounds, border: Border { - radius: border_radius.into(), + radius: style.border_radius, width: style.background_border_width, color: style.background_border_color, }, @@ -433,20 +483,20 @@ where let toggler_foreground_bounds = Rectangle { x: bounds.x + if self.is_toggled { - bounds.width - 2.0 * space - (bounds.height - (4.0 * space)) + bounds.width - space - (bounds.height - (2.0 * space)) } else { - 2.0 * space + space }, - y: bounds.y + (2.0 * space), - width: bounds.height - (4.0 * space), - height: bounds.height - (4.0 * space), + y: bounds.y + space, + width: bounds.height - (2.0 * space), + height: bounds.height - (2.0 * space), }; renderer.fill_quad( renderer::Quad { bounds: toggler_foreground_bounds, border: Border { - radius: border_radius.into(), + radius: style.handle_radius, width: style.foreground_border_width, color: style.foreground_border_color, }, @@ -455,6 +505,102 @@ where style.foreground, ); } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + _state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::{ + accesskit::{Action, NodeBuilder, NodeId, Rect, Role}, + A11yNode, A11yTree, + }; + + let bounds = layout.bounds(); + let is_hovered = cursor.is_over(bounds); + let Rectangle { + x, + y, + width, + height, + } = bounds; + + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, + ); + + let mut node = NodeBuilder::new(Role::Switch); + node.add_action(Action::Focus); + node.add_action(Action::Default); + node.set_bounds(bounds); + if let Some(name) = self.name.as_ref() { + node.set_name(name.clone()); + } + match self.description.as_ref() { + Some(iced_accessibility::Description::Id(id)) => { + node.set_described_by( + id.iter() + .cloned() + .map(|id| NodeId::from(id)) + .collect::>(), + ); + } + Some(iced_accessibility::Description::Text(text)) => { + node.set_description(text.clone()); + } + None => {} + } + node.set_selected(self.is_toggled); + if is_hovered { + node.set_hovered(); + } + node.add_action(Action::Default); + if let Some(label) = self.label.as_ref() { + let mut label_node = NodeBuilder::new(Role::Label); + + label_node.set_name(label.clone()); + // TODO proper label bounds for the label + label_node.set_bounds(bounds); + + A11yTree::node_with_child_tree( + A11yNode::new(node, self.id.clone()), + A11yTree::leaf(label_node, self.label_id.clone().unwrap()), + ) + } else { + if let Some(labeled_by_widget) = self.labeled_by_widget.as_ref() { + node.set_labelled_by(labeled_by_widget.clone()); + } + A11yTree::leaf(node, self.id.clone()) + } + } + + fn id(&self) -> Option { + if self.label.is_some() { + Some(Id(iced_runtime::core::id::Internal::Set(vec![ + self.id.0.clone(), + self.label_id.clone().unwrap().0, + ]))) + } else { + Some(self.id.clone()) + } + } + + fn set_id(&mut self, id: Id) { + if let Id(id::Internal::Set(list)) = id { + if list.len() == 2 && self.label.is_some() { + self.id.0 = list[0].clone(); + self.label_id = Some(Id(list[1].clone())); + } + } else if self.label.is_none() { + self.id = id; + } + } } impl<'a, Message, Theme, Renderer> From> @@ -503,6 +649,12 @@ pub struct Style { pub foreground_border_width: f32, /// The [`Color`] of the foreground border of the toggler. pub foreground_border_color: Color, + /// The border radius of the toggler. + pub border_radius: Radius, + /// the radius of the handle of the toggler + pub handle_radius: Radius, + /// the space between the handle and the border of the toggler + pub handle_margin: f32, } /// The theme catalog of a [`Toggler`]. @@ -577,5 +729,8 @@ pub fn default(theme: &Theme, status: Status) -> Style { foreground_border_color: Color::TRANSPARENT, background_border_width: 0.0, background_border_color: Color::TRANSPARENT, + border_radius: Radius::from(8.0), + handle_radius: Radius::from(8.0), + handle_margin: 2.0, } } diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index e98f4da7ea..54eae1e08d 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -156,13 +156,6 @@ where ] } - fn diff(&self, tree: &mut widget::Tree) { - tree.diff_children(&[ - self.content.as_widget(), - self.tooltip.as_widget(), - ]); - } - fn state(&self) -> widget::tree::State { widget::tree::State::new(State::default()) } @@ -179,6 +172,13 @@ where self.content.as_widget().size_hint() } + fn diff(&mut self, tree: &mut widget::Tree) { + tree.diff_children(&mut [ + self.content.as_widget_mut(), + self.tooltip.as_widget_mut(), + ]) + } + fn layout( &self, tree: &mut widget::Tree, @@ -488,7 +488,9 @@ where container::draw_background(renderer, &style, layout.bounds()); let defaults = renderer::Style { + icon_color: inherited_style.icon_color, text_color: style.text_color.unwrap_or(inherited_style.text_color), + scale_factor: inherited_style.scale_factor, }; self.tooltip.as_widget().draw( diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 18633474e2..ee110519af 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -31,7 +31,8 @@ use std::ops::RangeInclusive; pub use crate::slider::{ - default, Catalog, Handle, HandleShape, Status, Style, StyleFn, + default, Catalog, Handle, HandleShape, RailBackground, Status, Style, + StyleFn, }; use crate::core::border::Border; @@ -448,7 +449,8 @@ where HandleShape::Rectangle { width, border_radius, - } => (f32::from(width), bounds.width, border_radius), + height, + } => (f32::from(width), f32::from(height), border_radius), }; let value = self.value.into() as f32; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index bd6feb008a..436e7f75f4 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -19,26 +19,54 @@ debug = ["iced_runtime/debug"] system = ["sysinfo"] program = [] x11 = ["winit/x11"] -wayland = ["winit/wayland"] +wayland = [ + "winit/wayland", + "sctk", + "wayland-protocols", + "raw-window-handle", + "iced_runtime/wayland", + "wayland-backend", + "xkbcommon", + "xkbcommon-dl", + "xkeysym", + "iced_runtime/wayland", +] wayland-dlopen = ["winit/wayland-dlopen"] wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] multi-window = ["iced_runtime/multi-window"] +a11y = ["iced_accessibility", "iced_runtime/a11y"] [dependencies] iced_futures.workspace = true iced_graphics.workspace = true iced_runtime.workspace = true - +iced_accessibility.workspace = true +iced_accessibility.optional = true +iced_accessibility.features = ["accesskit_winit"] log.workspace = true rustc-hash.workspace = true thiserror.workspace = true tracing.workspace = true window_clipboard.workspace = true +dnd.workspace = true winit.workspace = true sysinfo.workspace = true sysinfo.optional = true +[target.'cfg(target_os = "linux")'.dependencies] +raw-window-handle = { version = "0.6", optional = true } +sctk.workspace = true +sctk.optional = true +wayland-protocols.workspace = true +wayland-protocols.optional = true +wayland-backend = { version = "0.3.1", features = [ + "client_system", +], optional = true } +xkbcommon = { version = "0.7", features = ["wayland"], optional = true } +xkbcommon-dl = { version = "0.4.1", optional = true } +xkeysym = { version = "0.2.0", optional = true } + [target.'cfg(target_os = "windows")'.dependencies] winapi.workspace = true diff --git a/winit/src/a11y.rs b/winit/src/a11y.rs new file mode 100644 index 0000000000..8f59871754 --- /dev/null +++ b/winit/src/a11y.rs @@ -0,0 +1,64 @@ +use crate::futures::futures::channel::mpsc; +use crate::program::Control; + +use iced_accessibility::accesskit::{ + ActivationHandler, NodeBuilder, NodeId, Role, Tree, TreeUpdate, +}; +use iced_accessibility::accesskit_winit::Adapter; +use iced_runtime::core; + +pub struct WinitActivationHandler { + pub proxy: mpsc::UnboundedSender, + pub title: String, +} + +impl ActivationHandler for WinitActivationHandler { + fn request_initial_tree( + &mut self, + ) -> Option { + let node_id = core::id::window_node_id(); + + let _ = self + .proxy + .unbounded_send(Control::AccessibilityEnabled(true)); + let mut node = NodeBuilder::new(Role::Window); + node.set_name(self.title.clone()); + let node = node.build(); + let root = NodeId(node_id); + Some(TreeUpdate { + nodes: vec![(root, node)], + tree: Some(Tree::new(root)), + focus: root, + }) + } +} + +pub struct WinitActionHandler { + pub id: core::window::Id, + pub proxy: mpsc::UnboundedSender, +} + +impl iced_accessibility::accesskit::ActionHandler for WinitActionHandler { + fn do_action( + &mut self, + request: iced_accessibility::accesskit::ActionRequest, + ) { + let _ = self + .proxy + .unbounded_send(Control::Accessibility(self.id, request)); + } +} + +pub struct WinitDeactivationHandler { + pub proxy: mpsc::UnboundedSender, +} + +impl iced_accessibility::accesskit::DeactivationHandler + for WinitDeactivationHandler +{ + fn deactivate_accessibility(&mut self) { + let _ = self + .proxy + .unbounded_send(Control::AccessibilityEnabled(false)); + } +} diff --git a/winit/src/application/drag_resize.rs b/winit/src/application/drag_resize.rs new file mode 100644 index 0000000000..9f109d3907 --- /dev/null +++ b/winit/src/application/drag_resize.rs @@ -0,0 +1,138 @@ +use winit::window::{CursorIcon, ResizeDirection}; + +/// If supported by winit, returns a closure that implements cursor resize support. +pub fn event_func( + window: &dyn winit::window::Window, + border_size: f64, +) -> Option< + Box< + dyn FnMut( + &dyn winit::window::Window, + &winit::event::WindowEvent, + ) -> bool, + >, +> { + if window.drag_resize_window(ResizeDirection::East).is_ok() { + // Keep track of cursor when it is within a resizeable border. + let mut cursor_prev_resize_direction = None; + + Some(Box::new( + move |window: &dyn winit::window::Window, + window_event: &winit::event::WindowEvent| + -> bool { + // Keep track of border resize state and set cursor icon when in range + match window_event { + winit::event::WindowEvent::CursorMoved { + position, .. + } => { + if !window.is_decorated() { + let location = cursor_resize_direction( + window.surface_size(), + *position, + border_size, + ); + if location != cursor_prev_resize_direction { + window.set_cursor( + resize_direction_cursor_icon(location) + .into(), + ); + cursor_prev_resize_direction = location; + return true; + } + } + } + winit::event::WindowEvent::MouseInput { + state: winit::event::ElementState::Pressed, + button: winit::event::MouseButton::Left, + .. + } => { + if let Some(direction) = cursor_prev_resize_direction { + let _res = window.drag_resize_window(direction); + return true; + } + } + _ => (), + } + + false + }, + )) + } else { + None + } +} + +/// Get the cursor icon that corresponds to the resize direction. +fn resize_direction_cursor_icon( + resize_direction: Option, +) -> CursorIcon { + match resize_direction { + Some(resize_direction) => match resize_direction { + ResizeDirection::East => CursorIcon::EResize, + ResizeDirection::North => CursorIcon::NResize, + ResizeDirection::NorthEast => CursorIcon::NeResize, + ResizeDirection::NorthWest => CursorIcon::NwResize, + ResizeDirection::South => CursorIcon::SResize, + ResizeDirection::SouthEast => CursorIcon::SeResize, + ResizeDirection::SouthWest => CursorIcon::SwResize, + ResizeDirection::West => CursorIcon::WResize, + }, + None => CursorIcon::Default, + } +} + +/// Identifies resize direction based on cursor position and window dimensions. +#[allow(clippy::similar_names)] +fn cursor_resize_direction( + win_size: winit::dpi::PhysicalSize, + position: winit::dpi::PhysicalPosition, + border_size: f64, +) -> Option { + enum XDirection { + West, + East, + Default, + } + + enum YDirection { + North, + South, + Default, + } + + let xdir = if position.x < border_size { + XDirection::West + } else if position.x > (win_size.width as f64 - border_size) { + XDirection::East + } else { + XDirection::Default + }; + + let ydir = if position.y < border_size { + YDirection::North + } else if position.y > (win_size.height as f64 - border_size) { + YDirection::South + } else { + YDirection::Default + }; + + Some(match xdir { + XDirection::West => match ydir { + YDirection::North => ResizeDirection::NorthWest, + YDirection::South => ResizeDirection::SouthWest, + YDirection::Default => ResizeDirection::West, + }, + + XDirection::East => match ydir { + YDirection::North => ResizeDirection::NorthEast, + YDirection::South => ResizeDirection::SouthEast, + YDirection::Default => ResizeDirection::East, + }, + + XDirection::Default => match ydir { + YDirection::North => ResizeDirection::North, + YDirection::South => ResizeDirection::South, + YDirection::Default => return None, + }, + }) +} diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs new file mode 100644 index 0000000000..9989a88fcb --- /dev/null +++ b/winit/src/application/state.rs @@ -0,0 +1,231 @@ +use crate::application; +use crate::conversion; +use crate::core::mouse; +use crate::core::{Color, Size}; +use crate::graphics::Viewport; +use crate::runtime::Debug; +use crate::Application; + +use std::marker::PhantomData; +use winit::event::{Touch, WindowEvent}; +use winit::window::Window; + +/// The state of a windowed [`Application`]. +#[allow(missing_debug_implementations)] +pub struct State +where + A::Theme: application::DefaultStyle, +{ + title: String, + scale_factor: f64, + viewport: Viewport, + viewport_version: usize, + cursor_position: Option>, + modifiers: winit::keyboard::ModifiersState, + theme: A::Theme, + appearance: application::Appearance, + application: PhantomData, +} + +impl State +where + A::Theme: application::DefaultStyle, +{ + /// Creates a new [`State`] for the provided [`Application`] and window. + pub fn new(application: &A, window: &Window) -> Self { + let title = application.title(); + let scale_factor = application.scale_factor(); + let theme = application.theme(); + let appearance = application.style(&theme); + + let viewport = { + let physical_size = window.inner_size(); + + Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor() * scale_factor, + ) + }; + + Self { + title, + scale_factor, + viewport, + viewport_version: 0, + cursor_position: None, + modifiers: winit::keyboard::ModifiersState::default(), + theme, + appearance, + application: PhantomData, + } + } + + /// Returns the current [`Viewport`] of the [`State`]. + pub fn viewport(&self) -> &Viewport { + &self.viewport + } + + /// Returns the version of the [`Viewport`] of the [`State`]. + /// + /// The version is incremented every time the [`Viewport`] changes. + pub fn viewport_version(&self) -> usize { + self.viewport_version + } + + /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`]. + pub fn physical_size(&self) -> Size { + self.viewport.physical_size() + } + + /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`]. + pub fn logical_size(&self) -> Size { + self.viewport.logical_size() + } + + /// Returns the current scale factor of the [`Viewport`] of the [`State`]. + pub fn scale_factor(&self) -> f64 { + self.viewport.scale_factor() + } + + /// Returns the current cursor position of the [`State`]. + pub fn cursor(&self) -> mouse::Cursor { + self.cursor_position + .map(|cursor_position| { + conversion::cursor_position( + cursor_position, + self.viewport.scale_factor(), + ) + }) + .map(mouse::Cursor::Available) + .unwrap_or(mouse::Cursor::Unavailable) + } + + /// Returns the current keyboard modifiers of the [`State`]. + pub fn modifiers(&self) -> winit::keyboard::ModifiersState { + self.modifiers + } + + /// Returns the current theme of the [`State`]. + pub fn theme(&self) -> &A::Theme { + &self.theme + } + + /// Returns the current title of the [`State`]. + pub fn title(&self) -> &str { + &self.title + } + + /// Returns the current background [`Color`] of the [`State`]. + pub fn background_color(&self) -> Color { + self.appearance.background_color + } + + /// Returns the current icon [`Color`] of the [`State`]. + pub fn icon_color(&self) -> Color { + self.appearance.icon_color + } + + /// Returns the current text [`Color`] of the [`State`]. + pub fn text_color(&self) -> Color { + self.appearance.text_color + } + + /// Processes the provided window event and updates the [`State`] + /// accordingly. + pub fn update( + &mut self, + window: &Window, + event: &WindowEvent, + _debug: &mut Debug, + ) { + match event { + WindowEvent::Resized(new_size) => { + let size = Size::new(new_size.width, new_size.height); + + self.viewport = Viewport::with_physical_size( + size, + window.scale_factor() * self.scale_factor, + ); + + self.viewport_version = self.viewport_version.wrapping_add(1); + } + WindowEvent::ScaleFactorChanged { + scale_factor: new_scale_factor, + .. + } => { + let size = self.viewport.physical_size(); + + self.viewport = Viewport::with_physical_size( + size, + new_scale_factor * self.scale_factor, + ); + + self.viewport_version = self.viewport_version.wrapping_add(1); + } + WindowEvent::CursorMoved { position, .. } + | WindowEvent::Touch(Touch { + location: position, .. + }) => { + self.cursor_position = Some(*position); + } + WindowEvent::CursorLeft { .. } => { + self.cursor_position = None; + } + WindowEvent::ModifiersChanged(new_modifiers) => { + self.modifiers = new_modifiers.state(); + } + #[cfg(feature = "debug")] + WindowEvent::KeyboardInput { + event: + winit::event::KeyEvent { + logical_key: + winit::keyboard::Key::Named( + winit::keyboard::NamedKey::F12, + ), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } => _debug.toggle(), + _ => {} + } + } + + /// Synchronizes the [`State`] with its [`Application`] and its respective + /// window. + /// + /// Normally an [`Application`] should be synchronized with its [`State`] + /// and window after calling [`crate::application::update`]. + pub fn synchronize(&mut self, application: &A, window: &Window) { + // Update window title + let new_title = application.title(); + + if self.title != new_title { + window.set_title(&new_title); + + self.title = new_title; + } + + // Update scale factor and size + let new_scale_factor = application.scale_factor(); + let new_size = window.inner_size(); + let current_size = self.viewport.physical_size(); + + if self.scale_factor != new_scale_factor + || (current_size.width, current_size.height) + != (new_size.width, new_size.height) + { + self.viewport = Viewport::with_physical_size( + Size::new(new_size.width, new_size.height), + window.scale_factor() * new_scale_factor, + ); + self.viewport_version = self.viewport_version.wrapping_add(1); + + self.scale_factor = new_scale_factor; + } + + // Update theme and appearance + self.theme = application.theme(); + self.appearance = application.style(&self.theme); + } +} diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index d54a1fe08b..26381d2554 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -1,46 +1,117 @@ //! Access the clipboard. +use std::sync::Mutex; +use std::{any::Any, borrow::Cow}; + +use crate::core::clipboard::DndSource; use crate::core::clipboard::Kind; use std::sync::Arc; +use winit::dpi::LogicalSize; use winit::window::{Window, WindowId}; +use dnd::{DndAction, DndDestinationRectangle, DndSurface, Icon}; +use window_clipboard::{ + dnd::DndProvider, + mime::{self, ClipboardData, ClipboardStoreData}, +}; + /// A buffer for short-term storage and transfer within and between /// applications. #[allow(missing_debug_implementations)] pub struct Clipboard { state: State, + pub(crate) requested_logical_size: Arc>>>, +} + +pub(crate) struct StartDnd { + pub(crate) internal: bool, + pub(crate) source_surface: Option, + pub(crate) icon_surface: Option>, + pub(crate) content: Box, + pub(crate) actions: DndAction, } enum State { Connected { clipboard: window_clipboard::Clipboard, + sender: ControlSender, // Held until drop to satisfy the safety invariants of // `window_clipboard::Clipboard`. // // Note that the field ordering is load-bearing. #[allow(dead_code)] - window: Arc, + window: Arc, + queued_events: Vec, }, Unavailable, } +#[derive(Debug, Clone)] +pub(crate) struct ControlSender { + pub(crate) sender: iced_futures::futures::channel::mpsc::UnboundedSender< + crate::program::Control, + >, + pub(crate) proxy: winit::event_loop::EventLoopProxy, +} + +impl dnd::Sender for ControlSender { + fn send( + &self, + event: dnd::DndEvent, + ) -> Result<(), std::sync::mpsc::SendError>> { + let res = self + .sender + .unbounded_send(crate::program::Control::Dnd(event)) + .map_err(|_err| { + std::sync::mpsc::SendError(dnd::DndEvent::Offer( + None, + dnd::OfferEvent::Leave, + )) + }); + self.proxy.wake_up(); + res + } +} + impl Clipboard { /// Creates a new [`Clipboard`] for the given window. - pub fn connect(window: Arc) -> Clipboard { - // SAFETY: The window handle will stay alive throughout the entire - // lifetime of the `window_clipboard::Clipboard` because we hold - // the `Arc` together with `State`, and enum variant fields - // get dropped in declaration order. + pub(crate) fn connect( + window: Arc, + sender: ControlSender, + ) -> Clipboard { #[allow(unsafe_code)] - let clipboard = - unsafe { window_clipboard::Clipboard::connect(&window) }; + let state = + unsafe { window_clipboard::Clipboard::connect(window.as_ref()) } + .ok() + .map(|c| State::Connected { + clipboard: c, + sender: sender.clone(), + window, + queued_events: Vec::new(), + }) + .unwrap_or(State::Unavailable); - let state = match clipboard { - Ok(clipboard) => State::Connected { clipboard, window }, - Err(_) => State::Unavailable, - }; + #[cfg(target_os = "linux")] + if let State::Connected { clipboard, .. } = &state { + clipboard.init_dnd(Box::new(sender)); + } - Clipboard { state } + Clipboard { + state, + requested_logical_size: Arc::new(Mutex::new(None)), + } + } + + pub(crate) fn proxy(&self) -> Option { + if let State::Connected { + sender: ControlSender { proxy, .. }, + .. + } = &self.state + { + Some(proxy.clone()) + } else { + None + } } /// Creates a new [`Clipboard`] that isn't associated with a window. @@ -48,6 +119,19 @@ impl Clipboard { pub fn unconnected() -> Clipboard { Clipboard { state: State::Unavailable, + requested_logical_size: Arc::new(Mutex::new(None)), + } + } + + pub(crate) fn get_queued(&mut self) -> Vec { + match &mut self.state { + State::Connected { queued_events, .. } => { + std::mem::take(queued_events) + } + State::Unavailable => { + log::error!("Invalid request for queued dnd events"); + Vec::::new() + } } } @@ -91,14 +175,158 @@ impl Clipboard { State::Unavailable => None, } } + + pub(crate) fn start_dnd_winit( + &self, + internal: bool, + source_surface: DndSurface, + icon_surface: Option, + content: Box, + actions: DndAction, + ) { + match &self.state { + State::Connected { clipboard, .. } => { + _ = clipboard.start_dnd( + internal, + source_surface, + icon_surface, + content, + actions, + ) + } + State::Unavailable => {} + } + } } impl crate::core::Clipboard for Clipboard { fn read(&self, kind: Kind) -> Option { - self.read(kind) + match (&self.state, kind) { + (State::Connected { clipboard, .. }, Kind::Standard) => { + clipboard.read().ok() + } + (State::Connected { clipboard, .. }, Kind::Primary) => { + clipboard.read_primary().and_then(|res| res.ok()) + } + (State::Unavailable, _) => None, + } } fn write(&mut self, kind: Kind, contents: String) { - self.write(kind, contents); + match (&mut self.state, kind) { + (State::Connected { clipboard, .. }, Kind::Standard) => { + _ = clipboard.write(contents) + } + (State::Connected { clipboard, .. }, Kind::Primary) => { + _ = clipboard.write_primary(contents) + } + (State::Unavailable, _) => {} + } + } + fn read_data( + &self, + kind: Kind, + mimes: Vec, + ) -> Option<(Vec, String)> { + match (&self.state, kind) { + (State::Connected { clipboard, .. }, Kind::Standard) => { + clipboard.read_raw(mimes).and_then(|res| res.ok()) + } + (State::Connected { clipboard, .. }, Kind::Primary) => { + clipboard.read_primary_raw(mimes).and_then(|res| res.ok()) + } + (State::Unavailable, _) => None, + } + } + + fn write_data( + &mut self, + kind: Kind, + contents: ClipboardStoreData< + Box, + >, + ) { + match (&mut self.state, kind) { + (State::Connected { clipboard, .. }, Kind::Standard) => { + _ = clipboard.write_data(contents) + } + (State::Connected { clipboard, .. }, Kind::Primary) => { + _ = clipboard.write_primary_data(contents) + } + (State::Unavailable, _) => {} + } + } + + fn start_dnd( + &mut self, + internal: bool, + source_surface: Option, + icon_surface: Option>, + content: Box, + actions: DndAction, + ) { + match &mut self.state { + State::Connected { + queued_events, + sender, + .. + } => { + _ = sender + .sender + .unbounded_send(crate::program::Control::StartDnd); + queued_events.push(StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + }); + } + State::Unavailable => {} + } + } + + fn register_dnd_destination( + &self, + surface: DndSurface, + rectangles: Vec, + ) { + match &self.state { + State::Connected { clipboard, .. } => { + _ = clipboard.register_dnd_destination(surface, rectangles) + } + State::Unavailable => {} + } + } + + fn end_dnd(&self) { + match &self.state { + State::Connected { clipboard, .. } => _ = clipboard.end_dnd(), + State::Unavailable => {} + } + } + + fn peek_dnd(&self, mime: String) -> Option<(Vec, String)> { + match &self.state { + State::Connected { clipboard, .. } => clipboard + .peek_offer::(Some(Cow::Owned(mime))) + .ok() + .map(|res| (res.0, res.1)), + State::Unavailable => None, + } + } + + fn set_action(&self, action: DndAction) { + match &self.state { + State::Connected { clipboard, .. } => { + _ = clipboard.set_action(action) + } + State::Unavailable => {} + } + } + + fn request_logical_window_size(&self, width: f32, height: f32) { + let mut logical_size = self.requested_logical_size.lock().unwrap(); + *logical_size = Some(LogicalSize::new(width, height)); } } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 8e6f7aaef2..632295816f 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -2,11 +2,16 @@ //! //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime +use std::hash::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; + use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; use crate::core::window; use crate::core::{Event, Point, Size}; +use iced_futures::core::event::PlatformSpecific; /// Converts some [`window::Settings`] into some `WindowAttributes` from `winit`. pub fn window_attributes( @@ -19,7 +24,7 @@ pub fn window_attributes( attributes = attributes .with_title(title) - .with_inner_size(winit::dpi::LogicalSize { + .with_surface_size(winit::dpi::LogicalSize { width: settings.size.width, height: settings.size.height, }) @@ -43,17 +48,19 @@ pub fn window_attributes( } if let Some(min_size) = settings.min_size { - attributes = attributes.with_min_inner_size(winit::dpi::LogicalSize { - width: min_size.width, - height: min_size.height, - }); + attributes = + attributes.with_min_surface_size(winit::dpi::LogicalSize { + width: min_size.width, + height: min_size.height, + }); } if let Some(max_size) = settings.max_size { - attributes = attributes.with_max_inner_size(winit::dpi::LogicalSize { - width: max_size.width, - height: max_size.height, - }); + attributes = + attributes.with_max_surface_size(winit::dpi::LogicalSize { + width: max_size.width, + height: max_size.height, + }); } #[cfg(any( @@ -137,7 +144,7 @@ pub fn window_event( use winit::event::WindowEvent; match event { - WindowEvent::Resized(new_size) => { + WindowEvent::SurfaceResized(new_size) => { let logical_size = new_size.to_logical(scale_factor); Some(Event::Window(window::Event::Resized(Size { @@ -300,6 +307,20 @@ pub fn window_event( Some(Event::Window(window::Event::Moved(Point::new(x, y)))) } + #[cfg(feature = "wayland")] + WindowEvent::SuggestedBounds(bounds) => { + let size = bounds.map(|bounds| { + let size = bounds.to_logical(scale_factor); + Size::new(size.width, size.height) + }); + + Some(Event::PlatformSpecific(PlatformSpecific::Wayland( + iced_runtime::core::event::wayland::Event::Window( + iced_runtime::core::event::wayland::WindowEvent::SuggestedBounds(size), + ), + ))) + } + _ => None, } } @@ -335,10 +356,12 @@ pub fn position( } window::Position::SpecificWith(to_position) => { if let Some(monitor) = monitor { - let start = monitor.position(); + let start = monitor.position().unwrap_or_default(); - let resolution: winit::dpi::LogicalSize = - monitor.size().to_logical(monitor.scale_factor()); + let resolution: winit::dpi::LogicalSize = monitor + .current_video_mode() + .map(|m| m.size().to_logical(monitor.scale_factor())) + .unwrap_or_default(); let position = to_position( size, @@ -364,10 +387,12 @@ pub fn position( } window::Position::Centered => { if let Some(monitor) = monitor { - let start = monitor.position(); + let start = monitor.position().unwrap_or_default(); - let resolution: winit::dpi::LogicalSize = - monitor.size().to_logical(monitor.scale_factor()); + let resolution: winit::dpi::LogicalSize = monitor + .current_video_mode() + .map(|m| m.size().to_logical(monitor.scale_factor())) + .unwrap_or_default(); let centered: winit::dpi::PhysicalPosition = winit::dpi::LogicalPosition { @@ -427,10 +452,10 @@ pub fn mode(mode: Option) -> window::Mode { /// [`winit`]: https://github.com/rust-windowing/winit pub fn mouse_interaction( interaction: mouse::Interaction, -) -> winit::window::CursorIcon { +) -> Option { use mouse::Interaction; - match interaction { + Some(match interaction { Interaction::None | Interaction::Idle => { winit::window::CursorIcon::Default } @@ -457,7 +482,10 @@ pub fn mouse_interaction( Interaction::Move => winit::window::CursorIcon::Move, Interaction::Copy => winit::window::CursorIcon::Copy, Interaction::Help => winit::window::CursorIcon::Help, - } + Interaction::Hide => { + return None; + } + }) } /// Converts a `MouseButton` from [`winit`] to an [`iced`] mouse button. @@ -511,7 +539,11 @@ pub fn touch_event( touch: winit::event::Touch, scale_factor: f64, ) -> touch::Event { - let id = touch::Finger(touch.id); + // TODO we probably should get the actual internal id in some way instead + let mut s = DefaultHasher::new(); + touch.finger_id.hash(&mut s); + + let id = touch::Finger(s.finish()); let position = { let location = touch.location.to_logical::(scale_factor); @@ -1143,3 +1175,13 @@ pub fn icon(icon: window::Icon) -> Option { fn is_private_use(c: char) -> bool { ('\u{E000}'..='\u{F8FF}').contains(&c) } + +#[cfg(feature = "a11y")] +pub(crate) fn a11y( + event: iced_accessibility::accesskit::ActionRequest, +) -> Event { + // XXX + let id = + iced_runtime::core::id::Id::from(u128::from(event.target.0) as u64); + Event::A11y(id, event) +} diff --git a/winit/src/error.rs b/winit/src/error.rs index 7687fb1750..48c79a360c 100644 --- a/winit/src/error.rs +++ b/winit/src/error.rs @@ -10,7 +10,7 @@ pub enum Error { /// The application window could not be created. #[error("the application window could not be created")] - WindowCreationFailed(winit::error::OsError), + WindowCreationFailed(winit::error::RequestError), /// The application graphics context could not be created. #[error("the application graphics context could not be created")] diff --git a/winit/src/lib.rs b/winit/src/lib.rs index ed2910d64a..c75036b3e9 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -24,8 +24,11 @@ pub use iced_runtime::core; pub use iced_runtime::futures; pub use winit; +#[cfg(feature = "a11y")] +pub mod a11y; pub mod clipboard; pub mod conversion; +pub mod platform_specific; pub mod settings; #[cfg(feature = "program")] @@ -44,3 +47,5 @@ pub use settings::Settings; #[cfg(feature = "program")] pub use program::Program; + +pub use platform_specific::*; diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs new file mode 100644 index 0000000000..4ef88dd7d9 --- /dev/null +++ b/winit/src/platform_specific/mod.rs @@ -0,0 +1,174 @@ +//! Wayland specific shell +//! + +use iced_graphics::Compositor; +use iced_runtime::{core::window, platform_specific, Debug}; +use sctk::reexports::client::Connection; +use wayland::sctk_event::UserInterfaces; + +#[cfg(all(feature = "wayland", target_os = "linux"))] +pub mod wayland; + +#[cfg(all(feature = "wayland", target_os = "linux"))] +pub use wayland::*; +use wayland_backend::client::Backend; + +use crate::{program::WindowManager, Program}; + +#[derive(Debug)] +pub enum Event { + #[cfg(all(feature = "wayland", target_os = "linux"))] + Wayland(sctk_event::SctkEvent), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SurfaceIdWrapper { + LayerSurface(window::Id), + Window(window::Id), + Popup(window::Id), + SessionLock(window::Id), +} +impl SurfaceIdWrapper { + pub fn inner(&self) -> window::Id { + match self { + SurfaceIdWrapper::LayerSurface(id) => *id, + SurfaceIdWrapper::Window(id) => *id, + SurfaceIdWrapper::Popup(id) => *id, + SurfaceIdWrapper::SessionLock(id) => *id, + } + } +} + +#[derive(Debug, Default)] +pub struct PlatformSpecific { + #[cfg(all(feature = "wayland", target_os = "linux"))] + wayland: WaylandSpecific, +} + +impl PlatformSpecific { + pub(crate) fn send_action( + &mut self, + action: iced_runtime::platform_specific::Action, + ) { + match action { + #[cfg(all(feature = "wayland", target_os = "linux"))] + platform_specific::Action::Wayland(a) => { + self.send_wayland(wayland::Action::Action(a)); + } + } + } + + pub(crate) fn send_ready(&mut self) { + #[cfg(all(feature = "wayland", target_os = "linux"))] + { + self.send_wayland(wayland::Action::Ready); + } + } + + pub(crate) fn update_subsurfaces( + &mut self, + id: window::Id, + window: &dyn winit::window::Window, + ) { + #[cfg(all(feature = "wayland", target_os = "linux"))] + { + use sctk::reexports::client::{ + protocol::wl_surface::WlSurface, Proxy, + }; + use wayland_backend::client::ObjectId; + + let Ok(backend) = window.rwh_06_display_handle().display_handle() + else { + log::error!("No display handle"); + return; + }; + + let conn = match backend.as_raw() { + raw_window_handle::RawDisplayHandle::Wayland( + wayland_display_handle, + ) => { + let backend = unsafe { + Backend::from_foreign_display( + wayland_display_handle.display.as_ptr().cast(), + ) + }; + Connection::from_backend(backend) + } + _ => { + return; + } + }; + + let Ok(raw) = window.rwh_06_window_handle().window_handle() else { + log::error!("Invalid window handle {id:?}"); + return; + }; + let wl_surface = match raw.as_raw() { + raw_window_handle::RawWindowHandle::Wayland( + wayland_window_handle, + ) => { + let res = unsafe { + ObjectId::from_ptr( + WlSurface::interface(), + wayland_window_handle.surface.as_ptr().cast(), + ) + }; + let Ok(id) = res else { + log::error!( + "Could not create WlSurface Id from window" + ); + return; + }; + let Ok(surface) = WlSurface::from_id(&conn, id) else { + log::error!("Could not create WlSurface from Id"); + return; + }; + surface + } + + _ => { + log::error!("Unexpected window handle type"); + return; + } + }; + self.wayland.update_subsurfaces(id, &wl_surface); + } + } +} + +pub(crate) fn handle_event<'a, P, C>( + e: Event, + events: &mut Vec<(Option, iced_runtime::core::Event)>, + platform_specific: &mut PlatformSpecific, + program: &'a P, + compositor: &mut C, + window_manager: &mut WindowManager, + debug: &mut Debug, + user_interfaces: &mut UserInterfaces<'a, P>, + clipboard: &mut crate::Clipboard, + #[cfg(feature = "a11y")] adapters: &mut std::collections::HashMap< + window::Id, + (u64, iced_accessibility::accesskit_winit::Adapter), + >, +) where + P: Program, + C: Compositor, +{ + match e { + #[cfg(all(feature = "wayland", target_os = "linux"))] + Event::Wayland(e) => { + platform_specific.wayland.handle_event( + e, + events, + program, + compositor, + window_manager, + debug, + user_interfaces, + clipboard, + #[cfg(feature = "a11y")] + adapters, + ); + } + } +} diff --git a/winit/src/platform_specific/wayland/commands/activation.rs b/winit/src/platform_specific/wayland/commands/activation.rs new file mode 100644 index 0000000000..fa0510317e --- /dev/null +++ b/winit/src/platform_specific/wayland/commands/activation.rs @@ -0,0 +1,31 @@ +use crate::core::window::Id as SurfaceId; +use iced_runtime::{ + self, + platform_specific::{self, wayland}, + task, Action, Task, +}; + +pub fn request_token( + app_id: Option, + window: Option, +) -> Task> { + task::oneshot(|channel| { + Action::PlatformSpecific(platform_specific::Action::Wayland( + wayland::Action::Activation( + wayland::activation::Action::RequestToken { + app_id, + window, + channel, + }, + ), + )) + }) +} + +pub fn activate(window: SurfaceId, token: String) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Activation( + wayland::activation::Action::Activate { window, token }, + )), + )) +} diff --git a/winit/src/platform_specific/wayland/commands/layer_surface.rs b/winit/src/platform_specific/wayland/commands/layer_surface.rs new file mode 100644 index 0000000000..84c580b4e3 --- /dev/null +++ b/winit/src/platform_specific/wayland/commands/layer_surface.rs @@ -0,0 +1,116 @@ +//! Interact with the window of your application. + +use crate::core::window::Id as SurfaceId; +use iced_runtime::{ + self, + platform_specific::{ + self, + wayland::{ + self, + layer_surface::{IcedMargin, SctkLayerSurfaceSettings}, + }, + }, + task, Action, Task, +}; + +pub use sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}; + +// TODO ASHLEY: maybe implement as builder that outputs a batched commands +/// +pub fn get_layer_surface( + builder: SctkLayerSurfaceSettings, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::LayerSurface { builder }, + )), + )) +} + +/// +pub fn destroy_layer_surface(id: SurfaceId) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Destroy(id), + )), + )) +} + +/// +pub fn set_size( + id: SurfaceId, + width: Option, + height: Option, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Size { id, width, height }, + )), + )) +} +/// +pub fn set_anchor(id: SurfaceId, anchor: Anchor) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Anchor { id, anchor }, + )), + )) +} +/// +pub fn set_exclusive_zone(id: SurfaceId, zone: i32) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::ExclusiveZone { + id, + exclusive_zone: zone, + }, + )), + )) +} + +/// +pub fn set_margin( + id: SurfaceId, + top: i32, + right: i32, + bottom: i32, + left: i32, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Margin { + id, + margin: IcedMargin { + top, + right, + bottom, + left, + }, + }, + )), + )) +} + +/// +pub fn set_keyboard_interactivity( + id: SurfaceId, + keyboard_interactivity: KeyboardInteractivity, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::KeyboardInteractivity { + id, + keyboard_interactivity, + }, + )), + )) +} + +/// +pub fn set_layer(id: SurfaceId, layer: Layer) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::LayerSurface( + wayland::layer_surface::Action::Layer { id, layer }, + )), + )) +} diff --git a/winit/src/platform_specific/wayland/commands/mod.rs b/winit/src/platform_specific/wayland/commands/mod.rs new file mode 100644 index 0000000000..d097333d09 --- /dev/null +++ b/winit/src/platform_specific/wayland/commands/mod.rs @@ -0,0 +1,6 @@ +//! Interact with the wayland objects of your application. + +pub mod activation; +pub mod layer_surface; +pub mod popup; +pub mod session_lock; diff --git a/winit/src/platform_specific/wayland/commands/popup.rs b/winit/src/platform_specific/wayland/commands/popup.rs new file mode 100644 index 0000000000..f70d97f45d --- /dev/null +++ b/winit/src/platform_specific/wayland/commands/popup.rs @@ -0,0 +1,42 @@ +//! Interact with the popups of your application. +use crate::core::window::Id as SurfaceId; +use iced_runtime::{ + self, + platform_specific::{ + self, + wayland::{self, popup::SctkPopupSettings}, + }, + task, Action, Task, +}; + +/// +/// +pub fn get_popup(popup: SctkPopupSettings) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Popup( + wayland::popup::Action::Popup { popup }, + )), + )) +} + +/// +pub fn set_size( + id: SurfaceId, + width: u32, + height: u32, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Popup( + wayland::popup::Action::Size { id, width, height }, + )), + )) +} + +/// +pub fn destroy_popup(id: SurfaceId) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Popup( + wayland::popup::Action::Destroy { id }, + )), + )) +} diff --git a/winit/src/platform_specific/wayland/commands/session_lock.rs b/winit/src/platform_specific/wayland/commands/session_lock.rs new file mode 100644 index 0000000000..5a327552ad --- /dev/null +++ b/winit/src/platform_specific/wayland/commands/session_lock.rs @@ -0,0 +1,42 @@ +use crate::core::window::Id as SurfaceId; +use iced_runtime::{ + self, + platform_specific::{self, wayland}, + task, Action, Task, +}; +use sctk::reexports::client::protocol::wl_output::WlOutput; + +pub fn lock() -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::SessionLock( + wayland::session_lock::Action::Lock, + )), + )) +} + +pub fn unlock() -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::SessionLock( + wayland::session_lock::Action::Unlock, + )), + )) +} + +pub fn get_lock_surface( + id: SurfaceId, + output: WlOutput, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::SessionLock( + wayland::session_lock::Action::LockSurface { id, output }, + )), + )) +} + +pub fn destroy_lock_surface(id: SurfaceId) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::SessionLock( + wayland::session_lock::Action::DestroyLockSurface { id }, + )), + )) +} diff --git a/winit/src/platform_specific/wayland/conversion.rs b/winit/src/platform_specific/wayland/conversion.rs new file mode 100644 index 0000000000..e980157fdc --- /dev/null +++ b/winit/src/platform_specific/wayland/conversion.rs @@ -0,0 +1,120 @@ +use iced_runtime::core::{ + keyboard, + mouse::{self, ScrollDelta}, +}; +use sctk::{ + reexports::client::protocol::wl_pointer::AxisSource, + seat::{ + keyboard::Modifiers, + pointer::{ + AxisScroll, BTN_EXTRA, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, + BTN_SIDE, + }, + }, +}; + +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +#[error("the futures executor could not be created")] +pub struct KeyCodeError(u32); + +pub fn pointer_button_to_native(button: u32) -> Option { + if button == BTN_LEFT { + Some(mouse::Button::Left) + } else if button == BTN_RIGHT { + Some(mouse::Button::Right) + } else if button == BTN_MIDDLE { + Some(mouse::Button::Middle) + } else if button == BTN_SIDE { + Some(mouse::Button::Back) + } else if button == BTN_EXTRA { + Some(mouse::Button::Forward) + } else { + button.try_into().ok().map(mouse::Button::Other) + } +} + +pub fn pointer_axis_to_native( + source: Option, + horizontal: AxisScroll, + vertical: AxisScroll, +) -> Option { + source.map(|source| match source { + AxisSource::Wheel | AxisSource::WheelTilt => ScrollDelta::Lines { + x: -1. * horizontal.discrete as f32, + y: -1. * vertical.discrete as f32, + }, + _ => ScrollDelta::Pixels { + x: -1. * horizontal.absolute as f32, + y: -1. * vertical.absolute as f32, + }, + }) +} + +pub fn modifiers_to_native(mods: Modifiers) -> keyboard::Modifiers { + let mut native_mods = keyboard::Modifiers::empty(); + if mods.alt { + native_mods = native_mods.union(keyboard::Modifiers::ALT); + } + if mods.ctrl { + native_mods = native_mods.union(keyboard::Modifiers::CTRL); + } + if mods.logo { + native_mods = native_mods.union(keyboard::Modifiers::LOGO); + } + if mods.shift { + native_mods = native_mods.union(keyboard::Modifiers::SHIFT); + } + // TODO Ashley: missing modifiers as platform specific additions? + // if mods.caps_lock { + // native_mods = native_mods.union(keyboard::Modifier); + // } + // if mods.num_lock { + // native_mods = native_mods.union(keyboard::Modifiers::); + // } + native_mods +} + +// pub fn keysym_to_vkey(keysym: RawKeysym) -> Option { +// key_conversion.get(&keysym).cloned() +// } + +// pub(crate) fn cursor_icon(cursor: winit::window::CursorIcon) -> CursorIcon { +// match cursor { +// CursorIcon::Default => todo!(), +// CursorIcon::ContextMenu => todo!(), +// CursorIcon::Help => todo!(), +// CursorIcon::Pointer => todo!(), +// CursorIcon::Progress => todo!(), +// CursorIcon::Wait => todo!(), +// CursorIcon::Cell => todo!(), +// CursorIcon::Crosshair => todo!(), +// CursorIcon::Text => todo!(), +// CursorIcon::VerticalText => todo!(), +// CursorIcon::Alias => todo!(), +// CursorIcon::Copy => todo!(), +// CursorIcon::Move => todo!(), +// CursorIcon::NoDrop => todo!(), +// CursorIcon::NotAllowed => todo!(), +// CursorIcon::Grab => todo!(), +// CursorIcon::Grabbing => todo!(), +// CursorIcon::EResize => todo!(), +// CursorIcon::NResize => todo!(), +// CursorIcon::NeResize => todo!(), +// CursorIcon::NwResize => todo!(), +// CursorIcon::SResize => todo!(), +// CursorIcon::SeResize => todo!(), +// CursorIcon::SwResize => todo!(), +// CursorIcon::WResize => todo!(), +// CursorIcon::EwResize => todo!(), +// CursorIcon::NsResize => todo!(), +// CursorIcon::NeswResize => todo!(), +// CursorIcon::NwseResize => todo!(), +// CursorIcon::ColResize => todo!(), +// CursorIcon::RowResize => todo!(), +// CursorIcon::AllScroll => todo!(), +// CursorIcon::ZoomIn => todo!(), +// CursorIcon::ZoomOut => todo!(), +// _ => todo!(), +// } +// } diff --git a/winit/src/platform_specific/wayland/event_loop/control_flow.rs b/winit/src/platform_specific/wayland/event_loop/control_flow.rs new file mode 100644 index 0000000000..bc920ed478 --- /dev/null +++ b/winit/src/platform_specific/wayland/event_loop/control_flow.rs @@ -0,0 +1,56 @@ +/// Set by the user callback given to the [`EventLoop::run`] method. +/// +/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted. +/// +/// Defaults to [`Poll`]. +/// +/// ## Persistency +/// +/// Almost every change is persistent between multiple calls to the event loop closure within a +/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset. +/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will +/// reset the control flow to [`Poll`]. +/// +/// [`ExitWithCode`]: Self::ExitWithCode +/// [`Poll`]: Self::Poll +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ControlFlow { + /// When the current loop iteration finishes, immediately begin a new iteration regardless of + /// whether or not new events are available to process. + /// + /// ## Platform-specific + /// + /// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes + /// the events in the queue may be sent before the next `requestAnimationFrame` callback, for + /// example when the scaling of the page has changed. This should be treated as an implementation + /// detail which should not be relied on. + Poll, + /// When the current loop iteration finishes, suspend the thread until another event arrives. + Wait, + /// When the current loop iteration finishes, suspend the thread until either another event + /// arrives or the given time is reached. + /// + /// Useful for implementing efficient timers. Applications which want to render at the display's + /// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API + /// to reduce odds of missed frames. + /// + /// [`Poll`]: Self::Poll + WaitUntil(std::time::Instant), + /// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set, + /// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will + /// result in the `control_flow` parameter being reset to `ExitWithCode`. + /// + /// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this + /// with exit code 0. + /// + /// ## Platform-specific + /// + /// - **Android / iOS / WASM:** The supplied exit code is unused. + /// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used, + /// which can cause surprises with negative exit values (`-42` would end up as `214`). See + /// [`std::process::exit`]. + /// + /// [`LoopDestroyed`]: Event::LoopDestroyed + /// [`Exit`]: ControlFlow::Exit + ExitWithCode(i32), +} diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs new file mode 100644 index 0000000000..74b3cb0c62 --- /dev/null +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -0,0 +1,387 @@ +pub mod control_flow; +pub mod proxy; +pub mod state; + +#[cfg(feature = "a11y")] +use crate::platform_specific::SurfaceIdWrapper; +use crate::{ + futures::futures::channel::mpsc, + platform_specific::wayland::{ + handlers::{ + wp_fractional_scaling::FractionalScalingManager, + wp_viewporter::ViewporterState, + }, + sctk_event::SctkEvent, + }, + program::Control, + subsurface_widget::SubsurfaceState, +}; + +use raw_window_handle::HasDisplayHandle; +use sctk::reexports::{ + calloop_wayland_source::WaylandSource, client::protocol::wl_subcompositor, +}; +use sctk::{ + activation::ActivationState, + compositor::CompositorState, + globals::GlobalData, + output::OutputState, + reexports::{ + calloop::{self, EventLoop}, + client::{ + globals::registry_queue_init, ConnectError, Connection, Proxy, + }, + }, + registry::RegistryState, + seat::SeatState, + session_lock::SessionLockState, + shell::{wlr_layer::LayerShell, xdg::XdgShell, WaylandSurface}, + shm::Shm, +}; +use state::{FrameStatus, SctkWindow}; +#[cfg(feature = "a11y")] +use std::sync::{Arc, Mutex}; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, +}; +use tracing::error; +use wayland_backend::client::Backend; +use winit::event_loop::OwnedDisplayHandle; + +use self::state::SctkState; + +#[derive(Debug, Default, Clone, Copy)] +pub struct Features { + // TODO +} + +pub struct SctkEventLoop { + pub(crate) event_loop: EventLoop<'static, SctkState>, + pub(crate) _features: Features, + pub(crate) state: SctkState, +} + +impl SctkEventLoop { + pub(crate) fn new( + winit_event_sender: mpsc::UnboundedSender, + proxy: winit::event_loop::EventLoopProxy, + display: OwnedDisplayHandle, + ) -> Result< + calloop::channel::Sender, + Box, + > { + let (action_tx, action_rx) = calloop::channel::channel(); + let res = std::thread::spawn(move || { + let Ok(dh) = display.display_handle() else { + log::error!("Failed to get display handle"); + return Ok(()); + }; + let raw_window_handle::RawDisplayHandle::Wayland(wayland_dh) = + dh.as_raw() + else { + panic!("Invalid wayland display handle."); + }; + + let backend = unsafe { + Backend::from_foreign_display( + wayland_dh.display.as_ptr().cast(), + ) + }; + let connection = Connection::from_backend(backend); + + let _display = connection.display(); + let (globals, event_queue) = + registry_queue_init(&connection).unwrap(); + let event_loop = + calloop::EventLoop::::try_new().unwrap(); + let loop_handle = event_loop.handle(); + + let qh = event_queue.handle(); + let registry_state = RegistryState::new(&globals); + + _ = loop_handle + .insert_source(action_rx, |event, _, state| { + match event { + calloop::channel::Event::Msg(e) => match e { + crate::platform_specific::Action::Action(a) => { + if let Err(err) = state.handle_action(a) { + log::warn!("{err:?}"); + } + } + crate::platform_specific::Action::TrackWindow( + window, + id, + ) => { + state.windows.push(SctkWindow { window, id }); + } + crate::Action::RemoveWindow(id) => { + // TODO clean up popups matching the window. + state.windows.retain(|window| id != window.id); + } + crate::platform_specific::Action::SetCursor(icon) => { + if let Some(seat) = state.seats.get_mut(0) { + seat.icon = Some(icon); + seat.set_cursor(&state.connection, icon); + } + } + crate::platform_specific::Action::RequestRedraw(id) => { + let e = state.frame_status.entry(id).or_insert(FrameStatus::RequestedRedraw); + if matches!(e, FrameStatus::Received) { + *e = FrameStatus::Ready; + } + } + crate::platform_specific::Action::PrePresentNotify( + _, + ) => { + // TODO + } + crate::platform_specific::Action::Ready => { + state.ready = true; + } + crate::Action::Dropped(id) => { + _ = state.destroyed.remove(&id.inner()); + } + }, + calloop::channel::Event::Closed => { + log::info!("Calloop channel closed."); + } + } + }) + .unwrap(); + let wayland_source = + WaylandSource::new(connection.clone(), event_queue); + + let wayland_dispatcher = calloop::Dispatcher::new( + wayland_source, + |_, queue, winit_state| queue.dispatch_pending(winit_state), + ); + + let _wayland_source_dispatcher = event_loop + .handle() + .register_dispatcher(wayland_dispatcher.clone()) + .unwrap(); + + let (viewporter_state, fractional_scaling_manager) = + match FractionalScalingManager::new(&globals, &qh) { + Ok(m) => { + let viewporter_state = + match ViewporterState::new(&globals, &qh) { + Ok(s) => Some(s), + Err(e) => { + error!( + "Failed to initialize viewporter: {}", + e + ); + None + } + }; + (viewporter_state, Some(m)) + } + Err(e) => { + error!( + "Failed to initialize fractional scaling manager: {}", + e + ); + (None, None) + } + }; + + let mut state = Self { + event_loop, + state: SctkState { + connection, + registry_state, + seat_state: SeatState::new(&globals, &qh), + output_state: OutputState::new(&globals, &qh), + compositor_state: CompositorState::bind(&globals, &qh) + .expect("wl_compositor is not available"), + shm_state: Shm::bind(&globals, &qh) + .expect("wl_shm is not available"), + xdg_shell_state: XdgShell::bind(&globals, &qh) + .expect("xdg shell is not available"), + layer_shell: LayerShell::bind(&globals, &qh).ok(), + activation_state: ActivationState::bind(&globals, &qh).ok(), + session_lock_state: SessionLockState::new(&globals, &qh), + session_lock: None, + + queue_handle: qh, + loop_handle, + + _cursor_surface: None, + _multipool: None, + outputs: Vec::new(), + seats: Vec::new(), + windows: Vec::new(), + layer_surfaces: Vec::new(), + popups: Vec::new(), + lock_surfaces: Vec::new(), + _kbd_focus: None, + touch_points: HashMap::new(), + sctk_events: Vec::new(), + frame_status: HashMap::new(), + fractional_scaling_manager, + viewporter_state, + compositor_updates: Default::default(), + events_sender: winit_event_sender, + proxy, + id_map: Default::default(), + to_commit: HashMap::new(), + ready: true, + destroyed: HashSet::new(), + pending_popup: Default::default(), + activation_token_ctr: 0, + token_senders: HashMap::new(), + }, + _features: Default::default(), + }; + let wl_compositor = state + .state + .registry_state + .bind_one(&state.state.queue_handle, 1..=6, GlobalData) + .unwrap(); + let wl_subcompositor = state.state.registry_state.bind_one( + &state.state.queue_handle, + 1..=1, + GlobalData, + ); + let wp_viewporter = state.state.registry_state.bind_one( + &state.state.queue_handle, + 1..=1, + GlobalData, + ); + let wl_shm = state + .state + .registry_state + .bind_one(&state.state.queue_handle, 1..=1, GlobalData) + .unwrap(); + let wp_dmabuf = state + .state + .registry_state + .bind_one(&state.state.queue_handle, 2..=4, GlobalData) + .ok(); + let wp_alpha_modifier = state + .state + .registry_state + .bind_one(&state.state.queue_handle, 1..=1, ()) + .ok(); + + if let (Ok(wl_subcompositor), Ok(wp_viewporter)) = + (wl_subcompositor, wp_viewporter) + { + state::send_event( + &state.state.events_sender, + &state.state.proxy, + SctkEvent::Subcompositor(SubsurfaceState { + wl_compositor, + wl_subcompositor, + wp_viewporter, + wl_shm, + wp_dmabuf, + wp_alpha_modifier, + qh: state.state.queue_handle.clone(), + buffers: HashMap::new(), + unmapped_subsurfaces: Vec::new(), + }), + ); + } else { + log::warn!("Subsurfaces not supported.") + } + + log::info!("SCTK setup complete."); + loop { + match state + .state + .events_sender + .unbounded_send(Control::AboutToWait) + { + Ok(_) => {} + Err(err) => { + log::error!( + "SCTK failed to send Control::AboutToWait. {err:?}" + ); + if state.state.events_sender.is_closed() { + return Ok(()); + } + } + } + if !state.state.ready { + continue; + } + + if let Err(err) = + state.event_loop.dispatch(None, &mut state.state) + { + log::error!("SCTK dispatch error: {err}"); + } + let had_events = !state.state.sctk_events.is_empty(); + let mut wake_up = had_events; + + for s in state + .state + .layer_surfaces + .iter() + .map(|s| s.surface.wl_surface()) + .chain( + state.state.popups.iter().map(|s| s.popup.wl_surface()), + ) + .chain( + state + .state + .lock_surfaces + .iter() + .map(|s| s.session_lock_surface.wl_surface()), + ) + { + let id = s.id(); + if state + .state + .frame_status + .get(&id) + .map(|v| !matches!(v, state::FrameStatus::Ready)) + .unwrap_or(true) + || !state.state.id_map.contains_key(&id) + { + continue; + } + wake_up = true; + + _ = s.frame(&state.state.queue_handle, s.clone()); + _ = state.state.frame_status.remove(&id); + _ = state.state.events_sender.unbounded_send( + Control::Winit( + winit::window::WindowId::from(id.as_ptr() as u64), + winit::event::WindowEvent::RedrawRequested, + ), + ); + } + + for e in state.state.sctk_events.drain(..) { + if let SctkEvent::Winit(id, e) = e { + _ = state + .state + .events_sender + .unbounded_send(Control::Winit(id, e)); + } else { + _ = state.state.events_sender.unbounded_send( + Control::PlatformSpecific( + crate::platform_specific::Event::Wayland(e), + ), + ); + } + } + + if wake_up { + state.state.proxy.wake_up(); + } + } + }); + + if res.is_finished() { + log::warn!("SCTK thread finished."); + res.join().map(|_: Result<(), ConnectError>| action_tx) + } else { + Ok(action_tx) + } + } +} diff --git a/winit/src/platform_specific/wayland/event_loop/proxy.rs b/winit/src/platform_specific/wayland/event_loop/proxy.rs new file mode 100644 index 0000000000..7140cef708 --- /dev/null +++ b/winit/src/platform_specific/wayland/event_loop/proxy.rs @@ -0,0 +1,66 @@ +use iced_futures::futures::{ + channel::mpsc, + task::{Context, Poll}, + Sink, +}; +use sctk::reexports::calloop; +use std::pin::Pin; + +/// An event loop proxy that implements `Sink`. +#[derive(Debug)] +pub struct Proxy { + raw: calloop::channel::Sender, +} + +impl Clone for Proxy { + fn clone(&self) -> Self { + Self { + raw: self.raw.clone(), + } + } +} + +impl Proxy { + /// Creates a new [`Proxy`] from an `EventLoopProxy`. + pub fn new(raw: calloop::channel::Sender) -> Self { + Self { raw } + } + /// send an event + pub fn send_event(&self, message: Message) { + let _ = self.raw.send(message); + } +} + +impl Sink for Proxy { + type Error = mpsc::SendError; + + fn poll_ready( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn start_send( + self: Pin<&mut Self>, + message: Message, + ) -> Result<(), Self::Error> { + let _ = self.raw.send(message); + + Ok(()) + } + + fn poll_flush( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } +} diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs new file mode 100644 index 0000000000..99e7f47c71 --- /dev/null +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -0,0 +1,1133 @@ +use crate::{ + handlers::activation::IcedRequestData, platform_specific::{ + wayland::{ + handlers::{ + wp_fractional_scaling::FractionalScalingManager, + wp_viewporter::ViewporterState, + }, + sctk_event::{ + LayerSurfaceEventVariant, SctkEvent, + }, + }, + Event, + }, program::Control +}; +use iced_futures::futures::channel::{mpsc, oneshot}; +use raw_window_handle::HasWindowHandle; +use std::{ + collections::{HashMap, HashSet}, + convert::Infallible, + fmt::Debug, sync::{atomic::AtomicU32, Arc, Mutex}, time::Duration, +}; +use wayland_backend::client::ObjectId; +use winit::{dpi::{LogicalPosition, LogicalSize}, platform::wayland::WindowExtWayland}; + +use iced_runtime::{ + core::{self, touch, Point}, + keyboard::Modifiers, + platform_specific::{ + self, + wayland::{ + layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, + popup::SctkPopupSettings, + Action, + }, + }, +}; +use sctk::{ + activation::{ActivationState, RequestData}, + compositor::CompositorState, + error::GlobalError, + output::OutputState, + reexports::{ + calloop::{timer::TimeoutAction, LoopHandle}, + client::{ + delegate_noop, + protocol::{ + wl_keyboard::WlKeyboard, + wl_output::WlOutput, + wl_region::WlRegion, + wl_seat::WlSeat, + wl_subsurface::WlSubsurface, + wl_surface::{self, WlSurface}, + wl_touch::WlTouch, + }, + Connection, Proxy, QueueHandle, + }, + }, + registry::RegistryState, + seat::{ + keyboard::KeyEvent, pointer::{CursorIcon, PointerData, ThemedPointer}, touch::TouchData, SeatState + }, + session_lock::{ + SessionLock, SessionLockState, SessionLockSurface, + SessionLockSurfaceConfigure, + }, + shell::{ + wlr_layer::{ + Anchor, KeyboardInteractivity, Layer, LayerShell, LayerSurface, + LayerSurfaceConfigure, + }, + xdg::{ + popup::{Popup, PopupConfigure}, + XdgPositioner, XdgShell, + }, + WaylandSurface, + }, + shm::{multi::MultiPool, Shm}, +}; +use wayland_protocols::{wp::{ + fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, + viewporter::client::wp_viewport::WpViewport, +}, xdg::shell::client::xdg_surface::XdgSurface}; + +pub static TOKEN_CTR: AtomicU32 = AtomicU32::new(0); + +#[derive(Debug)] +pub(crate) struct SctkSeat { + pub(crate) seat: WlSeat, + pub(crate) kbd: Option, + pub(crate) kbd_focus: Option, + pub(crate) last_kbd_press: Option<(KeyEvent, u32)>, + pub(crate) ptr: Option, + pub(crate) ptr_focus: Option, + pub(crate) last_ptr_press: Option<(u32, u32, u32)>, // (time, button, serial) + pub(crate) touch: Option, + pub(crate) last_touch_down: Option<(u32, i32, u32)>, // (time, point, serial) + pub(crate) _modifiers: Modifiers, + // Cursor icon currently set (by CSDs, or application) + pub(crate) active_icon: Option, + // Cursor icon set by application + pub(crate) icon: Option, +} + +impl SctkSeat { + pub(crate) fn set_cursor(&mut self, conn: &Connection, icon: CursorIcon) { + if let Some(ptr) = self.ptr.as_ref() { + _ = ptr.set_cursor(conn, icon); + self.active_icon = Some(icon); + } + } +} + +#[derive(Debug, Clone)] +pub struct SctkLayerSurface { + pub(crate) id: core::window::Id, + pub(crate) surface: LayerSurface, + pub(crate) current_size: Option>, + pub(crate) layer: Layer, + pub(crate) anchor: Anchor, + pub(crate) keyboard_interactivity: KeyboardInteractivity, + pub(crate) margin: IcedMargin, + pub(crate) exclusive_zone: i32, + pub(crate) last_configure: Option, + pub(crate) _pending_requests: + Vec, + pub(crate) wp_fractional_scale: Option, + pub(crate) common: Arc>, +} + +impl SctkLayerSurface { + pub(crate) fn set_size(&mut self, w: Option, h: Option) { + let mut common = self.common.lock().unwrap(); + common.requested_size = (w, h); + + let (w, h) = (w.unwrap_or_default(), h.unwrap_or_default()); + self.surface.set_size(w, h); + } + + pub(crate) fn update_viewport(&mut self, w: u32, h: u32) { + let common = self.common.lock().unwrap(); + self.current_size = Some(LogicalSize::new(w, h)); + if let Some(viewport) = common.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination(w as i32, h as i32); + } + } +} + +#[derive(Debug, Clone)] +pub enum PopupParent { + LayerSurface(WlSurface), + Window(WlSurface), + Popup(WlSurface), +} + +impl PopupParent { + pub fn wl_surface(&self) -> &WlSurface { + match self { + PopupParent::LayerSurface(s) + | PopupParent::Window(s) + | PopupParent::Popup(s) => s, + } + } +} + +#[derive(Debug, Clone)] +pub enum CommonSurface { + Popup(Popup, Arc), + Layer(LayerSurface), + Lock(SessionLockSurface) +} + +impl CommonSurface { + pub fn wl_surface(&self) -> &WlSurface { + let wl_surface = + match self { + CommonSurface::Popup(popup, _) => popup.wl_surface(), + CommonSurface::Layer(layer_surface) => layer_surface.wl_surface(), + CommonSurface::Lock(session_lock_surface) => session_lock_surface.wl_surface(), + }; + wl_surface + } +} + +#[derive(Debug, Clone)] +pub struct Common { + pub(crate) fractional_scale: Option, + pub(crate) has_focus: bool, + pub(crate) ime_pos: LogicalPosition, + pub(crate) ime_size: LogicalSize, + pub(crate) size: LogicalSize, + pub(crate) requested_size: (Option, Option), + pub(crate) wp_viewport: Option, +} + +impl Default for Common { + fn default() -> Self { + Self { fractional_scale: Default::default(), has_focus: Default::default(), ime_pos: Default::default(), ime_size: Default::default(), size: LogicalSize::new(1, 1), requested_size: (None, None), wp_viewport: None} + } +} + +impl From> for Common { + fn from(value: LogicalSize) -> Self { + Common { + size: value, + ..Default::default() + } + } +} + + + +#[derive(Debug)] +pub struct SctkPopup { + pub(crate) popup: Popup, + pub(crate) last_configure: Option, + pub(crate) _pending_requests: + Vec, + pub(crate) data: SctkPopupData, + pub(crate) common: Arc>, + pub(crate) wp_fractional_scale: Option, +} + +impl SctkPopup { + pub(crate) fn set_size(&mut self, w: u32, h: u32, token: u32) { + // update geometry + self.popup + .xdg_surface() + .set_window_geometry(0, 0, w as i32, h as i32); + // update positioner + self.data.positioner.set_size(w as i32, h as i32); + self.popup.reposition(&self.data.positioner, token); + } +} + +#[derive(Debug)] +pub struct SctkLockSurface { + pub(crate) id: core::window::Id, + pub(crate) session_lock_surface: SessionLockSurface, + pub(crate) last_configure: Option, + pub(crate) wp_fractional_scale: Option, + pub(crate) wp_viewport: Option, + pub(crate) common: Arc>, +} + +#[derive(Debug)] +pub struct SctkPopupData { + pub(crate) id: core::window::Id, + pub(crate) parent: PopupParent, + pub(crate) toplevel: WlSurface, + pub(crate) positioner: Arc, +} + +pub struct SctkWindow { + pub(crate) window: Arc, + pub(crate) id: core::window::Id +} + +impl SctkWindow { + pub fn wl_surface(&self, conn: &Connection) -> WlSurface { + let window_handle = self.window.window_handle().unwrap(); + let ptr = { + let raw_window_handle::RawWindowHandle::Wayland(h) = window_handle.as_raw() else { + panic!("Invalid window handle"); + }; + h.surface + + }; + let id = unsafe { ObjectId::from_ptr(WlSurface::interface(), ptr.as_ptr().cast()) }.unwrap(); + WlSurface::from_id(conn, id).unwrap() + } + + pub fn xdg_surface(&self, conn: &Connection) -> XdgSurface { + let window_handle = self.window.xdg_surface_handle().unwrap(); + let ptr = { + let h = window_handle.xdg_surface_handle().expect("Invalid window handle"); + h.as_raw() + + }; + let id = unsafe { ObjectId::from_ptr(XdgSurface::interface(), ptr.as_ptr().cast()) }.unwrap(); + XdgSurface::from_id(conn, id).unwrap() + } +} + +pub(crate) enum FrameStatus { + /// Received frame, but redraw wasn't requested + Received, + /// Requested redraw, but frame wasn't received + RequestedRedraw, + /// Ready for requested redraw + Ready +} + +/// Wrapper to carry sctk state. +pub struct SctkState { + pub(crate) connection: Connection, + + /// the cursor wl_surface + pub(crate) _cursor_surface: Option, + /// a memory pool + pub(crate) _multipool: Option>, + + // all present outputs + pub(crate) outputs: Vec, + // though (for now) only one seat will be active in an iced application at a time, all ought to be tracked + // Active seat is the first seat in the list + pub(crate) seats: Vec, + // Windows / Surfaces + /// Window list containing all SCTK windows. Since those windows aren't allowed + /// to be sent to other threads, they live on the event loop's thread + /// and requests from winit's windows are being forwarded to them either via + /// `WindowUpdate` or buffer on the associated with it `WindowHandle`. + pub(crate) windows: Vec, + pub(crate) layer_surfaces: Vec, + pub(crate) popups: Vec, + pub(crate) lock_surfaces: Vec, + pub(crate) _kbd_focus: Option, + pub(crate) touch_points: HashMap, + + /// Window updates, which are coming from SCTK or the compositor, which require + /// calling back to the sctk's downstream. They are handled right in the event loop, + /// unlike the ones coming from buffers on the `WindowHandle`'s. + pub compositor_updates: Vec, + + /// A sink for window and device events that is being filled during dispatching + /// event loop and forwarded downstream afterwards. + pub(crate) sctk_events: Vec, + pub(crate) frame_status: HashMap, + + /// Send events to winit + pub(crate) events_sender: mpsc::UnboundedSender, + pub(crate) proxy: winit::event_loop::EventLoopProxy, + + // handles + pub(crate) queue_handle: QueueHandle, + pub(crate) loop_handle: LoopHandle<'static, Self>, + + // sctk state objects + /// Viewporter state on the given window. + pub viewporter_state: Option, + pub(crate) fractional_scaling_manager: Option, + pub(crate) registry_state: RegistryState, + pub(crate) seat_state: SeatState, + pub(crate) output_state: OutputState, + pub(crate) compositor_state: CompositorState, + pub(crate) shm_state: Shm, + pub(crate) xdg_shell_state: XdgShell, + pub(crate) layer_shell: Option, + pub(crate) activation_state: Option, + pub(crate) session_lock_state: SessionLockState, + pub(crate) session_lock: Option, + pub(crate) id_map: HashMap, + pub(crate) to_commit: HashMap, + pub(crate) destroyed: HashSet, + pub(crate) ready: bool, + pub(crate) pending_popup: Option<(SctkPopupSettings, usize)>, + + pub(crate) activation_token_ctr: u32, + pub(crate) token_senders: HashMap>>, +} + +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +pub enum PopupCreationError { + /// Positioner creation failed + #[error("Positioner creation failed")] + PositionerCreationFailed(GlobalError), + + /// The specified parent is missing + #[error("The specified parent is missing")] + ParentMissing, + + /// The specified size is missing + #[error("The specified size is missing")] + SizeMissing, + + /// Popup creation failed + #[error("Popup creation failed")] + PopupCreationFailed(GlobalError), +} + +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +pub enum LayerSurfaceCreationError { + /// Layer shell is not supported by the compositor + #[error("Layer shell is not supported by the compositor")] + LayerShellNotSupported, + + /// WlSurface creation failed + #[error("WlSurface creation failed")] + WlSurfaceCreationFailed(GlobalError), + + /// LayerSurface creation failed + #[error("Layer Surface creation failed")] + LayerSurfaceCreationFailed(GlobalError), +} + +pub(crate) fn receive_frame(frame_status: &mut HashMap, s: &WlSurface) { + let e = frame_status.entry(s.id()).or_insert(FrameStatus::Received); + if matches!(e, FrameStatus::RequestedRedraw) { + *e = FrameStatus::Ready; + } +} + +impl SctkState { + pub fn request_redraw(&mut self, surface: &WlSurface) { + let e = self.frame_status.entry(surface.id()).or_insert(FrameStatus::RequestedRedraw); + if matches!(e, FrameStatus::Received) { + *e = FrameStatus::Ready; + } + } + + pub fn scale_factor_changed( + &mut self, + surface: &WlSurface, + scale_factor: f64, + legacy: bool, + ) { + let mut id = None; + + if let Some(popup) = self + .popups + .iter_mut() + .find(|p| p.popup.wl_surface() == surface) + { + id = Some(popup.data.id); + if legacy && popup.wp_fractional_scale.is_some() { + return; + } + let mut common = popup.common.lock().unwrap(); + common.fractional_scale = Some(scale_factor); + if legacy { + popup.popup.wl_surface().set_buffer_scale(scale_factor as _); + } + } + + if let Some(layer_surface) = self + .layer_surfaces + .iter_mut() + .find(|l| l.surface.wl_surface() == surface) + { + id = Some(layer_surface.id); + if legacy && layer_surface.wp_fractional_scale.is_some() { + return; + } + let mut common = layer_surface.common.lock().unwrap(); + common.fractional_scale = Some(scale_factor); + if legacy { + let _ = + layer_surface.surface.wl_surface().set_buffer_scale(scale_factor as i32); + } + + } + + if let Some(lock_surface) = self + .lock_surfaces + .iter_mut() + .find(|l| l.session_lock_surface.wl_surface() == surface) + { + id = Some(lock_surface.id); + if legacy && lock_surface.wp_fractional_scale.is_some() { + return; + } + let mut common = lock_surface.common.lock().unwrap(); + common.fractional_scale = Some(scale_factor); + if legacy { + let _ = + lock_surface.session_lock_surface.wl_surface().set_buffer_scale(scale_factor as i32); + } + } + + if let Some(id) = id { + self.sctk_events.push(SctkEvent::SurfaceScaleFactorChanged(scale_factor, surface.clone(), id)); + } + + // TODO winit sets cursor size after handling the change for the window, so maybe that should be done as well. + } +} + +impl SctkState { + pub fn get_popup( + &mut self, + settings: SctkPopupSettings, + ) -> Result< + (core::window::Id, WlSurface, WlSurface, CommonSurface, Arc>), + PopupCreationError, + > { + let (parent, toplevel) = if let Some(parent) = + self.layer_surfaces.iter().find(|l| l.id == settings.parent) + { + ( + PopupParent::LayerSurface(parent.surface.wl_surface().clone()), + parent.surface.wl_surface().clone(), + ) + } else if let Some(parent) = + self.windows.iter().find(|w| w.id == settings.parent) + { + ( + PopupParent::Window(parent.wl_surface(&self.connection)), + parent.wl_surface(&self.connection), + ) + } else if let Some(i) = self + .popups + .iter() + .position(|p| p.data.id == settings.parent) + { + let parent = &self.popups[i]; + ( + PopupParent::Popup(parent.popup.wl_surface().clone()), + parent.data.toplevel.clone(), + ) + } else { + return Err(PopupCreationError::ParentMissing); + }; + + let size = if settings.positioner.size.is_none() { + log::info!("No configured popup size"); + (1, 1) + } else { + settings.positioner.size.unwrap() + }; + + let positioner = XdgPositioner::new(&self.xdg_shell_state) + .map_err(PopupCreationError::PositionerCreationFailed)?; + positioner.set_anchor(settings.positioner.anchor); + positioner.set_anchor_rect( + settings.positioner.anchor_rect.x, + settings.positioner.anchor_rect.y, + settings.positioner.anchor_rect.width, + settings.positioner.anchor_rect.height, + ); + if let Ok(constraint_adjustment) = + settings.positioner.constraint_adjustment.try_into() + { + positioner.set_constraint_adjustment(constraint_adjustment); + } + positioner.set_gravity(settings.positioner.gravity); + positioner.set_offset( + settings.positioner.offset.0, + settings.positioner.offset.1, + ); + if settings.positioner.reactive { + positioner.set_reactive(); + } + positioner.set_size(size.0 as i32, size.1 as i32); + + let grab = settings.grab; + + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + _ = self.id_map.insert(wl_surface.id(), settings.id.clone()); + + let (toplevel, popup) = match &parent { + PopupParent::LayerSurface(parent) => { + let Some(parent_layer_surface) = self + .layer_surfaces + .iter() + .find(|w| w.surface.wl_surface() == parent) + else { + return Err(PopupCreationError::ParentMissing); + }; + let popup = Popup::from_surface( + None, + &positioner, + &self.queue_handle, + wl_surface.clone(), + &self.xdg_shell_state, + ) + .map_err(PopupCreationError::PopupCreationFailed)?; + parent_layer_surface.surface.get_popup(popup.xdg_popup()); + (parent_layer_surface.surface.wl_surface(), popup) + } + PopupParent::Window(parent) => { + let Some(parent_window) = self + .windows + .iter() + .find(|w| &w.wl_surface(&self.connection) == parent) + else { + return Err(PopupCreationError::ParentMissing); + }; + ( + &parent_window.wl_surface(&self.connection), + Popup::from_surface( + Some(&parent_window.xdg_surface(&self.connection)), + &positioner, + &self.queue_handle, + wl_surface.clone(), + &self.xdg_shell_state, + ) + .map_err(PopupCreationError::PopupCreationFailed)?, + ) + } + PopupParent::Popup(parent) => { + let Some(parent_xdg) = self.popups.iter().find_map(|p| { + (p.popup.wl_surface() == parent) + .then(|| p.popup.xdg_surface()) + }) else { + return Err(PopupCreationError::ParentMissing); + }; + + ( + &toplevel, + Popup::from_surface( + Some(parent_xdg), + &positioner, + &self.queue_handle, + wl_surface.clone(), + &self.xdg_shell_state, + ) + .map_err(PopupCreationError::PopupCreationFailed)?, + ) + } + }; + if grab { + if let Some(s) = self.seats.first() { + let ptr_data = s.ptr.as_ref().and_then(|p| p.pointer().data::()).and_then(|data| data.latest_button_serial()); + if let Some(serial) = ptr_data.or_else(|| s.touch.as_ref().and_then(|t| t.data::()).and_then(|t| t.latest_down_serial())).or_else(|| s.last_kbd_press + .as_ref() + .map(|p| p.1)) { + popup.xdg_popup().grab( + &s.seat, + serial + ); + } + } else { + log::error!("Can't take grab on popup. Missing serial."); + } + } + popup.xdg_surface().set_window_geometry(0, 0, size.0 as i32, size.1 as i32); + _ = wl_surface.frame(&self.queue_handle, wl_surface.clone()); + wl_surface.commit(); + + let wp_viewport = self.viewporter_state.as_ref().map(|state| { + let viewport = + state.get_viewport(popup.wl_surface(), &self.queue_handle); + viewport.set_destination(size.0 as i32, size.1 as i32); + viewport + }); + let wp_fractional_scale = + self.fractional_scaling_manager.as_ref().map(|fsm| { + fsm.fractional_scaling(popup.wl_surface(), &self.queue_handle) + }); + let mut common: Common = LogicalSize::new(size.0, size.1).into(); + common.wp_viewport = wp_viewport; + let common = Arc::new(Mutex::new(common)); + let positioner = Arc::new(positioner); + + self.popups.push(SctkPopup { + popup: popup.clone(), + data: SctkPopupData { + id: settings.id, + parent: parent.clone(), + toplevel: toplevel.clone(), + positioner: positioner.clone(), + }, + last_configure: None, + _pending_requests: Default::default(), + wp_fractional_scale, + common: common.clone() + }); + + Ok(( + settings.id, + parent.wl_surface().clone(), + toplevel.clone(), + CommonSurface::Popup(popup.clone(), positioner.clone()), + common + )) + } + + pub fn get_layer_surface( + &mut self, + SctkLayerSurfaceSettings { + id, + layer, + keyboard_interactivity, + pointer_interactivity, + anchor, + output, + namespace, + margin, + size, + exclusive_zone, + .. + }: SctkLayerSurfaceSettings, + ) -> Result<(core::window::Id, CommonSurface, Arc>), LayerSurfaceCreationError> { + let wl_output = match output { + IcedOutput::All => None, // TODO + IcedOutput::Active => None, + IcedOutput::Output(output) => Some(output), + }; + + let layer_shell = self + .layer_shell + .as_ref() + .ok_or(LayerSurfaceCreationError::LayerShellNotSupported)?; + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + _ = self.id_map.insert(wl_surface.id(), id.clone()); + let mut size = size.unwrap_or((None, None)); + if anchor.contains(Anchor::BOTTOM.union(Anchor::TOP)) { + size.1 = None; + } else { + size.1 = Some(size.1.unwrap_or(1).max(1)); + } + if anchor.contains(Anchor::LEFT.union(Anchor::RIGHT)) { + size.0 = None; + } else { + size.0 = Some(size.0.unwrap_or(1).max(1)); + } + let layer_surface = layer_shell.create_layer_surface( + &self.queue_handle, + wl_surface.clone(), + layer, + Some(namespace), + wl_output.as_ref(), + ); + layer_surface.set_anchor(anchor); + layer_surface.set_keyboard_interactivity(keyboard_interactivity); + layer_surface.set_margin( + margin.top, + margin.right, + margin.bottom, + margin.left, + ); + layer_surface + .set_size(size.0.unwrap_or_default(), size.1.unwrap_or_default()); + layer_surface.set_exclusive_zone(exclusive_zone); + if !pointer_interactivity { + let region = self + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); + layer_surface.set_input_region(Some(®ion)); + region.destroy(); + } + layer_surface.commit(); + + let wp_viewport = self.viewporter_state.as_ref().map(|state| { + state.get_viewport(layer_surface.wl_surface(), &self.queue_handle) + }); + let wp_fractional_scale = + self.fractional_scaling_manager.as_ref().map(|fsm| { + fsm.fractional_scaling( + layer_surface.wl_surface(), + &self.queue_handle, + ) + }); + let mut common = Common::from(LogicalSize::new(size.0.unwrap_or(1), size.1.unwrap_or(1))); + common.requested_size = size; + common.wp_viewport = wp_viewport; + let common = Arc::new(Mutex::new(common)); + self.layer_surfaces.push(SctkLayerSurface { + id, + surface: layer_surface.clone(), + current_size: None, + layer, + // builder needs to be refactored such that these fields are accessible + anchor, + keyboard_interactivity, + margin, + exclusive_zone, + last_configure: None, + _pending_requests: Vec::new(), + wp_fractional_scale, + common: common.clone() + }); + Ok((id, CommonSurface::Layer(layer_surface), common)) + } + pub fn get_lock_surface( + &mut self, + id: core::window::Id, + output: &WlOutput, + ) -> Option<(CommonSurface, Arc>)> { + if let Some(lock) = self.session_lock.as_ref() { + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + _ = self.id_map.insert(wl_surface.id(), id.clone()); + let session_lock_surface = lock.create_lock_surface( + wl_surface.clone(), + output, + &self.queue_handle, + ); + let wp_viewport = self.viewporter_state.as_ref().map(|state| { + let viewport = + state.get_viewport(&wl_surface, &self.queue_handle); + viewport + }); + let wp_fractional_scale = + self.fractional_scaling_manager.as_ref().map(|fsm| { + fsm.fractional_scaling(&wl_surface, &self.queue_handle) + }); + let common = Arc::new(Mutex::new(Common::from( + LogicalSize::new(1, 1) + ))); + self.lock_surfaces.push(SctkLockSurface { + id, + session_lock_surface: session_lock_surface.clone(), + last_configure: None, + wp_fractional_scale, + wp_viewport, + common: common.clone() + }); + Some((CommonSurface::Lock(session_lock_surface), common)) + } else { + None + } + } + + pub(crate) fn handle_action( + &mut self, + action: iced_runtime::platform_specific::wayland::Action, + ) -> Result<(), Infallible> { + match action { + Action::LayerSurface(action) => match action { + platform_specific::wayland::layer_surface::Action::LayerSurface { + builder, + } => { + let title = builder.namespace.clone(); + if let Ok((id, surface, common)) = self.get_layer_surface(builder) { + // TODO Ashley: all surfaces should probably have an optional title for a11y if nothing else + let wl_surface = surface.wl_surface().clone(); + receive_frame(&mut self.frame_status, &wl_surface); + send_event(&self.events_sender, &self.proxy, + SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Created(self.queue_handle.clone(), surface, id, common, self.connection.display(), title), + id: wl_surface.clone(), + } + ); + } + } + platform_specific::wayland::layer_surface::Action::Size { + id, + width, + height, + } => { + if let Some(layer_surface) = self.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.set_size(width, height); + let wl_surface = layer_surface.surface.wl_surface(); + receive_frame(&mut self.frame_status, &wl_surface); + if let Some(mut prev_configure) = layer_surface.last_configure.clone() { + prev_configure.new_size = (width.unwrap_or(prev_configure.new_size.0), width.unwrap_or(prev_configure.new_size.1)); + _ = send_event(&self.events_sender, &self.proxy, + SctkEvent::LayerSurfaceEvent { variant: LayerSurfaceEventVariant::Configure(prev_configure, wl_surface.clone(), false), id: wl_surface.clone()}); + + } + } + }, + platform_specific::wayland::layer_surface::Action::Destroy(id) => { + if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) { + let l = self.layer_surfaces.remove(i); + if let Some(destroyed) = self.id_map.remove(&l.surface.wl_surface().id()) { + _ = self.destroyed.insert(destroyed); + } + send_event(&self.events_sender, &self.proxy, SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Done, + id: l.surface.wl_surface().clone(), + } + ); + + } + }, + platform_specific::wayland::layer_surface::Action::Anchor { id, anchor } => { + if let Some(layer_surface) = self.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.anchor = anchor; + layer_surface.surface.set_anchor(anchor); + _ = self.to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + + } + } + platform_specific::wayland::layer_surface::Action::ExclusiveZone { + id, + exclusive_zone, + } => { + if let Some(layer_surface) = self.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.exclusive_zone = exclusive_zone; + layer_surface.surface.set_exclusive_zone(exclusive_zone); + _ = self.to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + } + }, + platform_specific::wayland::layer_surface::Action::Margin { + id, + margin, + } => { + if let Some(layer_surface) = self.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.margin = margin; + layer_surface.surface.set_margin(margin.top, margin.right, margin.bottom, margin.left); + _ = self.to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + } + }, + platform_specific::wayland::layer_surface::Action::KeyboardInteractivity { id, keyboard_interactivity } => { + if let Some(layer_surface) = self.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.keyboard_interactivity = keyboard_interactivity; + layer_surface.surface.set_keyboard_interactivity(keyboard_interactivity); + _ = self.to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + + } + }, + platform_specific::wayland::layer_surface::Action::Layer { id, layer } => { + if let Some(layer_surface) = self.layer_surfaces.iter_mut().find(|l| l.id == id) { + layer_surface.layer = layer; + layer_surface.surface.set_layer(layer); + _ = self.to_commit.insert(id, layer_surface.surface.wl_surface().clone()); + + } + }, + }, + Action::Popup(action) => match action { + platform_specific::wayland::popup::Action::Popup { popup, .. } => { + let parent_mismatch = self.popups.last().is_some_and(|p| { + self.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p| *p != popup.parent) + }); + if !self.destroyed.is_empty() || parent_mismatch { + if parent_mismatch { + for i in 0..self.popups.len() { + let id = self.id_map.get(&self.popups[i].popup.wl_surface().id()); + if let Some(id) = id { + if *id != popup.parent { + _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy{id: *id})); + } + } + } + } + if self.pending_popup.replace((popup, 0)).is_none() { + let timer = sctk::reexports::calloop::timer::Timer::from_duration(Duration::from_millis(30)); + let queue_handle = self.queue_handle.clone(); + _ = self.loop_handle.insert_source(timer, move |_, _, state| { + let Some((popup, attempt)) = state.pending_popup.take() else { + return TimeoutAction::Drop; + }; + if !state.destroyed.is_empty() || state.popups.last().is_some_and(|p| { + state.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p| *p != popup.parent) + }) { + if attempt < 5 { + state.pending_popup = Some((popup, attempt+1)); + TimeoutAction::ToDuration(Duration::from_millis(30)) + } + else { + TimeoutAction::Drop + } + } else { + match state.get_popup(popup) { + Ok((id, parent_id, toplevel_id, surface, common)) => { + let wl_surface = surface.wl_surface().clone(); + receive_frame(&mut state.frame_status, &wl_surface); + send_event(&state.events_sender, &state.proxy, + SctkEvent::PopupEvent { + variant: crate::platform_specific::wayland::sctk_event::PopupEventVariant::Created(queue_handle.clone(), surface, id, common, state.connection.display()), + toplevel_id, parent_id, id: wl_surface }); + } + Err(err) => { + log::error!("Failed to create popup. {err:?}"); + } + }; + TimeoutAction::Drop + } + }); + } + // log::error!("Invalid popup Id {:?}", popup.id); + } else { + self.pending_popup = None; + match self.get_popup(popup) { + Ok((id, parent_id, toplevel_id, surface, common)) => { + let wl_surface = surface.wl_surface().clone(); + + receive_frame(&mut self.frame_status, &wl_surface); + send_event(&self.events_sender, &self.proxy, + SctkEvent::PopupEvent { + variant: crate::platform_specific::wayland::sctk_event::PopupEventVariant::Created(self.queue_handle.clone(), surface, id, common, self.connection.display()), + toplevel_id, parent_id, id: wl_surface }); + } + Err(err) => { + log::error!("Failed to create popup. {err:?}"); + } + } + } + }, + // XXX popup destruction must be done carefully + // first destroy the uppermost popup, then work down to the requested popup + platform_specific::wayland::popup::Action::Destroy { id } => { + let sctk_popup = match self + .popups + .iter() + .position(|s| s.data.id == id) + { + Some(p) => self.popups.remove(p), + None => { + log::warn!("No popup to destroy"); + return Ok(()); + }, + }; + let mut to_destroy = vec![sctk_popup]; + while let Some(popup_to_destroy) = to_destroy.last() { + match popup_to_destroy.data.parent.clone() { + PopupParent::LayerSurface(_) | PopupParent::Window(_) => { + break; + } + PopupParent::Popup(popup_to_destroy_first) => { + let popup_to_destroy_first = self + .popups + .iter() + .position(|p| p.popup.wl_surface() == &popup_to_destroy_first) + .unwrap(); + let popup_to_destroy_first = self.popups.remove(popup_to_destroy_first); + to_destroy.push(popup_to_destroy_first); + } + } + } + for popup in to_destroy.into_iter().rev() { + if let Some(id) = self.id_map.remove(&popup.popup.wl_surface().id()) { + _ = self.destroyed.insert(id); + } + _ = send_event(&self.events_sender, &self.proxy, + SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Done, toplevel_id: popup.data.toplevel.clone(), parent_id: popup.data.parent.wl_surface().clone(), id: popup.popup.wl_surface().clone() }); + } + }, + platform_specific::wayland::popup::Action::Size { id, width, height } => { + if let Some(sctk_popup) = self + .popups + .iter_mut() + .find(|s| s.data.id == id) + { + // update geometry + // update positioner + sctk_popup.set_size(width, height, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); + let surface = sctk_popup.popup.wl_surface().clone(); + _ = send_event(&self.events_sender, &self.proxy, + SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(width, height), toplevel_id: sctk_popup.data.parent.wl_surface().clone(), parent_id: sctk_popup.data.parent.wl_surface().clone(), id: surface }); + } + }, + }, + Action::Activation(activation_event) => match activation_event { + platform_specific::wayland::activation::Action::RequestToken { app_id, window, channel } => { + if let Some(activation_state) = self.activation_state.as_ref() { + let (seat_and_serial, surface) = if let Some(id) = window { + let surface = self.windows.iter().find(|w| w.id == id) + .map(|w| w.wl_surface(&self.connection).clone()) + .or_else(|| self.layer_surfaces.iter().find(|l| l.id == id) + .map(|l| l.surface.wl_surface().clone()) + ); + let seat_and_serial = surface.as_ref().and_then(|surface| { + self.seats.first().and_then(|seat| if seat.kbd_focus.as_ref().map(|focus| focus == surface).unwrap_or(false) { + seat.last_kbd_press.as_ref().map(|(_, serial)| (seat.seat.clone(), *serial)) + } else if seat.ptr_focus.as_ref().map(|focus| focus == surface).unwrap_or(false) { + seat.last_ptr_press.as_ref().map(|(_, _, serial)| (seat.seat.clone(), *serial)) + } else { + None + }) + }); + + (seat_and_serial, surface) + } else { + (None, None) + }; + + + activation_state.request_token_with_data(&self.queue_handle, + IcedRequestData::new(RequestData { + app_id, + seat_and_serial, + surface, + }, + self.activation_token_ctr + ) + ); + _ = self.token_senders.insert(self.activation_token_ctr, channel); + self.activation_token_ctr = self.activation_token_ctr.wrapping_add(1); + } else { + // if we don't have the global, we don't want to stall the app + _ = channel.send(None); + + } + }, + platform_specific::wayland::activation::Action::Activate { window, token } => { + if let Some(activation_state) = self.activation_state.as_ref() { + if let Some(surface) = self.windows.iter().find(|w| w.id == window).map(|w| w.wl_surface(&self.connection)) { + activation_state.activate::(&surface, token) + } + } + }, + }, + Action::SessionLock(action) => match action { + platform_specific::wayland::session_lock::Action::Lock => { + if self.session_lock.is_none() { + // TODO send message on error? When protocol doesn't exist. + self.session_lock = self.session_lock_state.lock(&self.queue_handle).ok(); + send_event(&self.events_sender, &self.proxy, SctkEvent::SessionLocked); + } + } + platform_specific::wayland::session_lock::Action::Unlock => { + if let Some(session_lock) = self.session_lock.take() { + session_lock.unlock(); + } + // Make sure server processes unlock before client exits + let _ = self.connection.roundtrip(); + + send_event(&self.events_sender, &self.proxy, SctkEvent::SessionUnlocked); + } + platform_specific::wayland::session_lock::Action::LockSurface { id, output } => { + // TODO how to handle this when there's no lock? + if let Some((surface, common)) = self.get_lock_surface(id, &output) { + let wl_surface = surface.wl_surface(); + receive_frame(&mut self.frame_status, &wl_surface); + send_event(&self.events_sender, &self.proxy, SctkEvent::SessionLockSurfaceCreated { queue_handle: self.queue_handle.clone(), surface, native_id: id, common, display: self.connection.display() }); + } + } + platform_specific::wayland::session_lock::Action::DestroyLockSurface { id } => { + if let Some(i) = + self.lock_surfaces.iter().position(|s| { + s.id == id + }) + { + let surface = self.lock_surfaces.remove(i); + if let Some(id) = self.id_map.remove(&surface.session_lock_surface.wl_surface().id()) { + _ = self.destroyed.insert(id); + } + + send_event(&self.events_sender, &self.proxy, SctkEvent::SessionLockSurfaceDone { surface: surface.session_lock_surface.wl_surface().clone() }); + } + } + } + }; + Ok(()) + } +} + +pub(crate) fn send_event(sender: &mpsc::UnboundedSender, proxy: &winit::event_loop::EventLoopProxy, sctk_event: SctkEvent) { + _ = sender.unbounded_send(Control::PlatformSpecific(Event::Wayland(sctk_event))); + proxy.wake_up(); +} + +delegate_noop!(SctkState: ignore WlSubsurface); +delegate_noop!(SctkState: ignore WlRegion); diff --git a/winit/src/platform_specific/wayland/handlers/activation.rs b/winit/src/platform_specific/wayland/handlers/activation.rs new file mode 100644 index 0000000000..5b8b0f5a82 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/activation.rs @@ -0,0 +1,47 @@ +use iced_futures::futures::channel::oneshot::Sender; +use sctk::{ + activation::{ActivationHandler, RequestData, RequestDataExt}, + delegate_activation, + reexports::client::protocol::{wl_seat::WlSeat, wl_surface::WlSurface}, +}; + +use crate::platform_specific::wayland::event_loop::state::SctkState; + +pub struct IcedRequestData { + id: u32, + data: RequestData, +} + +impl IcedRequestData { + pub fn new(data: RequestData, id: u32) -> IcedRequestData { + IcedRequestData { data, id } + } +} + +impl RequestDataExt for IcedRequestData { + fn app_id(&self) -> Option<&str> { + self.data.app_id() + } + + fn seat_and_serial(&self) -> Option<(&WlSeat, u32)> { + self.data.seat_and_serial() + } + + fn surface(&self) -> Option<&WlSurface> { + self.data.surface() + } +} + +impl ActivationHandler for SctkState { + type RequestData = IcedRequestData; + + fn new_token(&mut self, token: String, data: &Self::RequestData) { + if let Some(tx) = self.token_senders.remove(&data.id) { + _ = tx.send(Some(token)); + } else { + log::error!("Missing activation request Id."); + } + } +} + +delegate_activation!(SctkState, IcedRequestData); diff --git a/winit/src/platform_specific/wayland/handlers/compositor.rs b/winit/src/platform_specific/wayland/handlers/compositor.rs new file mode 100644 index 0000000000..a1b636839f --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/compositor.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MPL-2.0-only +use sctk::{ + compositor::CompositorHandler, + delegate_compositor, + reexports::client::{ + protocol::{wl_output, wl_surface}, + Connection, Proxy, QueueHandle, + }, +}; + +use crate::{ + event_loop::state::receive_frame, + platform_specific::wayland::event_loop::state::SctkState, +}; + +impl CompositorHandler for SctkState { + fn scale_factor_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + surface: &wl_surface::WlSurface, + new_factor: i32, + ) { + self.scale_factor_changed(surface, new_factor as f64, true); + } + + fn frame( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + surface: &wl_surface::WlSurface, + _time: u32, + ) { + _ = receive_frame(&mut self.frame_status, surface); + } + + fn transform_changed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _surface: &wl_surface::WlSurface, + _new_transform: wl_output::Transform, + ) { + // TODO + // this is not required + } + + fn surface_enter( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_surface::WlSurface, + _: &wl_output::WlOutput, + ) { + } + + fn surface_leave( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_surface::WlSurface, + _: &wl_output::WlOutput, + ) { + } +} + +delegate_compositor!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/mod.rs b/winit/src/platform_specific/wayland/handlers/mod.rs new file mode 100644 index 0000000000..e584f44db8 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/mod.rs @@ -0,0 +1,37 @@ +// handlers +pub mod activation; +pub mod compositor; +pub mod output; +pub mod seat; +pub mod session_lock; +pub mod shell; +pub mod subcompositor; +pub mod wp_fractional_scaling; +pub mod wp_viewporter; + +use sctk::{ + delegate_registry, delegate_shm, + output::OutputState, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, + seat::SeatState, + shm::{Shm, ShmHandler}, +}; + +use crate::platform_specific::wayland::event_loop::state::SctkState; + +impl ShmHandler for SctkState { + fn shm_state(&mut self) -> &mut Shm { + &mut self.shm_state + } +} + +impl ProvidesRegistryState for SctkState { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + registry_handlers![OutputState, SeatState,]; +} + +delegate_shm!(SctkState); +delegate_registry!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/output.rs b/winit/src/platform_specific/wayland/handlers/output.rs new file mode 100644 index 0000000000..8bc9291752 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/output.rs @@ -0,0 +1,49 @@ +use crate::platform_specific::wayland::{ + event_loop::state::SctkState, sctk_event::SctkEvent, +}; +use sctk::{delegate_output, output::OutputHandler}; + +impl OutputHandler for SctkState { + fn output_state(&mut self) -> &mut sctk::output::OutputState { + &mut self.output_state + } + + fn new_output( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + output: sctk::reexports::client::protocol::wl_output::WlOutput, + ) { + self.sctk_events.push(SctkEvent::NewOutput { + id: output.clone(), + info: self.output_state.info(&output), + }); + self.outputs.push(output); + } + + fn update_output( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + output: sctk::reexports::client::protocol::wl_output::WlOutput, + ) { + if let Some(info) = self.output_state.info(&output) { + self.sctk_events.push(SctkEvent::UpdateOutput { + id: output.clone(), + info, + }); + } + } + + fn output_destroyed( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + output: sctk::reexports::client::protocol::wl_output::WlOutput, + ) { + self.sctk_events.push(SctkEvent::RemovedOutput(output)); + // TODO clean up any layer surfaces on this output? + } +} + +delegate_output!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs new file mode 100644 index 0000000000..faba2fe2f1 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs @@ -0,0 +1,255 @@ +use crate::platform_specific::wayland::{ + event_loop::state::SctkState, + sctk_event::{KeyboardEventVariant, SctkEvent}, +}; +use sctk::reexports::client::Proxy; +use sctk::{ + delegate_keyboard, + seat::keyboard::{KeyboardHandler, Keysym}, +}; + +impl KeyboardHandler for SctkState { + fn enter( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, + _serial: u32, + _raw: &[u32], + _keysyms: &[Keysym], + ) { + self.request_redraw(surface); + let (i, mut is_active, _seat) = { + let (i, is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i, i == 0, s), + None => return, + }; + _ = my_seat.kbd_focus.replace(surface.clone()); + + let seat = my_seat.seat.clone(); + (i, is_active, seat) + }; + + // TODO Ashley: thoroughly test this + // swap the active seat to be the current seat if the current "active" seat is not focused on the application anyway + if !is_active && self.seats[0].kbd_focus.is_none() { + is_active = true; + self.seats.swap(0, i); + } + + if is_active { + let id = + winit::window::WindowId::from(surface.id().as_ptr() as u64); + if self.windows.iter().any(|w| w.window.id() == id) { + return; + } + self.sctk_events.push(SctkEvent::Winit( + id, + winit::event::WindowEvent::Focused(true), + )); + // self.sctk_events.push(SctkEvent::KeyboardEvent { + // variant: KeyboardEventVariant::Enter(surface.clone()), + // kbd_id: keyboard.clone(), + // seat_id: seat, + // }) + } + } + + fn leave( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, + _serial: u32, + ) { + self.request_redraw(surface); + let (is_active, seat, kbd) = { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + let seat = my_seat.seat.clone(); + let kbd = keyboard.clone(); + _ = my_seat.kbd_focus.take(); + (is_active, seat, kbd) + }; + + if is_active { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Leave(surface.clone()), + kbd_id: kbd, + seat_id: seat, + surface: surface.clone(), + }); + // if there is another seat with a keyboard focused on a surface make that the new active seat + if let Some(i) = + self.seats.iter().position(|s| s.kbd_focus.is_some()) + { + self.seats.swap(0, i); + let s = &self.seats[0]; + let id = + winit::window::WindowId::from(surface.id().as_ptr() as u64); + if self.windows.iter().any(|w| w.window.id() == id) { + return; + } + self.sctk_events.push(SctkEvent::Winit( + id, + winit::event::WindowEvent::Focused(true), + )); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Enter( + s.kbd_focus.clone().unwrap(), + ), + kbd_id: s.kbd.clone().unwrap(), + seat_id: s.seat.clone(), + surface: surface.clone(), + }) + } + } + } + + fn press_key( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + serial: u32, + event: sctk::seat::keyboard::KeyEvent, + ) { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + let seat_id = my_seat.seat.clone(); + let kbd_id = keyboard.clone(); + _ = my_seat.last_kbd_press.replace((event.clone(), serial)); + if is_active { + // FIXME can't create winit key events because of private field + // if let Some(id) = id { + // let physical_key = raw_keycode_to_physicalkey(event.raw_code); + // let (logical_key, location) = + // keysym_to_vkey_location(event.keysym); + // self.sctk_events.push(SctkEvent::Winit( + // id, + // winit::event::WindowEvent::KeyboardInput { + // device_id: Default::default(), + // event: winit::event::KeyEvent { + // physical_key, + // logical_key, + // text: event.utf8.map(|s| s.into()), + // location, + // state: winit::event::ElementState::Pressed, + // repeat: false, // TODO we don't have this info... + // }, + // is_synthetic: false, + // }, + // )) + // } + if let Some(surface) = my_seat.kbd_focus.clone() { + self.request_redraw(&surface); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Press(event), + kbd_id, + seat_id, + surface, + }); + } + } + } + + fn release_key( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + _serial: u32, + event: sctk::seat::keyboard::KeyEvent, + ) { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + let seat_id = my_seat.seat.clone(); + let kbd_id = keyboard.clone(); + + if is_active { + if let Some(surface) = my_seat.kbd_focus.clone() { + self.request_redraw(&surface); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Release(event), + kbd_id, + seat_id, + surface, + }); + } + } + } + + fn update_modifiers( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + _serial: u32, + modifiers: sctk::seat::keyboard::Modifiers, + layout: u32, + ) { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.kbd.as_ref() == Some(keyboard) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + let seat_id = my_seat.seat.clone(); + let kbd_id = keyboard.clone(); + + if is_active { + if let Some(surface) = my_seat.kbd_focus.clone() { + self.request_redraw(&surface); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Modifiers(modifiers), + kbd_id, + seat_id, + surface, + }); + } + } + } +} + +delegate_keyboard!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/seat/mod.rs b/winit/src/platform_specific/wayland/handlers/seat/mod.rs new file mode 100644 index 0000000000..38369b437b --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/seat/mod.rs @@ -0,0 +1,5 @@ +// TODO support multi-seat handling +pub mod keyboard; +pub mod pointer; +pub mod seat; +pub mod touch; diff --git a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs new file mode 100644 index 0000000000..798ecbe74b --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs @@ -0,0 +1,184 @@ +use crate::{ + event_loop::state::FrameStatus, + platform_specific::wayland::{ + event_loop::state::SctkState, sctk_event::SctkEvent, + }, +}; +use sctk::{ + delegate_pointer, + reexports::client::Proxy, + seat::pointer::{ + CursorIcon, PointerEvent, PointerEventKind, PointerHandler, + }, +}; +use winit::{ + dpi::PhysicalPosition, + event::{MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}, +}; + +impl PointerHandler for SctkState { + fn pointer_frame( + &mut self, + conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + pointer: &sctk::reexports::client::protocol::wl_pointer::WlPointer, + events: &[sctk::seat::pointer::PointerEvent], + ) { + let (is_active, my_seat) = + match self.seats.iter_mut().enumerate().find_map(|(i, s)| { + if s.ptr.as_ref().map(|p| p.pointer()) == Some(pointer) { + Some((i, s)) + } else { + None + } + }) { + Some((i, s)) => (i == 0, s), + None => return, + }; + + // track events, but only forward for the active seat + for e in events { + if my_seat.active_icon != my_seat.icon { + // Restore cursor that was set by appliction, or default + my_seat.set_cursor( + conn, + my_seat.icon.unwrap_or(CursorIcon::Default), + ); + } + + if is_active { + let id = winit::window::WindowId::from( + e.surface.id().as_ptr() as u64 + ); + if self.windows.iter().any(|w| w.window.id() == id) { + continue; + } + let entry = self + .frame_status + .entry(e.surface.id()) + .or_insert(FrameStatus::RequestedRedraw); + if matches!(entry, FrameStatus::Received) { + *entry = FrameStatus::Ready; + } + if let PointerEventKind::Motion { time } = &e.kind { + self.sctk_events.push(SctkEvent::PointerEvent { + variant: PointerEvent { + surface: e.surface.clone(), + position: e.position, + kind: PointerEventKind::Motion { time: *time }, + }, + ptr_id: pointer.clone(), + seat_id: my_seat.seat.clone(), + }); + } else { + self.sctk_events.push(SctkEvent::Winit( + id, + match e.kind { + PointerEventKind::Enter { serial } => { + WindowEvent::CursorEntered { + device_id: Default::default(), + } + } + PointerEventKind::Leave { serial } => { + WindowEvent::CursorLeft { + device_id: Default::default(), + } + } + PointerEventKind::Motion { time } => { + WindowEvent::CursorMoved { + device_id: Default::default(), + position: e.position.into(), + } + } + PointerEventKind::Press { + time, + button, + serial, + } => WindowEvent::MouseInput { + device_id: Default::default(), + state: winit::event::ElementState::Pressed, + button: wayland_button_to_winit(button), + }, + PointerEventKind::Release { + time, + button, + serial, + } => WindowEvent::MouseInput { + device_id: Default::default(), + state: winit::event::ElementState::Released, + button: wayland_button_to_winit(button), + }, + PointerEventKind::Axis { + time, + horizontal, + vertical, + source, + } => WindowEvent::MouseWheel { + device_id: Default::default(), + delta: if horizontal.discrete > 0 { + MouseScrollDelta::LineDelta( + horizontal.discrete as f32, + vertical.discrete as f32, + ) + } else { + MouseScrollDelta::PixelDelta( + PhysicalPosition::new( + horizontal.absolute, + vertical.absolute, + ), + ) + }, + phase: if horizontal.stop { + TouchPhase::Ended + } else { + TouchPhase::Moved + }, + }, + }, + )); + } + } + match e.kind { + PointerEventKind::Enter { .. } => { + _ = my_seat.ptr_focus.replace(e.surface.clone()); + } + PointerEventKind::Leave { .. } => { + _ = my_seat.ptr_focus.take(); + _ = my_seat.active_icon = None; + } + PointerEventKind::Press { + time, + button, + serial, + } => { + _ = my_seat.last_ptr_press.replace((time, button, serial)); + } + // TODO revisit events that ought to be handled and change internal state + _ => {} + } + } + } +} + +/// Convert the Wayland button into winit. +fn wayland_button_to_winit(button: u32) -> MouseButton { + // These values are coming from . + const BTN_LEFT: u32 = 0x110; + const BTN_RIGHT: u32 = 0x111; + const BTN_MIDDLE: u32 = 0x112; + const BTN_SIDE: u32 = 0x113; + const BTN_EXTRA: u32 = 0x114; + const BTN_FORWARD: u32 = 0x115; + const BTN_BACK: u32 = 0x116; + + match button { + BTN_LEFT => MouseButton::Left, + BTN_RIGHT => MouseButton::Right, + BTN_MIDDLE => MouseButton::Middle, + BTN_BACK | BTN_SIDE => MouseButton::Back, + BTN_FORWARD | BTN_EXTRA => MouseButton::Forward, + button => MouseButton::Other(button as u16), + } +} + +delegate_pointer!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/seat/seat.rs b/winit/src/platform_specific/wayland/handlers/seat/seat.rs new file mode 100644 index 0000000000..ee7d83e56e --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/seat/seat.rs @@ -0,0 +1,214 @@ +use crate::platform_specific::wayland::{ + event_loop::{state::SctkSeat, state::SctkState}, + sctk_event::{KeyboardEventVariant, SctkEvent, SeatEventVariant}, +}; +use iced_runtime::keyboard::Modifiers; +use sctk::{ + delegate_seat, + reexports::client::{protocol::wl_keyboard::WlKeyboard, Proxy}, + seat::{pointer::ThemeSpec, SeatHandler}, +}; + +impl SeatHandler for SctkState { + fn seat_state(&mut self) -> &mut sctk::seat::SeatState { + &mut self.seat_state + } + + fn new_seat( + &mut self, + _conn: &sctk::reexports::client::Connection, + qh: &sctk::reexports::client::QueueHandle, + seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + ) { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::New, + id: seat.clone(), + }); + + self.seats.push(SctkSeat { + seat, + kbd: None, + ptr: None, + touch: None, + _modifiers: Modifiers::default(), + kbd_focus: None, + ptr_focus: None, + last_ptr_press: None, + last_kbd_press: None, + last_touch_down: None, + icon: None, + active_icon: None, + }); + } + + fn new_capability( + &mut self, + _conn: &sctk::reexports::client::Connection, + qh: &sctk::reexports::client::QueueHandle, + seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + capability: sctk::seat::Capability, + ) { + let my_seat = match self.seats.iter_mut().find(|s| s.seat == seat) { + Some(s) => s, + None => { + self.seats.push(SctkSeat { + seat: seat.clone(), + kbd: None, + ptr: None, + touch: None, + + _modifiers: Modifiers::default(), + kbd_focus: None, + ptr_focus: None, + last_ptr_press: None, + last_kbd_press: None, + last_touch_down: None, + icon: None, + active_icon: None, + }); + self.seats.last_mut().unwrap() + } + }; + // TODO data device + match capability { + sctk::seat::Capability::Keyboard => { + let seat_clone = seat.clone(); + let seat_clone_2 = seat.clone(); + if let Ok(kbd) = self.seat_state.get_keyboard_with_repeat( + qh, + &seat, + None, + self.loop_handle.clone(), + Box::new(move |state, kbd: &WlKeyboard, e| { + let Some(my_seat) = state + .seats + .iter_mut() + .find(|s| s.seat == seat_clone_2) + else { + return; + }; + if let Some(surface) = my_seat.kbd_focus.clone() { + state.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Repeat(e), + kbd_id: kbd.clone(), + seat_id: seat_clone.clone(), + surface, + }); + } + }), + ) { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::NewCapability( + capability, + kbd.id(), + ), + id: seat.clone(), + }); + _ = my_seat.kbd.replace(kbd); + } + } + sctk::seat::Capability::Pointer => { + let surface = self.compositor_state.create_surface(qh); + + if let Ok(ptr) = self.seat_state.get_pointer_with_theme( + qh, + &seat, + self.shm_state.wl_shm(), + surface, + ThemeSpec::default(), + ) { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::NewCapability( + capability, + ptr.pointer().id(), + ), + id: seat.clone(), + }); + _ = my_seat.ptr.replace(ptr); + } + } + sctk::seat::Capability::Touch => { + if let Some(touch) = self.seat_state.get_touch(qh, &seat).ok() { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::NewCapability( + capability, + touch.id(), + ), + id: seat.clone(), + }); + _ = my_seat.touch.replace(touch); + } + } + _ => unimplemented!(), + } + } + + fn remove_capability( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + capability: sctk::seat::Capability, + ) { + let my_seat = match self.seats.iter_mut().find(|s| s.seat == seat) { + Some(s) => s, + None => return, + }; + + // TODO data device + match capability { + // TODO use repeating kbd? + sctk::seat::Capability::Keyboard => { + if let Some(kbd) = my_seat.kbd.take() { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::RemoveCapability( + capability, + kbd.id(), + ), + id: seat.clone(), + }); + } + } + sctk::seat::Capability::Pointer => { + if let Some(ptr) = my_seat.ptr.take() { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::RemoveCapability( + capability, + ptr.pointer().id(), + ), + id: seat.clone(), + }); + } + } + sctk::seat::Capability::Touch => { + if let Some(touch) = my_seat.touch.take() { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::RemoveCapability( + capability, + touch.id(), + ), + id: seat.clone(), + }); + } + } + _ => unimplemented!(), + } + } + + fn remove_seat( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + ) { + self.sctk_events.push(SctkEvent::SeatEvent { + variant: SeatEventVariant::Remove, + id: seat.clone(), + }); + if let Some(i) = self.seats.iter().position(|s| s.seat == seat) { + _ = self.seats.remove(i); + } + } +} + +delegate_seat!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/seat/touch.rs b/winit/src/platform_specific/wayland/handlers/seat/touch.rs new file mode 100644 index 0000000000..99e6cacad5 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/seat/touch.rs @@ -0,0 +1,157 @@ +// TODO handle multiple seats? + +use crate::{ + event_loop::state::FrameStatus, + platform_specific::wayland::{ + event_loop::state::SctkState, sctk_event::SctkEvent, + }, +}; +use iced_runtime::core::{touch, Point}; +use sctk::{ + delegate_touch, + reexports::client::{ + protocol::{wl_surface::WlSurface, wl_touch::WlTouch}, + Connection, Proxy, QueueHandle, + }, + seat::touch::TouchHandler, +}; + +impl TouchHandler for SctkState { + fn down( + &mut self, + _: &Connection, + _: &QueueHandle, + touch: &WlTouch, + serial: u32, + time: u32, + surface: WlSurface, + id: i32, + position: (f64, f64), + ) { + self.request_redraw(&surface); + let Some(my_seat) = self + .seats + .iter_mut() + .find(|s| s.touch.as_ref() == Some(touch)) + else { + return; + }; + + _ = my_seat.last_touch_down.replace((time, id, serial)); + + let id = touch::Finger(id as u64); + let position = Point::new(position.0 as f32, position.1 as f32); + _ = self.touch_points.insert(id, (surface.clone(), position)); + self.sctk_events.push(SctkEvent::TouchEvent { + variant: touch::Event::FingerPressed { id, position }, + touch_id: touch.clone(), + seat_id: my_seat.seat.clone(), + surface, + }); + } + + fn up( + &mut self, + _: &Connection, + _: &QueueHandle, + touch: &WlTouch, + _serial: u32, + _time: u32, + id: i32, + ) { + let Some(my_seat) = + self.seats.iter().find(|s| s.touch.as_ref() == Some(touch)) + else { + return; + }; + + let id = touch::Finger(id as u64); + if let Some((surface, position)) = self.touch_points.get(&id).cloned() { + self.sctk_events.push(SctkEvent::TouchEvent { + variant: touch::Event::FingerLifted { id, position }, + touch_id: touch.clone(), + seat_id: my_seat.seat.clone(), + surface, + }); + } + } + fn motion( + &mut self, + _: &Connection, + _: &QueueHandle, + touch: &WlTouch, + _time: u32, + id: i32, + position: (f64, f64), + ) { + let Some(my_seat) = + self.seats.iter().find(|s| s.touch.as_ref() == Some(touch)) + else { + return; + }; + + let id = touch::Finger(id as u64); + let position = Point::new(position.0 as f32, position.1 as f32); + if let Some((surface, position_ref)) = self.touch_points.get_mut(&id) { + let entry = self + .frame_status + .entry(surface.id()) + .or_insert(FrameStatus::RequestedRedraw); + if matches!(entry, FrameStatus::Received) { + *entry = FrameStatus::Ready; + } + *position_ref = position; + self.sctk_events.push(SctkEvent::TouchEvent { + variant: touch::Event::FingerMoved { id, position }, + touch_id: touch.clone(), + seat_id: my_seat.seat.clone(), + surface: surface.clone(), + }); + } + } + + fn shape( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlTouch, + _: i32, + _: f64, + _: f64, + ) { + } + + fn orientation( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlTouch, + _: i32, + _: f64, + ) { + } + + fn cancel( + &mut self, + _: &Connection, + _: &QueueHandle, + touch: &WlTouch, + ) { + let Some(my_seat) = + self.seats.iter().find(|s| s.touch.as_ref() == Some(touch)) + else { + return; + }; + + for (id, (surface, position)) in self.touch_points.drain() { + self.sctk_events.push(SctkEvent::TouchEvent { + variant: touch::Event::FingerLost { id, position }, + touch_id: touch.clone(), + seat_id: my_seat.seat.clone(), + surface, + }); + } + } +} + +delegate_touch!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/session_lock.rs b/winit/src/platform_specific/wayland/handlers/session_lock.rs new file mode 100644 index 0000000000..27ae70b720 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/session_lock.rs @@ -0,0 +1,58 @@ +use crate::platform_specific::wayland::{ + handlers::SctkState, sctk_event::SctkEvent, +}; +use sctk::{ + delegate_session_lock, + reexports::client::{Connection, QueueHandle}, + session_lock::{ + SessionLock, SessionLockHandler, SessionLockSurface, + SessionLockSurfaceConfigure, + }, +}; + +impl SessionLockHandler for SctkState { + fn locked( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _session_lock: SessionLock, + ) { + self.sctk_events.push(SctkEvent::SessionLocked); + } + + fn finished( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _session_lock: SessionLock, + ) { + self.sctk_events.push(SctkEvent::SessionLockFinished); + } + + fn configure( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + session_lock_surface: SessionLockSurface, + configure: SessionLockSurfaceConfigure, + _serial: u32, + ) { + let lock_surface = match self.lock_surfaces.iter_mut().find(|s| { + s.session_lock_surface.wl_surface() + == session_lock_surface.wl_surface() + }) { + Some(l) => l, + None => return, + }; + let first = lock_surface.last_configure.is_none(); + _ = lock_surface.last_configure.replace(configure.clone()); + self.sctk_events + .push(SctkEvent::SessionLockSurfaceConfigure { + surface: session_lock_surface.wl_surface().clone(), + configure, + first, + }); + } +} + +delegate_session_lock!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/shell/layer.rs b/winit/src/platform_specific/wayland/handlers/shell/layer.rs new file mode 100644 index 0000000000..d7d8b3e85e --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/shell/layer.rs @@ -0,0 +1,118 @@ +use crate::platform_specific::wayland::{ + event_loop::state::SctkState, + sctk_event::{LayerSurfaceEventVariant, SctkEvent}, +}; +use sctk::{ + delegate_layer, + reexports::client::Proxy, + shell::{ + wlr_layer::{Anchor, KeyboardInteractivity, LayerShellHandler}, + WaylandSurface, + }, +}; +use std::fmt::Debug; +use winit::dpi::LogicalSize; + +impl LayerShellHandler for SctkState { + fn closed( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + layer: &sctk::shell::wlr_layer::LayerSurface, + ) { + let layer = match self.layer_surfaces.iter().position(|s| { + s.surface.wl_surface().id() == layer.wl_surface().id() + }) { + Some(w) => self.layer_surfaces.remove(w), + None => return, + }; + + self.sctk_events.push(SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Done, + id: layer.surface.wl_surface().clone(), + }) + // TODO popup cleanup + } + + fn configure( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + layer: &sctk::shell::wlr_layer::LayerSurface, + mut configure: sctk::shell::wlr_layer::LayerSurfaceConfigure, + _serial: u32, + ) { + self.request_redraw(layer.wl_surface()); + let layer = + match self.layer_surfaces.iter_mut().find(|s| { + s.surface.wl_surface().id() == layer.wl_surface().id() + }) { + Some(l) => l, + None => return, + }; + let common = layer.common.lock().unwrap(); + let requested_size = common.requested_size; + drop(common); + configure.new_size.0 = if let Some(w) = requested_size.0 { + w + } else { + configure.new_size.0.max(1) + }; + configure.new_size.1 = if let Some(h) = requested_size.1 { + h + } else { + configure.new_size.1.max(1) + }; + + layer.update_viewport(configure.new_size.0, configure.new_size.1); + let first = layer.last_configure.is_none(); + _ = layer.last_configure.replace(configure.clone()); + let mut common = layer.common.lock().unwrap(); + common.size = + LogicalSize::new(configure.new_size.0, configure.new_size.1); + self.sctk_events.push(SctkEvent::LayerSurfaceEvent { + variant: LayerSurfaceEventVariant::Configure( + configure, + layer.surface.wl_surface().clone(), + first, + ), + id: layer.surface.wl_surface().clone(), + }); + } +} + +delegate_layer!(SctkState); + +#[allow(dead_code)] +/// A request to SCTK window from Winit window. +#[derive(Debug, Clone)] +pub enum LayerSurfaceRequest { + /// Set fullscreen. + /// + /// Passing `None` will set it on the current monitor. + Size(LogicalSize), + + /// Unset fullscreen. + UnsetFullscreen, + + /// Show cursor for the certain window or not. + ShowCursor(bool), + + /// Set anchor + Anchor(Anchor), + + /// Set margin + ExclusiveZone(i32), + + /// Set margin + Margin(u32), + + /// Passthrough mouse input to underlying windows. + KeyboardInteractivity(KeyboardInteractivity), + + /// Redraw was requested. + Redraw, + + /// Window should be closed. + Close, +} diff --git a/winit/src/platform_specific/wayland/handlers/shell/mod.rs b/winit/src/platform_specific/wayland/handlers/shell/mod.rs new file mode 100644 index 0000000000..5556c08d3e --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/shell/mod.rs @@ -0,0 +1,3 @@ +pub mod layer; +pub mod xdg_popup; +pub mod xdg_window; diff --git a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs new file mode 100644 index 0000000000..0975dc0b9e --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs @@ -0,0 +1,92 @@ +use crate::platform_specific::wayland::{ + event_loop::state::{self, PopupParent, SctkState}, + sctk_event::{PopupEventVariant, SctkEvent}, +}; +use sctk::{ + delegate_xdg_popup, reexports::client::Proxy, + shell::xdg::popup::PopupHandler, +}; + +impl PopupHandler for SctkState { + fn configure( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + popup: &sctk::shell::xdg::popup::Popup, + configure: sctk::shell::xdg::popup::PopupConfigure, + ) { + self.request_redraw(popup.wl_surface()); + let sctk_popup = match self.popups.iter_mut().find(|s| { + s.popup.wl_surface().clone() == popup.wl_surface().clone() + }) { + Some(p) => p, + None => return, + }; + let first = sctk_popup.last_configure.is_none(); + _ = sctk_popup.last_configure.replace(configure.clone()); + + self.sctk_events.push(SctkEvent::PopupEvent { + variant: PopupEventVariant::Configure( + configure, + popup.wl_surface().clone(), + first, + ), + id: popup.wl_surface().clone(), + toplevel_id: sctk_popup.data.toplevel.clone(), + parent_id: match &sctk_popup.data.parent { + PopupParent::LayerSurface(s) => s.clone(), + PopupParent::Window(s) => s.clone(), + PopupParent::Popup(s) => s.clone(), + }, + }); + } + + fn done( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + popup: &sctk::shell::xdg::popup::Popup, + ) { + let sctk_popup = match self.popups.iter().position(|s| { + s.popup.wl_surface().clone() == popup.wl_surface().clone() + }) { + Some(p) => self.popups.remove(p), + None => return, + }; + let mut to_destroy = vec![sctk_popup]; + while let Some(popup_to_destroy) = to_destroy.last() { + match popup_to_destroy.data.parent.clone() { + state::PopupParent::LayerSurface(_) + | state::PopupParent::Window(_) => { + break; + } + state::PopupParent::Popup(popup_to_destroy_first) => { + let popup_to_destroy_first = self + .popups + .iter() + .position(|p| { + p.popup.wl_surface() == &popup_to_destroy_first + }) + .unwrap(); + let popup_to_destroy_first = + self.popups.remove(popup_to_destroy_first); + to_destroy.push(popup_to_destroy_first); + } + } + } + for popup in to_destroy.into_iter().rev() { + if let Some(id) = self.id_map.remove(&popup.popup.wl_surface().id()) + { + _ = self.destroyed.insert(id); + } + + self.sctk_events.push(SctkEvent::PopupEvent { + variant: PopupEventVariant::Done, + toplevel_id: popup.data.toplevel.clone(), + parent_id: popup.data.parent.wl_surface().clone(), + id: popup.popup.wl_surface().clone(), + }); + } + } +} +delegate_xdg_popup!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/shell/xdg_window.rs b/winit/src/platform_specific/wayland/handlers/shell/xdg_window.rs new file mode 100644 index 0000000000..24da3cfa23 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/shell/xdg_window.rs @@ -0,0 +1,27 @@ +use crate::platform_specific::wayland::event_loop::state::SctkState; +use sctk::{ + delegate_xdg_shell, delegate_xdg_window, shell::xdg::window::WindowHandler, +}; + +impl WindowHandler for SctkState { + fn request_close( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + _window: &sctk::shell::xdg::window::Window, + ) { + } + + fn configure( + &mut self, + _conn: &sctk::reexports::client::Connection, + _qh: &sctk::reexports::client::QueueHandle, + _window: &sctk::shell::xdg::window::Window, + _configure: sctk::shell::xdg::window::WindowConfigure, + _serial: u32, + ) { + } +} + +delegate_xdg_window!(SctkState); +delegate_xdg_shell!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/subcompositor.rs b/winit/src/platform_specific/wayland/handlers/subcompositor.rs new file mode 100644 index 0000000000..40f9a0db8f --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/subcompositor.rs @@ -0,0 +1,4 @@ +use crate::platform_specific::wayland::handlers::SctkState; +use sctk::delegate_subcompositor; + +delegate_subcompositor!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs b/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs new file mode 100644 index 0000000000..5ad326afd7 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs @@ -0,0 +1,91 @@ +// From: https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/wayland/types/wp_fractional_scaling.rs +//! Handling of the fractional scaling. + + +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Dispatch; +use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle}; +use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; +use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::Event as FractionalScalingEvent; +use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; + +use sctk::globals::GlobalData; + +use crate::platform_specific::wayland::event_loop::state::SctkState; + +/// The scaling factor denominator. +const SCALE_DENOMINATOR: f64 = 120.; + +/// Fractional scaling manager. +#[derive(Debug)] +pub struct FractionalScalingManager { + manager: WpFractionalScaleManagerV1, +} + +pub struct FractionalScaling { + /// The surface used for scaling. + surface: WlSurface, +} + +impl FractionalScalingManager { + /// Create new viewporter. + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { manager }) + } + + pub fn fractional_scaling( + &self, + surface: &WlSurface, + queue_handle: &QueueHandle, + ) -> WpFractionalScaleV1 { + let data = FractionalScaling { + surface: surface.clone(), + }; + self.manager + .get_fractional_scale(surface, queue_handle, data) + } +} + +impl Dispatch + for FractionalScalingManager +{ + fn event( + _: &mut SctkState, + _: &WpFractionalScaleManagerV1, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + // No events. + } +} + +impl Dispatch + for FractionalScalingManager +{ + fn event( + state: &mut SctkState, + _: &WpFractionalScaleV1, + event: ::Event, + data: &FractionalScaling, + _: &Connection, + _: &QueueHandle, + ) { + if let FractionalScalingEvent::PreferredScale { scale } = event { + state.scale_factor_changed( + &data.surface, + scale as f64 / SCALE_DENOMINATOR, + false, + ); + } + } +} + +delegate_dispatch!(SctkState: [WpFractionalScaleManagerV1: GlobalData] => FractionalScalingManager); +delegate_dispatch!(SctkState: [WpFractionalScaleV1: FractionalScaling] => FractionalScalingManager); diff --git a/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs b/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs new file mode 100644 index 0000000000..c0ca1025c8 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs @@ -0,0 +1,71 @@ +//! Handling of the wp-viewporter. + + +use sctk::reexports::client::globals::{BindError, GlobalList}; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Dispatch; +use sctk::reexports::client::{ + delegate_dispatch, Connection, Proxy, QueueHandle, +}; +use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; +use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter; + +use sctk::globals::GlobalData; + +use crate::platform_specific::wayland::event_loop::state::SctkState; + +/// Viewporter. +#[derive(Debug)] +pub struct ViewporterState { + viewporter: WpViewporter, +} + +impl ViewporterState { + /// Create new viewporter. + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let viewporter = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { viewporter }) + } + + /// Get the viewport for the given object. + pub fn get_viewport( + &self, + surface: &WlSurface, + queue_handle: &QueueHandle, + ) -> WpViewport { + self.viewporter + .get_viewport(surface, queue_handle, GlobalData) + } +} + +impl Dispatch for ViewporterState { + fn event( + _: &mut SctkState, + _: &WpViewporter, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + // No events. + } +} + +impl Dispatch for ViewporterState { + fn event( + _: &mut SctkState, + _: &WpViewport, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + // No events. + } +} + +delegate_dispatch!(SctkState: [WpViewporter: GlobalData] => ViewporterState); +delegate_dispatch!(SctkState: [WpViewport: GlobalData] => ViewporterState); diff --git a/winit/src/platform_specific/wayland/keymap.rs b/winit/src/platform_specific/wayland/keymap.rs new file mode 100644 index 0000000000..0d96cfcaff --- /dev/null +++ b/winit/src/platform_specific/wayland/keymap.rs @@ -0,0 +1,895 @@ +// Borrowed from winit +use winit::keyboard::{KeyCode, NativeKeyCode, PhysicalKey}; +/// Map the raw X11-style keycode to the `KeyCode` enum. +/// +/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses. +pub fn raw_keycode_to_physicalkey(keycode: u32) -> PhysicalKey { + scancode_to_physicalkey(keycode.saturating_sub(8)) +} + +/// Map the linux scancode to Keycode. +/// +/// Both X11 and Wayland use keys with `+ 8` offset to linux scancode. +pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { + // The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as + // libxkbcommon's documentation seems to suggest that the keycode values we're interested in + // are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes, + // I can only hope they agree on what the keycodes mean. + // + // Some of the keycodes are likely superfluous for our purposes, and some are ones which are + // difficult to test the correctness of, or discover the purpose of. Because of this, they've + // either been commented out here, or not included at all. + PhysicalKey::Code(match scancode { + 0 => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(0)), + 1 => KeyCode::Escape, + 2 => KeyCode::Digit1, + 3 => KeyCode::Digit2, + 4 => KeyCode::Digit3, + 5 => KeyCode::Digit4, + 6 => KeyCode::Digit5, + 7 => KeyCode::Digit6, + 8 => KeyCode::Digit7, + 9 => KeyCode::Digit8, + 10 => KeyCode::Digit9, + 11 => KeyCode::Digit0, + 12 => KeyCode::Minus, + 13 => KeyCode::Equal, + 14 => KeyCode::Backspace, + 15 => KeyCode::Tab, + 16 => KeyCode::KeyQ, + 17 => KeyCode::KeyW, + 18 => KeyCode::KeyE, + 19 => KeyCode::KeyR, + 20 => KeyCode::KeyT, + 21 => KeyCode::KeyY, + 22 => KeyCode::KeyU, + 23 => KeyCode::KeyI, + 24 => KeyCode::KeyO, + 25 => KeyCode::KeyP, + 26 => KeyCode::BracketLeft, + 27 => KeyCode::BracketRight, + 28 => KeyCode::Enter, + 29 => KeyCode::ControlLeft, + 30 => KeyCode::KeyA, + 31 => KeyCode::KeyS, + 32 => KeyCode::KeyD, + 33 => KeyCode::KeyF, + 34 => KeyCode::KeyG, + 35 => KeyCode::KeyH, + 36 => KeyCode::KeyJ, + 37 => KeyCode::KeyK, + 38 => KeyCode::KeyL, + 39 => KeyCode::Semicolon, + 40 => KeyCode::Quote, + 41 => KeyCode::Backquote, + 42 => KeyCode::ShiftLeft, + 43 => KeyCode::Backslash, + 44 => KeyCode::KeyZ, + 45 => KeyCode::KeyX, + 46 => KeyCode::KeyC, + 47 => KeyCode::KeyV, + 48 => KeyCode::KeyB, + 49 => KeyCode::KeyN, + 50 => KeyCode::KeyM, + 51 => KeyCode::Comma, + 52 => KeyCode::Period, + 53 => KeyCode::Slash, + 54 => KeyCode::ShiftRight, + 55 => KeyCode::NumpadMultiply, + 56 => KeyCode::AltLeft, + 57 => KeyCode::Space, + 58 => KeyCode::CapsLock, + 59 => KeyCode::F1, + 60 => KeyCode::F2, + 61 => KeyCode::F3, + 62 => KeyCode::F4, + 63 => KeyCode::F5, + 64 => KeyCode::F6, + 65 => KeyCode::F7, + 66 => KeyCode::F8, + 67 => KeyCode::F9, + 68 => KeyCode::F10, + 69 => KeyCode::NumLock, + 70 => KeyCode::ScrollLock, + 71 => KeyCode::Numpad7, + 72 => KeyCode::Numpad8, + 73 => KeyCode::Numpad9, + 74 => KeyCode::NumpadSubtract, + 75 => KeyCode::Numpad4, + 76 => KeyCode::Numpad5, + 77 => KeyCode::Numpad6, + 78 => KeyCode::NumpadAdd, + 79 => KeyCode::Numpad1, + 80 => KeyCode::Numpad2, + 81 => KeyCode::Numpad3, + 82 => KeyCode::Numpad0, + 83 => KeyCode::NumpadDecimal, + 85 => KeyCode::Lang5, + 86 => KeyCode::IntlBackslash, + 87 => KeyCode::F11, + 88 => KeyCode::F12, + 89 => KeyCode::IntlRo, + 90 => KeyCode::Lang3, + 91 => KeyCode::Lang4, + 92 => KeyCode::Convert, + 93 => KeyCode::KanaMode, + 94 => KeyCode::NonConvert, + // 95 => KeyCode::KPJPCOMMA, + 96 => KeyCode::NumpadEnter, + 97 => KeyCode::ControlRight, + 98 => KeyCode::NumpadDivide, + 99 => KeyCode::PrintScreen, + 100 => KeyCode::AltRight, + // 101 => KeyCode::LINEFEED, + 102 => KeyCode::Home, + 103 => KeyCode::ArrowUp, + 104 => KeyCode::PageUp, + 105 => KeyCode::ArrowLeft, + 106 => KeyCode::ArrowRight, + 107 => KeyCode::End, + 108 => KeyCode::ArrowDown, + 109 => KeyCode::PageDown, + 110 => KeyCode::Insert, + 111 => KeyCode::Delete, + // 112 => KeyCode::MACRO, + 113 => KeyCode::AudioVolumeMute, + 114 => KeyCode::AudioVolumeDown, + 115 => KeyCode::AudioVolumeUp, + // 116 => KeyCode::POWER, + 117 => KeyCode::NumpadEqual, + // 118 => KeyCode::KPPLUSMINUS, + 119 => KeyCode::Pause, + // 120 => KeyCode::SCALE, + 121 => KeyCode::NumpadComma, + 122 => KeyCode::Lang1, + 123 => KeyCode::Lang2, + 124 => KeyCode::IntlYen, + 125 => KeyCode::SuperLeft, + 126 => KeyCode::SuperRight, + 127 => KeyCode::ContextMenu, + // 128 => KeyCode::STOP, + // 129 => KeyCode::AGAIN, + // 130 => KeyCode::PROPS, + // 131 => KeyCode::UNDO, + // 132 => KeyCode::FRONT, + // 133 => KeyCode::COPY, + // 134 => KeyCode::OPEN, + // 135 => KeyCode::PASTE, + // 136 => KeyCode::FIND, + // 137 => KeyCode::CUT, + // 138 => KeyCode::HELP, + // 139 => KeyCode::MENU, + // 140 => KeyCode::CALC, + // 141 => KeyCode::SETUP, + // 142 => KeyCode::SLEEP, + // 143 => KeyCode::WAKEUP, + // 144 => KeyCode::FILE, + // 145 => KeyCode::SENDFILE, + // 146 => KeyCode::DELETEFILE, + // 147 => KeyCode::XFER, + // 148 => KeyCode::PROG1, + // 149 => KeyCode::PROG2, + // 150 => KeyCode::WWW, + // 151 => KeyCode::MSDOS, + // 152 => KeyCode::COFFEE, + // 153 => KeyCode::ROTATE_DISPLAY, + // 154 => KeyCode::CYCLEWINDOWS, + // 155 => KeyCode::MAIL, + // 156 => KeyCode::BOOKMARKS, + // 157 => KeyCode::COMPUTER, + // 158 => KeyCode::BACK, + // 159 => KeyCode::FORWARD, + // 160 => KeyCode::CLOSECD, + // 161 => KeyCode::EJECTCD, + // 162 => KeyCode::EJECTCLOSECD, + 163 => KeyCode::MediaTrackNext, + 164 => KeyCode::MediaPlayPause, + 165 => KeyCode::MediaTrackPrevious, + 166 => KeyCode::MediaStop, + // 167 => KeyCode::RECORD, + // 168 => KeyCode::REWIND, + // 169 => KeyCode::PHONE, + // 170 => KeyCode::ISO, + // 171 => KeyCode::CONFIG, + // 172 => KeyCode::HOMEPAGE, + // 173 => KeyCode::REFRESH, + // 174 => KeyCode::EXIT, + // 175 => KeyCode::MOVE, + // 176 => KeyCode::EDIT, + // 177 => KeyCode::SCROLLUP, + // 178 => KeyCode::SCROLLDOWN, + // 179 => KeyCode::KPLEFTPAREN, + // 180 => KeyCode::KPRIGHTPAREN, + // 181 => KeyCode::NEW, + // 182 => KeyCode::REDO, + 183 => KeyCode::F13, + 184 => KeyCode::F14, + 185 => KeyCode::F15, + 186 => KeyCode::F16, + 187 => KeyCode::F17, + 188 => KeyCode::F18, + 189 => KeyCode::F19, + 190 => KeyCode::F20, + 191 => KeyCode::F21, + 192 => KeyCode::F22, + 193 => KeyCode::F23, + 194 => KeyCode::F24, + // 200 => KeyCode::PLAYCD, + // 201 => KeyCode::PAUSECD, + // 202 => KeyCode::PROG3, + // 203 => KeyCode::PROG4, + // 204 => KeyCode::DASHBOARD, + // 205 => KeyCode::SUSPEND, + // 206 => KeyCode::CLOSE, + // 207 => KeyCode::PLAY, + // 208 => KeyCode::FASTFORWARD, + // 209 => KeyCode::BASSBOOST, + // 210 => KeyCode::PRINT, + // 211 => KeyCode::HP, + // 212 => KeyCode::CAMERA, + // 213 => KeyCode::SOUND, + // 214 => KeyCode::QUESTION, + // 215 => KeyCode::EMAIL, + // 216 => KeyCode::CHAT, + // 217 => KeyCode::SEARCH, + // 218 => KeyCode::CONNECT, + // 219 => KeyCode::FINANCE, + // 220 => KeyCode::SPORT, + // 221 => KeyCode::SHOP, + // 222 => KeyCode::ALTERASE, + // 223 => KeyCode::CANCEL, + // 224 => KeyCode::BRIGHTNESSDOW, + // 225 => KeyCode::BRIGHTNESSU, + // 226 => KeyCode::MEDIA, + // 227 => KeyCode::SWITCHVIDEOMODE, + // 228 => KeyCode::KBDILLUMTOGGLE, + // 229 => KeyCode::KBDILLUMDOWN, + // 230 => KeyCode::KBDILLUMUP, + // 231 => KeyCode::SEND, + // 232 => KeyCode::REPLY, + // 233 => KeyCode::FORWARDMAIL, + // 234 => KeyCode::SAVE, + // 235 => KeyCode::DOCUMENTS, + // 236 => KeyCode::BATTERY, + // 237 => KeyCode::BLUETOOTH, + // 238 => KeyCode::WLAN, + // 239 => KeyCode::UWB, + 240 => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified), + // 241 => KeyCode::VIDEO_NEXT, + // 242 => KeyCode::VIDEO_PREV, + // 243 => KeyCode::BRIGHTNESS_CYCLE, + // 244 => KeyCode::BRIGHTNESS_AUTO, + // 245 => KeyCode::DISPLAY_OFF, + // 246 => KeyCode::WWAN, + // 247 => KeyCode::RFKILL, + // 248 => KeyCode::KEY_MICMUTE, + _ => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(scancode)), + }) +} + +pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option { + let code = match key { + PhysicalKey::Code(code) => code, + PhysicalKey::Unidentified(code) => { + return match code { + NativeKeyCode::Unidentified => Some(240), + NativeKeyCode::Xkb(raw) => Some(raw), + _ => None, + }; + } + }; + + match code { + KeyCode::Escape => Some(1), + KeyCode::Digit1 => Some(2), + KeyCode::Digit2 => Some(3), + KeyCode::Digit3 => Some(4), + KeyCode::Digit4 => Some(5), + KeyCode::Digit5 => Some(6), + KeyCode::Digit6 => Some(7), + KeyCode::Digit7 => Some(8), + KeyCode::Digit8 => Some(9), + KeyCode::Digit9 => Some(10), + KeyCode::Digit0 => Some(11), + KeyCode::Minus => Some(12), + KeyCode::Equal => Some(13), + KeyCode::Backspace => Some(14), + KeyCode::Tab => Some(15), + KeyCode::KeyQ => Some(16), + KeyCode::KeyW => Some(17), + KeyCode::KeyE => Some(18), + KeyCode::KeyR => Some(19), + KeyCode::KeyT => Some(20), + KeyCode::KeyY => Some(21), + KeyCode::KeyU => Some(22), + KeyCode::KeyI => Some(23), + KeyCode::KeyO => Some(24), + KeyCode::KeyP => Some(25), + KeyCode::BracketLeft => Some(26), + KeyCode::BracketRight => Some(27), + KeyCode::Enter => Some(28), + KeyCode::ControlLeft => Some(29), + KeyCode::KeyA => Some(30), + KeyCode::KeyS => Some(31), + KeyCode::KeyD => Some(32), + KeyCode::KeyF => Some(33), + KeyCode::KeyG => Some(34), + KeyCode::KeyH => Some(35), + KeyCode::KeyJ => Some(36), + KeyCode::KeyK => Some(37), + KeyCode::KeyL => Some(38), + KeyCode::Semicolon => Some(39), + KeyCode::Quote => Some(40), + KeyCode::Backquote => Some(41), + KeyCode::ShiftLeft => Some(42), + KeyCode::Backslash => Some(43), + KeyCode::KeyZ => Some(44), + KeyCode::KeyX => Some(45), + KeyCode::KeyC => Some(46), + KeyCode::KeyV => Some(47), + KeyCode::KeyB => Some(48), + KeyCode::KeyN => Some(49), + KeyCode::KeyM => Some(50), + KeyCode::Comma => Some(51), + KeyCode::Period => Some(52), + KeyCode::Slash => Some(53), + KeyCode::ShiftRight => Some(54), + KeyCode::NumpadMultiply => Some(55), + KeyCode::AltLeft => Some(56), + KeyCode::Space => Some(57), + KeyCode::CapsLock => Some(58), + KeyCode::F1 => Some(59), + KeyCode::F2 => Some(60), + KeyCode::F3 => Some(61), + KeyCode::F4 => Some(62), + KeyCode::F5 => Some(63), + KeyCode::F6 => Some(64), + KeyCode::F7 => Some(65), + KeyCode::F8 => Some(66), + KeyCode::F9 => Some(67), + KeyCode::F10 => Some(68), + KeyCode::NumLock => Some(69), + KeyCode::ScrollLock => Some(70), + KeyCode::Numpad7 => Some(71), + KeyCode::Numpad8 => Some(72), + KeyCode::Numpad9 => Some(73), + KeyCode::NumpadSubtract => Some(74), + KeyCode::Numpad4 => Some(75), + KeyCode::Numpad5 => Some(76), + KeyCode::Numpad6 => Some(77), + KeyCode::NumpadAdd => Some(78), + KeyCode::Numpad1 => Some(79), + KeyCode::Numpad2 => Some(80), + KeyCode::Numpad3 => Some(81), + KeyCode::Numpad0 => Some(82), + KeyCode::NumpadDecimal => Some(83), + KeyCode::Lang5 => Some(85), + KeyCode::IntlBackslash => Some(86), + KeyCode::F11 => Some(87), + KeyCode::F12 => Some(88), + KeyCode::IntlRo => Some(89), + KeyCode::Lang3 => Some(90), + KeyCode::Lang4 => Some(91), + KeyCode::Convert => Some(92), + KeyCode::KanaMode => Some(93), + KeyCode::NonConvert => Some(94), + KeyCode::NumpadEnter => Some(96), + KeyCode::ControlRight => Some(97), + KeyCode::NumpadDivide => Some(98), + KeyCode::PrintScreen => Some(99), + KeyCode::AltRight => Some(100), + KeyCode::Home => Some(102), + KeyCode::ArrowUp => Some(103), + KeyCode::PageUp => Some(104), + KeyCode::ArrowLeft => Some(105), + KeyCode::ArrowRight => Some(106), + KeyCode::End => Some(107), + KeyCode::ArrowDown => Some(108), + KeyCode::PageDown => Some(109), + KeyCode::Insert => Some(110), + KeyCode::Delete => Some(111), + KeyCode::AudioVolumeMute => Some(113), + KeyCode::AudioVolumeDown => Some(114), + KeyCode::AudioVolumeUp => Some(115), + KeyCode::NumpadEqual => Some(117), + KeyCode::Pause => Some(119), + KeyCode::NumpadComma => Some(121), + KeyCode::Lang1 => Some(122), + KeyCode::Lang2 => Some(123), + KeyCode::IntlYen => Some(124), + KeyCode::SuperLeft => Some(125), + KeyCode::SuperRight => Some(126), + KeyCode::ContextMenu => Some(127), + KeyCode::MediaTrackNext => Some(163), + KeyCode::MediaPlayPause => Some(164), + KeyCode::MediaTrackPrevious => Some(165), + KeyCode::MediaStop => Some(166), + KeyCode::F13 => Some(183), + KeyCode::F14 => Some(184), + KeyCode::F15 => Some(185), + KeyCode::F16 => Some(186), + KeyCode::F17 => Some(187), + KeyCode::F18 => Some(188), + KeyCode::F19 => Some(189), + KeyCode::F20 => Some(190), + KeyCode::F21 => Some(191), + KeyCode::F22 => Some(192), + KeyCode::F23 => Some(193), + KeyCode::F24 => Some(194), + _ => None, + } +} + +pub fn keysym_to_key(keysym: u32) -> Key { + use xkbcommon_dl::keysyms; + Key::Named(match keysym { + // TTY function keys + keysyms::BackSpace => Named::Backspace, + keysyms::Tab => Named::Tab, + // keysyms::Linefeed => Named::Linefeed, + keysyms::Clear => Named::Clear, + keysyms::Return => Named::Enter, + keysyms::Pause => Named::Pause, + keysyms::Scroll_Lock => Named::ScrollLock, + keysyms::Sys_Req => Named::PrintScreen, + keysyms::Escape => Named::Escape, + keysyms::Delete => Named::Delete, + + // IME keys + keysyms::Multi_key => Named::Compose, + keysyms::Codeinput => Named::CodeInput, + keysyms::SingleCandidate => Named::SingleCandidate, + keysyms::MultipleCandidate => Named::AllCandidates, + keysyms::PreviousCandidate => Named::PreviousCandidate, + + // Japanese key + keysyms::Kanji => Named::KanjiMode, + keysyms::Muhenkan => Named::NonConvert, + keysyms::Henkan_Mode => Named::Convert, + keysyms::Romaji => Named::Romaji, + keysyms::Hiragana => Named::Hiragana, + keysyms::Hiragana_Katakana => Named::HiraganaKatakana, + keysyms::Zenkaku => Named::Zenkaku, + keysyms::Hankaku => Named::Hankaku, + keysyms::Zenkaku_Hankaku => Named::ZenkakuHankaku, + // keysyms::Touroku => Named::Touroku, + // keysyms::Massyo => Named::Massyo, + keysyms::Kana_Lock => Named::KanaMode, + keysyms::Kana_Shift => Named::KanaMode, + keysyms::Eisu_Shift => Named::Alphanumeric, + keysyms::Eisu_toggle => Named::Alphanumeric, + // NOTE: The next three items are aliases for values we've already mapped. + // keysyms::Kanji_Bangou => Named::CodeInput, + // keysyms::Zen_Koho => Named::AllCandidates, + // keysyms::Mae_Koho => Named::PreviousCandidate, + + // Cursor control & motion + keysyms::Home => Named::Home, + keysyms::Left => Named::ArrowLeft, + keysyms::Up => Named::ArrowUp, + keysyms::Right => Named::ArrowRight, + keysyms::Down => Named::ArrowDown, + // keysyms::Prior => Named::PageUp, + keysyms::Page_Up => Named::PageUp, + // keysyms::Next => Named::PageDown, + keysyms::Page_Down => Named::PageDown, + keysyms::End => Named::End, + // keysyms::Begin => Named::Begin, + + // Misc. functions + keysyms::Select => Named::Select, + keysyms::Print => Named::PrintScreen, + keysyms::Execute => Named::Execute, + keysyms::Insert => Named::Insert, + keysyms::Undo => Named::Undo, + keysyms::Redo => Named::Redo, + keysyms::Menu => Named::ContextMenu, + keysyms::Find => Named::Find, + keysyms::Cancel => Named::Cancel, + keysyms::Help => Named::Help, + keysyms::Break => Named::Pause, + keysyms::Mode_switch => Named::ModeChange, + // keysyms::script_switch => Named::ModeChange, + keysyms::Num_Lock => Named::NumLock, + + // Keypad keys + // keysyms::KP_Space => return Key::Character(" "), + keysyms::KP_Tab => Named::Tab, + keysyms::KP_Enter => Named::Enter, + keysyms::KP_F1 => Named::F1, + keysyms::KP_F2 => Named::F2, + keysyms::KP_F3 => Named::F3, + keysyms::KP_F4 => Named::F4, + keysyms::KP_Home => Named::Home, + keysyms::KP_Left => Named::ArrowLeft, + keysyms::KP_Up => Named::ArrowUp, + keysyms::KP_Right => Named::ArrowRight, + keysyms::KP_Down => Named::ArrowDown, + // keysyms::KP_Prior => Named::PageUp, + keysyms::KP_Page_Up => Named::PageUp, + // keysyms::KP_Next => Named::PageDown, + keysyms::KP_Page_Down => Named::PageDown, + keysyms::KP_End => Named::End, + // This is the key labeled "5" on the numpad when NumLock is off. + // keysyms::KP_Begin => Named::Begin, + keysyms::KP_Insert => Named::Insert, + keysyms::KP_Delete => Named::Delete, + // keysyms::KP_Equal => Named::Equal, + // keysyms::KP_Multiply => Named::Multiply, + // keysyms::KP_Add => Named::Add, + // keysyms::KP_Separator => Named::Separator, + // keysyms::KP_Subtract => Named::Subtract, + // keysyms::KP_Decimal => Named::Decimal, + // keysyms::KP_Divide => Named::Divide, + + // keysyms::KP_0 => return Key::Character("0"), + // keysyms::KP_1 => return Key::Character("1"), + // keysyms::KP_2 => return Key::Character("2"), + // keysyms::KP_3 => return Key::Character("3"), + // keysyms::KP_4 => return Key::Character("4"), + // keysyms::KP_5 => return Key::Character("5"), + // keysyms::KP_6 => return Key::Character("6"), + // keysyms::KP_7 => return Key::Character("7"), + // keysyms::KP_8 => return Key::Character("8"), + // keysyms::KP_9 => return Key::Character("9"), + + // Function keys + keysyms::F1 => Named::F1, + keysyms::F2 => Named::F2, + keysyms::F3 => Named::F3, + keysyms::F4 => Named::F4, + keysyms::F5 => Named::F5, + keysyms::F6 => Named::F6, + keysyms::F7 => Named::F7, + keysyms::F8 => Named::F8, + keysyms::F9 => Named::F9, + keysyms::F10 => Named::F10, + keysyms::F11 => Named::F11, + keysyms::F12 => Named::F12, + keysyms::F13 => Named::F13, + keysyms::F14 => Named::F14, + keysyms::F15 => Named::F15, + keysyms::F16 => Named::F16, + keysyms::F17 => Named::F17, + keysyms::F18 => Named::F18, + keysyms::F19 => Named::F19, + keysyms::F20 => Named::F20, + keysyms::F21 => Named::F21, + keysyms::F22 => Named::F22, + keysyms::F23 => Named::F23, + keysyms::F24 => Named::F24, + keysyms::F25 => Named::F25, + keysyms::F26 => Named::F26, + keysyms::F27 => Named::F27, + keysyms::F28 => Named::F28, + keysyms::F29 => Named::F29, + keysyms::F30 => Named::F30, + keysyms::F31 => Named::F31, + keysyms::F32 => Named::F32, + keysyms::F33 => Named::F33, + keysyms::F34 => Named::F34, + keysyms::F35 => Named::F35, + + // Modifiers + keysyms::Shift_L => Named::Shift, + keysyms::Shift_R => Named::Shift, + keysyms::Control_L => Named::Control, + keysyms::Control_R => Named::Control, + keysyms::Caps_Lock => Named::CapsLock, + // keysyms::Shift_Lock => Named::ShiftLock, + + // keysyms::Meta_L => Named::Meta, + // keysyms::Meta_R => Named::Meta, + keysyms::Alt_L => Named::Alt, + keysyms::Alt_R => Named::Alt, + keysyms::Super_L => Named::Super, + keysyms::Super_R => Named::Super, + keysyms::Hyper_L => Named::Hyper, + keysyms::Hyper_R => Named::Hyper, + + // XKB function and modifier keys + // keysyms::ISO_Lock => Named::IsoLock, + // keysyms::ISO_Level2_Latch => Named::IsoLevel2Latch, + keysyms::ISO_Level3_Shift => Named::AltGraph, + keysyms::ISO_Level3_Latch => Named::AltGraph, + keysyms::ISO_Level3_Lock => Named::AltGraph, + // keysyms::ISO_Level5_Shift => Named::IsoLevel5Shift, + // keysyms::ISO_Level5_Latch => Named::IsoLevel5Latch, + // keysyms::ISO_Level5_Lock => Named::IsoLevel5Lock, + // keysyms::ISO_Group_Shift => Named::IsoGroupShift, + // keysyms::ISO_Group_Latch => Named::IsoGroupLatch, + // keysyms::ISO_Group_Lock => Named::IsoGroupLock, + keysyms::ISO_Next_Group => Named::GroupNext, + // keysyms::ISO_Next_Group_Lock => Named::GroupNextLock, + keysyms::ISO_Prev_Group => Named::GroupPrevious, + // keysyms::ISO_Prev_Group_Lock => Named::GroupPreviousLock, + keysyms::ISO_First_Group => Named::GroupFirst, + // keysyms::ISO_First_Group_Lock => Named::GroupFirstLock, + keysyms::ISO_Last_Group => Named::GroupLast, + // keysyms::ISO_Last_Group_Lock => Named::GroupLastLock, + // + keysyms::ISO_Left_Tab => Named::Tab, + // keysyms::ISO_Move_Line_Up => Named::IsoMoveLineUp, + // keysyms::ISO_Move_Line_Down => Named::IsoMoveLineDown, + // keysyms::ISO_Partial_Line_Up => Named::IsoPartialLineUp, + // keysyms::ISO_Partial_Line_Down => Named::IsoPartialLineDown, + // keysyms::ISO_Partial_Space_Left => Named::IsoPartialSpaceLeft, + // keysyms::ISO_Partial_Space_Right => Named::IsoPartialSpaceRight, + // keysyms::ISO_Set_Margin_Left => Named::IsoSetMarginLeft, + // keysyms::ISO_Set_Margin_Right => Named::IsoSetMarginRight, + // keysyms::ISO_Release_Margin_Left => Named::IsoReleaseMarginLeft, + // keysyms::ISO_Release_Margin_Right => Named::IsoReleaseMarginRight, + // keysyms::ISO_Release_Both_Margins => Named::IsoReleaseBothMargins, + // keysyms::ISO_Fast_Cursor_Left => Named::IsoFastCursorLeft, + // keysyms::ISO_Fast_Cursor_Right => Named::IsoFastCursorRight, + // keysyms::ISO_Fast_Cursor_Up => Named::IsoFastCursorUp, + // keysyms::ISO_Fast_Cursor_Down => Named::IsoFastCursorDown, + // keysyms::ISO_Continuous_Underline => Named::IsoContinuousUnderline, + // keysyms::ISO_Discontinuous_Underline => Named::IsoDiscontinuousUnderline, + // keysyms::ISO_Emphasize => Named::IsoEmphasize, + // keysyms::ISO_Center_Object => Named::IsoCenterObject, + keysyms::ISO_Enter => Named::Enter, + + // dead_grave..dead_currency + + // dead_lowline..dead_longsolidusoverlay + + // dead_a..dead_capital_schwa + + // dead_greek + + // First_Virtual_Screen..Terminate_Server + + // AccessX_Enable..AudibleBell_Enable + + // Pointer_Left..Pointer_Drag5 + + // Pointer_EnableKeys..Pointer_DfltBtnPrev + + // ch..C_H + + // 3270 terminal keys + // keysyms::3270_Duplicate => Named::Duplicate, + // keysyms::3270_FieldMark => Named::FieldMark, + // keysyms::3270_Right2 => Named::Right2, + // keysyms::3270_Left2 => Named::Left2, + // keysyms::3270_BackTab => Named::BackTab, + keysyms::_3270_EraseEOF => Named::EraseEof, + // keysyms::3270_EraseInput => Named::EraseInput, + // keysyms::3270_Reset => Named::Reset, + // keysyms::3270_Quit => Named::Quit, + // keysyms::3270_PA1 => Named::Pa1, + // keysyms::3270_PA2 => Named::Pa2, + // keysyms::3270_PA3 => Named::Pa3, + // keysyms::3270_Test => Named::Test, + keysyms::_3270_Attn => Named::Attn, + // keysyms::3270_CursorBlink => Named::CursorBlink, + // keysyms::3270_AltCursor => Named::AltCursor, + // keysyms::3270_KeyClick => Named::KeyClick, + // keysyms::3270_Jump => Named::Jump, + // keysyms::3270_Ident => Named::Ident, + // keysyms::3270_Rule => Named::Rule, + // keysyms::3270_Copy => Named::Copy, + keysyms::_3270_Play => Named::Play, + // keysyms::3270_Setup => Named::Setup, + // keysyms::3270_Record => Named::Record, + // keysyms::3270_ChangeScreen => Named::ChangeScreen, + // keysyms::3270_DeleteWord => Named::DeleteWord, + keysyms::_3270_ExSelect => Named::ExSel, + keysyms::_3270_CursorSelect => Named::CrSel, + keysyms::_3270_PrintScreen => Named::PrintScreen, + keysyms::_3270_Enter => Named::Enter, + + keysyms::space => Named::Space, + // exclam..Sinh_kunddaliya + + // XFree86 + // keysyms::XF86_ModeLock => Named::ModeLock, + + // XFree86 - Backlight controls + keysyms::XF86_MonBrightnessUp => Named::BrightnessUp, + keysyms::XF86_MonBrightnessDown => Named::BrightnessDown, + // keysyms::XF86_KbdLightOnOff => Named::LightOnOff, + // keysyms::XF86_KbdBrightnessUp => Named::KeyboardBrightnessUp, + // keysyms::XF86_KbdBrightnessDown => Named::KeyboardBrightnessDown, + + // XFree86 - "Internet" + keysyms::XF86_Standby => Named::Standby, + keysyms::XF86_AudioLowerVolume => Named::AudioVolumeDown, + keysyms::XF86_AudioRaiseVolume => Named::AudioVolumeUp, + keysyms::XF86_AudioPlay => Named::MediaPlay, + keysyms::XF86_AudioStop => Named::MediaStop, + keysyms::XF86_AudioPrev => Named::MediaTrackPrevious, + keysyms::XF86_AudioNext => Named::MediaTrackNext, + keysyms::XF86_HomePage => Named::BrowserHome, + keysyms::XF86_Mail => Named::LaunchMail, + // keysyms::XF86_Start => Named::Start, + keysyms::XF86_Search => Named::BrowserSearch, + keysyms::XF86_AudioRecord => Named::MediaRecord, + + // XFree86 - PDA + keysyms::XF86_Calculator => Named::LaunchApplication2, + // keysyms::XF86_Memo => Named::Memo, + // keysyms::XF86_ToDoList => Named::ToDoList, + keysyms::XF86_Calendar => Named::LaunchCalendar, + keysyms::XF86_PowerDown => Named::Power, + // keysyms::XF86_ContrastAdjust => Named::AdjustContrast, + // keysyms::XF86_RockerUp => Named::RockerUp, + // keysyms::XF86_RockerDown => Named::RockerDown, + // keysyms::XF86_RockerEnter => Named::RockerEnter, + + // XFree86 - More "Internet" + keysyms::XF86_Back => Named::BrowserBack, + keysyms::XF86_Forward => Named::BrowserForward, + // keysyms::XF86_Stop => Named::Stop, + keysyms::XF86_Refresh => Named::BrowserRefresh, + keysyms::XF86_PowerOff => Named::Power, + keysyms::XF86_WakeUp => Named::WakeUp, + keysyms::XF86_Eject => Named::Eject, + keysyms::XF86_ScreenSaver => Named::LaunchScreenSaver, + keysyms::XF86_WWW => Named::LaunchWebBrowser, + keysyms::XF86_Sleep => Named::Standby, + keysyms::XF86_Favorites => Named::BrowserFavorites, + keysyms::XF86_AudioPause => Named::MediaPause, + // keysyms::XF86_AudioMedia => Named::AudioMedia, + keysyms::XF86_MyComputer => Named::LaunchApplication1, + // keysyms::XF86_VendorHome => Named::VendorHome, + // keysyms::XF86_LightBulb => Named::LightBulb, + // keysyms::XF86_Shop => Named::BrowserShop, + // keysyms::XF86_History => Named::BrowserHistory, + // keysyms::XF86_OpenURL => Named::OpenUrl, + // keysyms::XF86_AddFavorite => Named::AddFavorite, + // keysyms::XF86_HotLinks => Named::HotLinks, + // keysyms::XF86_BrightnessAdjust => Named::BrightnessAdjust, + // keysyms::XF86_Finance => Named::BrowserFinance, + // keysyms::XF86_Community => Named::BrowserCommunity, + keysyms::XF86_AudioRewind => Named::MediaRewind, + // keysyms::XF86_BackForward => Key::???, + // XF86_Launch0..XF86_LaunchF + + // XF86_ApplicationLeft..XF86_CD + keysyms::XF86_Calculater => Named::LaunchApplication2, // Nice typo, libxkbcommon :) + // XF86_Clear + keysyms::XF86_Close => Named::Close, + keysyms::XF86_Copy => Named::Copy, + keysyms::XF86_Cut => Named::Cut, + // XF86_Display..XF86_Documents + keysyms::XF86_Excel => Named::LaunchSpreadsheet, + // XF86_Explorer..XF86iTouch + keysyms::XF86_LogOff => Named::LogOff, + // XF86_Market..XF86_MenuPB + keysyms::XF86_MySites => Named::BrowserFavorites, + keysyms::XF86_New => Named::New, + // XF86_News..XF86_OfficeHome + keysyms::XF86_Open => Named::Open, + // XF86_Option + keysyms::XF86_Paste => Named::Paste, + keysyms::XF86_Phone => Named::LaunchPhone, + // XF86_Q + keysyms::XF86_Reply => Named::MailReply, + keysyms::XF86_Reload => Named::BrowserRefresh, + // XF86_RotateWindows..XF86_RotationKB + keysyms::XF86_Save => Named::Save, + // XF86_ScrollUp..XF86_ScrollClick + keysyms::XF86_Send => Named::MailSend, + keysyms::XF86_Spell => Named::SpellCheck, + keysyms::XF86_SplitScreen => Named::SplitScreenToggle, + // XF86_Support..XF86_User2KB + keysyms::XF86_Video => Named::LaunchMediaPlayer, + // XF86_WheelButton + keysyms::XF86_Word => Named::LaunchWordProcessor, + // XF86_Xfer + keysyms::XF86_ZoomIn => Named::ZoomIn, + keysyms::XF86_ZoomOut => Named::ZoomOut, + + // XF86_Away..XF86_Messenger + keysyms::XF86_WebCam => Named::LaunchWebCam, + keysyms::XF86_MailForward => Named::MailForward, + // XF86_Pictures + keysyms::XF86_Music => Named::LaunchMusicPlayer, + + // XF86_Battery..XF86_UWB + // + keysyms::XF86_AudioForward => Named::MediaFastForward, + // XF86_AudioRepeat + keysyms::XF86_AudioRandomPlay => Named::RandomToggle, + keysyms::XF86_Subtitle => Named::Subtitle, + keysyms::XF86_AudioCycleTrack => Named::MediaAudioTrack, + // XF86_CycleAngle..XF86_Blue + // + keysyms::XF86_Suspend => Named::Standby, + keysyms::XF86_Hibernate => Named::Hibernate, + // XF86_TouchpadToggle..XF86_TouchpadOff + // + keysyms::XF86_AudioMute => Named::AudioVolumeMute, + + // XF86_Switch_VT_1..XF86_Switch_VT_12 + + // XF86_Ungrab..XF86_ClearGrab + keysyms::XF86_Next_VMode => Named::VideoModeNext, + // keysyms::XF86_Prev_VMode => Named::VideoModePrevious, + // XF86_LogWindowTree..XF86_LogGrabInfo + + // SunFA_Grave..SunFA_Cedilla + + // keysyms::SunF36 => Named::F36 | Named::F11, + // keysyms::SunF37 => Named::F37 | Named::F12, + + // keysyms::SunSys_Req => Named::PrintScreen, + // The next couple of xkb (until SunStop) are already handled. + // SunPrint_Screen..SunPageDown + + // SunUndo..SunFront + keysyms::SUN_Copy => Named::Copy, + keysyms::SUN_Open => Named::Open, + keysyms::SUN_Paste => Named::Paste, + keysyms::SUN_Cut => Named::Cut, + + // SunPowerSwitch + keysyms::SUN_AudioLowerVolume => Named::AudioVolumeDown, + keysyms::SUN_AudioMute => Named::AudioVolumeMute, + keysyms::SUN_AudioRaiseVolume => Named::AudioVolumeUp, + // SUN_VideoDegauss + keysyms::SUN_VideoLowerBrightness => Named::BrightnessDown, + keysyms::SUN_VideoRaiseBrightness => Named::BrightnessUp, + // SunPowerSwitchShift + // + _ => return Key::Unidentified, + }) +} + +use iced_runtime::keyboard::{key::Named, Key, Location}; + +pub fn keysym_location(keysym: u32) -> Location { + use xkbcommon_dl::keysyms; + match keysym { + xkeysym::key::Shift_L + | keysyms::Control_L + | keysyms::Meta_L + | keysyms::Alt_L + | keysyms::Super_L + | keysyms::Hyper_L => Location::Left, + keysyms::Shift_R + | keysyms::Control_R + | keysyms::Meta_R + | keysyms::Alt_R + | keysyms::Super_R + | keysyms::Hyper_R => Location::Right, + keysyms::KP_0 + | keysyms::KP_1 + | keysyms::KP_2 + | keysyms::KP_3 + | keysyms::KP_4 + | keysyms::KP_5 + | keysyms::KP_6 + | keysyms::KP_7 + | keysyms::KP_8 + | keysyms::KP_9 + | keysyms::KP_Space + | keysyms::KP_Tab + | keysyms::KP_Enter + | keysyms::KP_F1 + | keysyms::KP_F2 + | keysyms::KP_F3 + | keysyms::KP_F4 + | keysyms::KP_Home + | keysyms::KP_Left + | keysyms::KP_Up + | keysyms::KP_Right + | keysyms::KP_Down + | keysyms::KP_Page_Up + | keysyms::KP_Page_Down + | keysyms::KP_End + | keysyms::KP_Begin + | keysyms::KP_Insert + | keysyms::KP_Delete + | keysyms::KP_Equal + | keysyms::KP_Multiply + | keysyms::KP_Add + | keysyms::KP_Separator + | keysyms::KP_Subtract + | keysyms::KP_Decimal + | keysyms::KP_Divide => Location::Numpad, + _ => Location::Standard, + } +} diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs new file mode 100644 index 0000000000..7fbd07b4d8 --- /dev/null +++ b/winit/src/platform_specific/wayland/mod.rs @@ -0,0 +1,211 @@ +pub mod commands; +pub mod conversion; +pub(crate) mod event_loop; +pub(crate) mod handlers; +pub mod keymap; +pub mod sctk_event; +pub mod subsurface_widget; +pub mod winit_window; + +use super::{PlatformSpecific, SurfaceIdWrapper}; +use crate::program::{Control, Program, WindowManager}; + +use iced_futures::futures::channel::mpsc; +use iced_graphics::Compositor; +use iced_runtime::core::window; +use iced_runtime::Debug; +use sctk::reexports::calloop; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::seat::keyboard::Modifiers; +use sctk_event::SctkEvent; +use sctk_event::UserInterfaces; +use std::{collections::HashMap, sync::Arc}; +use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; +use wayland_backend::client::ObjectId; +use winit::event_loop::OwnedDisplayHandle; +use winit::window::CursorIcon; + +pub(crate) enum Action { + Action(iced_runtime::platform_specific::wayland::Action), + SetCursor(CursorIcon), + RequestRedraw(ObjectId), + PrePresentNotify(ObjectId), + TrackWindow(Arc, window::Id), + RemoveWindow(window::Id), + Dropped(SurfaceIdWrapper), + Ready, +} + +impl std::fmt::Debug for Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Action(arg0) => f.debug_tuple("Action").field(arg0).finish(), + Self::SetCursor(arg0) => { + f.debug_tuple("SetCursor").field(arg0).finish() + } + Self::RequestRedraw(arg0) => { + f.debug_tuple("RequestRedraw").field(arg0).finish() + } + Self::PrePresentNotify(arg0) => { + f.debug_tuple("PrePresentNotify").field(arg0).finish() + } + Self::TrackWindow(_arg0, arg1) => { + f.debug_tuple("TrackWindow").field(arg1).finish() + } + Self::RemoveWindow(arg0) => { + f.debug_tuple("RemoveWindow").field(arg0).finish() + } + Self::Ready => write!(f, "Ready"), + Self::Dropped(_surface_id_wrapper) => write!(f, "Dropped"), + } + } +} + +#[derive(Debug, Default)] +pub(crate) struct WaylandSpecific { + winit_event_sender: Option>, + proxy: Option, + sender: Option>, + display_handle: Option, + modifiers: Modifiers, + surface_ids: HashMap, + destroyed_surface_ids: HashMap, + subsurface_ids: HashMap, + subsurface_state: Option, + surface_subsurfaces: HashMap>, +} + +impl PlatformSpecific { + pub(crate) fn with_wayland( + mut self, + tx: mpsc::UnboundedSender, + raw: winit::event_loop::EventLoopProxy, + display: OwnedDisplayHandle, + ) -> Self { + self.wayland.winit_event_sender = Some(tx); + self.wayland.display_handle = Some(display); + self.wayland.proxy = Some(raw); + // TODO remove this + self.wayland.sender = + crate::platform_specific::event_loop::SctkEventLoop::new( + self.wayland.winit_event_sender.clone().unwrap(), + self.wayland.proxy.clone().unwrap(), + self.wayland.display_handle.clone().unwrap(), + ) + .ok(); + self + } + + pub(crate) fn send_wayland(&mut self, action: Action) { + if self.wayland.sender.is_none() + && self.wayland.winit_event_sender.is_some() + && self.wayland.display_handle.is_some() + && self.wayland.proxy.is_some() + { + self.wayland.sender = + crate::platform_specific::event_loop::SctkEventLoop::new( + self.wayland.winit_event_sender.clone().unwrap(), + self.wayland.proxy.clone().unwrap(), + self.wayland.display_handle.clone().unwrap(), + ) + .ok(); + } + + if let Some(tx) = self.wayland.sender.as_ref() { + _ = tx.send(action); + } else { + log::error!("Failed to process wayland Action."); + } + } +} + +impl WaylandSpecific { + pub(crate) fn handle_event<'a, P, C>( + &mut self, + e: SctkEvent, + events: &mut Vec<(Option, iced_runtime::core::Event)>, + program: &'a P, + compositor: &mut C, + window_manager: &mut WindowManager, + debug: &mut Debug, + user_interfaces: &mut UserInterfaces<'a, P>, + clipboard: &mut crate::Clipboard, + #[cfg(feature = "a11y")] adapters: &mut HashMap< + window::Id, + (u64, iced_accessibility::accesskit_winit::Adapter), + >, + ) where + P: Program, + C: Compositor, + { + let Self { + winit_event_sender, + proxy, + sender, + display_handle, + surface_ids, + destroyed_surface_ids, + subsurface_ids, + modifiers, + subsurface_state, + surface_subsurfaces, + } = self; + + match e { + sctk_event => { + let Some(sender) = sender.as_ref() else { + log::error!("Missing calloop sender"); + return Default::default(); + }; + let Some(event_sender) = winit_event_sender.as_ref() else { + log::error!("Missing control sender"); + return Default::default(); + }; + let Some(proxy) = proxy.as_ref() else { + log::error!("Missing event loop proxy"); + return Default::default(); + }; + + sctk_event.process( + modifiers, + program, + compositor, + window_manager, + surface_ids, + subsurface_ids, + sender, + event_sender, + proxy, + debug, + user_interfaces, + events, + clipboard, + subsurface_state, + #[cfg(feature = "a11y")] + adapters, + ); + } + }; + } + + pub(crate) fn update_subsurfaces( + &mut self, + id: window::Id, + wl_surface: &WlSurface, + ) { + let subsurfaces = crate::subsurface_widget::take_subsurfaces(); + let mut entry = self.surface_subsurfaces.entry(id); + let surface_subsurfaces = entry.or_default(); + let Some(subsurface_state) = self.subsurface_state.as_mut() else { + return; + }; + + subsurface_state.update_subsurfaces( + id, + &mut self.subsurface_ids, + wl_surface, + surface_subsurfaces, + &subsurfaces, + ); + } +} diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs new file mode 100755 index 0000000000..a7939f48c9 --- /dev/null +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -0,0 +1,1210 @@ +use crate::{ + platform_specific::{ + wayland::{ + conversion::{ + modifiers_to_native, pointer_axis_to_native, + pointer_button_to_native, + }, + keymap::{self, keysym_to_key}, + subsurface_widget::SubsurfaceState, + }, + SurfaceIdWrapper, + }, + program::{Control, Program}, + Clipboard, +}; + +use dnd::DndSurface; +use iced_futures::{ + core::{ + event::{ + wayland::{LayerEvent, PopupEvent, SessionLockEvent}, + PlatformSpecific, + }, + Clipboard as _, + }, + futures::channel::mpsc, +}; +use iced_graphics::Compositor; +use iced_runtime::{ + core::{ + event::wayland, + keyboard, mouse, touch, + window::{self, Id as SurfaceId}, + Point, + }, + keyboard::{key, Key, Location}, + user_interface, Debug, +}; + +use sctk::{ + output::OutputInfo, + reexports::{ + calloop::channel, + client::{ + backend::ObjectId, + protocol::{ + wl_display::WlDisplay, wl_keyboard::WlKeyboard, + wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat, + wl_surface::WlSurface, wl_touch::WlTouch, + }, + Proxy, QueueHandle, + }, + csd_frame::WindowManagerCapabilities, + }, + seat::{ + keyboard::{KeyEvent, Modifiers}, + pointer::{PointerEvent, PointerEventKind}, + Capability, + }, + session_lock::SessionLockSurfaceConfigure, + shell::{ + wlr_layer::LayerSurfaceConfigure, + xdg::{popup::PopupConfigure, window::WindowConfigure}, + }, +}; +use std::{ + collections::HashMap, + num::NonZeroU32, + sync::{Arc, Mutex}, + time::Instant, +}; +use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport; +use winit::{ + dpi::PhysicalSize, event::WindowEvent, event_loop::EventLoopProxy, + window::WindowId, +}; +use xkeysym::Keysym; + +use super::{ + event_loop::state::{Common, CommonSurface, SctkState}, + keymap::raw_keycode_to_physicalkey, + winit_window::SctkWinitWindow, +}; + +pub enum IcedSctkEvent { + /// Emitted when new events arrive from the OS to be processed. + /// + /// This event type is useful as a place to put code that should be done before you start + /// processing events, such as updating frame timing information for benchmarking or checking + /// the [`StartCause`][crate::event::StartCause] to see if a timer set by + /// [`ControlFlow::WaitUntil`](crate::platform_specific::wayland::event_loop::ControlFlow::WaitUntil) has elapsed. + NewEvents(StartCause), + + /// An event produced by sctk + SctkEvent(SctkEvent), + + /// Emitted when all of the event loop's input events have been processed and redraw processing + /// is about to begin. + /// + /// This event is useful as a place to put your code that should be run after all + /// state-changing events have been handled and you want to do stuff (updating state, performing + /// calculations, etc) that happens as the "main body" of your event loop. If your program only draws + /// graphics when something changes, it's usually better to do it in response to + /// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted + /// immediately after this event. Programs that draw graphics continuously, like most games, + /// can render here unconditionally for simplicity. + MainEventsCleared, + + /// Emitted after [`MainEventsCleared`] when a window should be redrawn. + /// + /// This gets triggered in two scenarios: + /// - The OS has performed an operation that's invalidated the window's contents (such as + /// resizing the window). + /// - The application has explicitly requested a redraw via [`Window::request_redraw`]. + /// + /// During each iteration of the event loop, Winit will aggregate duplicate redraw requests + /// into a single event, to help avoid duplicating rendering work. + /// + /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless + /// something changes, like most non-game GUIs. + /// + /// [`MainEventsCleared`]: Self::MainEventsCleared + RedrawRequested(ObjectId), + + /// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to + /// be taken away from the program. If there are no `RedrawRequested` events, it is emitted + /// immediately after `MainEventsCleared`. + /// + /// This event is useful for doing any cleanup or bookkeeping work after all the rendering + /// tasks have been completed. + /// + /// [`RedrawRequested`]: Self::RedrawRequested + RedrawEventsCleared, + + /// Emitted when the event loop is being shut down. + /// + /// This is irreversible - if this event is emitted, it is guaranteed to be the last event that + /// gets emitted. You generally want to treat this as an "do on quit" event. + LoopDestroyed, + + /// Frame callback event + Frame(WlSurface, u32), +} + +#[derive(Debug, Clone)] +pub enum SctkEvent { + // + // Input events + // + SeatEvent { + variant: SeatEventVariant, + id: WlSeat, + }, + PointerEvent { + variant: PointerEvent, + ptr_id: WlPointer, + seat_id: WlSeat, + }, + KeyboardEvent { + variant: KeyboardEventVariant, + kbd_id: WlKeyboard, + seat_id: WlSeat, + surface: WlSurface, + }, + TouchEvent { + variant: touch::Event, + touch_id: WlTouch, + seat_id: WlSeat, + surface: WlSurface, + }, + // TODO data device & touch + + // + // Surface Events + // + WindowEvent { + variant: WindowEventVariant, + id: WlSurface, + }, + LayerSurfaceEvent { + variant: LayerSurfaceEventVariant, + id: WlSurface, + }, + PopupEvent { + variant: PopupEventVariant, + /// this may be the Id of a window or layer surface + toplevel_id: WlSurface, + /// this may be any SurfaceId + parent_id: WlSurface, + /// the id of this popup + id: WlSurface, + }, + + // + // output events + // + NewOutput { + id: WlOutput, + info: Option, + }, + UpdateOutput { + id: WlOutput, + info: OutputInfo, + }, + RemovedOutput(WlOutput), + // + // compositor events + // + ScaleFactorChanged { + factor: f64, + id: WlOutput, + inner_size: winit::dpi::PhysicalSize, + }, + + /// session lock events + SessionLocked, + SessionLockFinished, + SessionLockSurfaceCreated { + queue_handle: QueueHandle, + surface: CommonSurface, + native_id: SurfaceId, + common: Arc>, + display: WlDisplay, + }, + SessionLockSurfaceConfigure { + surface: WlSurface, + configure: SessionLockSurfaceConfigure, + first: bool, + }, + SessionLockSurfaceDone { + surface: WlSurface, + }, + SessionUnlocked, + SurfaceScaleFactorChanged(f64, WlSurface, window::Id), + Winit(WindowId, WindowEvent), + Subcompositor(SubsurfaceState), +} + +#[cfg(feature = "a11y")] +#[derive(Debug, Clone)] +pub struct ActionRequestEvent { + pub surface_id: ObjectId, + pub request: iced_accessibility::accesskit::ActionRequest, +} + +#[derive(Debug, Clone)] +pub enum SeatEventVariant { + New, + Remove, + NewCapability(Capability, ObjectId), + RemoveCapability(Capability, ObjectId), +} + +#[derive(Debug, Clone)] +pub enum KeyboardEventVariant { + Leave(WlSurface), + Enter(WlSurface), + Press(KeyEvent), + Repeat(KeyEvent), + Release(KeyEvent), + Modifiers(Modifiers), +} + +#[derive(Debug, Clone)] +pub enum WindowEventVariant { + Created(WlSurface, SurfaceId), + /// + Close, + /// + WmCapabilities(WindowManagerCapabilities), + /// + ConfigureBounds { + width: u32, + height: u32, + }, + /// + Configure((NonZeroU32, NonZeroU32), WindowConfigure, WlSurface, bool), + Size((NonZeroU32, NonZeroU32), WlSurface, bool), + /// window state changed + StateChanged(sctk::reexports::csd_frame::WindowState), + /// Scale Factor + ScaleFactorChanged(f64, Option), +} + +#[derive(Debug, Clone)] +pub enum PopupEventVariant { + /// Popup Created + Created( + QueueHandle, + CommonSurface, + SurfaceId, + Arc>, + WlDisplay, + ), + /// + Done, + /// + Configure(PopupConfigure, WlSurface, bool), + /// + RepositionionedPopup { token: u32 }, + /// size + Size(u32, u32), + /// Scale Factor + ScaleFactorChanged(f64, Option), +} + +#[derive(Debug, Clone)] +pub enum LayerSurfaceEventVariant { + /// sent after creation of the layer surface + Created( + QueueHandle, + CommonSurface, + SurfaceId, + Arc>, + WlDisplay, + String, + ), + /// + Done, + /// + Configure(LayerSurfaceConfigure, WlSurface, bool), + /// Scale Factor + ScaleFactorChanged(f64, Option), +} + +/// Describes the reason the event loop is resuming. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StartCause { + /// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the + /// moment the timeout was requested and the requested resume time. The actual resume time is + /// guaranteed to be equal to or after the requested resume time. + /// + /// [`ControlFlow::WaitUntil`]: crate::platform_specific::wayland::event_loop::ControlFlow::WaitUntil + ResumeTimeReached { + start: Instant, + requested_resume: Instant, + }, + + /// Sent if the OS has new events to send to the window, after a wait was requested. Contains + /// the moment the wait was requested and the resume time, if requested. + WaitCancelled { + start: Instant, + requested_resume: Option, + }, + + /// Sent if the event loop is being resumed after the loop's control flow was set to + /// [`ControlFlow::Poll`]. + /// + /// [`ControlFlow::Poll`]: crate::platform_specific::wayland::event_loop::ControlFlow::Poll + Poll, + + /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. + Init, +} + +/// Pending update to a window requested by the user. +#[derive(Default, Debug, Clone, Copy)] +pub struct SurfaceUserRequest { + /// Whether `redraw` was requested. + pub redraw_requested: bool, + + /// Wether the frame should be refreshed. + pub refresh_frame: bool, +} + +// The window update coming from the compositor. +#[derive(Default, Debug, Clone)] +pub struct SurfaceCompositorUpdate { + /// New window configure. + pub configure: Option, + + /// New scale factor. + pub scale_factor: Option, +} +pub type UserInterfaces<'a, P> = HashMap< + SurfaceId, + user_interface::UserInterface< + 'a, +

::Message, +

::Theme, +

::Renderer, + >, + rustc_hash::FxBuildHasher, +>; + +impl SctkEvent { + pub(crate) fn process<'a, P, C>( + self, + modifiers: &mut Modifiers, + program: &'a P, + compositor: &mut C, + window_manager: &mut crate::program::WindowManager, + surface_ids: &mut HashMap, + subsurface_ids: &mut HashMap, + sctk_tx: &channel::Sender, + control_sender: &mpsc::UnboundedSender, + proxy: &EventLoopProxy, + debug: &mut Debug, + user_interfaces: &mut UserInterfaces<'a, P>, + events: &mut Vec<(Option, iced_runtime::core::Event)>, + clipboard: &mut Clipboard, + subsurface_state: &mut Option, + #[cfg(feature = "a11y")] adapters: &mut HashMap< + window::Id, + (u64, iced_accessibility::accesskit_winit::Adapter), + >, + ) where + P: Program, + C: Compositor, + { + match self { + // TODO Ashley: Platform specific multi-seat events? + SctkEvent::SeatEvent { .. } => Default::default(), + SctkEvent::PointerEvent { variant, .. } => match variant.kind { + PointerEventKind::Enter { .. } => { + events.push(( + surface_ids + .get(&variant.surface.id()) + .map(|id| id.inner()), + iced_runtime::core::Event::Mouse( + mouse::Event::CursorEntered, + ), + )); + } + PointerEventKind::Leave { .. } => events.push(( + surface_ids.get(&variant.surface.id()).map(|id| id.inner()), + iced_runtime::core::Event::Mouse(mouse::Event::CursorLeft), + )), + PointerEventKind::Motion { .. } => { + let offset = if let Some((x_offset, y_offset, _)) = + subsurface_ids.get(&variant.surface.id()) + { + (*x_offset, *y_offset) + } else { + (0, 0) + }; + let id = surface_ids + .get(&variant.surface.id()) + .map(|id| id.inner()); + if let Some(w) = + id.clone().and_then(|id| window_manager.get_mut(id)) + { + w.state.set_logical_cursor_pos( + ( + variant.position.0 + offset.0 as f64, + variant.position.1 + offset.1 as f64, + ) + .into(), + ) + } + events.push(( + id, + iced_runtime::core::Event::Mouse( + mouse::Event::CursorMoved { + position: Point::new( + variant.position.0 as f32 + offset.0 as f32, + variant.position.1 as f32 + offset.1 as f32, + ), + }, + ), + )); + } + PointerEventKind::Press { + time: _, + button, + serial: _, + } => { + if let Some(e) = pointer_button_to_native(button).map(|b| { + iced_runtime::core::Event::Mouse( + mouse::Event::ButtonPressed(b), + ) + }) { + events.push(( + surface_ids + .get(&variant.surface.id()) + .map(|id| id.inner()), + e, + )); + } + } // TODO Ashley: conversion + PointerEventKind::Release { + time: _, + button, + serial: _, + } => { + if let Some(e) = pointer_button_to_native(button).map(|b| { + iced_runtime::core::Event::Mouse( + mouse::Event::ButtonReleased(b), + ) + }) { + events.push(( + surface_ids + .get(&variant.surface.id()) + .map(|id| id.inner()), + e, + )); + } + } // TODO Ashley: conversion + PointerEventKind::Axis { + time: _, + horizontal, + vertical, + source, + } => { + if let Some(e) = + pointer_axis_to_native(source, horizontal, vertical) + .map(|a| { + iced_runtime::core::Event::Mouse( + mouse::Event::WheelScrolled { delta: a }, + ) + }) + { + events.push(( + surface_ids + .get(&variant.surface.id()) + .map(|id| id.inner()), + e, + )); + } + } // TODO Ashley: conversion + }, + SctkEvent::KeyboardEvent { + variant, + kbd_id: _, + seat_id, + surface, + } => match variant { + KeyboardEventVariant::Leave(surface) => { + if let Some(e) = + surface_ids.get(&surface.id()).and_then(|id| match id { + SurfaceIdWrapper::LayerSurface(_id) => Some( + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Layer( + LayerEvent::Unfocused, + surface.clone(), + id.inner(), + ), + ), + ), + ), + SurfaceIdWrapper::Window(id) => { + Some(iced_runtime::core::Event::Window( + window::Event::Unfocused, + )) + } + SurfaceIdWrapper::Popup(_id) => Some( + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Popup( + PopupEvent::Unfocused, + surface.clone(), + id.inner(), + ), + ), + ), + ), + SurfaceIdWrapper::SessionLock(_) => Some( + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::SessionLock( + SessionLockEvent::Unfocused( + surface.clone(), + id.inner(), + ), + ), + ), + ), + ), + }) + { + events.push(( + surface_ids.get(&surface.id()).map(|id| id.inner()), + e, + )); + } + + events.push(( + surface_ids.get(&surface.id()).map(|id| id.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Seat( + wayland::SeatEvent::Leave, + seat_id, + )), + ), + )) + } + KeyboardEventVariant::Enter(surface) => { + if let Some(e) = + surface_ids.get(&surface.id()).and_then(|id| { + match id { + SurfaceIdWrapper::LayerSurface(_id) => Some( + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Layer( + LayerEvent::Focused, + surface.clone(), + id.inner(), + ), + ), + ), + ), + SurfaceIdWrapper::Window(id) => { + Some(iced_runtime::core::Event::Window( + window::Event::Focused, + )) + } + SurfaceIdWrapper::Popup(_id) => Some( + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Popup( + PopupEvent::Focused, + surface.clone(), + id.inner(), + ), + ), + ), + ), + SurfaceIdWrapper::SessionLock(_) => Some( + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::SessionLock( + SessionLockEvent::Focused( + surface.clone(), + id.inner(), + ), + ), + ), + ), + ), + } + .map(|e| (Some(id.inner()), e)) + }) + { + events.push(e); + } + + events.push(( + surface_ids.get(&surface.id()).map(|id| id.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Seat( + wayland::SeatEvent::Enter, + seat_id, + )), + ), + )); + } + KeyboardEventVariant::Press(ke) => { + let (key, location) = keysym_to_vkey_location(ke.keysym); + let physical_key = raw_keycode_to_physicalkey(ke.raw_code); + let physical_key = + crate::conversion::physical_key(physical_key); + + events.push(( + surface_ids.get(&surface.id()).map(|id| id.inner()), + iced_runtime::core::Event::Keyboard( + keyboard::Event::KeyPressed { + key: key.clone(), + location: location, + text: ke.utf8.map(|s| s.into()), + modifiers: modifiers_to_native(*modifiers), + physical_key, + modified_key: key, // TODO calculate without Ctrl? + }, + ), + )) + } + KeyboardEventVariant::Repeat(KeyEvent { + keysym, + utf8, + raw_code, + .. + }) => { + let (key, location) = keysym_to_vkey_location(keysym); + let physical_key = raw_keycode_to_physicalkey(raw_code); + let physical_key = + crate::conversion::physical_key(physical_key); + + events.push(( + surface_ids.get(&surface.id()).map(|id| id.inner()), + iced_runtime::core::Event::Keyboard( + keyboard::Event::KeyPressed { + key: key.clone(), + location: location, + text: utf8.map(|s| s.into()), + modifiers: modifiers_to_native(*modifiers), + physical_key, + modified_key: key, // TODO calculate without Ctrl? + }, + ), + )) + } + KeyboardEventVariant::Release(ke) => { + let (k, location) = keysym_to_vkey_location(ke.keysym); + let physical_key = raw_keycode_to_physicalkey(ke.raw_code); + let physical_key = + crate::conversion::physical_key(physical_key); + events.push(( + surface_ids.get(&surface.id()).map(|id| id.inner()), + iced_runtime::core::Event::Keyboard( + keyboard::Event::KeyReleased { + key: k.clone(), + location, + modifiers: modifiers_to_native(*modifiers), + modified_key: k, + physical_key: physical_key, + }, + ), + )) + } + KeyboardEventVariant::Modifiers(new_mods) => { + *modifiers = new_mods; + events.push(( + surface_ids.get(&surface.id()).map(|id| id.inner()), + iced_runtime::core::Event::Keyboard( + keyboard::Event::ModifiersChanged( + modifiers_to_native(new_mods), + ), + ), + )) + } + }, + SctkEvent::TouchEvent { + variant, + touch_id: _, + seat_id: _, + surface, + } => events.push(( + surface_ids.get(&surface.id()).map(|id| id.inner()), + iced_runtime::core::Event::Touch(variant), + )), + SctkEvent::WindowEvent { .. } => {} + SctkEvent::LayerSurfaceEvent { + variant, + id: surface, + } => match variant { + LayerSurfaceEventVariant::Done => { + if let Some(id) = surface_ids.remove(&surface.id()) { + if let Some(w) = window_manager.remove(id.inner()) { + if clipboard + .window_id() + .is_some_and(|id| w.raw.id() == id) + { + clipboard.register_dnd_destination( + DndSurface(Arc::new(Box::new( + w.raw.clone(), + ))), + Vec::new(), + ); + *clipboard = Clipboard::unconnected(); + } + } + events.push(( + Some(id.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Layer( + LayerEvent::Done, + surface, + id.inner(), + ), + ), + ), + )); + } + } + LayerSurfaceEventVariant::Created( + queue_handle, + surface, + surface_id, + common, + display, + .., + ) => { + let wl_surface = surface.wl_surface(); + let object_id = wl_surface.id(); + let wrapper = + SurfaceIdWrapper::LayerSurface(surface_id.clone()); + _ = surface_ids.insert(object_id.clone(), wrapper.clone()); + let sctk_winit = SctkWinitWindow::new( + sctk_tx.clone(), + common, + wrapper, + surface, + display, + queue_handle, + ); + + #[cfg(feature = "a11y")] + { + use crate::a11y::*; + use iced_accessibility::accesskit::{ + ActivationHandler, NodeBuilder, NodeId, Role, Tree, + TreeUpdate, + }; + use iced_accessibility::accesskit_winit::Adapter; + + let node_id = iced_runtime::core::id::window_node_id(); + + let activation_handler = WinitActivationHandler { + proxy: control_sender.clone(), + title: String::new(), + }; + + let action_handler = WinitActionHandler { + id: surface_id, + proxy: control_sender.clone(), + }; + + let deactivation_handler = WinitDeactivationHandler { + proxy: control_sender.clone(), + }; + _ = adapters.insert( + surface_id, + ( + node_id, + Adapter::with_direct_handlers( + sctk_winit.as_ref(), + activation_handler, + action_handler, + deactivation_handler, + ), + ), + ); + } + + let window = window_manager.insert( + surface_id, sctk_winit, program, compositor, + false, // TODO do we want to get this value here? + 0, + ); + _ = surface_ids.insert(object_id, wrapper.clone()); + let logical_size = window.size(); + + if clipboard.window_id().is_none() { + *clipboard = Clipboard::connect( + window.raw.clone(), + crate::clipboard::ControlSender { + sender: control_sender.clone(), + proxy: proxy.clone(), + }, + ); + } + + let _ = user_interfaces.insert( + surface_id, + crate::program::build_user_interface( + program, + user_interface::Cache::default(), + &mut window.renderer, + logical_size, + debug, + surface_id, + window.raw.clone(), + window.prev_dnd_destination_rectangles_count, + clipboard, + ), + ); + } + LayerSurfaceEventVariant::ScaleFactorChanged(..) => {} + LayerSurfaceEventVariant::Configure( + configure, + surface, + first, + ) => { + if let Some(w) = surface_ids + .get(&surface.id()) + .and_then(|id| window_manager.get_mut(id.inner())) + { + let scale = w.state.scale_factor(); + let p_w = (configure.new_size.0.max(1) as f64 * scale) + .ceil() as u32; + let p_h = (configure.new_size.1.max(1) as f64 * scale) + .ceil() as u32; + w.state.update( + w.raw.as_ref(), + &WindowEvent::SurfaceResized(PhysicalSize::new( + p_w, p_h, + )), + debug, + ); + } + } + }, + SctkEvent::PopupEvent { + variant, + id: surface, + .. + } => { + match variant { + PopupEventVariant::Done => { + if let Some(e) = + surface_ids.remove(&surface.id()).map(|id| { + if let Some(w) = + window_manager.remove(id.inner()) + { + clipboard.register_dnd_destination( + DndSurface(Arc::new(Box::new( + w.raw.clone(), + ))), + Vec::new(), + ); + if clipboard + .window_id() + .is_some_and(|id| w.raw.id() == id) + { + *clipboard = Clipboard::unconnected(); + } + } + _ = user_interfaces.remove(&id.inner()); + + ( + Some(id.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Popup( + PopupEvent::Done, + surface, + id.inner(), + ), + ), + ), + ) + }) + { + events.push(e) + } + } + PopupEventVariant::Created( + queue_handle, + surface, + surface_id, + common, + display, + ) => { + let wl_surface = surface.wl_surface(); + let wrapper = SurfaceIdWrapper::Popup(surface_id); + _ = surface_ids + .insert(wl_surface.id(), wrapper.clone()); + let sctk_winit = SctkWinitWindow::new( + sctk_tx.clone(), + common, + wrapper, + surface, + display, + queue_handle, + ); + #[cfg(feature = "a11y")] + { + use crate::a11y::*; + use iced_accessibility::accesskit::{ + ActivationHandler, NodeBuilder, NodeId, Role, + Tree, TreeUpdate, + }; + use iced_accessibility::accesskit_winit::Adapter; + + let node_id = + iced_runtime::core::id::window_node_id(); + + let activation_handler = WinitActivationHandler { + proxy: control_sender.clone(), + title: String::new(), + }; + + let action_handler = WinitActionHandler { + id: surface_id, + proxy: control_sender.clone(), + }; + + let deactivation_handler = + WinitDeactivationHandler { + proxy: control_sender.clone(), + }; + _ = adapters.insert( + surface_id, + ( + node_id, + Adapter::with_direct_handlers( + sctk_winit.as_ref(), + activation_handler, + action_handler, + deactivation_handler, + ), + ), + ); + } + + if clipboard.window_id().is_none() { + *clipboard = Clipboard::connect( + sctk_winit.clone(), + crate::clipboard::ControlSender { + sender: control_sender.clone(), + proxy: proxy.clone(), + }, + ); + } + + let window = window_manager.insert( + surface_id, sctk_winit, program, compositor, + false, // TODO do we want to get this value here? + 0, + ); + let logical_size = window.size(); + + let _ = user_interfaces.insert( + surface_id, + crate::program::build_user_interface( + program, + user_interface::Cache::default(), + &mut window.renderer, + logical_size, + debug, + surface_id, + window.raw.clone(), + window.prev_dnd_destination_rectangles_count, + clipboard, + ), + ); + } + PopupEventVariant::Configure(_, _, _) => {} // TODO + PopupEventVariant::RepositionionedPopup { token: _ } => {} + PopupEventVariant::Size(_, _) => {} + PopupEventVariant::ScaleFactorChanged(..) => {} + } + } + SctkEvent::NewOutput { id, info } => events.push(( + None, + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Output( + wayland::OutputEvent::Created(info), + id, + )), + ), + )), + SctkEvent::UpdateOutput { id, info } => events.push(( + None, + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Output( + wayland::OutputEvent::InfoUpdate(info), + id, + )), + ), + )), + SctkEvent::RemovedOutput(id) => events.push(( + None, + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::Output( + wayland::OutputEvent::Removed, + id, + )), + ), + )), + SctkEvent::ScaleFactorChanged { + factor: _, + id: _, + inner_size: _, + } => Default::default(), + SctkEvent::SessionLocked => events.push(( + None, + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::SessionLock( + wayland::SessionLockEvent::Locked, + )), + ), + )), + SctkEvent::SessionLockFinished => events.push(( + None, + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::SessionLock( + wayland::SessionLockEvent::Finished, + )), + ), + )), + SctkEvent::SessionLockSurfaceCreated { + queue_handle, + surface, + native_id: surface_id, + common, + display, + } => { + let wl_surface = surface.wl_surface(); + let object_id = wl_surface.id().clone(); + let wrapper = SurfaceIdWrapper::SessionLock(surface_id.clone()); + _ = surface_ids.insert(object_id.clone(), wrapper.clone()); + let sctk_winit = SctkWinitWindow::new( + sctk_tx.clone(), + common, + wrapper, + surface, + display, + queue_handle, + ); + + #[cfg(feature = "a11y")] + { + use crate::a11y::*; + use iced_accessibility::accesskit::{ + ActivationHandler, NodeBuilder, NodeId, Role, Tree, + TreeUpdate, + }; + use iced_accessibility::accesskit_winit::Adapter; + + let node_id = iced_runtime::core::id::window_node_id(); + + let activation_handler = WinitActivationHandler { + proxy: control_sender.clone(), + // TODO lock screen title + title: String::new(), + }; + + let action_handler = WinitActionHandler { + id: surface_id, + proxy: control_sender.clone(), + }; + + let deactivation_handler = WinitDeactivationHandler { + proxy: control_sender.clone(), + }; + _ = adapters.insert( + surface_id, + ( + node_id, + Adapter::with_direct_handlers( + sctk_winit.as_ref(), + activation_handler, + action_handler, + deactivation_handler, + ), + ), + ); + } + + if clipboard.window_id().is_none() { + *clipboard = Clipboard::connect( + sctk_winit.clone(), + crate::clipboard::ControlSender { + sender: control_sender.clone(), + proxy: proxy.clone(), + }, + ); + } + + let window = window_manager.insert( + surface_id, sctk_winit, program, compositor, + false, // TODO do we want to get this value here? + 0, + ); + _ = surface_ids.insert(object_id, wrapper.clone()); + let logical_size = window.size(); + + let _ = user_interfaces.insert( + surface_id, + crate::program::build_user_interface( + program, + user_interface::Cache::default(), + &mut window.renderer, + logical_size, + debug, + surface_id, + window.raw.clone(), + window.prev_dnd_destination_rectangles_count, + clipboard, + ), + ); + } + SctkEvent::SessionLockSurfaceConfigure { .. } => {} + SctkEvent::SessionLockSurfaceDone { surface } => { + if let Some(id) = surface_ids.remove(&surface.id()) { + _ = window_manager.remove(id.inner()); + } + } + SctkEvent::SessionUnlocked => events.push(( + None, + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland(wayland::Event::SessionLock( + wayland::SessionLockEvent::Unlocked, + )), + ), + )), + SctkEvent::Winit(_, _) => {} + SctkEvent::SurfaceScaleFactorChanged(scale, _, id) => { + if let Some(w) = window_manager.get_mut(id) { + w.state.update_scale_factor(scale); + } + } + SctkEvent::Subcompositor(s) => { + *subsurface_state = Some(s); + } + } + } +} + +fn keysym_to_vkey_location(keysym: Keysym) -> (Key, Location) { + let raw = keysym.raw(); + let mut key = keysym_to_key(raw); + if matches!(key, key::Key::Unidentified) { + // XXX is there a better way to do this? + // we need to be able to determine the actual character for the key + // not the combination, so this seems to be correct + let mut utf8 = xkbcommon::xkb::keysym_to_utf8(keysym); + // remove null terminator + _ = utf8.pop(); + if utf8.len() > 0 { + key = Key::Character(utf8.into()); + } + } + + let location = keymap::keysym_location(raw); + (key, location) +} diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs new file mode 100644 index 0000000000..1f657300df --- /dev/null +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -0,0 +1,702 @@ +// TODO z-order option? + +use crate::core::{ + layout::{self, Layout}, + mouse, renderer, + widget::{self, Widget}, + ContentFit, Element, Length, Rectangle, Size, +}; +use std::{ + cell::RefCell, + collections::HashMap, + fmt::Debug, + future::Future, + hash::{Hash, Hasher}, + mem, + os::unix::io::{AsFd, OwnedFd}, + pin::Pin, + ptr, + sync::{Arc, Mutex, Weak}, + task, +}; + +use crate::futures::futures::channel::oneshot; +use iced_futures::core::window; +use sctk::{ + compositor::SurfaceData, + globals::GlobalData, + reexports::client::{ + delegate_noop, + protocol::{ + wl_buffer::{self, WlBuffer}, + wl_compositor::WlCompositor, + wl_shm::{self, WlShm}, + wl_shm_pool::{self, WlShmPool}, + wl_subcompositor::WlSubcompositor, + wl_subsurface::WlSubsurface, + wl_surface::WlSurface, + }, + Connection, Dispatch, Proxy, QueueHandle, + }, +}; +use wayland_backend::client::ObjectId; +use wayland_protocols::wp::{ + alpha_modifier::v1::client::{ + wp_alpha_modifier_surface_v1::WpAlphaModifierSurfaceV1, + wp_alpha_modifier_v1::WpAlphaModifierV1, + }, + linux_dmabuf::zv1::client::{ + zwp_linux_buffer_params_v1::{self, ZwpLinuxBufferParamsV1}, + zwp_linux_dmabuf_v1::{self, ZwpLinuxDmabufV1}, + }, + viewporter::client::{ + wp_viewport::WpViewport, wp_viewporter::WpViewporter, + }, +}; + +use crate::platform_specific::{ + event_loop::state::SctkState, SurfaceIdWrapper, +}; + +#[derive(Debug)] +pub struct Plane { + pub fd: OwnedFd, + pub plane_idx: u32, + pub offset: u32, + pub stride: u32, +} + +#[derive(Debug)] +pub struct Dmabuf { + pub width: i32, + pub height: i32, + pub planes: Vec, + pub format: u32, + pub modifier: u64, +} + +#[derive(Debug)] +pub struct Shmbuf { + pub fd: OwnedFd, + pub offset: i32, + pub width: i32, + pub height: i32, + pub stride: i32, + pub format: wl_shm::Format, +} + +#[derive(Debug)] +pub enum BufferSource { + Shm(Shmbuf), + Dma(Dmabuf), +} + +impl From for BufferSource { + fn from(buf: Shmbuf) -> Self { + Self::Shm(buf) + } +} + +impl From for BufferSource { + fn from(buf: Dmabuf) -> Self { + Self::Dma(buf) + } +} + +#[derive(Debug)] +struct SubsurfaceBufferInner { + source: Arc, + _sender: oneshot::Sender<()>, +} + +/// Refcounted type containing a `BufferSource` with a sender that is signaled +/// all references are dropped and `wl_buffer`s created from the source are +/// released. +#[derive(Clone, Debug)] +pub struct SubsurfaceBuffer(Arc); + +pub struct BufferData { + source: WeakBufferSource, + // This reference is held until the surface `release`s the buffer + subsurface_buffer: Mutex>, +} + +impl BufferData { + fn for_buffer(buffer: &WlBuffer) -> Option<&Self> { + buffer.data::() + } +} + +/// Future signalled when subsurface buffer is released +pub struct SubsurfaceBufferRelease(oneshot::Receiver<()>); + +impl SubsurfaceBufferRelease { + /// Non-blocking check if buffer is released yet, without awaiting + pub fn released(&mut self) -> bool { + self.0.try_recv() == Ok(None) + } +} + +impl Future for SubsurfaceBufferRelease { + type Output = (); + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> task::Poll<()> { + Pin::new(&mut self.0).poll(cx).map(|_| ()) + } +} + +impl SubsurfaceBuffer { + pub fn new(source: Arc) -> (Self, SubsurfaceBufferRelease) { + let (_sender, receiver) = oneshot::channel(); + let subsurface_buffer = + SubsurfaceBuffer(Arc::new(SubsurfaceBufferInner { + source, + _sender, + })); + (subsurface_buffer, SubsurfaceBufferRelease(receiver)) + } + + // Behavior of `wl_buffer::released` is undefined if attached to multiple surfaces. To allow + // things like that, create a new `wl_buffer` each time. + fn create_buffer( + &self, + shm: &WlShm, + dmabuf: Option<&ZwpLinuxDmabufV1>, + qh: &QueueHandle, + ) -> Option { + // create reference to source, that is dropped on release + match self.0.source.as_ref() { + BufferSource::Shm(buf) => { + let pool = shm.create_pool( + buf.fd.as_fd(), + buf.offset + buf.height * buf.stride, + qh, + GlobalData, + ); + let buffer = pool.create_buffer( + buf.offset, + buf.width, + buf.height, + buf.stride, + buf.format, + qh, + BufferData { + source: WeakBufferSource(Arc::downgrade( + &self.0.source, + )), + subsurface_buffer: Mutex::new(Some(self.clone())), + }, + ); + pool.destroy(); + Some(buffer) + } + BufferSource::Dma(buf) => { + if let Some(dmabuf) = dmabuf { + let params = dmabuf.create_params(qh, GlobalData); + for plane in &buf.planes { + let modifier_hi = (buf.modifier >> 32) as u32; + let modifier_lo = (buf.modifier & 0xffffffff) as u32; + params.add( + plane.fd.as_fd(), + plane.plane_idx, + plane.offset, + plane.stride, + modifier_hi, + modifier_lo, + ); + } + // Will cause protocol error if format is not supported + Some(params.create_immed( + buf.width, + buf.height, + buf.format, + zwp_linux_buffer_params_v1::Flags::empty(), + qh, + BufferData { + source: WeakBufferSource(Arc::downgrade( + &self.0.source, + )), + subsurface_buffer: Mutex::new(Some(self.clone())), + }, + )) + } else { + None + } + } + } + } +} + +impl PartialEq for SubsurfaceBuffer { + fn eq(&self, rhs: &Self) -> bool { + Arc::ptr_eq(&self.0, &rhs.0) + } +} + +impl Dispatch for SctkState { + fn event( + _: &mut SctkState, + _: &WlShmPool, + _: wl_shm_pool::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + unreachable!() + } +} + +impl Dispatch for SctkState { + fn event( + _: &mut SctkState, + _: &ZwpLinuxDmabufV1, + _: zwp_linux_dmabuf_v1::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for SctkState { + fn event( + _: &mut SctkState, + _: &ZwpLinuxBufferParamsV1, + _: zwp_linux_buffer_params_v1::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for SctkState { + fn event( + _: &mut SctkState, + _: &WlBuffer, + event: wl_buffer::Event, + data: &BufferData, + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_buffer::Event::Release => { + // Release reference to `SubsurfaceBuffer` + _ = data.subsurface_buffer.lock().unwrap().take(); + } + _ => unreachable!(), + } + } +} + +#[doc(hidden)] +#[derive(Clone, Debug)] +pub(crate) struct WeakBufferSource(Weak); + +impl PartialEq for WeakBufferSource { + fn eq(&self, rhs: &Self) -> bool { + Weak::ptr_eq(&self.0, &rhs.0) + } +} + +impl Eq for WeakBufferSource {} + +impl Hash for WeakBufferSource { + fn hash(&self, state: &mut H) { + ptr::hash::(self.0.as_ptr(), state) + } +} + +// create wl_buffer from BufferSource (avoid create_immed?) +// release +#[derive(Debug, Clone)] +#[doc(hidden)] +pub struct SubsurfaceState { + pub wl_compositor: WlCompositor, + pub wl_subcompositor: WlSubcompositor, + pub wp_viewporter: WpViewporter, + pub wl_shm: WlShm, + pub wp_dmabuf: Option, + pub wp_alpha_modifier: Option, + pub qh: QueueHandle, + pub(crate) buffers: HashMap>, + pub unmapped_subsurfaces: Vec, +} + +impl SubsurfaceState { + fn create_subsurface(&self, parent: &WlSurface) -> SubsurfaceInstance { + let wl_surface = self + .wl_compositor + .create_surface(&self.qh, SurfaceData::new(None, 1)); + + // Use empty input region so parent surface gets pointer events + let region = self.wl_compositor.create_region(&self.qh, ()); + wl_surface.set_input_region(Some(®ion)); + region.destroy(); + + let wl_subsurface = self.wl_subcompositor.get_subsurface( + &wl_surface, + parent, + &self.qh, + (), + ); + + let wp_viewport = self.wp_viewporter.get_viewport( + &wl_surface, + &self.qh, + sctk::globals::GlobalData, + ); + + let wp_alpha_modifier_surface = + self.wp_alpha_modifier.as_ref().map(|wp_alpha_modifier| { + wp_alpha_modifier.get_surface(&wl_surface, &self.qh, ()) + }); + + SubsurfaceInstance { + wl_surface, + wl_subsurface, + wp_viewport, + wp_alpha_modifier_surface, + wl_buffer: None, + bounds: None, + } + } + + // Update `subsurfaces` from `view_subsurfaces` + pub(crate) fn update_subsurfaces( + &mut self, + parent_id: window::Id, + subsurface_ids: &mut HashMap, + parent: &WlSurface, + subsurfaces: &mut Vec, + view_subsurfaces: &[SubsurfaceInfo], + ) { + // Subsurfaces aren't destroyed immediately to sync removal with parent + // surface commit. Since `destroy` is immediate. + // + // They should be safe to destroy by the next time `update_subsurfaces` + // is run. + self.unmapped_subsurfaces.clear(); + + // Remove cached `wl_buffers` for any `BufferSource`s that no longer exist. + self.buffers.retain(|k, v| { + let retain = k.0.strong_count() > 0; + if !retain { + v.iter().for_each(|b| b.destroy()); + } + retain + }); + + // If view requested fewer subsurfaces than there currently are, + // unmap excess. + while view_subsurfaces.len() < subsurfaces.len() { + let subsurface = subsurfaces.pop().unwrap(); + subsurface.unmap(); + self.unmapped_subsurfaces.push(subsurface); + } + // Create new subsurfaces if there aren't enough. + while subsurfaces.len() < view_subsurfaces.len() { + subsurfaces.push(self.create_subsurface(parent)); + } + // Attach buffers to subsurfaces, set viewports, and commit. + for (subsurface_data, subsurface) in + view_subsurfaces.iter().zip(subsurfaces.iter_mut()) + { + subsurface.attach_and_commit( + parent_id, + subsurface_ids, + subsurface_data, + self, + ); + } + + if let Some(backend) = parent.backend().upgrade() { + subsurface_ids.retain(|k, _| backend.info(k.clone()).is_ok()); + } + } + + // Cache `wl_buffer` for use when `BufferSource` is used in future + // (Avoid creating wl_buffer each buffer swap) + fn insert_cached_wl_buffer(&mut self, buffer: WlBuffer) { + let source = BufferData::for_buffer(&buffer).unwrap().source.clone(); + self.buffers.entry(source).or_default().push(buffer); + } + + // Gets a cached `wl_buffer` for the `SubsurfaceBuffer`, if any. And stores `SubsurfaceBuffer` + // reference to be releated on `wl_buffer` release. + // + // If `wl_buffer` isn't released, it is destroyed instead. + fn get_cached_wl_buffer( + &mut self, + subsurface_buffer: &SubsurfaceBuffer, + ) -> Option { + let buffers = self.buffers.get_mut(&WeakBufferSource( + Arc::downgrade(&subsurface_buffer.0.source), + ))?; + while let Some(buffer) = buffers.pop() { + let mut subsurface_buffer_ref = buffer + .data::() + .unwrap() + .subsurface_buffer + .lock() + .unwrap(); + if subsurface_buffer_ref.is_none() { + *subsurface_buffer_ref = Some(subsurface_buffer.clone()); + drop(subsurface_buffer_ref); + return Some(buffer); + } else { + buffer.destroy(); + } + } + None + } +} + +impl Drop for SubsurfaceState { + fn drop(&mut self) { + self.buffers + .values() + .flatten() + .for_each(|buffer| buffer.destroy()); + } +} + +#[derive(Clone, Debug)] +pub(crate) struct SubsurfaceInstance { + pub(crate) wl_surface: WlSurface, + wl_subsurface: WlSubsurface, + wp_viewport: WpViewport, + wp_alpha_modifier_surface: Option, + wl_buffer: Option, + bounds: Option>, +} + +impl SubsurfaceInstance { + // TODO correct damage? no damage/commit if unchanged? + fn attach_and_commit( + &mut self, + parent_id: window::Id, + subsurface_ids: &mut HashMap, + info: &SubsurfaceInfo, + state: &mut SubsurfaceState, + ) { + let buffer_changed; + + let old_buffer = self.wl_buffer.take(); + let old_buffer_data = + old_buffer.as_ref().and_then(|b| BufferData::for_buffer(&b)); + let buffer = if old_buffer_data.is_some_and(|b| { + b.subsurface_buffer.lock().unwrap().as_ref() == Some(&info.buffer) + }) { + // Same "BufferSource" is already attached to this subsurface. Don't create new `wl_buffer`. + buffer_changed = false; + old_buffer.unwrap() + } else { + if let Some(old_buffer) = old_buffer { + state.insert_cached_wl_buffer(old_buffer); + } + + buffer_changed = true; + + if let Some(buffer) = state.get_cached_wl_buffer(&info.buffer) { + buffer + } else if let Some(buffer) = info.buffer.create_buffer( + &state.wl_shm, + state.wp_dmabuf.as_ref(), + &state.qh, + ) { + buffer + } else { + // TODO log error + self.wl_surface.attach(None, 0, 0); + return; + } + }; + + // XXX scale factor? + let bounds_changed = self.bounds != Some(info.bounds); + // wlroots seems to have issues changing buffer without running this + if bounds_changed || buffer_changed { + self.wl_subsurface + .set_position(info.bounds.x as i32, info.bounds.y as i32); + self.wp_viewport.set_destination( + info.bounds.width as i32, + info.bounds.height as i32, + ); + } + if buffer_changed { + self.wl_surface.attach(Some(&buffer), 0, 0); + self.wl_surface.damage(0, 0, i32::MAX, i32::MAX); + } + if buffer_changed || bounds_changed { + _ = self.wl_surface.frame(&state.qh, self.wl_surface.clone()); + self.wl_surface.commit(); + } + + if let Some(wp_alpha_modifier_surface) = &self.wp_alpha_modifier_surface + { + let alpha = (info.alpha.clamp(0.0, 1.0) * u32::MAX as f32) as u32; + wp_alpha_modifier_surface.set_multiplier(alpha); + } + + _ = subsurface_ids.insert( + self.wl_surface.id(), + (info.bounds.x as i32, info.bounds.y as i32, parent_id), + ); + + self.wl_buffer = Some(buffer); + self.bounds = Some(info.bounds); + } + + pub fn unmap(&self) { + self.wl_surface.attach(None, 0, 0); + self.wl_surface.commit(); + } +} + +impl Drop for SubsurfaceInstance { + fn drop(&mut self) { + self.wp_viewport.destroy(); + self.wl_subsurface.destroy(); + self.wl_surface.destroy(); + if let Some(wl_buffer) = self.wl_buffer.as_ref() { + wl_buffer.destroy(); + } + } +} + +pub(crate) struct SubsurfaceInfo { + pub buffer: SubsurfaceBuffer, + pub bounds: Rectangle, + pub alpha: f32, +} + +thread_local! { + static SUBSURFACES: RefCell> = RefCell::new(Vec::new()); +} + +pub(crate) fn take_subsurfaces() -> Vec { + SUBSURFACES.with(|subsurfaces| mem::take(&mut *subsurfaces.borrow_mut())) +} + +#[must_use] +pub struct Subsurface<'a> { + buffer_size: Size, + buffer: &'a SubsurfaceBuffer, + width: Length, + height: Length, + content_fit: ContentFit, + alpha: f32, +} + +impl<'a, Message, Theme, Renderer> Widget + for Subsurface<'a> +where + Renderer: renderer::Renderer, +{ + fn size(&self) -> Size { + Size::new(self.width, self.height) + } + + // Based on image widget + fn layout( + &self, + _tree: &mut widget::Tree, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let raw_size = + limits.resolve(self.width, self.height, self.buffer_size); + + let full_size = self.content_fit.fit(self.buffer_size, raw_size); + + let final_size = Size { + width: match self.width { + Length::Shrink => f32::min(raw_size.width, full_size.width), + _ => raw_size.width, + }, + height: match self.height { + Length::Shrink => f32::min(raw_size.height, full_size.height), + _ => raw_size.height, + }, + }; + + layout::Node::new(final_size) + } + + fn draw( + &self, + _state: &widget::Tree, + _renderer: &mut Renderer, + _theme: &Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor: mouse::Cursor, + _viewport: &Rectangle, + ) { + // Instead of using renderer, we need to add surface to a list that is + // read by the iced-sctk shell. + SUBSURFACES.with(|subsurfaces| { + subsurfaces.borrow_mut().push(SubsurfaceInfo { + buffer: self.buffer.clone(), + bounds: layout.bounds(), + alpha: self.alpha, + }) + }); + } +} + +impl<'a> Subsurface<'a> { + pub fn new( + buffer_width: u32, + buffer_height: u32, + buffer: &'a SubsurfaceBuffer, + ) -> Self { + Self { + buffer_size: Size::new(buffer_width as f32, buffer_height as f32), + buffer, + // Matches defaults of image widget + width: Length::Shrink, + height: Length::Shrink, + content_fit: ContentFit::Contain, + alpha: 1., + } + } + + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + pub fn content_fit(mut self, content_fit: ContentFit) -> Self { + self.content_fit = content_fit; + self + } + + pub fn alpha(mut self, alpha: f32) -> Self { + self.alpha = alpha; + self + } +} + +impl<'a, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + Message: Clone + 'a, + Renderer: renderer::Renderer, +{ + fn from(subsurface: Subsurface<'a>) -> Self { + Self::new(subsurface) + } +} + +delegate_noop!(SctkState: ignore WpAlphaModifierV1); +delegate_noop!(SctkState: ignore WpAlphaModifierSurfaceV1); diff --git a/winit/src/platform_specific/wayland/winit_window.rs b/winit/src/platform_specific/wayland/winit_window.rs new file mode 100644 index 0000000000..eb7fc9dc77 --- /dev/null +++ b/winit/src/platform_specific/wayland/winit_window.rs @@ -0,0 +1,437 @@ +use crate::platform_specific::wayland::Action; +use raw_window_handle::HandleError; +use sctk::reexports::{ + calloop::channel, + client::{ + protocol::{wl_display::WlDisplay, wl_surface::WlSurface}, + Proxy, QueueHandle, + }, +}; +use std::sync::{Arc, Mutex}; +use winit::{ + dpi::LogicalSize, + error::{NotSupportedError, RequestError}, + window::WindowButtons, +}; + +use crate::platform_specific::SurfaceIdWrapper; + +use super::event_loop::state::{ + Common, CommonSurface, SctkLayerSurface, SctkLockSurface, SctkPopup, + SctkState, TOKEN_CTR, +}; + +#[derive(Debug)] +pub(crate) enum Surface { + Popup(SctkPopup), + Layer(SctkLayerSurface), + Lock(SctkLockSurface), +} + +impl Surface {} + +pub struct SctkWinitWindow { + tx: channel::Sender, + id: SurfaceIdWrapper, + surface: CommonSurface, + common: Arc>, + display: WlDisplay, + pub(crate) queue_handle: QueueHandle, + wait_redraw: bool, +} + +impl Drop for SctkWinitWindow { + fn drop(&mut self) { + self.tx.send(Action::Dropped(self.id)).unwrap(); + } +} + +impl SctkWinitWindow { + pub(crate) fn new( + tx: channel::Sender, + common: Arc>, + id: SurfaceIdWrapper, + surface: CommonSurface, + display: WlDisplay, + queue_handle: QueueHandle, + ) -> Arc { + Arc::new(Self { + tx, + common, + id, + surface, + display, + queue_handle, + wait_redraw: false, + }) + } +} + +impl winit::window::Window for SctkWinitWindow { + fn id(&self) -> winit::window::WindowId { + winit::window::WindowId::from( + self.surface.wl_surface().id().as_ptr() as u64 + ) + } + + fn scale_factor(&self) -> f64 { + let guard = self.common.lock().unwrap(); + guard.fractional_scale.unwrap_or(1.) + } + + fn request_redraw(&self) { + let surface = self.surface.wl_surface(); + _ = self.tx.send(Action::RequestRedraw(surface.id())); + } + + fn pre_present_notify(&self) { + let surface = self.surface.wl_surface(); + _ = surface.frame(&self.queue_handle, surface.clone()); + _ = self + .tx + .send(Action::PrePresentNotify(self.surface.wl_surface().id())); + } + + fn set_cursor(&self, cursor: winit::window::Cursor) { + match cursor { + winit::window::Cursor::Icon(icon) => { + _ = self.tx.send(Action::SetCursor(icon)); + } + winit::window::Cursor::Custom(_) => { + // TODO + } + } + } + + fn set_cursor_visible(&self, visible: bool) { + // TODO + } + + fn surface_size(&self) -> winit::dpi::PhysicalSize { + let guard = self.common.lock().unwrap(); + let size = guard.size; + size.to_physical(guard.fractional_scale.unwrap_or(1.)) + } + + fn request_surface_size( + &self, + size: winit::dpi::Size, + ) -> Option> { + let mut guard = self.common.lock().unwrap(); + self.request_redraw(); + let size: LogicalSize = + size.to_logical(guard.fractional_scale.unwrap_or(1.)); + match &self.surface { + CommonSurface::Popup(popup, positioner) => { + if size.width == 0 || size.height == 0 { + return None; + } + guard.size = size; + guard.requested_size.0 = Some(guard.size.width); + guard.requested_size.1 = Some(guard.size.height); + positioner.set_size( + guard.size.width as i32, + guard.size.height as i32, + ); + popup.xdg_surface().set_window_geometry( + 0, + 0, + guard.size.width as i32, + guard.size.height as i32, + ); + popup.xdg_popup().reposition( + positioner, + TOKEN_CTR + .fetch_add(1, std::sync::atomic::Ordering::Relaxed), + ); + if let Some(viewport) = guard.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination( + guard.size.width as i32, + guard.size.height as i32, + ); + } + } + CommonSurface::Layer(layer_surface) => { + guard.requested_size = ( + (size.width > 0).then_some(size.width), + (size.height > 0).then_some(size.height), + ); + if size.width > 0 { + guard.size.width = size.width; + } + if size.height > 0 { + guard.size.height = size.height; + } + layer_surface.set_size(size.width, size.height); + if let Some(viewport) = guard.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination( + guard.size.width as i32, + guard.size.height as i32, + ); + } + } + CommonSurface::Lock(_) => {} + } + None + } + + fn reset_dead_keys(&self) { + // TODO refer to winit for implementation + } + + fn set_outer_position(&self, position: winit::dpi::Position) {} + + fn outer_size(&self) -> winit::dpi::PhysicalSize { + // XXX not applicable to wrapped surfaces + Default::default() + } + + fn set_min_surface_size(&self, min_size: Option) { + // XXX not applicable to wrapped surfaces + } + + fn set_max_surface_size(&self, max_size: Option) { + // XXX not applicable to wrapped surfaces + } + + fn set_surface_resize_increments( + &self, + increments: Option, + ) { + log::warn!( + "`set_surface_resize_increments` is not implemented for Wayland" + ) + } + + fn set_title(&self, title: &str) { + // XXX not applicable to wrapped surfaces + } + + fn set_transparent(&self, transparent: bool) { + todo!() + } + + fn rwh_06_display_handle( + &self, + ) -> &dyn raw_window_handle::HasDisplayHandle { + self + } + + fn rwh_06_window_handle(&self) -> &dyn raw_window_handle::HasWindowHandle { + self + } + + fn current_monitor(&self) -> Option { + todo!() + } + + fn available_monitors( + &self, + ) -> Box> { + todo!() + } + + fn has_focus(&self) -> bool { + todo!() + } + + fn set_ime_cursor_area( + &self, + position: winit::dpi::Position, + size: winit::dpi::Size, + ) { + todo!() + } + + fn set_ime_allowed(&self, allowed: bool) { + todo!() + } + + fn set_ime_purpose(&self, purpose: winit::window::ImePurpose) { + todo!() + } + + fn set_blur(&self, blur: bool) { + // TODO + } + + fn set_visible(&self, visible: bool) {} + + fn is_visible(&self) -> Option { + None + } + + fn set_resizable(&self, resizable: bool) {} + + fn is_resizable(&self) -> bool { + false + } + + fn set_enabled_buttons(&self, buttons: winit::window::WindowButtons) { + // TODO v5 of xdg_shell. + } + + fn enabled_buttons(&self) -> winit::window::WindowButtons { + WindowButtons::all() + } + + fn set_minimized(&self, minimized: bool) { + // XXX not applicable to the wrapped surfaces + } + + fn is_minimized(&self) -> Option { + // XXX clients don't know whether they are minimized or not. + None + } + + fn set_maximized(&self, maximized: bool) { + // XXX can't minimize the wrapped surfaces + } + + fn is_maximized(&self) -> bool { + // XXX can't maximize the wrapped surfaces + false + } + + fn set_fullscreen(&self, fullscreen: Option) { + // XXX can't fullscreen the wrapped surfaces + } + + fn fullscreen(&self) -> Option { + // XXX can't fullscreen the wrapped surfaces + None + } + + fn set_decorations(&self, decorations: bool) { + // XXX no decorations supported for the wrapped surfaces + } + + fn is_decorated(&self) -> bool { + false + } + + fn set_window_level(&self, level: winit::window::WindowLevel) {} + + fn set_window_icon(&self, window_icon: Option) {} + + fn focus_window(&self) {} + + fn request_user_attention( + &self, + request_type: Option, + ) { + // XXX can't request attention on wrapped surfaces + } + + fn set_theme(&self, theme: Option) {} + + fn theme(&self) -> Option { + None + } + + fn set_content_protected(&self, protected: bool) {} + + fn title(&self) -> String { + String::new() + } + + fn show_window_menu(&self, _position: winit::dpi::Position) { + // XXX can't show window menu on wrapped surfaces + } + + fn primary_monitor(&self) -> Option { + None + } + + fn surface_resize_increments( + &self, + ) -> Option> { + None + } + + fn drag_window(&self) -> Result<(), winit::error::RequestError> { + Ok(()) + } + + fn drag_resize_window( + &self, + _direction: winit::window::ResizeDirection, + ) -> Result<(), winit::error::RequestError> { + Ok(()) + } + + fn set_cursor_hittest( + &self, + _hittest: bool, + ) -> Result<(), winit::error::RequestError> { + todo!() + } + + fn inner_position( + &self, + ) -> Result, winit::error::RequestError> + { + Err(RequestError::NotSupported(NotSupportedError::new( + "Not supported on wayland.", + ))) + } + + fn outer_position( + &self, + ) -> Result, winit::error::RequestError> + { + Err(RequestError::NotSupported(NotSupportedError::new( + "Not supported on wayland.", + ))) + } + + fn set_cursor_position( + &self, + position: winit::dpi::Position, + ) -> Result<(), winit::error::RequestError> { + todo!() + } + + fn set_cursor_grab( + &self, + mode: winit::window::CursorGrabMode, + ) -> Result<(), winit::error::RequestError> { + todo!() + } +} + +impl raw_window_handle::HasWindowHandle for SctkWinitWindow { + fn window_handle( + &self, + ) -> Result + { + let raw = raw_window_handle::WaylandWindowHandle::new({ + let ptr = self.surface.wl_surface().id().as_ptr(); + let Some(ptr) = std::ptr::NonNull::new(ptr as *mut _) else { + return Err(HandleError::Unavailable); + }; + ptr + }); + + unsafe { Ok(raw_window_handle::WindowHandle::borrow_raw(raw.into())) } + } +} + +impl raw_window_handle::HasDisplayHandle for SctkWinitWindow { + fn display_handle( + &self, + ) -> Result< + raw_window_handle::DisplayHandle<'_>, + raw_window_handle::HandleError, + > { + let raw = raw_window_handle::WaylandDisplayHandle::new({ + let ptr = self.display.id().as_ptr(); + std::ptr::NonNull::new(ptr as *mut _) + .expect("wl_proxy should never be null") + }); + + unsafe { Ok(raw_window_handle::DisplayHandle::borrow_raw(raw.into())) } + } +} diff --git a/winit/src/program.rs b/winit/src/program.rs index 8d1eec3af9..6a86252907 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -1,8 +1,11 @@ //! Create interactive, native cross-platform applications for WGPU. +#[path = "application/drag_resize.rs"] +mod drag_resize; mod state; mod window_manager; -pub use state::State; +pub use runtime::{default, Appearance, DefaultStyle}; +use winit::event_loop::OwnedDisplayHandle; use crate::conversion; use crate::core; @@ -10,8 +13,11 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::time::Instant; use crate::core::widget::operation; +use crate::core::widget::Operation; use crate::core::window; -use crate::core::{Color, Element, Point, Size, Theme}; +use crate::core::Clipboard as CoreClipboard; +use crate::core::Length; +use crate::core::{Element, Point, Size}; use crate::futures::futures::channel::mpsc; use crate::futures::futures::channel::oneshot; use crate::futures::futures::task; @@ -20,17 +26,28 @@ use crate::futures::subscription::{self, Subscription}; use crate::futures::{Executor, Runtime}; use crate::graphics; use crate::graphics::{compositor, Compositor}; +use crate::platform_specific; use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::Debug; use crate::runtime::{self, Action, Task}; use crate::{Clipboard, Error, Proxy, Settings}; +use dnd::DndSurface; +use dnd::Icon; +use iced_futures::core::widget::operation::search_id; +use iced_graphics::Viewport; +pub use state::State; +use window_clipboard::mime::ClipboardStoreData; +use winit::raw_window_handle::HasWindowHandle; -use window_manager::WindowManager; +pub(crate) use window_manager::WindowManager; use rustc_hash::FxHashMap; +use std::any::Any; use std::borrow::Cow; +use std::collections::HashMap; use std::mem::ManuallyDrop; use std::sync::Arc; +use std::time::Duration; /// An interactive, native, cross-platform, multi-windowed application. /// @@ -138,37 +155,6 @@ where } } -/// The appearance of a program. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Appearance { - /// The background [`Color`] of the application. - pub background_color: Color, - - /// The default text [`Color`] of the application. - pub text_color: Color, -} - -/// The default style of a [`Program`]. -pub trait DefaultStyle { - /// Returns the default style of a [`Program`]. - fn default_style(&self) -> Appearance; -} - -impl DefaultStyle for Theme { - fn default_style(&self) -> Appearance { - default(self) - } -} - -/// The default [`Appearance`] of a [`Program`] with the built-in [`Theme`]. -pub fn default(theme: &Theme) -> Appearance { - let palette = theme.extended_palette(); - - Appearance { - background_color: palette.background.base.color, - text_color: palette.background.base.text, - } -} /// Runs a [`Program`] with an executor, compositor, and the provided /// settings. pub fn run( @@ -187,11 +173,16 @@ where let mut debug = Debug::new(); debug.startup_started(); - let event_loop = EventLoop::with_user_event() - .build() - .expect("Create event loop"); + let event_loop = EventLoop::new().expect("Create event loop"); + #[cfg(feature = "wayland")] + let is_wayland = + winit::platform::wayland::EventLoopExtWayland::is_wayland(&event_loop); + #[cfg(not(feature = "wayland"))] + let is_wayland = false; - let (proxy, worker) = Proxy::new(event_loop.create_proxy()); + let (event_sender, event_receiver) = mpsc::unbounded(); + let (proxy, worker): (Proxy<

::Message>, _) = + Proxy::new(event_loop.create_proxy(), event_sender.clone()); let mut runtime = { let executor = @@ -223,7 +214,6 @@ where )); let (boot_sender, boot_receiver) = oneshot::channel(); - let (event_sender, event_receiver) = mpsc::unbounded(); let (control_sender, control_receiver) = mpsc::unbounded(); let instance = Box::pin(run_instance::( @@ -233,7 +223,8 @@ where debug, boot_receiver, event_receiver, - control_sender, + control_sender.clone(), + event_loop.owned_display_handle(), is_daemon, )); @@ -244,7 +235,7 @@ where context: task::Context<'static>, id: Option, boot: Option>, - sender: mpsc::UnboundedSender>>, + sender: mpsc::UnboundedSender>, receiver: mpsc::UnboundedReceiver, error: Option, @@ -258,6 +249,8 @@ where sender: oneshot::Sender>, fonts: Vec>, graphics_settings: graphics::Settings, + control_sender: mpsc::UnboundedSender, + is_wayland: bool, } let runner = Runner { @@ -268,6 +261,8 @@ where sender: boot_sender, fonts: settings.fonts, graphics_settings, + control_sender, + is_wayland, }), sender: event_sender, receiver: control_receiver, @@ -279,110 +274,34 @@ where canvas: None, }; - impl winit::application::ApplicationHandler> + impl winit::application::ApplicationHandler for Runner where Message: std::fmt::Debug, F: Future, C: Compositor + 'static, { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - let Some(BootConfig { - sender, - fonts, - graphics_settings, - }) = self.boot.take() - else { - return; - }; - - let window = { - let attributes = winit::window::WindowAttributes::default(); - - #[cfg(target_os = "windows")] - let attributes = { - use winit::platform::windows::WindowAttributesExtWindows; - attributes.with_drag_and_drop(false) - }; - - match event_loop.create_window(attributes.with_visible(false)) { - Ok(window) => Arc::new(window), - Err(error) => { - self.error = Some(Error::WindowCreationFailed(error)); - event_loop.exit(); - return; - } - } - }; - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - self.canvas = window.canvas(); - } - - let finish_boot = async move { - let mut compositor = - C::new(graphics_settings, window.clone()).await?; - - for font in fonts { - compositor.load_font(font); - } - - sender - .send(Boot { compositor }) - .ok() - .expect("Send boot event"); - - Ok::<_, graphics::Error>(()) - }; - - #[cfg(not(target_arch = "wasm32"))] - if let Err(error) = - crate::futures::futures::executor::block_on(finish_boot) - { - self.error = Some(Error::GraphicsCreationFailed(error)); - event_loop.exit(); - } - - #[cfg(target_arch = "wasm32")] - { - let is_booted = self.is_booted.clone(); - - wasm_bindgen_futures::spawn_local(async move { - finish_boot.await.expect("Finish boot!"); - - *is_booted.borrow_mut() = true; - }); - - event_loop - .set_control_flow(winit::event_loop::ControlFlow::Poll); - } + fn proxy_wake_up( + &mut self, + event_loop: &dyn winit::event_loop::ActiveEventLoop, + ) { + self.process_event(event_loop, None); } fn new_events( &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, + event_loop: &dyn winit::event_loop::ActiveEventLoop, cause: winit::event::StartCause, ) { if self.boot.is_some() { return; } - - #[cfg(target_arch = "wasm32")] - if !*self.is_booted.borrow() { - return; - } - - self.process_event( - event_loop, - Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)), - ); + self.process_event(event_loop, Some(Event::NewEvents(cause))); } fn window_event( &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, + event_loop: &dyn winit::event_loop::ActiveEventLoop, window_id: winit::window::WindowId, event: winit::event::WindowEvent, ) { @@ -395,10 +314,7 @@ where self.process_event( event_loop, - Event::EventLoopAwakened(winit::event::Event::WindowEvent { - window_id, - event, - }), + Some(Event::Winit(window_id, event)), ); // TODO: Remove when unnecessary @@ -418,49 +334,91 @@ where } } - fn user_event( + fn about_to_wait( &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - action: Action, + event_loop: &dyn winit::event_loop::ActiveEventLoop, ) { - self.process_event( - event_loop, - Event::EventLoopAwakened(winit::event::Event::UserEvent( - action, - )), - ); + self.process_event(event_loop, Some(Event::AboutToWait)); } - fn received_url( + fn can_create_surfaces( &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - url: String, + event_loop: &dyn winit::event_loop::ActiveEventLoop, ) { - self.process_event( - event_loop, - Event::EventLoopAwakened( - winit::event::Event::PlatformSpecific( - winit::event::PlatformSpecific::MacOS( - winit::event::MacOS::ReceivedUrl(url), - ), - ), - ), - ); - } + // create initial window + let Some(BootConfig { + sender, + fonts, + graphics_settings, + control_sender, + is_wayland, + }) = self.boot.take() + else { + return; + }; + + // XXX what to do if the program is a daemon? + // Can we avoid creating a useless window that hangs around forever? + + let window: Arc = match event_loop + .create_window( + winit::window::WindowAttributes::default() + .with_visible(false), + ) { + Ok(window) => Arc::from(window), + Err(error) => { + self.error = Some(Error::WindowCreationFailed(error)); + event_loop.exit(); + return; + } + }; - fn about_to_wait( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - ) { #[cfg(target_arch = "wasm32")] - if !*self.is_booted.borrow() { - return; + { + use winit::platform::web::WindowExtWebSys; + self.canvas = window.canvas(); } - self.process_event( - event_loop, - Event::EventLoopAwakened(winit::event::Event::AboutToWait), - ); + let finish_boot = async move { + let mut compositor = + C::new(graphics_settings, window.clone()).await?; + + for font in fonts { + compositor.load_font(font); + } + + sender + .send(Boot { + compositor, + is_wayland, + }) + .ok() + .expect("Send boot event"); + + Ok::<_, graphics::Error>(()) + }; + + #[cfg(not(target_arch = "wasm32"))] + if let Err(error) = + crate::futures::futures::executor::block_on(finish_boot) + { + self.error = Some(Error::GraphicsCreationFailed(error)); + event_loop.exit(); + } + + #[cfg(target_arch = "wasm32")] + { + let is_booted = self.is_booted.clone(); + + wasm_bindgen_futures::spawn_local(async move { + finish_boot.await.expect("Finish boot!"); + + *is_booted.borrow_mut() = true; + }); + + event_loop + .set_control_flow(winit::event_loop::ControlFlow::Poll); + } } } @@ -471,14 +429,16 @@ where { fn process_event( &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - event: Event>, + event_loop: &dyn winit::event_loop::ActiveEventLoop, + event: Option>, ) { if event_loop.exiting() { return; } - self.sender.start_send(event).expect("Send event"); + if let Some(event) = event { + self.sender.start_send(event).expect("Send event"); + } loop { let poll = self.instance.as_mut().poll(&mut self.context); @@ -512,6 +472,7 @@ where } => { let exit_on_close_request = settings.exit_on_close_request; + let resize_border = settings.resize_border; let visible = settings.visible; @@ -538,9 +499,11 @@ where log::info!("Window attributes for id `{id:#?}`: {window_attributes:#?}"); - let window = event_loop - .create_window(window_attributes) - .expect("Create window"); + let window = Arc::from( + event_loop + .create_window(window_attributes) + .expect("Create window"), + ); #[cfg(target_arch = "wasm32")] { @@ -590,18 +553,56 @@ where self.process_event( event_loop, - Event::WindowCreated { + Some(Event::WindowCreated { id, window, exit_on_close_request, make_visible: visible, on_open, - }, + resize_border, + }), ); } Control::Exit => { event_loop.exit(); } + Control::Dnd(e) => { + self.sender.start_send(Event::Dnd(e)).unwrap(); + } + #[cfg(feature = "a11y")] + Control::Accessibility(id, event) => { + self.process_event( + event_loop, + Some(Event::Accessibility(id, event)), + ); + } + #[cfg(feature = "a11y")] + Control::AccessibilityEnabled(event) => { + self.process_event( + event_loop, + Some(Event::AccessibilityEnabled(event)), + ); + } + Control::PlatformSpecific(e) => { + self.sender + .start_send(Event::PlatformSpecific(e)) + .unwrap(); + } + Control::AboutToWait => { + self.sender + .start_send(Event::AboutToWait) + .expect("Send event"); + } + Control::Winit(id, e) => { + self.sender + .start_send(Event::Winit(id, e)) + .expect("Send event"); + } + Control::StartDnd => { + self.sender + .start_send(Event::StartDnd) + .expect("Send event"); + } }, _ => { break; @@ -635,22 +636,32 @@ where struct Boot { compositor: C, + is_wayland: bool, } -#[derive(Debug)] -enum Event { +pub(crate) enum Event { WindowCreated { id: window::Id, - window: winit::window::Window, + window: Arc, exit_on_close_request: bool, make_visible: bool, on_open: oneshot::Sender, + resize_border: u32, }, - EventLoopAwakened(winit::event::Event), + Dnd(dnd::DndEvent), + #[cfg(feature = "a11y")] + Accessibility(window::Id, iced_accessibility::accesskit::ActionRequest), + #[cfg(feature = "a11y")] + AccessibilityEnabled(bool), + Winit(winit::window::WindowId, winit::event::WindowEvent), + AboutToWait, + UserEvent(Action), + NewEvents(winit::event::StartCause), + PlatformSpecific(crate::platform_specific::Event), + StartDnd, } -#[derive(Debug)] -enum Control { +pub(crate) enum Control { ChangeFlow(winit::event_loop::ControlFlow), Exit, CreateWindow { @@ -660,16 +671,26 @@ enum Control { monitor: Option, on_open: oneshot::Sender, }, + Dnd(dnd::DndEvent), + #[cfg(feature = "a11y")] + Accessibility(window::Id, iced_accessibility::accesskit::ActionRequest), + #[cfg(feature = "a11y")] + AccessibilityEnabled(bool), + PlatformSpecific(crate::platform_specific::Event), + AboutToWait, + Winit(winit::window::WindowId, winit::event::WindowEvent), + StartDnd, } -async fn run_instance( +async fn run_instance<'a, P, C>( mut program: P, mut runtime: Runtime, Action>, mut proxy: Proxy, mut debug: Debug, boot: oneshot::Receiver>, - mut event_receiver: mpsc::UnboundedReceiver>>, + mut event_receiver: mpsc::UnboundedReceiver>, mut control_sender: mpsc::UnboundedSender, + display_handle: OwnedDisplayHandle, is_daemon: bool, ) where P: Program + 'static, @@ -679,7 +700,21 @@ async fn run_instance( use winit::event; use winit::event_loop::ControlFlow; - let Boot { mut compositor } = boot.await.expect("Receive boot"); + let Boot { + mut compositor, + is_wayland, + } = boot.await.expect("Receive boot"); + + let mut platform_specific_handler = + crate::platform_specific::PlatformSpecific::default(); + #[cfg(all(feature = "wayland", target_os = "linux"))] + if is_wayland { + platform_specific_handler = platform_specific_handler.with_wayland( + control_sender.clone(), + proxy.raw.clone(), + display_handle, + ); + } let mut window_manager = WindowManager::new(); let mut is_window_opening = !is_daemon; @@ -688,17 +723,76 @@ async fn run_instance( let mut messages = Vec::new(); let mut actions = 0; + #[cfg(feature = "a11y")] + let (mut adapters, mut a11y_enabled) = if let Some((main_id, title, raw)) = + window_manager.ids().next().and_then(|id| { + window_manager + .get(id) + .map(|w| (id, w.state.title.clone(), w.raw.clone())) + }) { + let node_id = core::id::window_node_id(); + use crate::a11y::*; + use iced_accessibility::accesskit::{ + ActivationHandler, NodeBuilder, NodeId, Role, Tree, TreeUpdate, + }; + use iced_accessibility::accesskit_winit::Adapter; + + let activation_handler = WinitActivationHandler { + proxy: control_sender.clone(), + title: title.clone(), + }; + + let action_handler = WinitActionHandler { + id: main_id, + proxy: control_sender.clone(), + }; + + let deactivation_handler = WinitDeactivationHandler { + proxy: control_sender.clone(), + }; + ( + HashMap::from([( + main_id, + ( + node_id, + Adapter::with_direct_handlers( + raw.as_ref(), + activation_handler, + action_handler, + deactivation_handler, + ), + ), + )]), + false, + ) + } else { + (Default::default(), false) + }; + let mut ui_caches = FxHashMap::default(); - let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); + let mut user_interfaces: ManuallyDrop< + HashMap< + window::Id, + UserInterface< + '_, +

::Message, +

::Theme, +

::Renderer, + >, + rustc_hash::FxBuildHasher, + >, + > = ManuallyDrop::new(FxHashMap::default()); let mut clipboard = Clipboard::unconnected(); - debug.startup_finished(); + let mut cur_dnd_surface: Option = None; + debug.startup_finished(); loop { // Empty the queue if possible let event = if let Ok(event) = event_receiver.try_next() { event } else { + platform_specific_handler.send_ready(); event_receiver.next().await }; @@ -707,20 +801,254 @@ async fn run_instance( }; match event { + Event::StartDnd => { + let queued = clipboard.get_queued(); + for crate::clipboard::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + } in queued + { + let Some(window_id) = source_surface.and_then(|source| { + match source { + core::clipboard::DndSource::Surface(s) => Some(s), + core::clipboard::DndSource::Widget(w) => { + // search windows for widget with operation + user_interfaces.iter_mut().find_map( + |(ui_id, ui)| { + let Some(ui_renderer) = window_manager + .get_mut(ui_id.clone()) + .map(|w| &w.renderer) + else { + return None; + }; + + let operation: Box> = + Box::new(operation::map( + Box::new(search_id::search_id( + w.clone(), + )), + |_| {}, + )); + let mut current_operation = + Some(operation); + + while let Some(mut operation) = + current_operation.take() + { + ui.operate( + ui_renderer, + operation.as_mut(), + ); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some( + (), + ) => { + return Some(ui_id.clone()); + } + operation::Outcome::Chain( + next, + ) => { + current_operation = + Some(next); + } + } + } + None + }, + ) + } + } + }) else { + eprintln!("No source surface"); + continue; + }; + + let Some(window) = window_manager.get_mut(window_id) else { + eprintln!("No window"); + continue; + }; + + let state = &window.state; + let icon_surface = icon_surface + .map(|i| { + let i: Box = i; + i + }) + .map(|i| { + i.downcast::, + core::widget::tree::State, + )>>() + .unwrap() + }) + .map( + |e: Box< + Arc<( + core::Element< + 'static, + (), + P::Theme, + P::Renderer, + >, + core::widget::tree::State, + )>, + >| { + let mut renderer = compositor.create_renderer(); + + let e = Arc::into_inner(*e).unwrap(); + let (mut e, widget_state) = e; + let lim = core::layout::Limits::new( + Size::new(1., 1.), + Size::new( + state.viewport().physical_width() + as f32, + state.viewport().physical_height() + as f32, + ), + ); + + let mut tree = core::widget::Tree { + id: e.as_widget().id(), + tag: e.as_widget().tag(), + state: widget_state, + children: e.as_widget().children(), + }; + + let size = e + .as_widget() + .layout(&mut tree, &renderer, &lim); + e.as_widget_mut().diff(&mut tree); + + let size = lim.resolve( + Length::Shrink, + Length::Shrink, + size.size(), + ); + let viewport = Viewport::with_logical_size( + size, + state.viewport().scale_factor(), + ); + let mut surface = compositor.create_surface( + window.raw.clone(), + viewport.physical_width(), + viewport.physical_height(), + ); + + let mut ui = UserInterface::build( + e, + size, + user_interface::Cache::default(), + &mut renderer, + ); + _ = ui.draw( + &mut renderer, + state.theme(), + &renderer::Style { + icon_color: state.icon_color(), + text_color: state.text_color(), + scale_factor: state.scale_factor(), + }, + Default::default(), + ); + let mut bytes = compositor.screenshot( + &mut renderer, + &mut surface, + &viewport, + core::Color::TRANSPARENT, + &debug.overlay(), + ); + for pix in bytes.chunks_exact_mut(4) { + // rgba -> argb little endian + pix.swap(0, 2); + } + Icon::Buffer { + data: Arc::new(bytes), + width: viewport.physical_width(), + height: viewport.physical_height(), + transparent: true, + } + }, + ); + + clipboard.start_dnd_winit( + internal, + DndSurface(Arc::new(Box::new(window.raw.clone()))), + icon_surface, + content, + actions, + ); + } + } Event::WindowCreated { id, window, exit_on_close_request, make_visible, on_open, + resize_border, } => { let window = window_manager.insert( id, - Arc::new(window), + window, &program, &mut compositor, exit_on_close_request, + resize_border, + ); + #[cfg(feature = "wayland")] + platform_specific_handler.send_wayland( + platform_specific::Action::TrackWindow( + window.raw.clone(), + id, + ), ); + #[cfg(feature = "a11y")] + { + use crate::a11y::*; + use iced_accessibility::accesskit::{ + ActivationHandler, NodeBuilder, NodeId, Role, Tree, + TreeUpdate, + }; + use iced_accessibility::accesskit_winit::Adapter; + + let node_id = core::id::window_node_id(); + + let activation_handler = WinitActivationHandler { + proxy: control_sender.clone(), + title: window.state.title.clone(), + }; + + let action_handler = WinitActionHandler { + id, + proxy: control_sender.clone(), + }; + + let deactivation_handler = WinitDeactivationHandler { + proxy: control_sender.clone(), + }; + _ = adapters.insert( + id, + ( + node_id, + Adapter::with_direct_handlers( + window.raw.as_ref(), + activation_handler, + action_handler, + deactivation_handler, + ), + ), + ); + } let logical_size = window.state.logical_size(); @@ -733,6 +1061,9 @@ async fn run_instance( logical_size, &mut debug, id, + window.raw.clone(), + window.prev_dnd_destination_rectangles_count, + &mut clipboard, ), ); let _ = ui_caches.insert(id, user_interface::Cache::default()); @@ -742,7 +1073,7 @@ async fn run_instance( } events.push(( - id, + Some(id), core::Event::Window(window::Event::Opened { position: window.position(), size: window.size(), @@ -750,59 +1081,53 @@ async fn run_instance( )); if clipboard.window_id().is_none() { - clipboard = Clipboard::connect(window.raw.clone()); + clipboard = Clipboard::connect( + window.raw.clone(), + crate::clipboard::ControlSender { + sender: control_sender.clone(), + proxy: proxy.raw.clone(), + }, + ); } let _ = on_open.send(id); is_window_opening = false; } - Event::EventLoopAwakened(event) => { + Event::UserEvent(action) => { + run_action( + action, + &program, + &mut compositor, + &mut events, + &mut messages, + &mut clipboard, + &mut control_sender, + &mut debug, + &mut user_interfaces, + &mut window_manager, + &mut ui_caches, + &mut is_window_opening, + &mut platform_specific_handler, + ); + actions += 1; + } + Event::NewEvents( + event::StartCause::Init + | event::StartCause::ResumeTimeReached { .. }, + ) => { + if window_manager.ids().next().is_none() { + _ = control_sender + .start_send(Control::ChangeFlow(ControlFlow::Wait)); + } + for (_id, window) in window_manager.iter_mut() { + window.request_redraw(); + } + } + Event::Winit(window_id, event) => { match event { - event::Event::NewEvents( - event::StartCause::Init - | event::StartCause::ResumeTimeReached { .. }, - ) => { - for (_id, window) in window_manager.iter_mut() { - window.raw.request_redraw(); - } - } - event::Event::PlatformSpecific( - event::PlatformSpecific::MacOS( - event::MacOS::ReceivedUrl(url), - ), - ) => { - runtime.broadcast( - subscription::Event::PlatformSpecific( - subscription::PlatformSpecific::MacOS( - subscription::MacOS::ReceivedUrl(url), - ), - ), - ); - } - event::Event::UserEvent(action) => { - run_action( - action, - &program, - &mut compositor, - &mut events, - &mut messages, - &mut clipboard, - &mut control_sender, - &mut debug, - &mut user_interfaces, - &mut window_manager, - &mut ui_caches, - &mut is_window_opening, - ); - actions += 1; - } - event::Event::WindowEvent { - window_id: id, - event: event::WindowEvent::RedrawRequested, - .. - } => { + event::WindowEvent::RedrawRequested => { let Some((id, window)) = - window_manager.get_mut_alias(id) + window_manager.get_mut_alias(window_id) else { continue; }; @@ -835,18 +1160,32 @@ async fn run_instance( &mut window.renderer, window.state.theme(), &renderer::Style { + icon_color: window.state.icon_color(), text_color: window.state.text_color(), + scale_factor: window.state.scale_factor(), }, cursor, ); + platform_specific_handler + .update_subsurfaces(id, window.raw.as_ref()); debug.draw_finished(); if new_mouse_interaction != window.mouse_interaction { - window.raw.set_cursor( + if let Some(interaction) = conversion::mouse_interaction( new_mouse_interaction, - ), - ); + ) + { + if matches!( + window.mouse_interaction, + mouse::Interaction::Hide + ) { + window.raw.set_cursor_visible(true); + } + window.raw.set_cursor(interaction.into()) + } else { + window.raw.set_cursor_visible(false); + } window.mouse_interaction = new_mouse_interaction; } @@ -857,13 +1196,13 @@ async fn run_instance( status: core::event::Status::Ignored, }); - let _ = control_sender.start_send(Control::ChangeFlow( - match ui_state { + if control_sender + .start_send(Control::ChangeFlow(match ui_state { user_interface::State::Updated { redraw_request: Some(redraw_request), } => match redraw_request { window::RedrawRequest::NextFrame => { - window.raw.request_redraw(); + window.request_redraw(); ControlFlow::Wait } @@ -872,30 +1211,121 @@ async fn run_instance( } }, _ => ControlFlow::Wait, - }, - )); + })) + .is_err() + { + panic!("send error"); + } let physical_size = window.state.physical_size(); - if physical_size.width == 0 || physical_size.height == 0 { continue; } - if window.viewport_version != window.state.viewport_version() { let logical_size = window.state.logical_size(); - debug.layout_started(); - let ui = user_interfaces + let mut ui = user_interfaces .remove(&id) - .expect("Remove user interface"); + .expect("Remove user interface") + .relayout(logical_size, &mut window.renderer); - let _ = user_interfaces.insert( - id, - ui.relayout(logical_size, &mut window.renderer), - ); + #[cfg(feature = "a11y")] + { + use iced_accessibility::{ + accesskit::{ + NodeBuilder, NodeId, Role, Tree, + TreeUpdate, + }, + A11yId, A11yNode, A11yTree, + }; + if let Some(Some((a11y_id, adapter))) = + a11y_enabled.then(|| adapters.get_mut(&id)) + { + // TODO cleanup duplication + let child_tree = + ui.a11y_nodes(window.state.cursor()); + let mut root = + NodeBuilder::new(Role::Window); + root.set_name( + window.state.title.to_string(), + ); + let window_tree = + A11yTree::node_with_child_tree( + A11yNode::new(root, *a11y_id), + child_tree, + ); + let tree = Tree::new(NodeId(*a11y_id)); + + let focus = + Arc::new(std::sync::Mutex::new(None)); + let focus_clone = focus.clone(); + let operation: Box> = + Box::new(operation::map( + Box::new( + operation::focusable::find_focused( + ), + ), + move |id| { + let mut guard = focus.lock().unwrap(); + _ = guard.replace(id); + }, + )); + let mut current_operation = Some(operation); + + while let Some(mut operation) = + current_operation.take() + { + ui.operate( + &window.renderer, + operation.as_mut(), + ); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(()) => { + break; + } + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } + } + } + let mut guard = focus_clone.lock().unwrap(); + let focus = guard + .take() + .map(|id| A11yId::Widget(id)); + tracing::debug!( + "focus: {:?}\ntree root: {:?}\n children: {:?}", + &focus, + window_tree + .root() + .iter() + .map(|n| (n.node().role(), n.id())) + .collect::>(), + window_tree + .children() + .iter() + .map(|n| (n.node().role(), n.id())) + .collect::>() + ); + let focus = focus + .filter(|f_id| { + window_tree.contains(f_id) + }) + .map(|id| id.into()) + .unwrap_or_else(|| tree.root); + adapter.update_if_active(|| TreeUpdate { + nodes: window_tree.into(), + tree: Some(tree), + focus, + }); + } + } + + let _ = user_interfaces.insert(id, ui); debug.layout_finished(); debug.draw_started(); @@ -906,24 +1336,39 @@ async fn run_instance( &mut window.renderer, window.state.theme(), &renderer::Style { + icon_color: window.state.icon_color(), text_color: window.state.text_color(), + scale_factor: window + .state + .scale_factor(), }, window.state.cursor(), ); + platform_specific_handler + .update_subsurfaces(id, window.raw.as_ref()); debug.draw_finished(); if new_mouse_interaction != window.mouse_interaction { - window.raw.set_cursor( + if let Some(interaction) = conversion::mouse_interaction( new_mouse_interaction, - ), - ); + ) + { + if matches!( + window.mouse_interaction, + mouse::Interaction::Hide + ) { + window.raw.set_cursor_visible(true); + } + window.raw.set_cursor(interaction.into()) + } else { + window.raw.set_cursor_visible(false); + } window.mouse_interaction = new_mouse_interaction; } - compositor.configure_surface( &mut window.surface, physical_size.width, @@ -934,6 +1379,7 @@ async fn run_instance( window.state.viewport_version(); } + window.raw.pre_present_notify(); debug.render_started(); match compositor.present( &mut window.renderer, @@ -945,33 +1391,45 @@ async fn run_instance( Ok(()) => { debug.render_finished(); } - Err(error) => match error { - // This is an unrecoverable error. - compositor::SurfaceError::OutOfMemory => { - panic!("{:?}", error); - } - _ => { - debug.render_finished(); - - log::error!( - "Error {error:?} when \ + Err(error) => { + match error { + // This is an unrecoverable error. + compositor::SurfaceError::OutOfMemory => { + panic!("{:?}", error); + } + compositor::SurfaceError::NoDamage => { + debug.render_finished(); + + // TODO Ideally there would be a way to know if some widget wants to animate? + let _ = control_sender.start_send( + Control::ChangeFlow( + ControlFlow::WaitUntil( + Instant::now().checked_add( + Duration::from_millis(100), + ).unwrap_or(Instant::now()), + ), + ), + ); + } + _ => { + debug.render_finished(); + log::error!( + "Error {error:?} when \ presenting surface." - ); + ); - // Try rendering all windows again next frame. - for (_id, window) in - window_manager.iter_mut() - { - window.raw.request_redraw(); + // Try rendering all windows again next frame. + for (_id, window) in + window_manager.iter_mut() + { + window.request_redraw(); + } } } - }, + } } } - event::Event::WindowEvent { - event: window_event, - window_id, - } => { + window_event => { if !is_daemon && matches!( window_event, @@ -993,6 +1451,15 @@ async fn run_instance( continue; }; + // Initiates a drag resize window state when found. + if let Some(func) = + window.drag_resize_window_func.as_mut() + { + if func(window.raw.as_ref(), &window_event) { + continue; + } + } + if matches!( window_event, winit::event::WindowEvent::CloseRequested @@ -1013,135 +1480,346 @@ async fn run_instance( &mut window_manager, &mut ui_caches, &mut is_window_opening, + &mut platform_specific_handler, ); } else { window.state.update( - &window.raw, + window.raw.as_ref(), &window_event, &mut debug, ); - if let Some(event) = conversion::window_event( window_event, window.state.scale_factor(), window.state.modifiers(), ) { - events.push((id, event)); + events.push((Some(id), event)); } } } - event::Event::AboutToWait => { - if events.is_empty() && messages.is_empty() { - continue; + _ => {} + } + } + Event::AboutToWait => { + let skip = events.is_empty() && messages.is_empty(); + if skip + && window_manager.iter_mut().all(|(_, w)| !w.resize_enabled) + { + continue; + } + + debug.event_processing_started(); + let mut uis_stale = false; + let mut resized = false; + for (id, window) in window_manager.iter_mut() { + if skip && !window.resize_enabled { + continue; + } + let mut window_events = vec![]; + + events.retain(|(window_id, event)| { + if *window_id == Some(id) { + window_events.push(event.clone()); + false + } else { + true } + }); + let no_window_events = window_events.is_empty(); + #[cfg(feature = "wayland")] + window_events.push(core::Event::PlatformSpecific( + core::event::PlatformSpecific::Wayland( + core::event::wayland::Event::RequestResize, + ), + )); + let (ui_state, statuses) = user_interfaces + .get_mut(&id) + .expect("Get user interface") + .update( + &window_events, + window.state.cursor(), + &mut window.renderer, + &mut clipboard, + &mut messages, + ); - debug.event_processing_started(); - let mut uis_stale = false; + let mut needs_redraw = + !no_window_events || !messages.is_empty(); - for (id, window) in window_manager.iter_mut() { - let mut window_events = vec![]; + if let Some(requested_size) = + clipboard.requested_logical_size.lock().unwrap().take() + { + let requested_physical_size = + winit::dpi::PhysicalSize::new( + (requested_size.width as f64 + * window.state.scale_factor()) + .ceil() as u32, + (requested_size.height as f64 + * window.state.scale_factor()) + .ceil() as u32, + ); + let physical_size = window.state.physical_size(); + if requested_physical_size.width != physical_size.width + || requested_physical_size.height + != physical_size.height + { + // FIXME what to do when we are stuck in a configure event/resize request loop + // We don't have control over how winit handles this. + window.resize_enabled = true; + resized = true; + needs_redraw = true; + let s = winit::dpi::Size::Physical( + requested_physical_size, + ); + _ = window.raw.request_surface_size(s); + window.raw.set_min_surface_size(Some(s)); + window.raw.set_max_surface_size(Some(s)); + window.state.synchronize( + &program, + id, + window.raw.as_ref(), + ); + } + } + if needs_redraw { + window.request_redraw(); + } else { + continue; + } - events.retain(|(window_id, event)| { - if *window_id == id { - window_events.push(event.clone()); - false - } else { - true - } - }); + if !uis_stale { + uis_stale = + matches!(ui_state, user_interface::State::Outdated); + } - if window_events.is_empty() && messages.is_empty() { - continue; - } + for (event, status) in + window_events.into_iter().zip(statuses.into_iter()) + { + runtime.broadcast(subscription::Event::Interaction { + window: id, + event, + status, + }); + } + } - let (ui_state, statuses) = user_interfaces - .get_mut(&id) - .expect("Get user interface") - .update( - &window_events, - window.state.cursor(), - &mut window.renderer, - &mut clipboard, - &mut messages, - ); + if !resized && skip { + continue; + } - window.raw.request_redraw(); + for (id, event) in events.drain(..) { + runtime.broadcast(subscription::Event::Interaction { + window: id.unwrap_or(window::Id::NONE), + event, + status: core::event::Status::Ignored, + }); + } - if !uis_stale { - uis_stale = matches!( - ui_state, - user_interface::State::Outdated - ); - } + debug.event_processing_finished(); - for (event, status) in window_events - .into_iter() - .zip(statuses.into_iter()) - { - runtime.broadcast( - subscription::Event::Interaction { - window: id, - event, - status, - }, - ); - } - } + if !messages.is_empty() || uis_stale { + let cached_interfaces: FxHashMap< + window::Id, + user_interface::Cache, + > = ManuallyDrop::into_inner(user_interfaces) + .drain() + .map(|(id, ui)| (id, ui.into_cache())) + .collect(); - for (id, event) in events.drain(..) { - runtime.broadcast( - subscription::Event::Interaction { - window: id, - event, - status: core::event::Status::Ignored, - }, - ); - } + update( + &mut program, + &mut runtime, + &mut debug, + &mut messages, + ); - debug.event_processing_finished(); + for (id, window) in window_manager.iter_mut() { + window.state.synchronize( + &program, + id, + window.raw.as_ref(), + ); - if !messages.is_empty() || uis_stale { - let cached_interfaces: FxHashMap< - window::Id, - user_interface::Cache, - > = ManuallyDrop::into_inner(user_interfaces) - .drain() - .map(|(id, ui)| (id, ui.into_cache())) - .collect(); + window.request_redraw(); + } - update( - &mut program, - &mut runtime, - &mut debug, - &mut messages, - ); + user_interfaces = ManuallyDrop::new(build_user_interfaces( + &program, + &mut debug, + &mut window_manager, + cached_interfaces, + &mut clipboard, + )); - for (id, window) in window_manager.iter_mut() { - window.state.synchronize( - &program, - id, - &window.raw, - ); + if actions > 0 { + proxy.free_slots(actions); + actions = 0; + } + } - window.raw.request_redraw(); - } + debug.draw_started(); - user_interfaces = - ManuallyDrop::new(build_user_interfaces( - &program, - &mut debug, - &mut window_manager, - cached_interfaces, - )); - - if actions > 0 { - proxy.free_slots(actions); - actions = 0; + for (id, window) in window_manager.iter_mut() { + // TODO: Avoid redrawing all the time by forcing widgets to + // request redraws on state changes + // + // Then, we can use the `interface_state` here to decide if a redraw + // is needed right away, or simply wait until a specific time. + let redraw_event = core::Event::Window( + window::Event::RedrawRequested(Instant::now()), + ); + + let cursor = window.state.cursor(); + + let ui = user_interfaces + .get_mut(&id) + .expect("Get user interface"); + + let (ui_state, _) = ui.update( + &[redraw_event.clone()], + cursor, + &mut window.renderer, + &mut clipboard, + &mut messages, + ); + + let new_mouse_interaction = { + let state = &window.state; + + ui.draw( + &mut window.renderer, + state.theme(), + &renderer::Style { + icon_color: state.icon_color(), + text_color: state.text_color(), + scale_factor: state.scale_factor(), + }, + cursor, + ) + }; + + if new_mouse_interaction != window.mouse_interaction { + if let Some(interaction) = + conversion::mouse_interaction(new_mouse_interaction) + { + if matches!( + window.mouse_interaction, + mouse::Interaction::Hide + ) { + window.raw.set_cursor_visible(true); } + window.raw.set_cursor(interaction.into()) + } else { + window.raw.set_cursor_visible(false); + } + + window.mouse_interaction = new_mouse_interaction; + } + + // TODO once widgets can request to be redrawn, we can avoid always requesting a + // redraw + window.request_redraw(); + runtime.broadcast(subscription::Event::Interaction { + window: id, + event: redraw_event, + status: core::event::Status::Ignored, + }); + + let _ = control_sender.start_send(Control::ChangeFlow( + match ui_state { + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + window::RedrawRequest::NextFrame => { + window.request_redraw(); + + ControlFlow::Wait + } + window::RedrawRequest::At(at) => { + ControlFlow::WaitUntil(at) + } + }, + _ => ControlFlow::Wait, + }, + )); + } + + debug.draw_finished(); + } + + Event::Dnd(e) => { + match &e { + dnd::DndEvent::Offer(_, dnd::OfferEvent::Leave) => { + events.push((cur_dnd_surface, core::Event::Dnd(e))); + // XXX can't clear the dnd surface on leave because + // the data event comes after + // cur_dnd_surface = None; + } + dnd::DndEvent::Offer( + _, + dnd::OfferEvent::Enter { surface, .. }, + ) => { + let window_handle = surface.0.window_handle().ok(); + let window_id = window_manager.iter_mut().find_map( + |(id, window)| { + if window + .raw + .window_handle() + .ok() + .zip(window_handle) + .map(|(a, b)| a == b) + .unwrap_or_default() + { + Some(id) + } else { + None + } + }, + ); + cur_dnd_surface = window_id; + events.push((cur_dnd_surface, core::Event::Dnd(e))); + } + dnd::DndEvent::Offer(..) => { + events.push((cur_dnd_surface, core::Event::Dnd(e))); + } + dnd::DndEvent::Source(_) => { + for w in window_manager.ids() { + events.push((Some(w), core::Event::Dnd(e.clone()))); } } + }; + } + #[cfg(feature = "a11y")] + Event::Accessibility(id, e) => { + match e.action { + iced_accessibility::accesskit::Action::Focus => { + // TODO send a command for this + } _ => {} } + events.push((Some(id), conversion::a11y(e))); + } + #[cfg(feature = "a11y")] + Event::AccessibilityEnabled(enabled) => { + a11y_enabled = enabled; + } + Event::PlatformSpecific(e) => { + crate::platform_specific::handle_event( + e, + &mut events, + &mut platform_specific_handler, + &program, + &mut compositor, + &mut window_manager, + &mut debug, + &mut user_interfaces, + &mut clipboard, + #[cfg(feature = "a11y")] + &mut adapters, + ); + } + _ => { + // log ignored events? } } } @@ -1150,13 +1828,16 @@ async fn run_instance( } /// Builds a window's [`UserInterface`] for the [`Program`]. -fn build_user_interface<'a, P: Program>( +pub(crate) fn build_user_interface<'a, P: Program>( program: &'a P, cache: user_interface::Cache, renderer: &mut P::Renderer, size: Size, debug: &mut Debug, id: window::Id, + raw: Arc, + prev_dnd_destination_rectangles_count: usize, + clipboard: &mut Clipboard, ) -> UserInterface<'a, P::Message, P::Theme, P::Renderer> where P::Theme: DefaultStyle, @@ -1169,6 +1850,17 @@ where let user_interface = UserInterface::build(view, size, cache, renderer); debug.layout_finished(); + let dnd_rectangles = user_interface + .dnd_rectangles(prev_dnd_destination_rectangles_count, renderer); + let new_dnd_rectangles_count = dnd_rectangles.as_ref().len(); + if new_dnd_rectangles_count > 0 || prev_dnd_destination_rectangles_count > 0 + { + clipboard.register_dnd_destination( + DndSurface(Arc::new(Box::new(raw.clone()))), + dnd_rectangles.into_rectangles(), + ); + } + user_interface } @@ -1200,7 +1892,7 @@ fn run_action( action: Action, program: &P, compositor: &mut C, - events: &mut Vec<(window::Id, core::Event)>, + events: &mut Vec<(Option, core::Event)>, messages: &mut Vec, clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender, @@ -1212,6 +1904,7 @@ fn run_action( window_manager: &mut WindowManager, ui_caches: &mut FxHashMap, is_window_opening: &mut bool, + platform_specific: &mut crate::platform_specific::PlatformSpecific, ) where P: Program, C: Compositor + 'static, @@ -1232,6 +1925,13 @@ fn run_action( clipboard::Action::Write { target, contents } => { clipboard.write(target, contents); } + clipboard::Action::WriteData(contents, kind) => { + clipboard.write_data(kind, ClipboardStoreData(contents)) + } + clipboard::Action::ReadData(allowed, tx, kind) => { + let contents = clipboard.read_data(kind, allowed); + _ = tx.send(contents); + } }, Action::Window(action) => match action { window::Action::Open(id, settings, channel) => { @@ -1252,18 +1952,35 @@ fn run_action( window::Action::Close(id) => { let _ = ui_caches.remove(&id); let _ = interfaces.remove(&id); + #[cfg(feature = "wayland")] + platform_specific + .send_wayland(platform_specific::Action::RemoveWindow(id)); if let Some(window) = window_manager.remove(id) { + clipboard.register_dnd_destination( + DndSurface(Arc::new(Box::new(window.raw.clone()))), + Vec::new(), + ); + let proxy = clipboard.proxy(); if clipboard.window_id() == Some(window.raw.id()) { *clipboard = window_manager .first() .map(|window| window.raw.clone()) - .map(Clipboard::connect) + .zip(proxy) + .map(|(w, proxy)| { + Clipboard::connect( + w, + crate::clipboard::ControlSender { + sender: control_sender.clone(), + proxy, + }, + ) + }) .unwrap_or_else(Clipboard::unconnected); } events.push(( - id, + Some(id), core::Event::Window(core::window::Event::Closed), )); } @@ -1287,11 +2004,12 @@ fn run_action( } window::Action::Resize(id, size) => { if let Some(window) = window_manager.get_mut(id) { - let _ = window.raw.request_inner_size( + let _ = window.raw.request_surface_size( winit::dpi::LogicalSize { width: size.width, height: size.height, - }, + } + .into(), ); } } @@ -1299,7 +2017,7 @@ fn run_action( if let Some(window) = window_manager.get_mut(id) { let size = window .raw - .inner_size() + .surface_size() .to_logical(window.raw.scale_factor()); let _ = channel.send(Size::new(size.width, size.height)); @@ -1354,7 +2072,8 @@ fn run_action( winit::dpi::LogicalPosition { x: position.x, y: position.y, - }, + } + .into(), ); } } @@ -1421,7 +2140,8 @@ fn run_action( winit::dpi::LogicalPosition { x: point.x, y: point.y, - }, + } + .into(), ); } } @@ -1514,6 +2234,42 @@ fn run_action( .start_send(Control::Exit) .expect("Send control action"); } + Action::Dnd(a) => match a { + iced_runtime::dnd::DndAction::RegisterDndDestination { + surface, + rectangles, + } => { + clipboard.register_dnd_destination(surface, rectangles); + } + iced_runtime::dnd::DndAction::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + } => { + clipboard.start_dnd( + internal, + source_surface, + icon_surface.map(|d| d as Box), + content, + actions, + ); + } + iced_runtime::dnd::DndAction::EndDnd => { + clipboard.end_dnd(); + } + iced_runtime::dnd::DndAction::PeekDnd(m, channel) => { + let data = clipboard.peek_dnd(m); + _ = channel.send(data); + } + iced_runtime::dnd::DndAction::SetAction(a) => { + clipboard.set_action(a); + } + }, + Action::PlatformSpecific(a) => { + platform_specific.send_action(a); + } } } @@ -1523,6 +2279,7 @@ pub fn build_user_interfaces<'a, P: Program, C>( debug: &mut Debug, window_manager: &mut WindowManager, mut cached_user_interfaces: FxHashMap, + clipboard: &mut Clipboard, ) -> FxHashMap> where C: Compositor, @@ -1532,18 +2289,36 @@ where .drain() .filter_map(|(id, cache)| { let window = window_manager.get_mut(id)?; - - Some(( + let interface = build_user_interface( + program, + cache, + &mut window.renderer, + window.state.logical_size(), + debug, id, - build_user_interface( - program, - cache, - &mut window.renderer, - window.state.logical_size(), - debug, - id, - ), - )) + window.raw.clone(), + window.prev_dnd_destination_rectangles_count, + clipboard, + ); + + let dnd_rectangles = interface.dnd_rectangles( + window.prev_dnd_destination_rectangles_count, + &window.renderer, + ); + let new_dnd_rectangles_count = dnd_rectangles.as_ref().len(); + if new_dnd_rectangles_count > 0 + || window.prev_dnd_destination_rectangles_count > 0 + { + clipboard.register_dnd_destination( + DndSurface(Arc::new(Box::new(window.raw.clone()))), + dnd_rectangles.into_rectangles(), + ); + } + + window.prev_dnd_destination_rectangles_count = + new_dnd_rectangles_count; + + Some((id, interface)) }) .collect() } diff --git a/winit/src/program/state.rs b/winit/src/program/state.rs index a7fa2788d9..5c7838c2e4 100644 --- a/winit/src/program/state.rs +++ b/winit/src/program/state.rs @@ -5,6 +5,7 @@ use crate::graphics::Viewport; use crate::program::{self, Program}; use std::fmt::{Debug, Formatter}; +use winit::dpi::LogicalPosition; use winit::event::{Touch, WindowEvent}; use winit::window::Window; @@ -13,7 +14,7 @@ pub struct State where P::Theme: program::DefaultStyle, { - title: String, + pub(crate) title: String, scale_factor: f64, viewport: Viewport, viewport_version: u64, @@ -47,7 +48,7 @@ where pub fn new( application: &P, window_id: window::Id, - window: &Window, + window: &dyn Window, ) -> Self { let title = application.title(window_id); let scale_factor = application.scale_factor(window_id); @@ -55,7 +56,7 @@ where let appearance = application.style(&theme); let viewport = { - let physical_size = window.inner_size(); + let physical_size = window.surface_size(); Viewport::with_physical_size( Size::new(physical_size.width, physical_size.height), @@ -102,6 +103,11 @@ where self.viewport.scale_factor() } + pub fn set_logical_cursor_pos(&mut self, pos: LogicalPosition) { + let physical = pos.to_physical(self.scale_factor()); + self.cursor_position = Some(physical); + } + /// Returns the current cursor position of the [`State`]. pub fn cursor(&self) -> mouse::Cursor { self.cursor_position @@ -135,15 +141,32 @@ where self.appearance.text_color } + /// Returns the current icon [`Color`] of the [`State`]. + pub fn icon_color(&self) -> Color { + self.appearance.icon_color + } + + /// Update the scale factor + pub(crate) fn update_scale_factor(&mut self, new_scale_factor: f64) { + let size = self.viewport.physical_size(); + + self.viewport = Viewport::with_physical_size( + size, + new_scale_factor * self.scale_factor, + ); + + self.viewport_version = self.viewport_version.wrapping_add(1); + } + /// Processes the provided window event and updates the [`State`] accordingly. pub fn update( &mut self, - window: &Window, + window: &dyn Window, event: &WindowEvent, _debug: &mut crate::runtime::Debug, ) { match event { - WindowEvent::Resized(new_size) => { + WindowEvent::SurfaceResized(new_size) => { let size = Size::new(new_size.width, new_size.height); self.viewport = Viewport::with_physical_size( @@ -157,14 +180,7 @@ where scale_factor: new_scale_factor, .. } => { - let size = self.viewport.physical_size(); - - self.viewport = Viewport::with_physical_size( - size, - new_scale_factor * self.scale_factor, - ); - - self.viewport_version = self.viewport_version.wrapping_add(1); + self.update_scale_factor(*new_scale_factor); } WindowEvent::CursorMoved { position, .. } | WindowEvent::Touch(Touch { @@ -204,7 +220,7 @@ where &mut self, application: &P, window_id: window::Id, - window: &Window, + window: &dyn Window, ) { // Update window title let new_title = application.title(window_id); @@ -216,13 +232,19 @@ where // Update scale factor and size let new_scale_factor = application.scale_factor(window_id); - let new_size = window.inner_size(); + let mut new_size = window.surface_size(); let current_size = self.viewport.physical_size(); - if self.scale_factor != new_scale_factor || (current_size.width, current_size.height) != (new_size.width, new_size.height) + && !(new_size.width == 0 && new_size.height == 0) { + if new_size.width == 0 { + new_size.width = current_size.width; + } + if new_size.height == 0 { + new_size.height = current_size.height; + } self.viewport = Viewport::with_physical_size( Size::new(new_size.width, new_size.height), window.scale_factor() * new_scale_factor, diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 3d22e15509..5ffc5ea5c1 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -35,12 +35,13 @@ where pub fn insert( &mut self, id: Id, - window: Arc, + window: Arc, application: &P, compositor: &mut C, exit_on_close_request: bool, + resize_border: u32, ) -> &mut Window { - let state = State::new(application, id, &window); + let state = State::new(application, id, window.as_ref()); let viewport_version = state.viewport_version(); let physical_size = state.physical_size(); let surface = compositor.create_surface( @@ -52,6 +53,11 @@ where let _ = self.aliases.insert(window.id(), id); + let drag_resize_window_func = super::drag_resize::event_func( + window.as_ref(), + resize_border as f64 * window.scale_factor(), + ); + let _ = self.entries.insert( id, Window { @@ -59,9 +65,12 @@ where state, viewport_version, exit_on_close_request, + drag_resize_window_func, surface, renderer, mouse_interaction: mouse::Interaction::None, + prev_dnd_destination_rectangles_count: 0, + resize_enabled: false, }, ); @@ -92,6 +101,10 @@ where self.entries.get_mut(&id) } + pub fn ids(&self) -> impl Iterator + '_ { + self.entries.keys().cloned() + } + pub fn get_mut_alias( &mut self, id: winit::window::WindowId, @@ -124,6 +137,12 @@ where } } +pub(crate) enum Frame { + None, + Waiting, + Ready, +} + #[allow(missing_debug_implementations)] pub struct Window where @@ -131,13 +150,23 @@ where C: Compositor, P::Theme: DefaultStyle, { - pub raw: Arc, - pub state: State

, + pub raw: Arc, + pub(crate) state: State

, pub viewport_version: u64, pub exit_on_close_request: bool, + pub drag_resize_window_func: Option< + Box< + dyn FnMut( + &dyn winit::window::Window, + &winit::event::WindowEvent, + ) -> bool, + >, + >, + pub prev_dnd_destination_rectangles_count: usize, pub mouse_interaction: mouse::Interaction, pub surface: C::Surface, pub renderer: P::Renderer, + pub resize_enabled: bool, } impl Window @@ -158,8 +187,12 @@ where } pub fn size(&self) -> Size { - let size = self.raw.inner_size().to_logical(self.raw.scale_factor()); + let size = self.raw.surface_size().to_logical(self.raw.scale_factor()); Size::new(size.width, size.height) } + + pub fn request_redraw(&mut self) { + self.raw.request_redraw(); + } } diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index d8ad8b3f01..a23275c344 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -4,14 +4,16 @@ use crate::futures::futures::{ task::{Context, Poll}, Future, Sink, StreamExt, }; +use crate::program::Event; use crate::runtime::Action; use std::pin::Pin; /// An event loop proxy with backpressure that implements `Sink`. #[derive(Debug)] pub struct Proxy { - raw: winit::event_loop::EventLoopProxy>, + pub(crate) raw: winit::event_loop::EventLoopProxy, sender: mpsc::Sender>, + event_sender: mpsc::UnboundedSender>, notifier: mpsc::Sender, } @@ -21,6 +23,7 @@ impl Clone for Proxy { raw: self.raw.clone(), sender: self.sender.clone(), notifier: self.notifier.clone(), + event_sender: self.event_sender.clone(), } } } @@ -30,11 +33,14 @@ impl Proxy { /// Creates a new [`Proxy`] from an `EventLoopProxy`. pub fn new( - raw: winit::event_loop::EventLoopProxy>, + raw: winit::event_loop::EventLoopProxy, + event_sender: mpsc::UnboundedSender>, ) -> (Self, impl Future) { let (notifier, mut processed) = mpsc::channel(Self::MAX_SIZE); - let (sender, mut receiver) = mpsc::channel(Self::MAX_SIZE); + let (sender, mut receiver): (mpsc::Sender>, _) = + mpsc::channel(Self::MAX_SIZE); let proxy = raw.clone(); + let event_sender_clone = event_sender.clone(); let worker = async move { let mut count = 0; @@ -43,7 +49,8 @@ impl Proxy { if count < Self::MAX_SIZE { select! { message = receiver.select_next_some() => { - let _ = proxy.send_event(message); + let _ = event_sender_clone.unbounded_send(Event::UserEvent(message)); + let _ = proxy.wake_up(); count += 1; } @@ -68,6 +75,7 @@ impl Proxy { raw, sender, notifier, + event_sender, }, worker, ) @@ -92,8 +100,8 @@ impl Proxy { where T: std::fmt::Debug, { - self.raw - .send_event(action) + self.event_sender + .unbounded_send(Event::UserEvent(action)) .expect("Send message to event loop"); } From f2a62030764336d0b6aed29e7b0c533cbd2a4983 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 9 Oct 2024 10:02:48 -0600 Subject: [PATCH 003/116] Make iced_wgpu build on redox --- wgpu/Cargo.toml | 3 +- wgpu/src/window.rs | 10 +-- wgpu/src/window/compositor.rs | 114 +++++++++++++++------------------- 3 files changed, 56 insertions(+), 71 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 35af5870be..02fe3356c3 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -46,7 +46,8 @@ lyon.optional = true resvg.workspace = true resvg.optional = true -[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies] +[target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))'.dependencies] + rustix = { version = "0.38" } raw-window-handle.workspace = true sctk.workspace = true diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index 92f1687372..011f60e5c5 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -1,19 +1,19 @@ //! Display rendering results on windows. pub mod compositor; -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] mod wayland; -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] mod x11; pub use compositor::Compositor; pub use wgpu::Surface; -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use rustix::fs::{major, minor}; -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use std::{fs::File, io::Read, path::PathBuf}; -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] fn ids_from_dev(dev: u64) -> Option<(u16, u16)> { let path = PathBuf::from(format!( "/sys/dev/char/{}:{}/device", diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 260f0171ae..1787f4e881 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -7,9 +7,9 @@ use crate::graphics::{self, Viewport}; use crate::settings::{self, Settings}; use crate::{Engine, Renderer}; -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use super::wayland::get_wayland_device_ids; -#[cfg(all(unix, not(target_os = "macos")))] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use super::x11::get_x11_device_ids; use std::future::Future; @@ -60,7 +60,7 @@ impl Compositor { settings: Settings, compatible_window: Option, ) -> Result { - #[cfg(all(unix, not(target_os = "macos")))] + #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] let ids = compatible_window.as_ref().and_then(|window| { get_wayland_device_ids(window) .or_else(|| get_x11_device_ids(window)) @@ -71,7 +71,7 @@ impl Compositor { // 2. and nobody set an adapter name, // 3. and the user didn't request the high power pref // => don't load the nvidia icd, as it might power on the gpu in hybrid setups causing severe delays - #[cfg(all(unix, not(target_os = "macos")))] + #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] if !matches!(ids, Some((0x10de, _))) && std::env::var_os("WGPU_ADAPTER_NAME").is_none() && std::env::var("WGPU_POWER_PREF").as_deref() != Ok("high") @@ -120,68 +120,52 @@ impl Compositor { compatible_surface: compatible_surface.as_ref(), force_fallback_adapter: false, }; + let mut adapter = None; + #[cfg_attr(not(unix), allow(dead_code))] + if std::env::var_os("WGPU_ADAPTER_NAME").is_none() { + #[cfg(all( + unix, + not(target_os = "macos"), + not(target_os = "redox") + ))] + if let Some((vendor_id, device_id)) = ids { + adapter = available_adapters + .into_iter() + .filter(|adapter| { + let info = adapter.get_info(); + info.device == device_id as u32 + && info.vendor == vendor_id as u32 + }) + .find(|adapter| { + if let Some(surface) = compatible_surface.as_ref() { + adapter.is_surface_supported(surface) + } else { + true + } + }); + } + } else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") { + adapter = available_adapters + .into_iter() + .filter(|adapter| { + let info = adapter.get_info(); + info.name == name + }) + .find(|adapter| { + if let Some(surface) = compatible_surface.as_ref() { + adapter.is_surface_supported(surface) + } else { + true + } + }); + } - let adapter = instance - .request_adapter(&adapter_options) - .await - .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?; - // start pop - // let mut adapter = None; - // #[cfg_attr(not(unix), allow(dead_code))] - // if std::env::var_os("WGPU_ADAPTER_NAME").is_none() { - // #[cfg(all(unix, not(target_os = "macos")))] - // if let Some((vendor_id, device_id)) = ids { - // adapter = available_adapters - // .into_iter() - // .filter(|adapter| { - // let info = adapter.get_info(); - // info.device == device_id as u32 - // && info.vendor == vendor_id as u32 - // }) - // .find(|adapter| { - // if let Some(surface) = compatible_surface.as_ref() { - // adapter.is_surface_supported(surface) - // } else { - // true - // } - // }); - // } - // } else if let Ok(name) = std::env::var("WGPU_ADAPTER_NAME") { - // adapter = available_adapters - // .into_iter() - // .filter(|adapter| { - // let info = adapter.get_info(); - // info.name == name - // }) - // .find(|adapter| { - // if let Some(surface) = compatible_surface.as_ref() { - // adapter.is_surface_supported(surface) - // } else { - // true - // } - // }); - // } - - // let adapter = - // match adapter { - // Some(adapter) => adapter, - // None => instance - // .request_adapter(&wgpu::RequestAdapterOptions { - // power_preference: - // wgpu::util::power_preference_from_env().unwrap_or( - // if settings.antialiasing.is_none() { - // wgpu::PowerPreference::LowPower - // } else { - // wgpu::PowerPreference::HighPerformance - // }, - // ), - // compatible_surface: compatible_surface.as_ref(), - // force_fallback_adapter: false, - // }) - // .await?, - // }; - // end pop - // TODO(POP): Merge conflict ensued with above stuff, is your code still needed? + let adapter = match adapter { + Some(adapter) => adapter, + None => instance.request_adapter(&adapter_options).await.ok_or( + Error::NoAdapterFound(format!("{:?}", adapter_options)), + )?, + }; log::info!("Selected: {:#?}", adapter.get_info()); let (format, alpha_mode) = compatible_surface From c607218b89aba2fae66279c137ee40b2ad87e9bb Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 16 Oct 2024 15:20:21 -0400 Subject: [PATCH 004/116] add reserved Id --- core/src/window/id.rs | 3 +++ winit/src/program.rs | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/src/window/id.rs b/core/src/window/id.rs index c006a08259..3ded5aaa0b 100644 --- a/core/src/window/id.rs +++ b/core/src/window/id.rs @@ -11,11 +11,14 @@ static COUNT: AtomicU64 = AtomicU64::new(1); impl Id { /// No window will match this Id pub const NONE: Id = Id(0); + pub const RESERVED: Id = Id(1); /// Creates a new unique window [`Id`]. pub fn unique() -> Id { let id = Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed)); if id.0 == 0 { + Id(COUNT.fetch_add(2, atomic::Ordering::Relaxed)) + } else if id.0 == 1 { Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed)) } else { id diff --git a/winit/src/program.rs b/winit/src/program.rs index 6a86252907..ec113677b9 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -198,7 +198,13 @@ where let task = if let Some(window_settings) = window_settings { let mut task = Some(task); - let (_id, open) = runtime::window::open(window_settings); + let open = iced_runtime::task::oneshot(|channel| { + iced_runtime::Action::Window(iced_runtime::window::Action::Open( + iced_runtime::core::window::Id::RESERVED, + window_settings, + channel, + )) + }); open.then(move |_| task.take().unwrap_or(Task::none())) } else { @@ -357,9 +363,6 @@ where return; }; - // XXX what to do if the program is a daemon? - // Can we avoid creating a useless window that hangs around forever? - let window: Arc = match event_loop .create_window( winit::window::WindowAttributes::default() From a72e2478c4606be61078ce1d18c2680bb2e2b0d1 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 16 Oct 2024 15:20:36 -0400 Subject: [PATCH 005/116] cleanup --- core/src/overlay.rs | 1 - runtime/src/dnd.rs | 4 +--- runtime/src/lib.rs | 4 ++-- runtime/src/platform_specific/mod.rs | 5 ++--- runtime/src/program/state.rs | 3 --- runtime/src/user_interface.rs | 1 - src/application.rs | 8 ++++---- src/daemon.rs | 12 ++++++------ src/error.rs | 8 ++------ src/lib.rs | 13 ------------- src/program.rs | 22 +++++++++++----------- 11 files changed, 28 insertions(+), 53 deletions(-) diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 1cb98f8982..d0afe18ed6 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -9,7 +9,6 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; -use crate::widget::Operation; use crate::widget::Tree; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; diff --git a/runtime/src/dnd.rs b/runtime/src/dnd.rs index 2fd3924499..0461becea8 100644 --- a/runtime/src/dnd.rs +++ b/runtime/src/dnd.rs @@ -2,10 +2,8 @@ use std::any::Any; -use bytes::buf::Take; use dnd::{DndDestinationRectangle, DndSurface}; -use iced_core::{clipboard::DndSource, Vector}; -use iced_futures::MaybeSend; +use iced_core::clipboard::DndSource; use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; use crate::{oneshot, task, Action, Task}; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8516561f90..fd6eb6d0b3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -43,7 +43,6 @@ pub use user_interface::UserInterface; use crate::core::widget; use crate::futures::futures::channel::oneshot; -use dnd::DndAction; use std::borrow::Cow; use std::fmt; @@ -78,6 +77,7 @@ pub enum Action { /// This will normally close any application windows and /// terminate the runtime loop. Exit, + /// Run a Dnd action. Dnd(crate::dnd::DndAction), @@ -130,7 +130,7 @@ where Action::PlatformSpecific(action) => { write!(f, "Action::PlatformSpecific({:?})", action) } - Action::Dnd(action) => write!(f, "Action::Dnd"), + Action::Dnd(_) => write!(f, "Action::Dnd"), } } } diff --git a/runtime/src/platform_specific/mod.rs b/runtime/src/platform_specific/mod.rs index a8f761b75a..572288b3cb 100644 --- a/runtime/src/platform_specific/mod.rs +++ b/runtime/src/platform_specific/mod.rs @@ -1,8 +1,6 @@ //! Platform specific actions defined for wayland -use std::{fmt, marker::PhantomData}; - -use iced_futures::MaybeSend; +use std::fmt; #[cfg(feature = "wayland")] /// Platform specific actions defined for wayland @@ -20,6 +18,7 @@ impl fmt::Debug for Action { match self { #[cfg(feature = "wayland")] Action::Wayland(action) => action.fmt(_f), + #[cfg(not(feature = "wayland"))] _ => Ok(()), } } diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index c0befaa2b0..1eb2d770c7 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -1,5 +1,3 @@ -use iced_core::widget::operation::Outcome; - use crate::core::event::{self, Event}; use crate::core::mouse; use crate::core::renderer; @@ -211,7 +209,6 @@ where operation::Outcome::Chain(next) => { current_operation = Some(next); } - _ => {} }; } } diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index f6184d144c..15b12b3c74 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -2,7 +2,6 @@ use iced_core::clipboard::DndDestinationRectangles; use iced_core::widget::tree::NAMED; -use iced_core::widget::Operation; use crate::core::event::{self, Event}; use crate::core::layout; diff --git a/src/application.rs b/src/application.rs index acd38062b3..17070afb02 100644 --- a/src/application.rs +++ b/src/application.rs @@ -154,7 +154,7 @@ pub struct Application { } impl Application

{ - #[cfg(any(feature = "winit", feature = "wayland"))] + #[cfg(feature = "winit")] /// Runs the [`Application`]. /// /// The state of the [`Application`] must implement [`Default`]. @@ -162,7 +162,7 @@ impl Application

{ /// instead. /// /// [`run_with`]: Self::run_with - pub fn run(self) -> Result + pub fn run(self) -> crate::Result where Self: 'static, P::State: Default, @@ -170,9 +170,9 @@ impl Application

{ self.raw.run(self.settings, Some(self.window)) } - #[cfg(any(feature = "winit", feature = "wayland"))] + #[cfg(feature = "winit")] /// Runs the [`Application`] with a closure that creates the initial state. - pub fn run_with(self, initialize: I) -> Result + pub fn run_with(self, initialize: I) -> crate::Result where Self: 'static, I: FnOnce() -> (P::State, Task) + 'static, diff --git a/src/daemon.rs b/src/daemon.rs index 689b5222a4..b672ca13cb 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,12 +1,12 @@ //! Create and run daemons that run in the background. use crate::application; use crate::program::{self, Program}; -#[cfg(any(feature = "winit", feature = "wayland"))] +#[cfg(feature = "winit")] pub use crate::shell::program::{Appearance, DefaultStyle}; use crate::window; use crate::{Element, Executor, Font, Result, Settings, Subscription, Task}; -#[cfg(not(any(feature = "winit", feature = "wayland")))] +#[cfg(not(feature = "winit"))] use crate::runtime::{Appearance, DefaultStyle}; use std::borrow::Cow; @@ -103,7 +103,7 @@ pub struct Daemon { } impl Daemon

{ - #[cfg(any(feature = "winit", feature = "wayland"))] + #[cfg(feature = "winit")] /// Runs the [`Daemon`]. /// /// The state of the [`Daemon`] must implement [`Default`]. @@ -111,7 +111,7 @@ impl Daemon

{ /// instead. /// /// [`run_with`]: Self::run_with - pub fn run(self) -> Result + pub fn run(self) -> crate::Result where Self: 'static, P::State: Default, @@ -119,9 +119,9 @@ impl Daemon

{ self.raw.run(self.settings, None) } - #[cfg(any(feature = "winit", feature = "wayland"))] + #[cfg(feature = "winit")] /// Runs the [`Daemon`] with a closure that creates the initial state. - pub fn run_with(self, initialize: I) -> Result + pub fn run_with(self, initialize: I) -> crate::Result where Self: 'static, I: FnOnce() -> (P::State, Task) + 'static, diff --git a/src/error.rs b/src/error.rs index 3285592f85..5f78a8f5ac 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ use crate::futures; use crate::graphics; -#[cfg(any(feature = "winit", feature = "wayland"))] +#[cfg(feature = "winit")] use crate::shell; /// An error that occurred while running an application. @@ -19,7 +19,7 @@ pub enum Error { GraphicsCreationFailed(graphics::Error), } -#[cfg(any(feature = "winit", feature = "wayland"))] +#[cfg(feature = "winit")] impl From for Error { fn from(error: shell::Error) -> Error { match error { @@ -30,10 +30,6 @@ impl From for Error { shell::Error::WindowCreationFailed(error) => { Error::WindowCreationFailed(Box::new(error)) } - #[cfg(feature = "wayland")] - shell::Error::WindowCreationFailed(error) => { - Error::WindowCreationFailed(Box::new(error)) - } shell::Error::GraphicsCreationFailed(error) => { Error::GraphicsCreationFailed(error) } diff --git a/src/lib.rs b/src/lib.rs index 5a0fa80a3c..3af592172a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -512,19 +512,6 @@ pub use application::Application; #[cfg(feature = "winit")] pub use program::Program; -// wayland application -// #[cfg(feature = "wayland")] -// pub mod wayland; -// #[cfg(feature = "wayland")] -// pub use wayland::application; -// #[cfg(feature = "wayland")] -// pub use wayland::application::Application; -// #[cfg(feature = "wayland")] -// pub use wayland::program; -// #[doc(inline)] -// #[cfg(feature = "wayland")] -// pub use wayland::program::Program; - #[cfg(feature = "advanced")] pub mod advanced; diff --git a/src/program.rs b/src/program.rs index bac6ddfc39..f005771b86 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,13 +1,13 @@ use crate::core::text; use crate::graphics::compositor; -#[cfg(any(feature = "winit", feature = "wayland"))] +#[cfg(feature = "winit")] use crate::shell; -#[cfg(any(feature = "winit", feature = "wayland"))] +#[cfg(feature = "winit")] pub use crate::shell::program::{Appearance, DefaultStyle}; use crate::window; -use crate::{Element, Executor, Result, Settings, Subscription, Task}; +use crate::{Element, Executor, Subscription, Task}; -#[cfg(not(any(feature = "winit", feature = "wayland")))] +#[cfg(not(feature = "winit"))] pub use crate::runtime::{Appearance, DefaultStyle}; /// The internal definition of a [`Program`]. @@ -66,7 +66,7 @@ pub trait Program: Sized { 1.0 } - #[cfg(any(feature = "winit", feature = "wayland"))] + #[cfg(feature = "winit")] /// Runs the [`Program`]. /// /// The state of the [`Program`] must implement [`Default`]. @@ -76,9 +76,9 @@ pub trait Program: Sized { /// [`run_with`]: Self::run_with fn run( self, - settings: Settings, + settings: crate::Settings, window_settings: Option, - ) -> Result + ) -> crate::Result where Self: 'static, Self::State: Default, @@ -88,14 +88,14 @@ pub trait Program: Sized { }) } - #[cfg(any(feature = "winit", feature = "wayland"))] + #[cfg(feature = "winit")] /// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state. fn run_with( self, - settings: Settings, + settings: crate::Settings, window_settings: Option, initialize: I, - ) -> Result + ) -> crate::Result where Self: 'static, I: FnOnce() -> (Self::State, Task) + 'static, @@ -184,7 +184,7 @@ pub trait Program: Sized { Instance, ::Compositor, >( - Settings { + crate::Settings { id: settings.id, fonts: settings.fonts, default_font: settings.default_font, From c1bde7511e8d6705b825b472b3f246c3dd2a7eca Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 16 Oct 2024 17:35:05 -0400 Subject: [PATCH 006/116] fix: derive serde traits --- core/src/font.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/font.rs b/core/src/font.rs index 2b68decf90..227d470576 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -71,6 +71,7 @@ pub enum Family { /// The weight of some text. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Weight { Thin, ExtraLight, @@ -87,6 +88,7 @@ pub enum Weight { /// The width of some text. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Stretch { UltraCondensed, ExtraCondensed, @@ -103,6 +105,7 @@ pub enum Stretch { /// The style of some text. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Style { #[default] Normal, From a7193241c356eed8ace38abe1461cf3a9b6ed466 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 17 Oct 2024 13:23:45 -0400 Subject: [PATCH 007/116] cargo fmt --- .../platform_specific/wayland/conversion.rs | 3 +- .../wayland/event_loop/state.rs | 197 ++++++++++++------ .../wayland/handlers/wp_fractional_scaling.rs | 1 - .../wayland/handlers/wp_viewporter.rs | 1 - 4 files changed, 131 insertions(+), 71 deletions(-) diff --git a/winit/src/platform_specific/wayland/conversion.rs b/winit/src/platform_specific/wayland/conversion.rs index e980157fdc..5da22cc332 100644 --- a/winit/src/platform_specific/wayland/conversion.rs +++ b/winit/src/platform_specific/wayland/conversion.rs @@ -7,8 +7,7 @@ use sctk::{ seat::{ keyboard::Modifiers, pointer::{ - AxisScroll, BTN_EXTRA, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, - BTN_SIDE, + AxisScroll, BTN_EXTRA, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, BTN_SIDE, }, }, }; diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 99e7f47c71..2e90516ee6 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1,26 +1,31 @@ use crate::{ - handlers::activation::IcedRequestData, platform_specific::{ + handlers::activation::IcedRequestData, + platform_specific::{ wayland::{ handlers::{ wp_fractional_scaling::FractionalScalingManager, wp_viewporter::ViewporterState, }, - sctk_event::{ - LayerSurfaceEventVariant, SctkEvent, - }, + sctk_event::{LayerSurfaceEventVariant, SctkEvent}, }, Event, - }, program::Control + }, + program::Control, }; use iced_futures::futures::channel::{mpsc, oneshot}; use raw_window_handle::HasWindowHandle; use std::{ collections::{HashMap, HashSet}, convert::Infallible, - fmt::Debug, sync::{atomic::AtomicU32, Arc, Mutex}, time::Duration, + fmt::Debug, + sync::{atomic::AtomicU32, Arc, Mutex}, + time::Duration, }; use wayland_backend::client::ObjectId; -use winit::{dpi::{LogicalPosition, LogicalSize}, platform::wayland::WindowExtWayland}; +use winit::{ + dpi::{LogicalPosition, LogicalSize}, + platform::wayland::WindowExtWayland, +}; use iced_runtime::{ core::{self, touch, Point}, @@ -57,7 +62,10 @@ use sctk::{ }, registry::RegistryState, seat::{ - keyboard::KeyEvent, pointer::{CursorIcon, PointerData, ThemedPointer}, touch::TouchData, SeatState + keyboard::KeyEvent, + pointer::{CursorIcon, PointerData, ThemedPointer}, + touch::TouchData, + SeatState, }, session_lock::{ SessionLock, SessionLockState, SessionLockSurface, @@ -76,10 +84,13 @@ use sctk::{ }, shm::{multi::MultiPool, Shm}, }; -use wayland_protocols::{wp::{ - fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, - viewporter::client::wp_viewport::WpViewport, -}, xdg::shell::client::xdg_surface::XdgSurface}; +use wayland_protocols::{ + wp::{ + fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, + viewporter::client::wp_viewport::WpViewport, + }, + xdg::shell::client::xdg_surface::XdgSurface, +}; pub static TOKEN_CTR: AtomicU32 = AtomicU32::new(0); @@ -167,16 +178,17 @@ impl PopupParent { pub enum CommonSurface { Popup(Popup, Arc), Layer(LayerSurface), - Lock(SessionLockSurface) + Lock(SessionLockSurface), } impl CommonSurface { pub fn wl_surface(&self) -> &WlSurface { - let wl_surface = - match self { + let wl_surface = match self { CommonSurface::Popup(popup, _) => popup.wl_surface(), CommonSurface::Layer(layer_surface) => layer_surface.wl_surface(), - CommonSurface::Lock(session_lock_surface) => session_lock_surface.wl_surface(), + CommonSurface::Lock(session_lock_surface) => { + session_lock_surface.wl_surface() + } }; wl_surface } @@ -195,7 +207,15 @@ pub struct Common { impl Default for Common { fn default() -> Self { - Self { fractional_scale: Default::default(), has_focus: Default::default(), ime_pos: Default::default(), ime_size: Default::default(), size: LogicalSize::new(1, 1), requested_size: (None, None), wp_viewport: None} + Self { + fractional_scale: Default::default(), + has_focus: Default::default(), + ime_pos: Default::default(), + ime_size: Default::default(), + size: LogicalSize::new(1, 1), + requested_size: (None, None), + wp_viewport: None, + } } } @@ -208,8 +228,6 @@ impl From> for Common { } } - - #[derive(Debug)] pub struct SctkPopup { pub(crate) popup: Popup, @@ -253,31 +271,39 @@ pub struct SctkPopupData { pub struct SctkWindow { pub(crate) window: Arc, - pub(crate) id: core::window::Id + pub(crate) id: core::window::Id, } impl SctkWindow { pub fn wl_surface(&self, conn: &Connection) -> WlSurface { let window_handle = self.window.window_handle().unwrap(); let ptr = { - let raw_window_handle::RawWindowHandle::Wayland(h) = window_handle.as_raw() else { + let raw_window_handle::RawWindowHandle::Wayland(h) = + window_handle.as_raw() + else { panic!("Invalid window handle"); }; h.surface - }; - let id = unsafe { ObjectId::from_ptr(WlSurface::interface(), ptr.as_ptr().cast()) }.unwrap(); + let id = unsafe { + ObjectId::from_ptr(WlSurface::interface(), ptr.as_ptr().cast()) + } + .unwrap(); WlSurface::from_id(conn, id).unwrap() } pub fn xdg_surface(&self, conn: &Connection) -> XdgSurface { let window_handle = self.window.xdg_surface_handle().unwrap(); let ptr = { - let h = window_handle.xdg_surface_handle().expect("Invalid window handle"); + let h = window_handle + .xdg_surface_handle() + .expect("Invalid window handle"); h.as_raw() - }; - let id = unsafe { ObjectId::from_ptr(XdgSurface::interface(), ptr.as_ptr().cast()) }.unwrap(); + let id = unsafe { + ObjectId::from_ptr(XdgSurface::interface(), ptr.as_ptr().cast()) + } + .unwrap(); XdgSurface::from_id(conn, id).unwrap() } } @@ -288,7 +314,7 @@ pub(crate) enum FrameStatus { /// Requested redraw, but frame wasn't received RequestedRedraw, /// Ready for requested redraw - Ready + Ready, } /// Wrapper to carry sctk state. @@ -395,7 +421,10 @@ pub enum LayerSurfaceCreationError { LayerSurfaceCreationFailed(GlobalError), } -pub(crate) fn receive_frame(frame_status: &mut HashMap, s: &WlSurface) { +pub(crate) fn receive_frame( + frame_status: &mut HashMap, + s: &WlSurface, +) { let e = frame_status.entry(s.id()).or_insert(FrameStatus::Received); if matches!(e, FrameStatus::RequestedRedraw) { *e = FrameStatus::Ready; @@ -404,7 +433,10 @@ pub(crate) fn receive_frame(frame_status: &mut HashMap, s impl SctkState { pub fn request_redraw(&mut self, surface: &WlSurface) { - let e = self.frame_status.entry(surface.id()).or_insert(FrameStatus::RequestedRedraw); + let e = self + .frame_status + .entry(surface.id()) + .or_insert(FrameStatus::RequestedRedraw); if matches!(e, FrameStatus::Received) { *e = FrameStatus::Ready; } @@ -446,10 +478,11 @@ impl SctkState { let mut common = layer_surface.common.lock().unwrap(); common.fractional_scale = Some(scale_factor); if legacy { - let _ = - layer_surface.surface.wl_surface().set_buffer_scale(scale_factor as i32); + let _ = layer_surface + .surface + .wl_surface() + .set_buffer_scale(scale_factor as i32); } - } if let Some(lock_surface) = self @@ -464,13 +497,19 @@ impl SctkState { let mut common = lock_surface.common.lock().unwrap(); common.fractional_scale = Some(scale_factor); if legacy { - let _ = - lock_surface.session_lock_surface.wl_surface().set_buffer_scale(scale_factor as i32); + let _ = lock_surface + .session_lock_surface + .wl_surface() + .set_buffer_scale(scale_factor as i32); } } if let Some(id) = id { - self.sctk_events.push(SctkEvent::SurfaceScaleFactorChanged(scale_factor, surface.clone(), id)); + self.sctk_events.push(SctkEvent::SurfaceScaleFactorChanged( + scale_factor, + surface.clone(), + id, + )); } // TODO winit sets cursor size after handling the change for the window, so maybe that should be done as well. @@ -482,7 +521,13 @@ impl SctkState { &mut self, settings: SctkPopupSettings, ) -> Result< - (core::window::Id, WlSurface, WlSurface, CommonSurface, Arc>), + ( + core::window::Id, + WlSurface, + WlSurface, + CommonSurface, + Arc>, + ), PopupCreationError, > { let (parent, toplevel) = if let Some(parent) = @@ -613,20 +658,32 @@ impl SctkState { }; if grab { if let Some(s) = self.seats.first() { - let ptr_data = s.ptr.as_ref().and_then(|p| p.pointer().data::()).and_then(|data| data.latest_button_serial()); - if let Some(serial) = ptr_data.or_else(|| s.touch.as_ref().and_then(|t| t.data::()).and_then(|t| t.latest_down_serial())).or_else(|| s.last_kbd_press + let ptr_data = s + .ptr .as_ref() - .map(|p| p.1)) { - popup.xdg_popup().grab( - &s.seat, - serial - ); + .and_then(|p| p.pointer().data::()) + .and_then(|data| data.latest_button_serial()); + if let Some(serial) = ptr_data + .or_else(|| { + s.touch + .as_ref() + .and_then(|t| t.data::()) + .and_then(|t| t.latest_down_serial()) + }) + .or_else(|| s.last_kbd_press.as_ref().map(|p| p.1)) + { + popup.xdg_popup().grab(&s.seat, serial); } } else { log::error!("Can't take grab on popup. Missing serial."); } } - popup.xdg_surface().set_window_geometry(0, 0, size.0 as i32, size.1 as i32); + popup.xdg_surface().set_window_geometry( + 0, + 0, + size.0 as i32, + size.1 as i32, + ); _ = wl_surface.frame(&self.queue_handle, wl_surface.clone()); wl_surface.commit(); @@ -656,7 +713,7 @@ impl SctkState { last_configure: None, _pending_requests: Default::default(), wp_fractional_scale, - common: common.clone() + common: common.clone(), }); Ok(( @@ -664,7 +721,7 @@ impl SctkState { parent.wl_surface().clone(), toplevel.clone(), CommonSurface::Popup(popup.clone(), positioner.clone()), - common + common, )) } @@ -683,7 +740,10 @@ impl SctkState { exclusive_zone, .. }: SctkLayerSurfaceSettings, - ) -> Result<(core::window::Id, CommonSurface, Arc>), LayerSurfaceCreationError> { + ) -> Result< + (core::window::Id, CommonSurface, Arc>), + LayerSurfaceCreationError, + > { let wl_output = match output { IcedOutput::All => None, // TODO IcedOutput::Active => None, @@ -746,7 +806,10 @@ impl SctkState { &self.queue_handle, ) }); - let mut common = Common::from(LogicalSize::new(size.0.unwrap_or(1), size.1.unwrap_or(1))); + let mut common = Common::from(LogicalSize::new( + size.0.unwrap_or(1), + size.1.unwrap_or(1), + )); common.requested_size = size; common.wp_viewport = wp_viewport; let common = Arc::new(Mutex::new(common)); @@ -763,7 +826,7 @@ impl SctkState { last_configure: None, _pending_requests: Vec::new(), wp_fractional_scale, - common: common.clone() + common: common.clone(), }); Ok((id, CommonSurface::Layer(layer_surface), common)) } @@ -787,19 +850,18 @@ impl SctkState { viewport }); let wp_fractional_scale = - self.fractional_scaling_manager.as_ref().map(|fsm| { - fsm.fractional_scaling(&wl_surface, &self.queue_handle) - }); - let common = Arc::new(Mutex::new(Common::from( - LogicalSize::new(1, 1) - ))); + self.fractional_scaling_manager.as_ref().map(|fsm| { + fsm.fractional_scaling(&wl_surface, &self.queue_handle) + }); + let common = + Arc::new(Mutex::new(Common::from(LogicalSize::new(1, 1)))); self.lock_surfaces.push(SctkLockSurface { id, session_lock_surface: session_lock_surface.clone(), last_configure: None, wp_fractional_scale, wp_viewport, - common: common.clone() + common: common.clone(), }); Some((CommonSurface::Lock(session_lock_surface), common)) } else { @@ -842,7 +904,6 @@ impl SctkState { prev_configure.new_size = (width.unwrap_or(prev_configure.new_size.0), width.unwrap_or(prev_configure.new_size.1)); _ = send_event(&self.events_sender, &self.proxy, SctkEvent::LayerSurfaceEvent { variant: LayerSurfaceEventVariant::Configure(prev_configure, wl_surface.clone(), false), id: wl_surface.clone()}); - } } }, @@ -857,7 +918,6 @@ impl SctkState { id: l.surface.wl_surface().clone(), } ); - } }, platform_specific::wayland::layer_surface::Action::Anchor { id, anchor } => { @@ -939,7 +999,7 @@ impl SctkState { TimeoutAction::Drop } } else { - match state.get_popup(popup) { + match state.get_popup(popup) { Ok((id, parent_id, toplevel_id, surface, common)) => { let wl_surface = surface.wl_surface().clone(); receive_frame(&mut state.frame_status, &wl_surface); @@ -947,7 +1007,7 @@ impl SctkState { SctkEvent::PopupEvent { variant: crate::platform_specific::wayland::sctk_event::PopupEventVariant::Created(queue_handle.clone(), surface, id, common, state.connection.display()), toplevel_id, parent_id, id: wl_surface }); - } + } Err(err) => { log::error!("Failed to create popup. {err:?}"); } @@ -959,16 +1019,15 @@ impl SctkState { // log::error!("Invalid popup Id {:?}", popup.id); } else { self.pending_popup = None; - match self.get_popup(popup) { + match self.get_popup(popup) { Ok((id, parent_id, toplevel_id, surface, common)) => { let wl_surface = surface.wl_surface().clone(); - receive_frame(&mut self.frame_status, &wl_surface); - send_event(&self.events_sender, &self.proxy, + send_event(&self.events_sender, &self.proxy, SctkEvent::PopupEvent { variant: crate::platform_specific::wayland::sctk_event::PopupEventVariant::Created(self.queue_handle.clone(), surface, id, common, self.connection.display()), toplevel_id, parent_id, id: wl_surface }); - } + } Err(err) => { log::error!("Failed to create popup. {err:?}"); } @@ -1068,7 +1127,6 @@ impl SctkState { } else { // if we don't have the global, we don't want to stall the app _ = channel.send(None); - } }, platform_specific::wayland::activation::Action::Activate { window, token } => { @@ -1124,8 +1182,13 @@ impl SctkState { } } -pub(crate) fn send_event(sender: &mpsc::UnboundedSender, proxy: &winit::event_loop::EventLoopProxy, sctk_event: SctkEvent) { - _ = sender.unbounded_send(Control::PlatformSpecific(Event::Wayland(sctk_event))); +pub(crate) fn send_event( + sender: &mpsc::UnboundedSender, + proxy: &winit::event_loop::EventLoopProxy, + sctk_event: SctkEvent, +) { + _ = sender + .unbounded_send(Control::PlatformSpecific(Event::Wayland(sctk_event))); proxy.wake_up(); } diff --git a/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs b/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs index 5ad326afd7..00c675d52d 100644 --- a/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs +++ b/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs @@ -1,7 +1,6 @@ // From: https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/wayland/types/wp_fractional_scaling.rs //! Handling of the fractional scaling. - use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Dispatch; diff --git a/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs b/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs index c0ca1025c8..8eacfbd302 100644 --- a/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs +++ b/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs @@ -1,6 +1,5 @@ //! Handling of the wp-viewporter. - use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Dispatch; From c116af5a07b37118a8fb363b45f0bdd9faf847ea Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 18 Oct 2024 10:11:22 -0400 Subject: [PATCH 008/116] clippy --- accessibility/src/id.rs | 10 +++++----- core/src/clipboard.rs | 17 ++++++----------- core/src/element.rs | 2 +- core/src/id.rs | 10 +++++----- core/src/overlay/group.rs | 1 - core/src/renderer/null.rs | 2 +- core/src/theme.rs | 1 + core/src/widget/text.rs | 4 +--- core/src/widget/tree.rs | 4 ++-- graphics/src/image.rs | 2 -- graphics/src/text/paragraph.rs | 4 ++-- renderer/src/fallback.rs | 2 +- runtime/src/dnd.rs | 4 ++-- src/application.rs | 4 +--- src/daemon.rs | 2 +- src/lib.rs | 2 +- tiny_skia/src/engine.rs | 6 +++--- tiny_skia/src/layer.rs | 6 +++--- widget/src/button.rs | 2 +- widget/src/checkbox.rs | 2 +- widget/src/container.rs | 2 +- widget/src/mouse_area.rs | 25 ++++++++++++------------- widget/src/pane_grid/content.rs | 2 -- widget/src/pane_grid/title_bar.rs | 2 -- widget/src/row.rs | 2 +- widget/src/scrollable.rs | 2 +- widget/src/tooltip.rs | 2 +- 27 files changed, 54 insertions(+), 70 deletions(-) diff --git a/accessibility/src/id.rs b/accessibility/src/id.rs index 752e51c192..e104d5d50c 100644 --- a/accessibility/src/id.rs +++ b/accessibility/src/id.rs @@ -124,12 +124,12 @@ impl From for u64 { } } -impl ToString for Id { - fn to_string(&self) -> String { +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.0 { - Internal::Unique(_) => "Undefined".to_string(), - Internal::Custom(_, id) => id.to_string(), - Internal::Set(_) => "Set".to_string(), + Internal::Unique(_) => write!(f, "Undefined"), + Internal::Custom(_, id) => write!(f, "{}", id.to_string()), + Internal::Set(_) => write!(f, "Set"), } } } diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs index 3e7458c024..0197354f67 100644 --- a/core/src/clipboard.rs +++ b/core/src/clipboard.rs @@ -20,7 +20,7 @@ pub trait Clipboard { /// Reads the current content of the [`Clipboard`] as text. fn read_data( &self, - kind: Kind, + _kind: Kind, _mimes: Vec, ) -> Option<(Vec, String)> { None @@ -29,7 +29,7 @@ pub trait Clipboard { /// Writes the given contents to the [`Clipboard`]. fn write_data( &mut self, - kind: Kind, + _kind: Kind, _contents: ClipboardStoreData< Box, >, @@ -70,7 +70,7 @@ pub trait Clipboard { } /// Request window size - fn request_logical_window_size(&self, width: f32, height: f32) {} + fn request_logical_window_size(&self, _width: f32, _height: f32) {} } /// The kind of [`Clipboard`]. @@ -141,10 +141,7 @@ pub fn peek_dnd( clipboard: &mut dyn Clipboard, mime: Option, ) -> Option { - let Some(mime) = mime.or_else(|| T::allowed().first().cloned().into()) - else { - return None; - }; + let mime = mime.or_else(|| T::allowed().first().cloned())?; clipboard .peek_dnd(mime) .and_then(|data| T::try_from(data).ok()) @@ -160,7 +157,7 @@ pub enum DndSource { } /// A list of DnD destination rectangles. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct DndDestinationRectangles { /// The rectangle of the DnD destination. rectangles: Vec, @@ -169,9 +166,7 @@ pub struct DndDestinationRectangles { impl DndDestinationRectangles { /// Creates a new [`DndDestinationRectangles`]. pub fn new() -> Self { - Self { - rectangles: Vec::new(), - } + Self::default() } /// Creates a new [`DndDestinationRectangles`] with the given capacity. diff --git a/core/src/element.rs b/core/src/element.rs index 3cb0f17a81..c7f8de6701 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -311,7 +311,7 @@ where } fn diff(&mut self, tree: &mut Tree) { - self.widget.diff(tree) + self.widget.diff(tree); } fn size(&self) -> Size { diff --git a/core/src/id.rs b/core/src/id.rs index 3fc74ce925..8af5d8ab66 100644 --- a/core/src/id.rs +++ b/core/src/id.rs @@ -60,12 +60,12 @@ impl From for NonZeroU128 { } } -impl ToString for Id { - fn to_string(&self) -> String { +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.0 { - Internal::Unique(_) => "Undefined".to_string(), - Internal::Custom(_, id) => id.to_string(), - Internal::Set(_) => "Set".to_string(), + Internal::Unique(_) => write!(f, "Undefined"), + Internal::Custom(_, id) => write!(f, "{}", id.to_string()), + Internal::Set(_) => write!(f, "Set"), } } } diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index d31f2234a6..6541d311a0 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -4,7 +4,6 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; -use crate::widget::Operation; use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size}; /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 36d73e2b54..a0c0a6b2cb 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,5 +1,5 @@ use crate::alignment; -use crate::image::{self, Image}; +use crate::image; use crate::renderer::{self, Renderer}; use crate::svg; use crate::text::{self, Text}; diff --git a/core/src/theme.rs b/core/src/theme.rs index 6b2c04da4f..50d72bdbc4 100644 --- a/core/src/theme.rs +++ b/core/src/theme.rs @@ -160,6 +160,7 @@ impl Theme { } } +#[allow(clippy::derivable_impls)] impl Default for Theme { fn default() -> Self { #[cfg(feature = "auto-detect-theme")] diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index d95095ba5d..8f6dd0a505 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -32,8 +32,6 @@ use crate::{ Widget, }; -use std::borrow::Cow; - pub use text::{LineHeight, Shaping, Wrapping}; /// A bunch of text. @@ -313,7 +311,7 @@ where } fn set_id(&mut self, id: crate::widget::Id) { - self.id = id + self.id = id; } } diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index af8ba86361..963a738137 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -269,13 +269,13 @@ impl Tree { new_children.iter().map(|c| c.borrow().id()).collect(), |tree, widget| { let borrowed: &mut dyn Widget<_, _, _> = widget.borrow_mut(); - tree.diff(borrowed) + tree.diff(borrowed); }, |widget| { let borrowed: &dyn Widget<_, _, _> = widget.borrow(); Self::new(borrowed) }, - ) + ); } /// Reconciles the children of the tree with the provided list of widgets using custom diff --git a/graphics/src/image.rs b/graphics/src/image.rs index fcafb27aa0..ee2cb2df63 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -4,8 +4,6 @@ pub use ::image as image_rs; use crate::core::image; use crate::core::svg; -use crate::core::Color; -use crate::core::Radians; use crate::core::Rectangle; /// A raster or vector image. diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 7fdf6d6b24..415ac5409f 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -1,8 +1,8 @@ //! Draw paragraphs. use crate::core; use crate::core::alignment; -use crate::core::text::{Hit, LineHeight, Shaping, Span, Text, Wrapping}; -use crate::core::{Font, Pixels, Point, Rectangle, Size}; +use crate::core::text::{Hit, Shaping, Span, Text, Wrapping}; +use crate::core::{Font, Point, Rectangle, Size}; use crate::text; use std::fmt; diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index f940f15833..0bceaed065 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -3,7 +3,7 @@ use crate::core::image; use crate::core::renderer; use crate::core::svg; use crate::core::{ - self, Background, Color, Image, Point, Rectangle, Size, Svg, Transformation, + self, Background, Color, Point, Rectangle, Size, Svg, Transformation, }; use crate::graphics; use crate::graphics::compositor; diff --git a/runtime/src/dnd.rs b/runtime/src/dnd.rs index 0461becea8..69c956f686 100644 --- a/runtime/src/dnd.rs +++ b/runtime/src/dnd.rs @@ -80,8 +80,8 @@ pub fn peek_dnd() -> Task> { task::oneshot(|tx| { Action::Dnd(DndAction::PeekDnd( T::allowed() - .get(0) - .map_or_else(|| String::new(), |s| s.to_string()), + .first() + .map_or_else(String::new, std::string::ToString::to_string), tx, )) }) diff --git a/src/application.rs b/src/application.rs index 17070afb02..2934eb9f3b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -36,9 +36,7 @@ use crate::runtime::{Appearance, DefaultStyle}; #[cfg(feature = "winit")] pub use crate::shell::program::{Appearance, DefaultStyle}; use crate::window; -use crate::{ - Element, Executor, Font, Result, Settings, Size, Subscription, Task, -}; +use crate::{Element, Executor, Font, Settings, Size, Subscription, Task}; use std::borrow::Cow; diff --git a/src/daemon.rs b/src/daemon.rs index b672ca13cb..2990821756 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -4,7 +4,7 @@ use crate::program::{self, Program}; #[cfg(feature = "winit")] pub use crate::shell::program::{Appearance, DefaultStyle}; use crate::window; -use crate::{Element, Executor, Font, Result, Settings, Subscription, Task}; +use crate::{Element, Executor, Font, Settings, Subscription, Task}; #[cfg(not(feature = "winit"))] use crate::runtime::{Appearance, DefaultStyle}; diff --git a/src/lib.rs b/src/lib.rs index 3af592172a..d49ccb5860 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -665,7 +665,7 @@ pub type Element< /// The result of running an iced program. pub type Result = std::result::Result<(), Error>; -#[cfg(any(feature = "winit"))] +#[cfg(feature = "winit")] /// Runs a basic iced application with default [`Settings`] given its title, /// update, and view logic. /// diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs index 7e5b517a6c..9393d5525d 100644 --- a/tiny_skia/src/engine.rs +++ b/tiny_skia/src/engine.rs @@ -1,5 +1,3 @@ -use tiny_skia::Transform; - use crate::core::renderer::Quad; use crate::core::{ Background, Color, Gradient, Rectangle, Size, Transformation, Vector, @@ -565,6 +563,8 @@ impl Engine { match image { #[cfg(feature = "image")] Image::Raster { handle, bounds } => { + use tiny_skia::Transform; + let physical_bounds = *bounds * _transformation; if !_clip_bounds.intersects(&physical_bounds) { @@ -608,7 +608,7 @@ impl Engine { let center = physical_bounds.center(); let radians = f32::from(handle.rotation); - let transform = Transform::default().post_rotate_at( + let transform = tiny_skia::Transform::default().post_rotate_at( radians.to_degrees(), center.x, center.y, diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index 30ee93bcad..3df644a2d3 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -1,6 +1,6 @@ use crate::core::renderer::Quad; use crate::core::{ - self, Background, Color, Point, Radians, Rectangle, Svg, Transformation, + self, Background, Color, Point, Rectangle, Svg, Transformation, }; use crate::graphics::damage; use crate::graphics::layer; @@ -118,10 +118,10 @@ impl Layer { pub fn draw_image(&mut self, image: Image, transformation: Transformation) { match image { Image::Raster { handle, bounds } => { - self.draw_raster(handle, bounds, transformation) + self.draw_raster(handle, bounds, transformation); } Image::Vector { handle, bounds } => { - self.draw_svg(handle, bounds, transformation) + self.draw_svg(handle, bounds, transformation); } } } diff --git a/widget/src/button.rs b/widget/src/button.rs index 5d4e580a4e..12931f244f 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -288,7 +288,7 @@ where } fn diff(&mut self, tree: &mut Tree) { - tree.diff_children(std::slice::from_mut(&mut self.content)) + tree.diff_children(std::slice::from_mut(&mut self.content)); } fn size(&self) -> Size { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 3956561cec..b36bd3dd3d 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -434,7 +434,7 @@ where size, line_height, shaping, - wrap, + wrap: _, } = &self.icon; let size = size.unwrap_or(Pixels(bounds.height * 0.7)); diff --git a/widget/src/container.rs b/widget/src/container.rs index 9fe70d7c73..b693108084 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -579,7 +579,7 @@ pub fn visible_bounds(id: Id) -> Task> { } task::widget(VisibleBounds { - target: id.into(), + target: id, depth: 0, scrollables: Vec::new(), bounds: None, diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 0eaddf5781..c125a06c1c 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -165,11 +165,11 @@ impl Default for State { fn default() -> Self { Self { is_hovered: Default::default(), - drag_initiated: Default::default(), + drag_initiated: None, is_out_of_bounds: true, - last_click: Default::default(), + last_click: None, cursor_position: None, - bounds: Default::default(), + bounds: Rectangle::default(), previous_click: None, } } @@ -346,7 +346,7 @@ where renderer: &Renderer, dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, ) { - if let Some(state) = state.children.iter().next() { + if let Some(state) = state.children.first() { self.content.as_widget().drag_destinations( state, layout, @@ -415,20 +415,19 @@ fn update( } if !cursor.is_over(layout.bounds()) { - if !state.is_out_of_bounds { - if widget + if !state.is_out_of_bounds + && widget .on_enter .as_ref() .or(widget.on_exit.as_ref()) .is_some() - { - if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { - state.is_out_of_bounds = true; - if let Some(message) = widget.on_exit.as_ref() { - shell.publish(message.clone()); - } - return event::Status::Captured; + { + if let Event::Mouse(mouse::Event::CursorMoved { .. }) = event { + state.is_out_of_bounds = true; + if let Some(message) = widget.on_exit.as_ref() { + shell.publish(message.clone()); } + return event::Status::Captured; } } diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index ff6ce9ea34..7e858a3a9f 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -1,5 +1,3 @@ -use iced_renderer::core::widget::Operation; - use crate::container; use crate::core::event::{self, Event}; use crate::core::layout; diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 1495a575f2..54b6390bad 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -1,5 +1,3 @@ -use iced_renderer::core::widget::Operation; - use crate::container; use crate::core::event::{self, Event}; use crate::core::layout; diff --git a/widget/src/row.rs b/widget/src/row.rs index 575dd24cd9..315243c370 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -204,7 +204,7 @@ where } fn diff(&mut self, tree: &mut Tree) { - tree.diff_children(&mut self.children) + tree.diff_children(&mut self.children); } fn size(&self) -> Size { diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 6841d19c80..cfa5985c98 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -456,7 +456,7 @@ where } fn diff(&mut self, tree: &mut Tree) { - tree.diff_children(std::slice::from_mut(&mut self.content)) + tree.diff_children(std::slice::from_mut(&mut self.content)); } fn size(&self) -> Size { diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 54eae1e08d..16bce89797 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -176,7 +176,7 @@ where tree.diff_children(&mut [ self.content.as_widget_mut(), self.tooltip.as_widget_mut(), - ]) + ]); } fn layout( From 17b5d3c60b2eff15821eeed72bf9a410540162b7 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 18 Oct 2024 16:19:36 -0400 Subject: [PATCH 009/116] fix: scrollable direction --- widget/src/scrollable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index cfa5985c98..120af2c601 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -818,7 +818,7 @@ where // TODO: Configurable speed/friction (?) Vector::new(x, y) * 60. } - mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y), + mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y), }; let is_shift_pressed = state.keyboard_modifiers.shift(); From 4e67eb9a14909ebda2539cc0c49db85ab5e69a24 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 18 Oct 2024 17:42:35 -0600 Subject: [PATCH 010/116] Fix doc links --- core/src/overlay.rs | 4 ++-- runtime/src/lib.rs | 10 +++++----- src/program.rs | 2 +- widget/src/button.rs | 2 +- widget/src/overlay/menu.rs | 2 +- widget/src/pick_list.rs | 2 +- winit/src/platform_specific/wayland/sctk_event.rs | 13 +++++++------ 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/core/src/overlay.rs b/core/src/overlay.rs index d0afe18ed6..e61ae83481 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -9,7 +9,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; -use crate::widget::Tree; +use crate::widget::{Operation, Tree}; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; /// An interactive component that can be displayed on top of other widgets. @@ -40,7 +40,7 @@ where &mut self, _layout: Layout<'_>, _renderer: &Renderer, - _operation: &mut dyn crate::widget::Operation, + _operation: &mut dyn Operation, ) { } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index fd6eb6d0b3..2d6ff62385 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -41,7 +41,7 @@ pub use program::Program; pub use task::Task; pub use user_interface::UserInterface; -use crate::core::widget; +use crate::core::{Color, widget}; use crate::futures::futures::channel::oneshot; use std::borrow::Cow; @@ -147,13 +147,13 @@ pub fn exit() -> Task { #[derive(Debug, Clone, Copy, PartialEq)] pub struct Appearance { /// The background [`Color`] of the application. - pub background_color: iced_core::Color, + pub background_color: Color, /// The default text [`Color`] of the application. - pub text_color: iced_core::Color, + pub text_color: Color, /// The default icon [`Color`] of the application. - pub icon_color: iced_core::Color, + pub icon_color: Color, } /// The default style of a [`Program`]. @@ -168,7 +168,7 @@ impl DefaultStyle for iced_core::Theme { } } -/// The default [`Appearance`] of a [`Program`] with the built-in [`Theme`]. +/// The default [`Appearance`] of a [`Program`] with the built-in [`iced_core::Theme`]. pub fn default(theme: &iced_core::Theme) -> Appearance { let palette = theme.extended_palette(); diff --git a/src/program.rs b/src/program.rs index f005771b86..c59e9f2501 100644 --- a/src/program.rs +++ b/src/program.rs @@ -89,7 +89,7 @@ pub trait Program: Sized { } #[cfg(feature = "winit")] - /// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state. + /// Runs the [`Program`] with the given [`Settings`](crate::Settings) and a closure that creates the initial state. fn run_with( self, settings: crate::Settings, diff --git a/widget/src/button.rs b/widget/src/button.rs index 12931f244f..012904dc72 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -900,7 +900,7 @@ fn disabled(style: Style) -> Style { } } -/// Produces a [`Command`] that focuses the [`Button`] with the given [`Id`]. +/// Produces a [`Task`] that focuses the [`Button`] with the given [`Id`]. pub fn focus(id: Id) -> Task { task::widget(operation::focusable::focus(id)) } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 1c86791ecc..5ef6abfc37 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -114,7 +114,7 @@ where self } - /// Sets the [`text::Wrap`] mode of the [`Menu`]. + /// Sets the [`text::Wrapping`] mode of the [`Menu`]. pub fn text_wrap(mut self, wrap: text::Wrapping) -> Self { self.text_wrap = wrap; self diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 042d32f1f0..2422c9cb0d 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -254,7 +254,7 @@ where self } - /// Sets the [`text::Wrap`] mode of the [`PickList`]. + /// Sets the [`text::Wrapping`] mode of the [`PickList`]. pub fn text_wrap(mut self, wrap: text::Wrapping) -> Self { self.text_wrap = wrap; self diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index a7939f48c9..1f455a9e66 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -87,8 +87,9 @@ pub enum IcedSctkEvent { /// /// This event type is useful as a place to put code that should be done before you start /// processing events, such as updating frame timing information for benchmarking or checking - /// the [`StartCause`][crate::event::StartCause] to see if a timer set by - /// [`ControlFlow::WaitUntil`](crate::platform_specific::wayland::event_loop::ControlFlow::WaitUntil) has elapsed. + /// the [`StartCause`] to see if a timer set by + /// [`ControlFlow::WaitUntil`](crate::platform_specific::wayland::event_loop::control_flow::ControlFlow::WaitUntil) + /// has elapsed. NewEvents(StartCause), /// An event produced by sctk @@ -101,7 +102,7 @@ pub enum IcedSctkEvent { /// state-changing events have been handled and you want to do stuff (updating state, performing /// calculations, etc) that happens as the "main body" of your event loop. If your program only draws /// graphics when something changes, it's usually better to do it in response to - /// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted + /// [`window::RedrawRequest`], which gets emitted /// immediately after this event. Programs that draw graphics continuously, like most games, /// can render here unconditionally for simplicity. MainEventsCleared, @@ -111,7 +112,7 @@ pub enum IcedSctkEvent { /// This gets triggered in two scenarios: /// - The OS has performed an operation that's invalidated the window's contents (such as /// resizing the window). - /// - The application has explicitly requested a redraw via [`Window::request_redraw`]. + /// - The application has explicitly requested a redraw via [`iced_runtime::core::Shell::request_redraw`]. /// /// During each iteration of the event loop, Winit will aggregate duplicate redraw requests /// into a single event, to help avoid duplicating rendering work. @@ -330,7 +331,7 @@ pub enum StartCause { /// moment the timeout was requested and the requested resume time. The actual resume time is /// guaranteed to be equal to or after the requested resume time. /// - /// [`ControlFlow::WaitUntil`]: crate::platform_specific::wayland::event_loop::ControlFlow::WaitUntil + /// [`ControlFlow::WaitUntil`]: crate::platform_specific::wayland::event_loop::control_flow::ControlFlow::WaitUntil ResumeTimeReached { start: Instant, requested_resume: Instant, @@ -346,7 +347,7 @@ pub enum StartCause { /// Sent if the event loop is being resumed after the loop's control flow was set to /// [`ControlFlow::Poll`]. /// - /// [`ControlFlow::Poll`]: crate::platform_specific::wayland::event_loop::ControlFlow::Poll + /// [`ControlFlow::Poll`]: crate::platform_specific::wayland::event_loop::control_flow::ControlFlow::Poll Poll, /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. From 1f0160777bf3f7bfcd7aad18d5bda5af6792cfb1 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 10:42:08 -0400 Subject: [PATCH 011/116] fix: docs --- core/src/overlay.rs | 2 +- runtime/src/lib.rs | 14 +-- src/program.rs | 2 +- widget/src/checkbox.rs | 6 +- widget/src/image.rs | 8 +- widget/src/scrollable.rs | 8 +- widget/src/slider.rs | 8 +- widget/src/svg.rs | 8 +- widget/src/toggler.rs | 8 +- .../platform_specific/wayland/sctk_event.rs | 92 ------------------- 10 files changed, 32 insertions(+), 124 deletions(-) diff --git a/core/src/overlay.rs b/core/src/overlay.rs index e61ae83481..b8ce136f56 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -35,7 +35,7 @@ where cursor: mouse::Cursor, ); - /// Applies an [`Operation`] to the [`Overlay`]. + /// Applies an [`crate::widget::Operation`] to the [`Overlay`]. fn operate( &mut self, _layout: Layout<'_>, diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2d6ff62385..615279dbb3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -41,7 +41,7 @@ pub use program::Program; pub use task::Task; pub use user_interface::UserInterface; -use crate::core::{Color, widget}; +use crate::core::{widget, Color}; use crate::futures::futures::channel::oneshot; use std::borrow::Cow; @@ -146,14 +146,14 @@ pub fn exit() -> Task { /// The appearance of a program. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Appearance { - /// The background [`Color`] of the application. - pub background_color: Color, + /// The background [`iced_core::Color`] of the application. + pub background_color: iced_core::Color, - /// The default text [`Color`] of the application. - pub text_color: Color, + /// The default text [`iced_core::Color`] of the application. + pub text_color: iced_core::Color, - /// The default icon [`Color`] of the application. - pub icon_color: Color, + /// The default icon [`iced_core::Color`] of the application. + pub icon_color: iced_core::Color, } /// The default style of a [`Program`]. diff --git a/src/program.rs b/src/program.rs index c59e9f2501..cb44eadb3f 100644 --- a/src/program.rs +++ b/src/program.rs @@ -89,7 +89,7 @@ pub trait Program: Sized { } #[cfg(feature = "winit")] - /// Runs the [`Program`] with the given [`Settings`](crate::Settings) and a closure that creates the initial state. + /// Runs the [`Program`] with the given settings, and a closure that creates the initial state. fn run_with( self, settings: crate::Settings, diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index b36bd3dd3d..d08976908f 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -263,14 +263,14 @@ where } #[cfg(feature = "a11y")] - /// Sets the name of the [`Button`]. + /// Sets the name of the [`Checkbox`]. pub fn name(mut self, name: impl Into>) -> Self { self.name = Some(name.into()); self } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Checkbox`]. pub fn description_widget( mut self, description: &T, @@ -282,7 +282,7 @@ where } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Checkbox`]. pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(iced_accessibility::Description::Text(description.into())); diff --git a/widget/src/image.rs b/widget/src/image.rs index 2e80094078..20cb300c22 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -149,14 +149,14 @@ impl<'a, Handle> Image<'a, Handle> { } #[cfg(feature = "a11y")] - /// Sets the name of the [`Button`]. + /// Sets the name of the [`Image`]. pub fn name(mut self, name: impl Into>) -> Self { self.name = Some(name.into()); self } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Image`]. pub fn description_widget( mut self, description: &T, @@ -168,7 +168,7 @@ impl<'a, Handle> Image<'a, Handle> { } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Image`]. pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(iced_accessibility::Description::Text(description.into())); @@ -176,7 +176,7 @@ impl<'a, Handle> Image<'a, Handle> { } #[cfg(feature = "a11y")] - /// Sets the label of the [`Button`]. + /// Sets the label of the [`Image`]. pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { self.label = Some(label.label().into_iter().map(|l| l.into()).collect()); diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 120af2c601..d125c7f4c8 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -268,14 +268,14 @@ where } #[cfg(feature = "a11y")] - /// Sets the name of the [`Button`]. + /// Sets the name of the [`Scrollable`]. pub fn name(mut self, name: impl Into>) -> Self { self.name = Some(name.into()); self } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Scrollable`]. pub fn description_widget( mut self, description: &impl iced_accessibility::Describes, @@ -287,7 +287,7 @@ where } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Scrollable`]. pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(iced_accessibility::Description::Text(description.into())); @@ -295,7 +295,7 @@ where } #[cfg(feature = "a11y")] - /// Sets the label of the [`Button`]. + /// Sets the label of the [`Scrollable`]. pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { self.label = Some(label.label().into_iter().map(|l| l.into()).collect()); diff --git a/widget/src/slider.rs b/widget/src/slider.rs index f770dfadd4..deb94b52b9 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -239,14 +239,14 @@ where } #[cfg(feature = "a11y")] - /// Sets the name of the [`Button`]. + /// Sets the name of the [`Slider`]. pub fn name(mut self, name: impl Into>) -> Self { self.name = Some(name.into()); self } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Slider`]. pub fn description_widget( mut self, description: &impl iced_accessibility::Describes, @@ -258,7 +258,7 @@ where } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Slider`]. pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(iced_accessibility::Description::Text(description.into())); @@ -266,7 +266,7 @@ where } #[cfg(feature = "a11y")] - /// Sets the label of the [`Button`]. + /// Sets the label of the [`Slider`]. pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { self.label = Some(label.label().into_iter().map(|l| l.into()).collect()); diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 7cb1afb9d3..72a45f291f 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -179,14 +179,14 @@ where } #[cfg(feature = "a11y")] - /// Sets the name of the [`Button`]. + /// Sets the name of the [`Svg`]. pub fn name(mut self, name: impl Into>) -> Self { self.name = Some(name.into()); self } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Svg`]. pub fn description_widget( mut self, description: &T, @@ -198,7 +198,7 @@ where } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Svg`]. pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(iced_accessibility::Description::Text(description.into())); @@ -206,7 +206,7 @@ where } #[cfg(feature = "a11y")] - /// Sets the label of the [`Button`]. + /// Sets the label of the [`Svg`]. pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self { self.label = Some(label.label().into_iter().map(|l| l.into()).collect()); diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 08aa7fe8ca..8d000bc380 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -263,14 +263,14 @@ where } #[cfg(feature = "a11y")] - /// Sets the name of the [`Button`]. + /// Sets the name of the [`Toggler`]. pub fn name(mut self, name: impl Into>) -> Self { self.name = Some(name.into()); self } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Toggler`]. pub fn description_widget( mut self, description: &T, @@ -282,7 +282,7 @@ where } #[cfg(feature = "a11y")] - /// Sets the description of the [`Button`]. + /// Sets the description of the [`Toggler`]. pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(iced_accessibility::Description::Text(description.into())); @@ -290,7 +290,7 @@ where } #[cfg(feature = "a11y")] - /// Sets the label of the [`Button`] using another widget. + /// Sets the label of the [`Toggler`] using another widget. pub fn labeled_by_widget( mut self, label: &dyn iced_accessibility::Labels, diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 1f455a9e66..6bfa328125 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -67,7 +67,6 @@ use std::{ collections::HashMap, num::NonZeroU32, sync::{Arc, Mutex}, - time::Instant, }; use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport; use winit::{ @@ -82,67 +81,6 @@ use super::{ winit_window::SctkWinitWindow, }; -pub enum IcedSctkEvent { - /// Emitted when new events arrive from the OS to be processed. - /// - /// This event type is useful as a place to put code that should be done before you start - /// processing events, such as updating frame timing information for benchmarking or checking - /// the [`StartCause`] to see if a timer set by - /// [`ControlFlow::WaitUntil`](crate::platform_specific::wayland::event_loop::control_flow::ControlFlow::WaitUntil) - /// has elapsed. - NewEvents(StartCause), - - /// An event produced by sctk - SctkEvent(SctkEvent), - - /// Emitted when all of the event loop's input events have been processed and redraw processing - /// is about to begin. - /// - /// This event is useful as a place to put your code that should be run after all - /// state-changing events have been handled and you want to do stuff (updating state, performing - /// calculations, etc) that happens as the "main body" of your event loop. If your program only draws - /// graphics when something changes, it's usually better to do it in response to - /// [`window::RedrawRequest`], which gets emitted - /// immediately after this event. Programs that draw graphics continuously, like most games, - /// can render here unconditionally for simplicity. - MainEventsCleared, - - /// Emitted after [`MainEventsCleared`] when a window should be redrawn. - /// - /// This gets triggered in two scenarios: - /// - The OS has performed an operation that's invalidated the window's contents (such as - /// resizing the window). - /// - The application has explicitly requested a redraw via [`iced_runtime::core::Shell::request_redraw`]. - /// - /// During each iteration of the event loop, Winit will aggregate duplicate redraw requests - /// into a single event, to help avoid duplicating rendering work. - /// - /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless - /// something changes, like most non-game GUIs. - /// - /// [`MainEventsCleared`]: Self::MainEventsCleared - RedrawRequested(ObjectId), - - /// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to - /// be taken away from the program. If there are no `RedrawRequested` events, it is emitted - /// immediately after `MainEventsCleared`. - /// - /// This event is useful for doing any cleanup or bookkeeping work after all the rendering - /// tasks have been completed. - /// - /// [`RedrawRequested`]: Self::RedrawRequested - RedrawEventsCleared, - - /// Emitted when the event loop is being shut down. - /// - /// This is irreversible - if this event is emitted, it is guaranteed to be the last event that - /// gets emitted. You generally want to treat this as an "do on quit" event. - LoopDestroyed, - - /// Frame callback event - Frame(WlSurface, u32), -} - #[derive(Debug, Clone)] pub enum SctkEvent { // @@ -324,36 +262,6 @@ pub enum LayerSurfaceEventVariant { ScaleFactorChanged(f64, Option), } -/// Describes the reason the event loop is resuming. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum StartCause { - /// Sent if the time specified by [`ControlFlow::WaitUntil`] has been reached. Contains the - /// moment the timeout was requested and the requested resume time. The actual resume time is - /// guaranteed to be equal to or after the requested resume time. - /// - /// [`ControlFlow::WaitUntil`]: crate::platform_specific::wayland::event_loop::control_flow::ControlFlow::WaitUntil - ResumeTimeReached { - start: Instant, - requested_resume: Instant, - }, - - /// Sent if the OS has new events to send to the window, after a wait was requested. Contains - /// the moment the wait was requested and the resume time, if requested. - WaitCancelled { - start: Instant, - requested_resume: Option, - }, - - /// Sent if the event loop is being resumed after the loop's control flow was set to - /// [`ControlFlow::Poll`]. - /// - /// [`ControlFlow::Poll`]: crate::platform_specific::wayland::event_loop::control_flow::ControlFlow::Poll - Poll, - - /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. - Init, -} - /// Pending update to a window requested by the user. #[derive(Default, Debug, Clone, Copy)] pub struct SurfaceUserRequest { From eb1b5d4179859c503131d980b4a5c1b9a4f214e3 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 10:44:40 -0400 Subject: [PATCH 012/116] refactor: disable wayland in winit shell by default --- winit/Cargo.toml | 4 +++- winit/src/platform_specific/mod.rs | 22 ++++++++++++++----- winit/src/platform_specific/wayland/mod.rs | 3 +-- .../platform_specific/wayland/sctk_event.rs | 12 +--------- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 436e7f75f4..4e9e20023b 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -14,7 +14,7 @@ keywords.workspace = true workspace = true [features] -default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] +default = ["x11"] debug = ["iced_runtime/debug"] system = ["sysinfo"] program = [] @@ -30,6 +30,8 @@ wayland = [ "xkbcommon-dl", "xkeysym", "iced_runtime/wayland", + "wayland-dlopen", + "wayland-csd-adwaita", ] wayland-dlopen = ["winit/wayland-dlopen"] wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index 4ef88dd7d9..608ca389ec 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -1,16 +1,17 @@ //! Wayland specific shell //! +use std::collections::HashMap; + use iced_graphics::Compositor; -use iced_runtime::{core::window, platform_specific, Debug}; -use sctk::reexports::client::Connection; -use wayland::sctk_event::UserInterfaces; +use iced_runtime::{core::window, user_interface, Debug}; #[cfg(all(feature = "wayland", target_os = "linux"))] pub mod wayland; #[cfg(all(feature = "wayland", target_os = "linux"))] pub use wayland::*; +#[cfg(all(feature = "wayland", target_os = "linux"))] use wayland_backend::client::Backend; use crate::{program::WindowManager, Program}; @@ -52,7 +53,7 @@ impl PlatformSpecific { ) { match action { #[cfg(all(feature = "wayland", target_os = "linux"))] - platform_specific::Action::Wayland(a) => { + iced_runtime::platform_specific::Action::Wayland(a) => { self.send_wayland(wayland::Action::Action(a)); } } @@ -92,7 +93,7 @@ impl PlatformSpecific { wayland_display_handle.display.as_ptr().cast(), ) }; - Connection::from_backend(backend) + sctk::reexports::client::Connection::from_backend(backend) } _ => { return; @@ -136,6 +137,17 @@ impl PlatformSpecific { } } +pub type UserInterfaces<'a, P> = HashMap< + window::Id, + user_interface::UserInterface< + 'a, +

::Message, +

::Theme, +

::Renderer, + >, + rustc_hash::FxBuildHasher, +>; + pub(crate) fn handle_event<'a, P, C>( e: Event, events: &mut Vec<(Option, iced_runtime::core::Event)>, diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 7fbd07b4d8..445b1b67be 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -18,7 +18,6 @@ use sctk::reexports::calloop; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::seat::keyboard::Modifiers; use sctk_event::SctkEvent; -use sctk_event::UserInterfaces; use std::{collections::HashMap, sync::Arc}; use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; use wayland_backend::client::ObjectId; @@ -128,7 +127,7 @@ impl WaylandSpecific { compositor: &mut C, window_manager: &mut WindowManager, debug: &mut Debug, - user_interfaces: &mut UserInterfaces<'a, P>, + user_interfaces: &mut super::UserInterfaces<'a, P>, clipboard: &mut crate::Clipboard, #[cfg(feature = "a11y")] adapters: &mut HashMap< window::Id, diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 6bfa328125..f4f51c3e8b 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -281,16 +281,6 @@ pub struct SurfaceCompositorUpdate { /// New scale factor. pub scale_factor: Option, } -pub type UserInterfaces<'a, P> = HashMap< - SurfaceId, - user_interface::UserInterface< - 'a, -

::Message, -

::Theme, -

::Renderer, - >, - rustc_hash::FxBuildHasher, ->; impl SctkEvent { pub(crate) fn process<'a, P, C>( @@ -305,7 +295,7 @@ impl SctkEvent { control_sender: &mpsc::UnboundedSender, proxy: &EventLoopProxy, debug: &mut Debug, - user_interfaces: &mut UserInterfaces<'a, P>, + user_interfaces: &mut crate::platform_specific::UserInterfaces<'a, P>, events: &mut Vec<(Option, iced_runtime::core::Event)>, clipboard: &mut Clipboard, subsurface_state: &mut Option, From 1cc06ae8cd85b32e5bf2c2cd8f652a53d0aa6530 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 16:42:14 -0400 Subject: [PATCH 013/116] fix: log warning instead of panicing for current_monitor --- winit/src/platform_specific/wayland/winit_window.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/winit/src/platform_specific/wayland/winit_window.rs b/winit/src/platform_specific/wayland/winit_window.rs index eb7fc9dc77..0ca4ceeb57 100644 --- a/winit/src/platform_specific/wayland/winit_window.rs +++ b/winit/src/platform_specific/wayland/winit_window.rs @@ -224,17 +224,21 @@ impl winit::window::Window for SctkWinitWindow { } fn current_monitor(&self) -> Option { - todo!() + tracing::warn!( + "current_monitor is not implemented for wayland windows." + ); + None } fn available_monitors( &self, ) -> Box> { - todo!() + Box::new(None.into_iter()) } fn has_focus(&self) -> bool { - todo!() + tracing::warn!("has_focus is not implemented for wayland windows."); + false } fn set_ime_cursor_area( From 68b3ede5ba60cf99996b41aebfb66e279c5c1d1d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 19:07:53 -0400 Subject: [PATCH 014/116] fix: filter out events with no window id, if the window id is needed --- winit/src/program.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/winit/src/program.rs b/winit/src/program.rs index ec113677b9..4f2566bdf8 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -1611,6 +1611,16 @@ async fn run_instance<'a, P, C>( } for (id, event) in events.drain(..) { + if id.is_none() + && matches!( + event, + core::Event::Keyboard(_) + | core::Event::Touch(_) + | core::Event::Mouse(_) + ) + { + continue; + } runtime.broadcast(subscription::Event::Interaction { window: id.unwrap_or(window::Id::NONE), event, From f0f7bbee2b1f630d42134be399197be048f8596e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 21 Oct 2024 23:49:59 -0400 Subject: [PATCH 015/116] fix(sctk): reduce event spam for redraw requests --- winit/src/platform_specific/mod.rs | 7 -- .../wayland/event_loop/mod.rs | 82 +++++++++---------- .../wayland/event_loop/state.rs | 1 - winit/src/platform_specific/wayland/mod.rs | 6 -- .../platform_specific/wayland/winit_window.rs | 24 +----- winit/src/program.rs | 3 +- winit/src/program/window_manager.rs | 7 +- 7 files changed, 48 insertions(+), 82 deletions(-) diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index 608ca389ec..5f1727620d 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -59,13 +59,6 @@ impl PlatformSpecific { } } - pub(crate) fn send_ready(&mut self) { - #[cfg(all(feature = "wayland", target_os = "linux"))] - { - self.send_wayland(wayland::Action::Ready); - } - } - pub(crate) fn update_subsurfaces( &mut self, id: window::Id, diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 74b3cb0c62..06961a1cd2 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -103,50 +103,49 @@ impl SctkEventLoop { _ = loop_handle .insert_source(action_rx, |event, _, state| { match event { - calloop::channel::Event::Msg(e) => match e { - crate::platform_specific::Action::Action(a) => { - if let Err(err) = state.handle_action(a) { - log::warn!("{err:?}"); + calloop::channel::Event::Msg(e) => match e { + crate::platform_specific::Action::Action(a) => { + if let Err(err) = state.handle_action(a) { + log::warn!("{err:?}"); + } } - } - crate::platform_specific::Action::TrackWindow( - window, - id, - ) => { - state.windows.push(SctkWindow { window, id }); - } - crate::Action::RemoveWindow(id) => { - // TODO clean up popups matching the window. - state.windows.retain(|window| id != window.id); - } - crate::platform_specific::Action::SetCursor(icon) => { - if let Some(seat) = state.seats.get_mut(0) { - seat.icon = Some(icon); - seat.set_cursor(&state.connection, icon); + crate::platform_specific::Action::TrackWindow( + window, + id, + ) => { + state.windows.push(SctkWindow { window, id }); } - } - crate::platform_specific::Action::RequestRedraw(id) => { - let e = state.frame_status.entry(id).or_insert(FrameStatus::RequestedRedraw); - if matches!(e, FrameStatus::Received) { - *e = FrameStatus::Ready; + crate::Action::RemoveWindow(id) => { + // TODO clean up popups matching the window. + state.windows.retain(|window| id != window.id); } + crate::platform_specific::Action::SetCursor( + icon, + ) => { + if let Some(seat) = state.seats.get_mut(0) { + seat.icon = Some(icon); + seat.set_cursor(&state.connection, icon); + } + } + crate::platform_specific::Action::RequestRedraw( + id, + ) => { + let e = state + .frame_status + .entry(id) + .or_insert(FrameStatus::RequestedRedraw); + if matches!(e, FrameStatus::Received) { + *e = FrameStatus::Ready; + } + } + crate::Action::Dropped(id) => { + _ = state.destroyed.remove(&id.inner()); + } + }, + calloop::channel::Event::Closed => { + log::info!("Calloop channel closed."); } - crate::platform_specific::Action::PrePresentNotify( - _, - ) => { - // TODO - } - crate::platform_specific::Action::Ready => { - state.ready = true; - } - crate::Action::Dropped(id) => { - _ = state.destroyed.remove(&id.inner()); - } - }, - calloop::channel::Event::Closed => { - log::info!("Calloop channel closed."); } - } }) .unwrap(); let wayland_source = @@ -227,7 +226,6 @@ impl SctkEventLoop { proxy, id_map: Default::default(), to_commit: HashMap::new(), - ready: true, destroyed: HashSet::new(), pending_popup: Default::default(), activation_token_ctr: 0, @@ -305,9 +303,6 @@ impl SctkEventLoop { } } } - if !state.state.ready { - continue; - } if let Err(err) = state.event_loop.dispatch(None, &mut state.state) @@ -370,7 +365,6 @@ impl SctkEventLoop { ); } } - if wake_up { state.state.proxy.wake_up(); } diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 2e90516ee6..09589c6cbc 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -378,7 +378,6 @@ pub struct SctkState { pub(crate) id_map: HashMap, pub(crate) to_commit: HashMap, pub(crate) destroyed: HashSet, - pub(crate) ready: bool, pub(crate) pending_popup: Option<(SctkPopupSettings, usize)>, pub(crate) activation_token_ctr: u32, diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 445b1b67be..e4fcfd9d16 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -28,11 +28,9 @@ pub(crate) enum Action { Action(iced_runtime::platform_specific::wayland::Action), SetCursor(CursorIcon), RequestRedraw(ObjectId), - PrePresentNotify(ObjectId), TrackWindow(Arc, window::Id), RemoveWindow(window::Id), Dropped(SurfaceIdWrapper), - Ready, } impl std::fmt::Debug for Action { @@ -45,16 +43,12 @@ impl std::fmt::Debug for Action { Self::RequestRedraw(arg0) => { f.debug_tuple("RequestRedraw").field(arg0).finish() } - Self::PrePresentNotify(arg0) => { - f.debug_tuple("PrePresentNotify").field(arg0).finish() - } Self::TrackWindow(_arg0, arg1) => { f.debug_tuple("TrackWindow").field(arg1).finish() } Self::RemoveWindow(arg0) => { f.debug_tuple("RemoveWindow").field(arg0).finish() } - Self::Ready => write!(f, "Ready"), Self::Dropped(_surface_id_wrapper) => write!(f, "Dropped"), } } diff --git a/winit/src/platform_specific/wayland/winit_window.rs b/winit/src/platform_specific/wayland/winit_window.rs index 0ca4ceeb57..36b2d5efd6 100644 --- a/winit/src/platform_specific/wayland/winit_window.rs +++ b/winit/src/platform_specific/wayland/winit_window.rs @@ -2,10 +2,7 @@ use crate::platform_specific::wayland::Action; use raw_window_handle::HandleError; use sctk::reexports::{ calloop::channel, - client::{ - protocol::{wl_display::WlDisplay, wl_surface::WlSurface}, - Proxy, QueueHandle, - }, + client::{protocol::wl_display::WlDisplay, Proxy, QueueHandle}, }; use std::sync::{Arc, Mutex}; use winit::{ @@ -16,19 +13,7 @@ use winit::{ use crate::platform_specific::SurfaceIdWrapper; -use super::event_loop::state::{ - Common, CommonSurface, SctkLayerSurface, SctkLockSurface, SctkPopup, - SctkState, TOKEN_CTR, -}; - -#[derive(Debug)] -pub(crate) enum Surface { - Popup(SctkPopup), - Layer(SctkLayerSurface), - Lock(SctkLockSurface), -} - -impl Surface {} +use super::event_loop::state::{Common, CommonSurface, SctkState, TOKEN_CTR}; pub struct SctkWinitWindow { tx: channel::Sender, @@ -37,7 +22,6 @@ pub struct SctkWinitWindow { common: Arc>, display: WlDisplay, pub(crate) queue_handle: QueueHandle, - wait_redraw: bool, } impl Drop for SctkWinitWindow { @@ -62,7 +46,6 @@ impl SctkWinitWindow { surface, display, queue_handle, - wait_redraw: false, }) } } @@ -87,9 +70,6 @@ impl winit::window::Window for SctkWinitWindow { fn pre_present_notify(&self) { let surface = self.surface.wl_surface(); _ = surface.frame(&self.queue_handle, surface.clone()); - _ = self - .tx - .send(Action::PrePresentNotify(self.surface.wl_surface().id())); } fn set_cursor(&self, cursor: winit::window::Cursor) { diff --git a/winit/src/program.rs b/winit/src/program.rs index 4f2566bdf8..70b00f0c54 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -795,7 +795,6 @@ async fn run_instance<'a, P, C>( let event = if let Ok(event) = event_receiver.try_next() { event } else { - platform_specific_handler.send_ready(); event_receiver.next().await }; @@ -1135,6 +1134,8 @@ async fn run_instance<'a, P, C>( continue; }; + window.redraw_requested = false; + // TODO: Avoid redrawing all the time by forcing widgets to // request redraws on state changes // diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 5ffc5ea5c1..85d7254ff0 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -71,6 +71,7 @@ where mouse_interaction: mouse::Interaction::None, prev_dnd_destination_rectangles_count: 0, resize_enabled: false, + redraw_requested: false, }, ); @@ -167,6 +168,7 @@ where pub surface: C::Surface, pub renderer: P::Renderer, pub resize_enabled: bool, + pub(crate) redraw_requested: bool, } impl Window @@ -193,6 +195,9 @@ where } pub fn request_redraw(&mut self) { - self.raw.request_redraw(); + if !self.redraw_requested { + self.redraw_requested = true; + self.raw.request_redraw(); + } } } From e6e6536259107d0eed2b96b4a7b4ead6335b9afa Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 22 Oct 2024 12:29:41 -0600 Subject: [PATCH 016/116] Implement fill_raw --- tiny_skia/src/layer.rs | 11 ++++++++++- tiny_skia/src/lib.rs | 5 ++++- wgpu/src/layer.rs | 11 ++++++++++- wgpu/src/lib.rs | 3 ++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs index 3df644a2d3..b4e9286f45 100644 --- a/tiny_skia/src/layer.rs +++ b/tiny_skia/src/layer.rs @@ -4,7 +4,7 @@ use crate::core::{ }; use crate::graphics::damage; use crate::graphics::layer; -use crate::graphics::text::{Editor, Paragraph, Text}; +use crate::graphics::text::{Editor, Paragraph, Raw, Text}; use crate::graphics::{self, Image}; use crate::Primitive; @@ -115,6 +115,15 @@ impl Layer { .push(Item::Cached(text, clip_bounds, transformation)); } + pub fn draw_raw(&mut self, raw: Raw, transformation: Transformation) { + let text = Text::Raw { + raw, + transformation, + }; + + self.text.push(Item::Live(text)); + } + pub fn draw_image(&mut self, image: Image, transformation: Transformation) { match image { Image::Raster { handle, bounds } => { diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 9085045790..5fa8fe75cd 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -314,7 +314,10 @@ impl core::text::Renderer for Renderer { layer.draw_text(text, position, color, clip_bounds, transformation); } - fn fill_raw(&mut self, _raw: Self::Raw) {} + fn fill_raw(&mut self, raw: Self::Raw) { + let (layer, transformation) = self.layers.current_mut(); + layer.draw_raw(raw, transformation); + } } #[cfg(feature = "geometry")] diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 5340a66b90..f62f04835f 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -5,7 +5,7 @@ use crate::core::{ use crate::graphics; use crate::graphics::color; use crate::graphics::layer; -use crate::graphics::text::{Editor, Paragraph}; +use crate::graphics::text::{Editor, Paragraph, Raw}; use crate::graphics::Mesh; use crate::image::{self, Image}; use crate::primitive::{self, Primitive}; @@ -113,6 +113,15 @@ impl Layer { self.pending_text.push(text); } + pub fn draw_raw(&mut self, raw: Raw, transformation: Transformation) { + let text = Text::Raw { + raw, + transformation, + }; + + self.pending_text.push(text); + } + pub fn draw_image(&mut self, image: Image, transformation: Transformation) { match image { Image::Raster { handle, bounds } => { diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index c0703996c1..06c8a1d841 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -522,7 +522,8 @@ impl core::text::Renderer for Renderer { } fn fill_raw(&mut self, raw: Self::Raw) { - // TODO + let (layer, transformation) = self.layers.current_mut(); + layer.draw_raw(raw, transformation); } } From 8e2f300e26e8f50757c222afbc28b6c6a425e2a5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 22 Oct 2024 14:28:24 -0400 Subject: [PATCH 017/116] fix: slider rendering --- widget/src/slider.rs | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/widget/src/slider.rs b/widget/src/slider.rs index deb94b52b9..a19b109003 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -511,7 +511,7 @@ where let radius = (radius) .max(2.0 * border_width) .min(bounds.height / 2.0) - .min(bounds.width / 2.0); + .min(bounds.width / 2.0 + 2.0 * border_width); (radius * 2.0, radius * 2.0, Radius::from(radius)) } HandleShape::Rectangle { @@ -519,12 +519,8 @@ where width, border_radius, } => { - let width = (f32::from(width)) - .max(2.0 * border_width) - .min(bounds.width); - let height = (f32::from(height)) - .max(2.0 * border_width) - .min(bounds.height); + let width = (f32::from(width)).max(2.0 * border_width); + let height = (f32::from(height)).max(2.0 * border_width); let mut border_radius: [f32; 4] = border_radius.into(); for r in &mut border_radius { *r = (*r) @@ -611,23 +607,9 @@ where renderer.fill_quad( renderer::Quad { bounds: Rectangle { - x: bounds.x, - y: rail_y - style.rail.width / 2.0, - width: offset + handle_width / 2.0, - height: style.rail.width, - }, - border: style.rail.border, - ..renderer::Quad::default() - }, - style.rail.backgrounds.0, - ); - - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: bounds.x, + x: bounds.x + offset + handle_width / 2.0, y: rail_y - style.rail.width / 2.0, - width: offset + handle_width / 2.0, + width: bounds.width - offset - handle_width / 2.0, height: style.rail.width, }, border: style.rail.border, From 17cbea2a66f07918b5d6b2c91629ac38fafb26fc Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 23 Oct 2024 18:33:45 -0400 Subject: [PATCH 018/116] refactor: apply requested size for surface after creation This should help popups start as the correct size, and also layer surfaces --- .../wayland/event_loop/mod.rs | 4 +- .../platform_specific/wayland/sctk_event.rs | 146 +++++++++++++++--- 2 files changed, 123 insertions(+), 27 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 06961a1cd2..64ef8c24f4 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -18,9 +18,7 @@ use crate::{ }; use raw_window_handle::HasDisplayHandle; -use sctk::reexports::{ - calloop_wayland_source::WaylandSource, client::protocol::wl_subcompositor, -}; +use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::{ activation::ActivationState, compositor::CompositorState, diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index f4f51c3e8b..3f35c3890d 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -741,20 +741,66 @@ impl SctkEvent { ); } - let _ = user_interfaces.insert( + let mut ui = crate::program::build_user_interface( + program, + user_interface::Cache::default(), + &mut window.renderer, + logical_size, + debug, surface_id, - crate::program::build_user_interface( - program, - user_interface::Cache::default(), - &mut window.renderer, - logical_size, - debug, - surface_id, - window.raw.clone(), - window.prev_dnd_destination_rectangles_count, - clipboard, - ), + window.raw.clone(), + window.prev_dnd_destination_rectangles_count, + clipboard, + ); + + _ = ui.update( + &vec![iced_runtime::core::Event::PlatformSpecific( + iced_runtime::core::event::PlatformSpecific::Wayland( + iced_runtime::core::event::wayland::Event::RequestResize, + ), + )], + window.state.cursor(), + &mut window.renderer, + clipboard, + &mut Vec::new(), ); + + if let Some(requested_size) = + clipboard.requested_logical_size.lock().unwrap().take() + { + let requested_physical_size = + winit::dpi::PhysicalSize::new( + (requested_size.width as f64 + * window.state.scale_factor()) + .ceil() as u32, + (requested_size.height as f64 + * window.state.scale_factor()) + .ceil() as u32, + ); + let physical_size = window.state.physical_size(); + if requested_physical_size.width != physical_size.width + || requested_physical_size.height + != physical_size.height + { + // FIXME what to do when we are stuck in a configure event/resize request loop + // We don't have control over how winit handles this. + window.resize_enabled = true; + + let s = winit::dpi::Size::Physical( + requested_physical_size, + ); + _ = window.raw.request_surface_size(s); + window.raw.set_min_surface_size(Some(s)); + window.raw.set_max_surface_size(Some(s)); + window.state.synchronize( + &program, + surface_id, + window.raw.as_ref(), + ); + } + } + + let _ = user_interfaces.insert(surface_id, ui); } LayerSurfaceEventVariant::ScaleFactorChanged(..) => {} LayerSurfaceEventVariant::Configure( @@ -901,20 +947,72 @@ impl SctkEvent { ); let logical_size = window.size(); - let _ = user_interfaces.insert( + let mut ui = crate::program::build_user_interface( + program, + user_interface::Cache::default(), + &mut window.renderer, + logical_size, + debug, surface_id, - crate::program::build_user_interface( - program, - user_interface::Cache::default(), - &mut window.renderer, - logical_size, - debug, - surface_id, - window.raw.clone(), - window.prev_dnd_destination_rectangles_count, - clipboard, - ), + window.raw.clone(), + window.prev_dnd_destination_rectangles_count, + clipboard, ); + + _ = ui.update( + &vec![iced_runtime::core::Event::PlatformSpecific( + iced_runtime::core::event::PlatformSpecific::Wayland( + iced_runtime::core::event::wayland::Event::RequestResize, + ), + )], + window.state.cursor(), + &mut window.renderer, + clipboard, + &mut Vec::new(), + ); + + if let Some(requested_size) = clipboard + .requested_logical_size + .lock() + .unwrap() + .take() + { + let requested_physical_size = + winit::dpi::PhysicalSize::new( + (requested_size.width as f64 + * window.state.scale_factor()) + .ceil() + as u32, + (requested_size.height as f64 + * window.state.scale_factor()) + .ceil() + as u32, + ); + let physical_size = window.state.physical_size(); + if requested_physical_size.width + != physical_size.width + || requested_physical_size.height + != physical_size.height + { + // FIXME what to do when we are stuck in a configure event/resize request loop + // We don't have control over how winit handles this. + window.resize_enabled = true; + + let s = winit::dpi::Size::Physical( + requested_physical_size, + ); + _ = window.raw.request_surface_size(s); + window.raw.set_min_surface_size(Some(s)); + window.raw.set_max_surface_size(Some(s)); + window.state.synchronize( + &program, + surface_id, + window.raw.as_ref(), + ); + } + } + + let _ = user_interfaces.insert(surface_id, ui); } PopupEventVariant::Configure(_, _, _) => {} // TODO PopupEventVariant::RepositionionedPopup { token: _ } => {} From 22ff17bedc565b22503d371137907ac1b98af43d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 23 Oct 2024 20:33:12 -0400 Subject: [PATCH 019/116] fix: reverse sctk scroll direction --- .../platform_specific/wayland/handlers/seat/pointer.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs index 798ecbe74b..a9bca881cb 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs @@ -117,14 +117,14 @@ impl PointerHandler for SctkState { device_id: Default::default(), delta: if horizontal.discrete > 0 { MouseScrollDelta::LineDelta( - horizontal.discrete as f32, - vertical.discrete as f32, + -horizontal.discrete as f32, + -vertical.discrete as f32, ) } else { MouseScrollDelta::PixelDelta( PhysicalPosition::new( - horizontal.absolute, - vertical.absolute, + -horizontal.absolute, + -vertical.absolute, ), ) }, From 73a2679edd8a8749ddc103da8f1b634613082750 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Wed, 30 Oct 2024 16:42:25 +0100 Subject: [PATCH 020/116] improv: expose set_blur --- runtime/src/window.rs | 10 ++++++++++ winit/src/program.rs | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 48c200ec99..2086ffecba 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -159,6 +159,9 @@ pub enum Action { /// This enables mouse events for the window and stops mouse events /// from being passed to whatever is underneath. DisableMousePassthrough(Id), + + /// Set window blur. + SetBlur(bool), } /// Subscribes to the frames of the window of the running application. @@ -456,3 +459,10 @@ pub fn enable_mouse_passthrough(id: Id) -> Task { pub fn disable_mouse_passthrough(id: Id) -> Task { task::effect(crate::Action::Window(Action::DisableMousePassthrough(id))) } + +/// Sets the blur effect for the window. +/// +/// This is only supported on platforms that support window blur. +pub fn set_blur(enable: bool) -> Task { + task::effect(crate::Action::Window(Action::SetBlur(enable))) +} diff --git a/winit/src/program.rs b/winit/src/program.rs index 70b00f0c54..ffc8dd020f 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -2202,6 +2202,11 @@ fn run_action( let _ = window.raw.set_cursor_hittest(true); } } + window::Action::SetBlur(enable) => { + if let Some(window) = window_manager.get_mut(0) { + window.raw.set_blur(enable); + } + } }, Action::System(action) => match action { system::Action::QueryInformation(_channel) => { From 7de6bdb32ac5040b3b3b5692c1a02cfba68050d2 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Wed, 30 Oct 2024 17:19:19 +0100 Subject: [PATCH 021/116] fix: use enable and disable methods --- runtime/src/window.rs | 20 +++++++++++++++----- winit/src/program.rs | 11 ++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 2086ffecba..ff9660b744 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -160,8 +160,11 @@ pub enum Action { /// from being passed to whatever is underneath. DisableMousePassthrough(Id), - /// Set window blur. - SetBlur(bool), + /// Enable window blur. + EnableBlur(Id), + + /// Disable window blur. + DisableBlur(Id), } /// Subscribes to the frames of the window of the running application. @@ -460,9 +463,16 @@ pub fn disable_mouse_passthrough(id: Id) -> Task { task::effect(crate::Action::Window(Action::DisableMousePassthrough(id))) } -/// Sets the blur effect for the window. +/// Enable the blur effect for a window. +/// +/// This is only supported on platforms that support window blur. +pub fn enable_blur(id: Id) -> Task { + task::effect(crate::Action::Window(Action::EnableBlur(id))) +} + +/// Enable the blur effect for a window. /// /// This is only supported on platforms that support window blur. -pub fn set_blur(enable: bool) -> Task { - task::effect(crate::Action::Window(Action::SetBlur(enable))) +pub fn disable_blur(id: Id) -> Task { + task::effect(crate::Action::Window(Action::DisableBlur(id))) } diff --git a/winit/src/program.rs b/winit/src/program.rs index ffc8dd020f..a12ec20bc2 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -2202,9 +2202,14 @@ fn run_action( let _ = window.raw.set_cursor_hittest(true); } } - window::Action::SetBlur(enable) => { - if let Some(window) = window_manager.get_mut(0) { - window.raw.set_blur(enable); + window::Action::EnableBlur(id) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_blur(true); + } + } + window::Action::DisableBlur(id) => { + if let Some(window) = window_manager.get_mut(id) { + window.raw.set_blur(false); } } }, From d0825db14df0c5400a681239a3f01c1548d9bb39 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Wed, 30 Oct 2024 17:49:24 +0100 Subject: [PATCH 022/116] fix: documentation --- runtime/src/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/window.rs b/runtime/src/window.rs index ff9660b744..59c8d59296 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -470,7 +470,7 @@ pub fn enable_blur(id: Id) -> Task { task::effect(crate::Action::Window(Action::EnableBlur(id))) } -/// Enable the blur effect for a window. +/// Disable the blur effect for a window. /// /// This is only supported on platforms that support window blur. pub fn disable_blur(id: Id) -> Task { From d7d18aa3de9825c06beb7b99edfdc9d5980a82db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Mon, 4 Nov 2024 20:33:27 +0100 Subject: [PATCH 023/116] fix: scrollbar width --- widget/src/scrollable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index d125c7f4c8..fcf6f2421d 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -376,9 +376,9 @@ pub struct Scrollbar { impl Default for Scrollbar { fn default() -> Self { Self { - width: 10.0, + width: 8.0, margin: 0.0, - scroller_width: 10.0, + scroller_width: 8.0, alignment: Anchor::Start, spacing: None, } From 5f3f9f59b1f00fa257b482998de2e6c8cffce276 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 4 Nov 2024 22:30:38 -0500 Subject: [PATCH 024/116] fix: avoid overwriting id when diffing children this can interact with the named IDs, and cause state mismatches, and doesn't need to be done, because the ID will be updated by the diff method if there is a Tag match anyways. --- core/src/widget/tree.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index 963a738137..b17d9a123f 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -334,9 +334,6 @@ impl Tree { ) { let c = &mut id_list[child_state_i]; - if len_changed { - c.id.clone_from(new_id); - } child_state_i += 1; c } else { From 66dc1fc1db7abd6aeafda8d26dc6642d66ee0940 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 5 Nov 2024 17:27:13 -0500 Subject: [PATCH 025/116] fix: offset DnD events in scrollable --- widget/src/scrollable.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index fcf6f2421d..87bcf9319c 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -763,10 +763,40 @@ where let translation = state.translation(self.direction, bounds, content_bounds); + let mut c_event = match event.clone() { + Event::Dnd(dnd::DndEvent::Offer( + id, + dnd::OfferEvent::Enter { + x, + y, + mime_types, + surface, + }, + )) => Event::Dnd(dnd::DndEvent::Offer( + id.clone(), + dnd::OfferEvent::Enter { + x: x + translation.x as f64, + y: y + translation.y as f64, + mime_types: mime_types.clone(), + surface: surface.clone(), + }, + )), + Event::Dnd(dnd::DndEvent::Offer( + id, + dnd::OfferEvent::Motion { x, y }, + )) => Event::Dnd(dnd::DndEvent::Offer( + id.clone(), + dnd::OfferEvent::Motion { + x: x + translation.x as f64, + y: y + translation.y as f64, + }, + )), + e => e, + }; self.content.as_widget_mut().on_event( &mut tree.children[0], - event.clone(), + c_event, content, cursor, renderer, From 8af9f398baab9b037c6661b8f571afa95a745208 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 6 Nov 2024 22:31:05 -0500 Subject: [PATCH 026/116] fix: tree diff improvements Search for the old child state with matching index --- core/src/widget/tree.rs | 195 +++++++++++++++++++++------------------- 1 file changed, 104 insertions(+), 91 deletions(-) diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index b17d9a123f..f9edd0360d 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -3,7 +3,7 @@ use crate::id::{Id, Internal}; use crate::Widget; use std::any::{self, Any}; use std::borrow::{Borrow, BorrowMut, Cow}; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::hash::Hash; use std::{fmt, mem}; @@ -164,93 +164,102 @@ impl Tree { { let borrowed: &mut dyn Widget = new.borrow_mut(); - let mut needs_reset = false; - let tag_match = self.tag == borrowed.tag(); - if let Some(Id(Internal::Custom(_, n))) = borrowed.id() { - if let Some((mut state, children)) = NAMED - .with(|named| named.borrow_mut().remove(&n)) - .or_else(|| { - //check self.id - if let Some(Id(Internal::Custom(_, ref name))) = self.id { - if name == &n { - Some(( - mem::replace(&mut self.state, State::None), - self.children - .iter_mut() - .map(|s| { - // take the data - mem::replace( - s, - Tree { - id: s.id.clone(), - tag: s.tag, - ..Tree::empty() - }, - ) - }) - .enumerate() - .collect(), - )) + + let mut tag_match = self.tag == borrowed.tag(); + if tag_match { + if let Some(Id(Internal::Custom(_, n))) = borrowed.id() { + if let Some((mut state, children)) = NAMED + .with(|named| named.borrow_mut().remove(&n)) + .or_else(|| { + //check self.id + if let Some(Id(Internal::Custom(_, ref name))) = self.id + { + if name == &n { + Some(( + mem::replace(&mut self.state, State::None), + self.children + .iter_mut() + .map(|s| { + // take the data + mem::replace( + s, + Tree { + id: s.id.clone(), + tag: s.tag, + ..Tree::empty() + }, + ) + }) + .enumerate() + .collect(), + )) + } else { + None + } } else { None } + }) + { + std::mem::swap(&mut self.state, &mut state); + let widget_children = borrowed.children(); + if !tag_match + || self.children.len() != widget_children.len() + { + self.children = borrowed.children(); } else { - None - } - }) - { - std::mem::swap(&mut self.state, &mut state); - let widget_children = borrowed.children(); - if !tag_match || self.children.len() != widget_children.len() { - self.children = borrowed.children(); - } else { - for (old_i, mut old) in children { - let Some(my_state) = self.children.get_mut(old_i) - else { - continue; - }; - if my_state.tag != old.tag || { - !match (&old.id, &my_state.id) { - ( - Some(Id(Internal::Custom(_, ref old_name))), - Some(Id(Internal::Custom(_, ref my_name))), - ) => old_name == my_name, - ( - Some(Id(Internal::Set(a))), - Some(Id(Internal::Set(b))), - ) => a.len() == b.len(), - ( - Some(Id(Internal::Unique(_))), - Some(Id(Internal::Unique(_))), - ) => true, - (None, None) => true, - _ => false, + for (old_i, mut old) in children { + let Some(my_state) = self.children.get_mut(old_i) + else { + continue; + }; + if my_state.tag != old.tag || { + !match (&old.id, &my_state.id) { + ( + Some(Id(Internal::Custom( + _, + ref old_name, + ))), + Some(Id(Internal::Custom( + _, + ref my_name, + ))), + ) => old_name == my_name, + ( + Some(Id(Internal::Set(a))), + Some(Id(Internal::Set(b))), + ) => a.len() == b.len(), + ( + Some(Id(Internal::Unique(_))), + Some(Id(Internal::Unique(_))), + ) => true, + (None, None) => true, + _ => false, + } + } { + continue; } - } { - continue; - } - mem::swap(my_state, &mut old); + mem::swap(my_state, &mut old); + } } + } else { + tag_match = false; } } else { - needs_reset = true; - } - } else if tag_match { - if let Some(id) = self.id.clone() { - borrowed.set_id(id); - } - if self.children.len() != borrowed.children().len() { - self.children = borrowed.children(); + if let Some(id) = self.id.clone() { + borrowed.set_id(id); + } + if self.children.len() != borrowed.children().len() { + self.children = borrowed.children(); + } } - } else { - needs_reset = true; } - if needs_reset { - *self = Self::new(borrowed); - let borrowed = new.borrow_mut(); + if tag_match { borrowed.diff(self); } else { + *self = Self::new(borrowed); + let borrowed = new.borrow_mut(); borrowed.diff(self); } } @@ -269,6 +278,7 @@ impl Tree { new_children.iter().map(|c| c.borrow().id()).collect(), |tree, widget| { let borrowed: &mut dyn Widget<_, _, _> = widget.borrow_mut(); + tree.diff(borrowed); }, |widget| { @@ -291,29 +301,26 @@ impl Tree { self.children.truncate(new_children.len()); } - let len_changed = self.children.len() != new_children.len(); - let children_len = self.children.len(); let (mut id_map, mut id_list): ( HashMap, - Vec<&mut Tree>, - ) = self.children.iter_mut().fold( + VecDeque<(usize, &mut Tree)>, + ) = self.children.iter_mut().enumerate().fold( (HashMap::new(), Vec::with_capacity(children_len)), - |(mut id_map, mut id_list), c| { + |(mut id_map, mut id_list), (i, c)| { if let Some(id) = c.id.as_ref() { if let Internal::Custom(_, ref name) = id.0 { let _ = id_map.insert(name.to_string(), c); } else { - id_list.push(c); + id_list.push((i, c)); } } else { - id_list.push(c); + id_list.push((i, c)); } (id_map, id_list) }, ); - let mut child_state_i = 0; let mut new_trees: Vec<(Tree, usize)> = Vec::with_capacity(new_children.len()); for (i, (new, new_id)) in @@ -327,14 +334,20 @@ impl Tree { } }) { c - } else if child_state_i < id_list.len() - && !matches!( - id_list[child_state_i].id, - Some(Id(Internal::Custom(_, _))) - ) - { - let c = &mut id_list[child_state_i]; - child_state_i += 1; + } else if let Some(i) = { + let mut found = None; + for c_i in 0..id_list.len() { + if id_list[c_i].0 == i { + found = Some(c_i); + break; + } + if i < c_i { + break; + } + } + found + } { + let c = id_list.remove(i).unwrap().1; c } else { let mut my_new_state = new_state(new); From 396fe4f18672c64a02243017c22c026a21e7f9c9 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 7 Nov 2024 17:00:12 -0500 Subject: [PATCH 027/116] fix --- core/src/widget/tree.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index f9edd0360d..c15e99ab6a 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -306,16 +306,16 @@ impl Tree { HashMap, VecDeque<(usize, &mut Tree)>, ) = self.children.iter_mut().enumerate().fold( - (HashMap::new(), Vec::with_capacity(children_len)), + (HashMap::new(), VecDeque::with_capacity(children_len)), |(mut id_map, mut id_list), (i, c)| { if let Some(id) = c.id.as_ref() { if let Internal::Custom(_, ref name) = id.0 { let _ = id_map.insert(name.to_string(), c); } else { - id_list.push((i, c)); + id_list.push_back((i, c)); } } else { - id_list.push((i, c)); + id_list.push_back((i, c)); } (id_map, id_list) }, From 21f857ed6ae49479f8c0c5e0343e86cafa375d9e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 11 Nov 2024 16:44:54 -0500 Subject: [PATCH 028/116] fix: a11y nodes --- widget/src/container.rs | 7 +------ widget/src/lazy/responsive.rs | 18 ++++++------------ widget/src/mouse_area.rs | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/widget/src/container.rs b/widget/src/container.rs index b693108084..dbc3e8fd82 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -394,13 +394,8 @@ where cursor: mouse::Cursor, ) -> iced_accessibility::A11yTree { let c_layout = layout.children().next().unwrap(); - let c_state = state.children.get(0); - self.content.as_widget().a11y_nodes( - c_layout, - c_state.unwrap_or(&Tree::empty()), - cursor, - ) + self.content.as_widget().a11y_nodes(c_layout, state, cursor) } fn drag_destinations( diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 1b2d04eb74..db0128f54f 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -337,18 +337,12 @@ where tree: &Tree, cursor_position: mouse::Cursor, ) -> iced_accessibility::A11yTree { - use std::rc::Rc; - - let tree = tree.state.downcast_ref::>>>(); - if let Some(tree) = tree.borrow().as_ref() { - self.content.borrow().element.as_widget().a11y_nodes( - layout, - &tree.children[0], - cursor_position, - ) - } else { - iced_accessibility::A11yTree::default() - } + let state = tree.state.downcast_ref::().tree.borrow(); + self.content.borrow().element.as_widget().a11y_nodes( + layout, + &*state, + cursor_position, + ) } fn id(&self) -> Option { diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index c125a06c1c..09469028f9 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -355,6 +355,22 @@ where ); } } + + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + let c_state = state.children.get(0); + + let ret = self.content.as_widget().a11y_nodes( + layout, + c_state.unwrap_or(&Tree::empty()), + cursor, + ); + return ret; + } } impl<'a, Message, Theme, Renderer> From> From f6c90ea5be3df9a1970f76b39798d94fc2252e8d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 12 Nov 2024 09:32:31 -0500 Subject: [PATCH 029/116] fix: feature gate a11y_nodes for mouse_area --- widget/src/mouse_area.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 09469028f9..544d394aba 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -356,6 +356,7 @@ where } } + #[cfg(feature = "a11y")] fn a11y_nodes( &self, layout: Layout<'_>, From dd1f6d6c6ebbb534ba19b6be3d61896bedce211b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 13 Nov 2024 17:53:17 -0500 Subject: [PATCH 030/116] fix: close subscriptions when exiting --- winit/src/program.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index a12ec20bc2..405f9ece4c 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -1096,7 +1096,7 @@ async fn run_instance<'a, P, C>( is_window_opening = false; } Event::UserEvent(action) => { - run_action( + let exited = run_action( action, &program, &mut compositor, @@ -1111,6 +1111,9 @@ async fn run_instance<'a, P, C>( &mut is_window_opening, &mut platform_specific_handler, ); + if exited { + runtime.track(None.into_iter()); + } actions += 1; } Event::NewEvents( @@ -1442,6 +1445,7 @@ async fn run_instance<'a, P, C>( && !is_window_opening && window_manager.is_empty() { + runtime.track(None.into_iter()); control_sender .start_send(Control::Exit) .expect("Send control action"); @@ -1469,7 +1473,7 @@ async fn run_instance<'a, P, C>( winit::event::WindowEvent::CloseRequested ) && window.exit_on_close_request { - run_action( + _ = run_action( Action::Window(runtime::window::Action::Close( id, )), @@ -1919,7 +1923,8 @@ fn run_action( ui_caches: &mut FxHashMap, is_window_opening: &mut bool, platform_specific: &mut crate::platform_specific::PlatformSpecific, -) where +) -> bool +where P: Program, C: Compositor + 'static, P::Theme: DefaultStyle, @@ -2257,6 +2262,7 @@ fn run_action( control_sender .start_send(Control::Exit) .expect("Send control action"); + return true; } Action::Dnd(a) => match a { iced_runtime::dnd::DndAction::RegisterDndDestination { @@ -2295,6 +2301,7 @@ fn run_action( platform_specific.send_action(a); } } + false } /// Build the user interface for every window. From e1e9fe73d611e270feec7f429c19d78a3867f045 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 13 Nov 2024 18:43:31 -0500 Subject: [PATCH 031/116] refactor: allow apps to request to be treated as a daemon --- src/program.rs | 1 + src/settings.rs | 5 +++++ winit/src/program.rs | 2 +- winit/src/settings.rs | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/program.rs b/src/program.rs index cb44eadb3f..464cc68bbc 100644 --- a/src/program.rs +++ b/src/program.rs @@ -191,6 +191,7 @@ pub trait Program: Sized { default_text_size: settings.default_text_size, antialiasing: settings.antialiasing, exit_on_close_request: settings.exit_on_close_request, + is_daemon: settings.exit_on_close_request, } .into(), renderer_settings, diff --git a/src/settings.rs b/src/settings.rs index 3b48ca1e31..77d0c0f4dd 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -35,6 +35,9 @@ pub struct Settings { /// If set to true the application will exit when the main window is closed. pub exit_on_close_request: bool, + + /// Whether the application is a daemon + pub is_daemon: bool, } impl Default for Settings { @@ -46,6 +49,7 @@ impl Default for Settings { default_text_size: Pixels(14.0), antialiasing: false, exit_on_close_request: false, + is_daemon: false, } } } @@ -56,6 +60,7 @@ impl From for iced_winit::Settings { iced_winit::Settings { id: settings.id, fonts: settings.fonts, + is_daemon: settings.is_daemon, } } } diff --git a/winit/src/program.rs b/winit/src/program.rs index 405f9ece4c..4ceb2d82da 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -193,7 +193,7 @@ where }; let (program, task) = runtime.enter(|| P::new(flags)); - let is_daemon = window_settings.is_none(); + let is_daemon = window_settings.is_none() || settings.is_daemon; let task = if let Some(window_settings) = window_settings { let mut task = Some(task); diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 78368a04aa..8723c1f554 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -12,4 +12,7 @@ pub struct Settings { /// The fonts to load on boot. pub fonts: Vec>, + + /// Whether the application should exit when no windows are left + pub is_daemon: bool, } From f615c31114fbddf4541a590f308bb589eb979b31 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 18 Nov 2024 15:25:43 -0800 Subject: [PATCH 032/116] Send `KeyboardEventVariant::Enter` on keyboard enter `cosmic-osd` expected this event, but it wasn't being sent. This isn't hard to fix, unless there's some non-obvious issues that this was commented to avoid (that is still applicable). --- .../wayland/handlers/seat/keyboard.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs index faba2fe2f1..2a37d7f8e6 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs @@ -20,7 +20,7 @@ impl KeyboardHandler for SctkState { _keysyms: &[Keysym], ) { self.request_redraw(surface); - let (i, mut is_active, _seat) = { + let (i, mut is_active, seat) = { let (i, is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { if s.kbd.as_ref() == Some(keyboard) { @@ -55,11 +55,12 @@ impl KeyboardHandler for SctkState { id, winit::event::WindowEvent::Focused(true), )); - // self.sctk_events.push(SctkEvent::KeyboardEvent { - // variant: KeyboardEventVariant::Enter(surface.clone()), - // kbd_id: keyboard.clone(), - // seat_id: seat, - // }) + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Enter(surface.clone()), + kbd_id: keyboard.clone(), + seat_id: seat, + surface: surface.clone(), + }); } } From 72052004709cfdc943923a142bdb03a3ec8bd10d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Nov 2024 12:58:25 -0500 Subject: [PATCH 033/116] fix: request logical size when autosizing --- winit/src/program.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index 4ceb2d82da..6d512c3d77 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -5,6 +5,7 @@ mod state; mod window_manager; pub use runtime::{default, Appearance, DefaultStyle}; +use winit::dpi::PhysicalSize; use winit::event_loop::OwnedDisplayHandle; use crate::conversion; @@ -1557,15 +1558,12 @@ async fn run_instance<'a, P, C>( if let Some(requested_size) = clipboard.requested_logical_size.lock().unwrap().take() { - let requested_physical_size = - winit::dpi::PhysicalSize::new( - (requested_size.width as f64 - * window.state.scale_factor()) - .ceil() as u32, - (requested_size.height as f64 - * window.state.scale_factor()) - .ceil() as u32, + let requested_physical_size: PhysicalSize = + winit::dpi::PhysicalSize::from_logical( + requested_size.cast::(), + window.state.scale_factor(), ); + let physical_size = window.state.physical_size(); if requested_physical_size.width != physical_size.width || requested_physical_size.height @@ -1576,8 +1574,8 @@ async fn run_instance<'a, P, C>( window.resize_enabled = true; resized = true; needs_redraw = true; - let s = winit::dpi::Size::Physical( - requested_physical_size, + let s = winit::dpi::Size::Logical( + requested_size.cast(), ); _ = window.raw.request_surface_size(s); window.raw.set_min_surface_size(Some(s)); From 344455105e731faa1ff24d8a58df97bdb40396ec Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 20 Nov 2024 18:08:33 -0500 Subject: [PATCH 034/116] fix: rebuild a11y tree when rebuilding widget tree --- winit/src/program.rs | 178 ++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 95 deletions(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index 6d512c3d77..8324cb1f05 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -1235,104 +1235,11 @@ async fn run_instance<'a, P, C>( { let logical_size = window.state.logical_size(); debug.layout_started(); - let mut ui = user_interfaces + let ui = user_interfaces .remove(&id) .expect("Remove user interface") .relayout(logical_size, &mut window.renderer); - #[cfg(feature = "a11y")] - { - use iced_accessibility::{ - accesskit::{ - NodeBuilder, NodeId, Role, Tree, - TreeUpdate, - }, - A11yId, A11yNode, A11yTree, - }; - if let Some(Some((a11y_id, adapter))) = - a11y_enabled.then(|| adapters.get_mut(&id)) - { - // TODO cleanup duplication - let child_tree = - ui.a11y_nodes(window.state.cursor()); - let mut root = - NodeBuilder::new(Role::Window); - root.set_name( - window.state.title.to_string(), - ); - let window_tree = - A11yTree::node_with_child_tree( - A11yNode::new(root, *a11y_id), - child_tree, - ); - let tree = Tree::new(NodeId(*a11y_id)); - - let focus = - Arc::new(std::sync::Mutex::new(None)); - let focus_clone = focus.clone(); - let operation: Box> = - Box::new(operation::map( - Box::new( - operation::focusable::find_focused( - ), - ), - move |id| { - let mut guard = focus.lock().unwrap(); - _ = guard.replace(id); - }, - )); - let mut current_operation = Some(operation); - - while let Some(mut operation) = - current_operation.take() - { - ui.operate( - &window.renderer, - operation.as_mut(), - ); - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(()) => { - break; - } - operation::Outcome::Chain(next) => { - current_operation = Some(next); - } - } - } - let mut guard = focus_clone.lock().unwrap(); - let focus = guard - .take() - .map(|id| A11yId::Widget(id)); - tracing::debug!( - "focus: {:?}\ntree root: {:?}\n children: {:?}", - &focus, - window_tree - .root() - .iter() - .map(|n| (n.node().role(), n.id())) - .collect::>(), - window_tree - .children() - .iter() - .map(|n| (n.node().role(), n.id())) - .collect::>() - ); - let focus = focus - .filter(|f_id| { - window_tree.contains(f_id) - }) - .map(|id| id.into()) - .unwrap_or_else(|| tree.root); - adapter.update_if_active(|| TreeUpdate { - nodes: window_tree.into(), - tree: Some(tree), - focus, - }); - } - } - let _ = user_interfaces.insert(id, ui); debug.layout_finished(); @@ -1665,6 +1572,10 @@ async fn run_instance<'a, P, C>( &mut window_manager, cached_interfaces, &mut clipboard, + #[cfg(feature = "a11y")] + a11y_enabled, + #[cfg(feature = "a11y")] + &mut adapters, )); if actions > 0 { @@ -2309,6 +2220,11 @@ pub fn build_user_interfaces<'a, P: Program, C>( window_manager: &mut WindowManager, mut cached_user_interfaces: FxHashMap, clipboard: &mut Clipboard, + #[cfg(feature = "a11y")] a11y_enabled: bool, + #[cfg(feature = "a11y")] adapters: &mut HashMap< + window::Id, + (u64, iced_accessibility::accesskit_winit::Adapter), + >, ) -> FxHashMap> where C: Compositor, @@ -2318,7 +2234,7 @@ where .drain() .filter_map(|(id, cache)| { let window = window_manager.get_mut(id)?; - let interface = build_user_interface( + let mut interface = build_user_interface( program, cache, &mut window.renderer, @@ -2329,6 +2245,78 @@ where window.prev_dnd_destination_rectangles_count, clipboard, ); + #[cfg(feature = "a11y")] + { + use iced_accessibility::{ + accesskit::{NodeBuilder, NodeId, Role, Tree, TreeUpdate}, + A11yId, A11yNode, A11yTree, + }; + if let Some(Some((a11y_id, adapter))) = + a11y_enabled.then(|| adapters.get_mut(&id)) + { + // TODO cleanup duplication + let child_tree = + interface.a11y_nodes(window.state.cursor()); + let mut root = NodeBuilder::new(Role::Window); + root.set_name(window.state.title.to_string()); + let window_tree = A11yTree::node_with_child_tree( + A11yNode::new(root, *a11y_id), + child_tree, + ); + let tree = Tree::new(NodeId(*a11y_id)); + + let focus = Arc::new(std::sync::Mutex::new(None)); + let focus_clone = focus.clone(); + let operation: Box> = + Box::new(operation::map( + Box::new(operation::focusable::find_focused()), + move |id| { + let mut guard = focus.lock().unwrap(); + _ = guard.replace(id); + }, + )); + let mut current_operation = Some(operation); + + while let Some(mut operation) = current_operation.take() { + interface.operate(&window.renderer, operation.as_mut()); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(()) => { + break; + } + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } + } + } + let mut guard = focus_clone.lock().unwrap(); + let focus = guard.take().map(|id| A11yId::Widget(id)); + tracing::debug!( + "focus: {:?}\ntree root: {:?}\n children: {:?}", + &focus, + window_tree + .root() + .iter() + .map(|n| (n.node().role(), n.id())) + .collect::>(), + window_tree + .children() + .iter() + .map(|n| (n.node().role(), n.id())) + .collect::>() + ); + let focus = focus + .filter(|f_id| window_tree.contains(f_id)) + .map(|id| id.into()) + .unwrap_or_else(|| tree.root); + adapter.update_if_active(|| TreeUpdate { + nodes: window_tree.into(), + tree: Some(tree), + focus, + }); + } + } let dnd_rectangles = interface.dnd_rectangles( window.prev_dnd_destination_rectangles_count, From 9954864a2dba6c6ec6726f13a291f4b2295533fb Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 21 Nov 2024 20:16:53 -0500 Subject: [PATCH 035/116] Revert "fix: rebuild a11y tree when rebuilding widget tree" This reverts commit c9bad732043282e9a43d7cba2608c444f4f2f253. --- winit/src/program.rs | 178 +++++++++++++++++++++++-------------------- 1 file changed, 95 insertions(+), 83 deletions(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index 8324cb1f05..6d512c3d77 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -1235,11 +1235,104 @@ async fn run_instance<'a, P, C>( { let logical_size = window.state.logical_size(); debug.layout_started(); - let ui = user_interfaces + let mut ui = user_interfaces .remove(&id) .expect("Remove user interface") .relayout(logical_size, &mut window.renderer); + #[cfg(feature = "a11y")] + { + use iced_accessibility::{ + accesskit::{ + NodeBuilder, NodeId, Role, Tree, + TreeUpdate, + }, + A11yId, A11yNode, A11yTree, + }; + if let Some(Some((a11y_id, adapter))) = + a11y_enabled.then(|| adapters.get_mut(&id)) + { + // TODO cleanup duplication + let child_tree = + ui.a11y_nodes(window.state.cursor()); + let mut root = + NodeBuilder::new(Role::Window); + root.set_name( + window.state.title.to_string(), + ); + let window_tree = + A11yTree::node_with_child_tree( + A11yNode::new(root, *a11y_id), + child_tree, + ); + let tree = Tree::new(NodeId(*a11y_id)); + + let focus = + Arc::new(std::sync::Mutex::new(None)); + let focus_clone = focus.clone(); + let operation: Box> = + Box::new(operation::map( + Box::new( + operation::focusable::find_focused( + ), + ), + move |id| { + let mut guard = focus.lock().unwrap(); + _ = guard.replace(id); + }, + )); + let mut current_operation = Some(operation); + + while let Some(mut operation) = + current_operation.take() + { + ui.operate( + &window.renderer, + operation.as_mut(), + ); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(()) => { + break; + } + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } + } + } + let mut guard = focus_clone.lock().unwrap(); + let focus = guard + .take() + .map(|id| A11yId::Widget(id)); + tracing::debug!( + "focus: {:?}\ntree root: {:?}\n children: {:?}", + &focus, + window_tree + .root() + .iter() + .map(|n| (n.node().role(), n.id())) + .collect::>(), + window_tree + .children() + .iter() + .map(|n| (n.node().role(), n.id())) + .collect::>() + ); + let focus = focus + .filter(|f_id| { + window_tree.contains(f_id) + }) + .map(|id| id.into()) + .unwrap_or_else(|| tree.root); + adapter.update_if_active(|| TreeUpdate { + nodes: window_tree.into(), + tree: Some(tree), + focus, + }); + } + } + let _ = user_interfaces.insert(id, ui); debug.layout_finished(); @@ -1572,10 +1665,6 @@ async fn run_instance<'a, P, C>( &mut window_manager, cached_interfaces, &mut clipboard, - #[cfg(feature = "a11y")] - a11y_enabled, - #[cfg(feature = "a11y")] - &mut adapters, )); if actions > 0 { @@ -2220,11 +2309,6 @@ pub fn build_user_interfaces<'a, P: Program, C>( window_manager: &mut WindowManager, mut cached_user_interfaces: FxHashMap, clipboard: &mut Clipboard, - #[cfg(feature = "a11y")] a11y_enabled: bool, - #[cfg(feature = "a11y")] adapters: &mut HashMap< - window::Id, - (u64, iced_accessibility::accesskit_winit::Adapter), - >, ) -> FxHashMap> where C: Compositor, @@ -2234,7 +2318,7 @@ where .drain() .filter_map(|(id, cache)| { let window = window_manager.get_mut(id)?; - let mut interface = build_user_interface( + let interface = build_user_interface( program, cache, &mut window.renderer, @@ -2245,78 +2329,6 @@ where window.prev_dnd_destination_rectangles_count, clipboard, ); - #[cfg(feature = "a11y")] - { - use iced_accessibility::{ - accesskit::{NodeBuilder, NodeId, Role, Tree, TreeUpdate}, - A11yId, A11yNode, A11yTree, - }; - if let Some(Some((a11y_id, adapter))) = - a11y_enabled.then(|| adapters.get_mut(&id)) - { - // TODO cleanup duplication - let child_tree = - interface.a11y_nodes(window.state.cursor()); - let mut root = NodeBuilder::new(Role::Window); - root.set_name(window.state.title.to_string()); - let window_tree = A11yTree::node_with_child_tree( - A11yNode::new(root, *a11y_id), - child_tree, - ); - let tree = Tree::new(NodeId(*a11y_id)); - - let focus = Arc::new(std::sync::Mutex::new(None)); - let focus_clone = focus.clone(); - let operation: Box> = - Box::new(operation::map( - Box::new(operation::focusable::find_focused()), - move |id| { - let mut guard = focus.lock().unwrap(); - _ = guard.replace(id); - }, - )); - let mut current_operation = Some(operation); - - while let Some(mut operation) = current_operation.take() { - interface.operate(&window.renderer, operation.as_mut()); - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(()) => { - break; - } - operation::Outcome::Chain(next) => { - current_operation = Some(next); - } - } - } - let mut guard = focus_clone.lock().unwrap(); - let focus = guard.take().map(|id| A11yId::Widget(id)); - tracing::debug!( - "focus: {:?}\ntree root: {:?}\n children: {:?}", - &focus, - window_tree - .root() - .iter() - .map(|n| (n.node().role(), n.id())) - .collect::>(), - window_tree - .children() - .iter() - .map(|n| (n.node().role(), n.id())) - .collect::>() - ); - let focus = focus - .filter(|f_id| window_tree.contains(f_id)) - .map(|id| id.into()) - .unwrap_or_else(|| tree.root); - adapter.update_if_active(|| TreeUpdate { - nodes: window_tree.into(), - tree: Some(tree), - focus, - }); - } - } let dnd_rectangles = interface.dnd_rectangles( window.prev_dnd_destination_rectangles_count, From 316f89b650721dae5877ac21c95247d6f667305d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 21 Nov 2024 20:19:19 -0500 Subject: [PATCH 036/116] fix: send a11y tree after actions this ensures focus is updated correctly --- winit/src/program.rs | 192 ++++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 93 deletions(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index 6d512c3d77..fd61f88f18 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -802,6 +802,7 @@ async fn run_instance<'a, P, C>( let Some(event) = event else { break; }; + let mut rebuild_a11y_tree = false; match event { Event::StartDnd => { @@ -1097,6 +1098,7 @@ async fn run_instance<'a, P, C>( is_window_opening = false; } Event::UserEvent(action) => { + rebuild_a11y_tree = true; let exited = run_action( action, &program, @@ -1130,6 +1132,18 @@ async fn run_instance<'a, P, C>( } } Event::Winit(window_id, event) => { + #[cfg(feature = "a11y")] + { + if let Some((id, window)) = + window_manager.get_mut_alias(window_id) + { + if let Some(Some((_, adapter))) = + a11y_enabled.then(|| adapters.get_mut(&id)) + { + adapter.process_event(window.raw.as_ref(), &event); + }; + } + } match event { event::WindowEvent::RedrawRequested => { let Some((id, window)) = @@ -1240,99 +1254,6 @@ async fn run_instance<'a, P, C>( .expect("Remove user interface") .relayout(logical_size, &mut window.renderer); - #[cfg(feature = "a11y")] - { - use iced_accessibility::{ - accesskit::{ - NodeBuilder, NodeId, Role, Tree, - TreeUpdate, - }, - A11yId, A11yNode, A11yTree, - }; - if let Some(Some((a11y_id, adapter))) = - a11y_enabled.then(|| adapters.get_mut(&id)) - { - // TODO cleanup duplication - let child_tree = - ui.a11y_nodes(window.state.cursor()); - let mut root = - NodeBuilder::new(Role::Window); - root.set_name( - window.state.title.to_string(), - ); - let window_tree = - A11yTree::node_with_child_tree( - A11yNode::new(root, *a11y_id), - child_tree, - ); - let tree = Tree::new(NodeId(*a11y_id)); - - let focus = - Arc::new(std::sync::Mutex::new(None)); - let focus_clone = focus.clone(); - let operation: Box> = - Box::new(operation::map( - Box::new( - operation::focusable::find_focused( - ), - ), - move |id| { - let mut guard = focus.lock().unwrap(); - _ = guard.replace(id); - }, - )); - let mut current_operation = Some(operation); - - while let Some(mut operation) = - current_operation.take() - { - ui.operate( - &window.renderer, - operation.as_mut(), - ); - - match operation.finish() { - operation::Outcome::None => {} - operation::Outcome::Some(()) => { - break; - } - operation::Outcome::Chain(next) => { - current_operation = Some(next); - } - } - } - let mut guard = focus_clone.lock().unwrap(); - let focus = guard - .take() - .map(|id| A11yId::Widget(id)); - tracing::debug!( - "focus: {:?}\ntree root: {:?}\n children: {:?}", - &focus, - window_tree - .root() - .iter() - .map(|n| (n.node().role(), n.id())) - .collect::>(), - window_tree - .children() - .iter() - .map(|n| (n.node().role(), n.id())) - .collect::>() - ); - let focus = focus - .filter(|f_id| { - window_tree.contains(f_id) - }) - .map(|id| id.into()) - .unwrap_or_else(|| tree.root); - adapter.update_if_active(|| TreeUpdate { - nodes: window_tree.into(), - tree: Some(tree), - focus, - }); - } - } - let _ = user_interfaces.insert(id, ui); debug.layout_finished(); @@ -1658,6 +1579,7 @@ async fn run_instance<'a, P, C>( window.request_redraw(); } + rebuild_a11y_tree = true; user_interfaces = ManuallyDrop::new(build_user_interfaces( &program, @@ -1807,6 +1729,7 @@ async fn run_instance<'a, P, C>( } #[cfg(feature = "a11y")] Event::Accessibility(id, e) => { + rebuild_a11y_tree = true; match e.action { iced_accessibility::accesskit::Action::Focus => { // TODO send a command for this @@ -1838,6 +1761,89 @@ async fn run_instance<'a, P, C>( // log ignored events? } } + #[cfg(feature = "a11y")] + { + use iced_accessibility::{ + accesskit::{NodeBuilder, NodeId, Role, Tree, TreeUpdate}, + A11yId, A11yNode, A11yTree, + }; + if !a11y_enabled || !rebuild_a11y_tree { + continue; + } + + for id in window_manager.ids() { + let Some((a11y_id, adapter)) = adapters.get_mut(&id) else { + continue; + }; + let Some(window) = window_manager.get(id) else { + continue; + }; + let interface = + user_interfaces.get_mut(&id).expect("Get user interface"); + + // TODO cleanup duplication + let child_tree = interface.a11y_nodes(window.state.cursor()); + let mut root = NodeBuilder::new(Role::Window); + root.set_name(window.state.title.to_string()); + let window_tree = A11yTree::node_with_child_tree( + A11yNode::new(root, *a11y_id), + child_tree, + ); + let tree = Tree::new(NodeId(*a11y_id)); + + let focus = Arc::new(std::sync::Mutex::new(None)); + let focus_clone = focus.clone(); + let operation: Box> = + Box::new(operation::map( + Box::new(operation::focusable::find_focused()), + move |id| { + let mut guard = focus.lock().unwrap(); + _ = guard.replace(id); + }, + )); + let mut current_operation = Some(operation); + + while let Some(mut operation) = current_operation.take() { + interface.operate(&window.renderer, operation.as_mut()); + + match operation.finish() { + operation::Outcome::None => {} + operation::Outcome::Some(()) => { + break; + } + operation::Outcome::Chain(next) => { + current_operation = Some(next); + } + } + } + let mut guard = focus_clone.lock().unwrap(); + let focus = guard + .take() + .map(|id| A11yId::Widget(id)) + .filter(|f_id| window_tree.contains(f_id)); + tracing::debug!( + "tree root: {:?}\nchildren: {:?}\nfocus: {:?}\n", + window_tree + .root() + .iter() + .map(|n| (n.node(), n.id())) + .collect::>(), + window_tree + .children() + .iter() + .map(|n| (n.node(), n.id())) + .collect::>(), + &focus, + ); + let focus = + focus.map(|id| id.into()).unwrap_or_else(|| tree.root); + adapter.update_if_active(|| TreeUpdate { + nodes: window_tree.into(), + tree: Some(tree), + focus, + }); + } + } } let _ = ManuallyDrop::into_inner(user_interfaces); From 64f986773aa4754ea86251c02c4d781c478a6112 Mon Sep 17 00:00:00 2001 From: Matt George Date: Sun, 27 Oct 2024 13:45:57 -0600 Subject: [PATCH 037/116] fix: correct event types for windows --- winit/src/program.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index fd61f88f18..933c643e0b 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -315,7 +315,7 @@ where #[cfg(target_os = "windows")] let is_move_or_resize = matches!( event, - winit::event::WindowEvent::Resized(_) + winit::event::WindowEvent::SurfaceResized(_) | winit::event::WindowEvent::Moved(_) ); @@ -331,12 +331,7 @@ where #[cfg(target_os = "windows")] { if is_move_or_resize { - self.process_event( - event_loop, - Event::EventLoopAwakened( - winit::event::Event::AboutToWait, - ), - ); + self.process_event(event_loop, Some(Event::AboutToWait)); } } } From 4becadbb57cb77f8d5eb59ffc0783a61591aea56 Mon Sep 17 00:00:00 2001 From: Matt George Date: Sun, 1 Dec 2024 08:17:21 -0700 Subject: [PATCH 038/116] fix: clear drag_initiated state on dbl click --- widget/src/mouse_area.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 544d394aba..1ce5001858 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -464,6 +464,7 @@ fn update( state.last_click = Some(click); if let mouse::click::Kind::Double = click.kind() { shell.publish(message.clone()); + state.drag_initiated = None; return event::Status::Captured; } } From 883eb0a6a980745c3406af36d68555d1258d890c Mon Sep 17 00:00:00 2001 From: Matt George Date: Wed, 30 Oct 2024 00:04:12 -0600 Subject: [PATCH 039/116] hard-code which targets have drag_resize support This replaces the previous check which appeared to rely on the assumption that initiating a drag_resize without a left mouse press wouldn't actually start a resize (this assumption is false on Windows). --- winit/src/application/drag_resize.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/winit/src/application/drag_resize.rs b/winit/src/application/drag_resize.rs index 9f109d3907..ba77f6a5eb 100644 --- a/winit/src/application/drag_resize.rs +++ b/winit/src/application/drag_resize.rs @@ -12,7 +12,7 @@ pub fn event_func( ) -> bool, >, > { - if window.drag_resize_window(ResizeDirection::East).is_ok() { + if drag_resize_supported() { // Keep track of cursor when it is within a resizeable border. let mut cursor_prev_resize_direction = None; @@ -62,6 +62,27 @@ pub fn event_func( } } +/// Test if the current target should be assumed to have winit drag_resize support +const fn drag_resize_supported() -> bool { + #[cfg(all( + unix, + not(target_vendor = "apple"), + not(target_os = "android"), + not(target_os = "emscripten") + ))] + { + return true; + } + + #[cfg(target_os = "windows")] + { + return true; + } + + #[allow(unreachable_code)] + false +} + /// Get the cursor icon that corresponds to the resize direction. fn resize_direction_cursor_icon( resize_direction: Option, From f646bac49d76334e75dead2100828232fd7c0f90 Mon Sep 17 00:00:00 2001 From: Matt George Date: Sun, 1 Dec 2024 04:51:18 -0700 Subject: [PATCH 040/116] use a variable instead of a fn for hard-coded check --- winit/src/application/drag_resize.rs | 45 ++++++++++++++-------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/winit/src/application/drag_resize.rs b/winit/src/application/drag_resize.rs index ba77f6a5eb..d7a30a6cf8 100644 --- a/winit/src/application/drag_resize.rs +++ b/winit/src/application/drag_resize.rs @@ -1,5 +1,27 @@ use winit::window::{CursorIcon, ResizeDirection}; +#[cfg(any( + all( + unix, + not(target_vendor = "apple"), + not(target_os = "android"), + not(target_os = "emscripten"), + ), + target_os = "windows", +))] +const DRAG_RESIZE_SUPPORTED: bool = true; + +#[cfg(not(any( + all( + unix, + not(target_vendor = "apple"), + not(target_os = "android"), + not(target_os = "emscripten"), + ), + target_os = "windows", +)))] +const DRAG_RESIZE_SUPPORTED: bool = false; + /// If supported by winit, returns a closure that implements cursor resize support. pub fn event_func( window: &dyn winit::window::Window, @@ -12,7 +34,7 @@ pub fn event_func( ) -> bool, >, > { - if drag_resize_supported() { + if DRAG_RESIZE_SUPPORTED { // Keep track of cursor when it is within a resizeable border. let mut cursor_prev_resize_direction = None; @@ -62,27 +84,6 @@ pub fn event_func( } } -/// Test if the current target should be assumed to have winit drag_resize support -const fn drag_resize_supported() -> bool { - #[cfg(all( - unix, - not(target_vendor = "apple"), - not(target_os = "android"), - not(target_os = "emscripten") - ))] - { - return true; - } - - #[cfg(target_os = "windows")] - { - return true; - } - - #[allow(unreachable_code)] - false -} - /// Get the cursor icon that corresponds to the resize direction. fn resize_direction_cursor_icon( resize_direction: Option, From 75ff8c8eeee1a86541f24fcb73b168f6d237e8a9 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sat, 23 Nov 2024 02:02:52 -0500 Subject: [PATCH 041/116] refactor: use cctk --- Cargo.toml | 2 +- core/Cargo.toml | 6 +-- core/src/event/wayland/mod.rs | 2 +- core/src/event/wayland/output.rs | 2 +- core/src/event/wayland/session_lock.rs | 2 +- examples/sctk_drag/Cargo.toml | 2 +- examples/sctk_subsurface/Cargo.toml | 3 +- examples/sctk_subsurface/src/main.rs | 2 +- examples/sctk_subsurface/src/wayland.rs | 4 +- examples/sctk_todos/Cargo.toml | 2 +- examples/sctk_todos/src/main.rs | 2 +- runtime/Cargo.toml | 6 +-- .../wayland/layer_surface.rs | 2 +- .../src/platform_specific/wayland/popup.rs | 2 +- .../platform_specific/wayland/session_lock.rs | 2 +- wgpu/Cargo.toml | 2 +- wgpu/src/window/wayland.rs | 8 +-- widget/Cargo.toml | 6 +-- winit/Cargo.toml | 6 +-- winit/src/platform_specific/mod.rs | 6 ++- .../wayland/commands/layer_surface.rs | 2 +- .../wayland/commands/session_lock.rs | 2 +- .../platform_specific/wayland/conversion.rs | 10 ++-- .../wayland/event_loop/mod.rs | 4 +- .../wayland/event_loop/proxy.rs | 2 +- .../wayland/event_loop/state.rs | 4 +- .../wayland/handlers/activation.rs | 2 +- .../wayland/handlers/compositor.rs | 2 +- .../platform_specific/wayland/handlers/mod.rs | 3 +- .../wayland/handlers/output.rs | 22 ++++---- .../wayland/handlers/seat/keyboard.rs | 52 +++++++++---------- .../wayland/handlers/seat/pointer.rs | 10 ++-- .../wayland/handlers/seat/seat.rs | 46 ++++++++-------- .../wayland/handlers/seat/touch.rs | 2 +- .../wayland/handlers/session_lock.rs | 2 +- .../wayland/handlers/shell/layer.rs | 16 +++--- .../wayland/handlers/shell/xdg_popup.rs | 16 +++--- .../wayland/handlers/shell/xdg_window.rs | 16 +++--- .../wayland/handlers/subcompositor.rs | 2 +- .../wayland/handlers/wp_fractional_scaling.rs | 16 +++--- .../wayland/handlers/wp_viewporter.rs | 14 ++--- winit/src/platform_specific/wayland/mod.rs | 6 +-- .../platform_specific/wayland/sctk_event.rs | 4 +- .../wayland/subsurface_widget.rs | 6 +-- .../platform_specific/wayland/winit_window.rs | 4 +- 45 files changed, 169 insertions(+), 165 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e2a413a4b..35b3c23905 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -197,7 +197,7 @@ pulldown-cmark = "0.11" qrcode = { version = "0.13", default-features = false } raw-window-handle = "0.6" rustc-hash = "2.0" -sctk = { package = "smithay-client-toolkit", version = "0.19.1" } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "27d70b6" } smol = "1.0" smol_str = "0.2" softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } diff --git a/core/Cargo.toml b/core/Cargo.toml index ce5b4d1dd2..f5406b4a4b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -17,7 +17,7 @@ workspace = true auto-detect-theme = ["dep:dark-light"] advanced = [] a11y = ["iced_accessibility"] -wayland = ["sctk"] +wayland = ["cctk"] [dependencies] bitflags.workspace = true @@ -38,8 +38,8 @@ window_clipboard.workspace = true dnd.workspace = true mime.workspace = true -sctk.workspace = true -sctk.optional = true +cctk.workspace = true +cctk.optional = true # /TODO(POP) dark-light.workspace = true diff --git a/core/src/event/wayland/mod.rs b/core/src/event/wayland/mod.rs index 0e908d6708..dd63fdc817 100644 --- a/core/src/event/wayland/mod.rs +++ b/core/src/event/wayland/mod.rs @@ -6,7 +6,7 @@ mod session_lock; mod window; use crate::{time::Instant, window::Id}; -use sctk::reexports::client::protocol::{ +use cctk::sctk::reexports::client::protocol::{ wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface, }; diff --git a/core/src/event/wayland/output.rs b/core/src/event/wayland/output.rs index c5024e85b7..4c7b6976dc 100644 --- a/core/src/event/wayland/output.rs +++ b/core/src/event/wayland/output.rs @@ -1,4 +1,4 @@ -use sctk::output::OutputInfo; +use cctk::sctk::output::OutputInfo; /// output events #[derive(Debug, Clone)] diff --git a/core/src/event/wayland/session_lock.rs b/core/src/event/wayland/session_lock.rs index db99566d95..07e9f51e8c 100644 --- a/core/src/event/wayland/session_lock.rs +++ b/core/src/event/wayland/session_lock.rs @@ -1,5 +1,5 @@ use crate::window::Id; -use sctk::reexports::client::protocol::wl_surface::WlSurface; +use cctk::sctk::reexports::client::protocol::wl_surface::WlSurface; /// session lock events #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/examples/sctk_drag/Cargo.toml b/examples/sctk_drag/Cargo.toml index e118b277f2..20372d1554 100644 --- a/examples/sctk_drag/Cargo.toml +++ b/examples/sctk_drag/Cargo.toml @@ -16,4 +16,4 @@ iced = { path = "../..", default-features = false, features = [ ] } env_logger = "0.10" # sctk = { package = "smithay-client-toolkit", path = "../../../fork/client-toolkit/" } -sctk.workspace = true +cctk.workspace = true diff --git a/examples/sctk_subsurface/Cargo.toml b/examples/sctk_subsurface/Cargo.toml index 30949510b2..126bc28350 100644 --- a/examples/sctk_subsurface/Cargo.toml +++ b/examples/sctk_subsurface/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "828b1eb" } +# sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "828b1eb" } iced = { path = "../..", default-features = false, features = [ "tokio", "wayland", @@ -17,3 +17,4 @@ env_logger = "0.10" futures-channel = "0.3.29" calloop = "0.12.3" rustix = { version = "0.38.30", features = ["fs", "shm"] } +cctk.workspace = true diff --git a/examples/sctk_subsurface/src/main.rs b/examples/sctk_subsurface/src/main.rs index 21a9a75fed..9d31e08a94 100644 --- a/examples/sctk_subsurface/src/main.rs +++ b/examples/sctk_subsurface/src/main.rs @@ -7,7 +7,7 @@ use iced::{ window::{self, Id, Settings}, Element, Length, Subscription, Task, }; -use sctk::reexports::client::{Connection, Proxy}; +use cctk::sctk::reexports::client::{Connection, Proxy}; mod wayland; diff --git a/examples/sctk_subsurface/src/wayland.rs b/examples/sctk_subsurface/src/wayland.rs index 315f168199..033dfc8ed2 100644 --- a/examples/sctk_subsurface/src/wayland.rs +++ b/examples/sctk_subsurface/src/wayland.rs @@ -5,7 +5,7 @@ use iced::{ }; use iced_runtime::futures::subscription; use rustix::{io::Errno, shm::ShmOFlags}; -use sctk::{ +use cctk::sctk::{ reexports::{ calloop_wayland_source::WaylandSource, client::{ @@ -40,7 +40,7 @@ impl ProvidesRegistryState for AppData { &mut self.registry_state } - sctk::registry_handlers!(); + cctk::sctk::registry_handlers!(); } impl ShmHandler for AppData { diff --git a/examples/sctk_todos/Cargo.toml b/examples/sctk_todos/Cargo.toml index 1638e2e1e7..fd14b95ed2 100644 --- a/examples/sctk_todos/Cargo.toml +++ b/examples/sctk_todos/Cargo.toml @@ -22,7 +22,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" iced_core.workspace = true once_cell = "1.15" -sctk.workspace = true +cctk.workspace = true log = "0.4.17" env_logger = "0.10.0" async-std = "1.0" diff --git a/examples/sctk_todos/src/main.rs b/examples/sctk_todos/src/main.rs index ce8e2c3f9e..7be16c835d 100644 --- a/examples/sctk_todos/src/main.rs +++ b/examples/sctk_todos/src/main.rs @@ -96,7 +96,7 @@ impl Todos { get_layer_surface(iced::platform_specific::runtime::wayland::layer_surface::SctkLayerSurfaceSettings { size: Some((None, Some(500))), pointer_interactivity: true, - keyboard_interactivity: sctk::shell::wlr_layer::KeyboardInteractivity::OnDemand, + keyboard_interactivity: cctk::sctk::shell::wlr_layer::KeyboardInteractivity::OnDemand, anchor: Anchor::LEFT.union(Anchor::RIGHT).union(Anchor::TOP), ..Default::default() }), diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index c4c60c254f..74f3f64d3a 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -17,15 +17,15 @@ workspace = true debug = [] multi-window = [] a11y = ["iced_accessibility", "iced_core/a11y"] -wayland = ["iced_core/wayland", "sctk"] +wayland = ["iced_core/wayland", "cctk"] [dependencies] bytes.workspace = true iced_core.workspace = true iced_futures.workspace = true iced_futures.features = ["thread-pool"] -sctk.workspace = true -sctk.optional = true +cctk.workspace = true +cctk.optional = true thiserror.workspace = true raw-window-handle.workspace = true iced_accessibility.workspace = true diff --git a/runtime/src/platform_specific/wayland/layer_surface.rs b/runtime/src/platform_specific/wayland/layer_surface.rs index 1629a463bd..f4419c96c0 100644 --- a/runtime/src/platform_specific/wayland/layer_surface.rs +++ b/runtime/src/platform_specific/wayland/layer_surface.rs @@ -1,7 +1,7 @@ use std::fmt; use iced_core::layout::Limits; -use sctk::{ +use cctk::sctk::{ reexports::client::protocol::wl_output::WlOutput, shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}, }; diff --git a/runtime/src/platform_specific/wayland/popup.rs b/runtime/src/platform_specific/wayland/popup.rs index 615a6d49d7..19b23e020f 100644 --- a/runtime/src/platform_specific/wayland/popup.rs +++ b/runtime/src/platform_specific/wayland/popup.rs @@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher}; use iced_core::layout::Limits; use iced_core::window::Id; use iced_core::Rectangle; -use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{ +use cctk::sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{ Anchor, Gravity, }; /// Popup creation details diff --git a/runtime/src/platform_specific/wayland/session_lock.rs b/runtime/src/platform_specific/wayland/session_lock.rs index adbc2390e4..86a9337e2c 100644 --- a/runtime/src/platform_specific/wayland/session_lock.rs +++ b/runtime/src/platform_specific/wayland/session_lock.rs @@ -2,7 +2,7 @@ use std::fmt; use iced_core::window::Id; -use sctk::reexports::client::protocol::wl_output::WlOutput; +use cctk::sctk::reexports::client::protocol::wl_output::WlOutput; /// Session lock action #[derive(Clone)] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 02fe3356c3..365aa11c7f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -50,7 +50,7 @@ resvg.optional = true rustix = { version = "0.38" } raw-window-handle.workspace = true -sctk.workspace = true +cctk.workspace = true wayland-protocols.workspace = true wayland-backend = { version = "0.3.3", features = ["client_system"] } wayland-client = { version = "0.31.2" } diff --git a/wgpu/src/window/wayland.rs b/wgpu/src/window/wayland.rs index b50f5d6909..031b8b2f10 100644 --- a/wgpu/src/window/wayland.rs +++ b/wgpu/src/window/wayland.rs @@ -1,10 +1,10 @@ use crate::graphics::compositor::Window; -use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle}; -use sctk::{ +use cctk::sctk::{ dmabuf::{DmabufFeedback, DmabufHandler, DmabufState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, }; +use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle}; use wayland_client::{ backend::Backend, globals::registry_queue_init, protocol::wl_buffer, Connection, QueueHandle, @@ -111,5 +111,5 @@ pub fn get_wayland_device_ids(window: &W) -> Option<(u16, u16)> { } } -sctk::delegate_dmabuf!(AppData); -sctk::delegate_registry!(AppData); +cctk::sctk::delegate_dmabuf!(AppData); +cctk::sctk::delegate_registry!(AppData); diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 1aec15e56d..7cc1f71949 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -28,15 +28,15 @@ markdown = ["dep:pulldown-cmark", "dep:url"] highlighter = ["dep:iced_highlighter"] advanced = [] a11y = ["iced_accessibility"] -wayland = ["sctk", "iced_runtime/wayland"] +wayland = ["cctk", "iced_runtime/wayland"] [dependencies] iced_renderer.workspace = true iced_runtime.workspace = true iced_accessibility.workspace = true iced_accessibility.optional = true -sctk.workspace = true -sctk.optional = true +cctk.workspace = true +cctk.optional = true num-traits.workspace = true once_cell.workspace = true rustc-hash.workspace = true diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 4e9e20023b..f16d109e86 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -21,7 +21,7 @@ program = [] x11 = ["winit/x11"] wayland = [ "winit/wayland", - "sctk", + "cctk", "wayland-protocols", "raw-window-handle", "iced_runtime/wayland", @@ -58,8 +58,8 @@ sysinfo.optional = true [target.'cfg(target_os = "linux")'.dependencies] raw-window-handle = { version = "0.6", optional = true } -sctk.workspace = true -sctk.optional = true +cctk.workspace = true +cctk.optional = true wayland-protocols.workspace = true wayland-protocols.optional = true wayland-backend = { version = "0.3.1", features = [ diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index 5f1727620d..2163818bb3 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -66,7 +66,7 @@ impl PlatformSpecific { ) { #[cfg(all(feature = "wayland", target_os = "linux"))] { - use sctk::reexports::client::{ + use cctk::sctk::reexports::client::{ protocol::wl_surface::WlSurface, Proxy, }; use wayland_backend::client::ObjectId; @@ -86,7 +86,9 @@ impl PlatformSpecific { wayland_display_handle.display.as_ptr().cast(), ) }; - sctk::reexports::client::Connection::from_backend(backend) + cctk::sctk::reexports::client::Connection::from_backend( + backend, + ) } _ => { return; diff --git a/winit/src/platform_specific/wayland/commands/layer_surface.rs b/winit/src/platform_specific/wayland/commands/layer_surface.rs index 84c580b4e3..839db95c38 100644 --- a/winit/src/platform_specific/wayland/commands/layer_surface.rs +++ b/winit/src/platform_specific/wayland/commands/layer_surface.rs @@ -13,7 +13,7 @@ use iced_runtime::{ task, Action, Task, }; -pub use sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}; +pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}; // TODO ASHLEY: maybe implement as builder that outputs a batched commands /// diff --git a/winit/src/platform_specific/wayland/commands/session_lock.rs b/winit/src/platform_specific/wayland/commands/session_lock.rs index 5a327552ad..d008b0d679 100644 --- a/winit/src/platform_specific/wayland/commands/session_lock.rs +++ b/winit/src/platform_specific/wayland/commands/session_lock.rs @@ -4,7 +4,7 @@ use iced_runtime::{ platform_specific::{self, wayland}, task, Action, Task, }; -use sctk::reexports::client::protocol::wl_output::WlOutput; +use cctk::sctk::reexports::client::protocol::wl_output::WlOutput; pub fn lock() -> Task { task::effect(Action::PlatformSpecific( diff --git a/winit/src/platform_specific/wayland/conversion.rs b/winit/src/platform_specific/wayland/conversion.rs index 5da22cc332..1db1abbb20 100644 --- a/winit/src/platform_specific/wayland/conversion.rs +++ b/winit/src/platform_specific/wayland/conversion.rs @@ -1,8 +1,4 @@ -use iced_runtime::core::{ - keyboard, - mouse::{self, ScrollDelta}, -}; -use sctk::{ +use cctk::sctk::{ reexports::client::protocol::wl_pointer::AxisSource, seat::{ keyboard::Modifiers, @@ -11,6 +7,10 @@ use sctk::{ }, }, }; +use iced_runtime::core::{ + keyboard, + mouse::{self, ScrollDelta}, +}; /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 64ef8c24f4..ecd230ebdc 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -18,8 +18,8 @@ use crate::{ }; use raw_window_handle::HasDisplayHandle; -use sctk::reexports::calloop_wayland_source::WaylandSource; -use sctk::{ +use cctk::sctk::reexports::calloop_wayland_source::WaylandSource; +use cctk::sctk::{ activation::ActivationState, compositor::CompositorState, globals::GlobalData, diff --git a/winit/src/platform_specific/wayland/event_loop/proxy.rs b/winit/src/platform_specific/wayland/event_loop/proxy.rs index 7140cef708..44c0949e3d 100644 --- a/winit/src/platform_specific/wayland/event_loop/proxy.rs +++ b/winit/src/platform_specific/wayland/event_loop/proxy.rs @@ -3,7 +3,7 @@ use iced_futures::futures::{ task::{Context, Poll}, Sink, }; -use sctk::reexports::calloop; +use cctk::sctk::reexports::calloop; use std::pin::Pin; /// An event loop proxy that implements `Sink`. diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 09589c6cbc..1dea4f5ad7 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -39,7 +39,7 @@ use iced_runtime::{ }, }, }; -use sctk::{ +use cctk::sctk::{ activation::{ActivationState, RequestData}, compositor::CompositorState, error::GlobalError, @@ -981,7 +981,7 @@ impl SctkState { } } if self.pending_popup.replace((popup, 0)).is_none() { - let timer = sctk::reexports::calloop::timer::Timer::from_duration(Duration::from_millis(30)); + let timer = cctk::sctk::reexports::calloop::timer::Timer::from_duration(Duration::from_millis(30)); let queue_handle = self.queue_handle.clone(); _ = self.loop_handle.insert_source(timer, move |_, _, state| { let Some((popup, attempt)) = state.pending_popup.take() else { diff --git a/winit/src/platform_specific/wayland/handlers/activation.rs b/winit/src/platform_specific/wayland/handlers/activation.rs index 5b8b0f5a82..26cfbeb817 100644 --- a/winit/src/platform_specific/wayland/handlers/activation.rs +++ b/winit/src/platform_specific/wayland/handlers/activation.rs @@ -1,5 +1,5 @@ use iced_futures::futures::channel::oneshot::Sender; -use sctk::{ +use cctk::sctk::{ activation::{ActivationHandler, RequestData, RequestDataExt}, delegate_activation, reexports::client::protocol::{wl_seat::WlSeat, wl_surface::WlSurface}, diff --git a/winit/src/platform_specific/wayland/handlers/compositor.rs b/winit/src/platform_specific/wayland/handlers/compositor.rs index a1b636839f..92b5c773c2 100644 --- a/winit/src/platform_specific/wayland/handlers/compositor.rs +++ b/winit/src/platform_specific/wayland/handlers/compositor.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MPL-2.0-only -use sctk::{ +use cctk::sctk::{ compositor::CompositorHandler, delegate_compositor, reexports::client::{ diff --git a/winit/src/platform_specific/wayland/handlers/mod.rs b/winit/src/platform_specific/wayland/handlers/mod.rs index e584f44db8..28264f1fd3 100644 --- a/winit/src/platform_specific/wayland/handlers/mod.rs +++ b/winit/src/platform_specific/wayland/handlers/mod.rs @@ -2,6 +2,7 @@ pub mod activation; pub mod compositor; pub mod output; +// pub mod overlap; pub mod seat; pub mod session_lock; pub mod shell; @@ -9,7 +10,7 @@ pub mod subcompositor; pub mod wp_fractional_scaling; pub mod wp_viewporter; -use sctk::{ +use cctk::sctk::{ delegate_registry, delegate_shm, output::OutputState, registry::{ProvidesRegistryState, RegistryState}, diff --git a/winit/src/platform_specific/wayland/handlers/output.rs b/winit/src/platform_specific/wayland/handlers/output.rs index 8bc9291752..bc227ea9d9 100644 --- a/winit/src/platform_specific/wayland/handlers/output.rs +++ b/winit/src/platform_specific/wayland/handlers/output.rs @@ -1,18 +1,18 @@ use crate::platform_specific::wayland::{ event_loop::state::SctkState, sctk_event::SctkEvent, }; -use sctk::{delegate_output, output::OutputHandler}; +use cctk::sctk::{delegate_output, output::OutputHandler}; impl OutputHandler for SctkState { - fn output_state(&mut self) -> &mut sctk::output::OutputState { + fn output_state(&mut self) -> &mut cctk::sctk::output::OutputState { &mut self.output_state } fn new_output( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - output: sctk::reexports::client::protocol::wl_output::WlOutput, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + output: cctk::sctk::reexports::client::protocol::wl_output::WlOutput, ) { self.sctk_events.push(SctkEvent::NewOutput { id: output.clone(), @@ -23,9 +23,9 @@ impl OutputHandler for SctkState { fn update_output( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - output: sctk::reexports::client::protocol::wl_output::WlOutput, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + output: cctk::sctk::reexports::client::protocol::wl_output::WlOutput, ) { if let Some(info) = self.output_state.info(&output) { self.sctk_events.push(SctkEvent::UpdateOutput { @@ -37,9 +37,9 @@ impl OutputHandler for SctkState { fn output_destroyed( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - output: sctk::reexports::client::protocol::wl_output::WlOutput, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + output: cctk::sctk::reexports::client::protocol::wl_output::WlOutput, ) { self.sctk_events.push(SctkEvent::RemovedOutput(output)); // TODO clean up any layer surfaces on this output? diff --git a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs index 2a37d7f8e6..8617f85eff 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs @@ -2,8 +2,8 @@ use crate::platform_specific::wayland::{ event_loop::state::SctkState, sctk_event::{KeyboardEventVariant, SctkEvent}, }; -use sctk::reexports::client::Proxy; -use sctk::{ +use cctk::sctk::reexports::client::Proxy; +use cctk::sctk::{ delegate_keyboard, seat::keyboard::{KeyboardHandler, Keysym}, }; @@ -11,10 +11,10 @@ use sctk::{ impl KeyboardHandler for SctkState { fn enter( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, - surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + keyboard: &cctk::sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + surface: &cctk::sctk::reexports::client::protocol::wl_surface::WlSurface, _serial: u32, _raw: &[u32], _keysyms: &[Keysym], @@ -56,20 +56,20 @@ impl KeyboardHandler for SctkState { winit::event::WindowEvent::Focused(true), )); self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Enter(surface.clone()), - kbd_id: keyboard.clone(), - seat_id: seat, - surface: surface.clone(), + variant: KeyboardEventVariant::Enter(surface.clone()), + kbd_id: keyboard.clone(), + seat_id: seat, + surface: surface.clone(), }); } } fn leave( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, - surface: &sctk::reexports::client::protocol::wl_surface::WlSurface, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + keyboard: &cctk::sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + surface: &cctk::sctk::reexports::client::protocol::wl_surface::WlSurface, _serial: u32, ) { self.request_redraw(surface); @@ -127,11 +127,11 @@ impl KeyboardHandler for SctkState { fn press_key( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + keyboard: &cctk::sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, serial: u32, - event: sctk::seat::keyboard::KeyEvent, + event: cctk::sctk::seat::keyboard::KeyEvent, ) { let (is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { @@ -183,11 +183,11 @@ impl KeyboardHandler for SctkState { fn release_key( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + keyboard: &cctk::sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, _serial: u32, - event: sctk::seat::keyboard::KeyEvent, + event: cctk::sctk::seat::keyboard::KeyEvent, ) { let (is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { @@ -218,11 +218,11 @@ impl KeyboardHandler for SctkState { fn update_modifiers( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + keyboard: &cctk::sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, _serial: u32, - modifiers: sctk::seat::keyboard::Modifiers, + modifiers: cctk::sctk::seat::keyboard::Modifiers, layout: u32, ) { let (is_active, my_seat) = diff --git a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs index a9bca881cb..b150904436 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs @@ -4,7 +4,7 @@ use crate::{ event_loop::state::SctkState, sctk_event::SctkEvent, }, }; -use sctk::{ +use cctk::sctk::{ delegate_pointer, reexports::client::Proxy, seat::pointer::{ @@ -19,10 +19,10 @@ use winit::{ impl PointerHandler for SctkState { fn pointer_frame( &mut self, - conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - pointer: &sctk::reexports::client::protocol::wl_pointer::WlPointer, - events: &[sctk::seat::pointer::PointerEvent], + conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + pointer: &cctk::sctk::reexports::client::protocol::wl_pointer::WlPointer, + events: &[cctk::sctk::seat::pointer::PointerEvent], ) { let (is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { diff --git a/winit/src/platform_specific/wayland/handlers/seat/seat.rs b/winit/src/platform_specific/wayland/handlers/seat/seat.rs index ee7d83e56e..71ae42de84 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/seat.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/seat.rs @@ -2,23 +2,23 @@ use crate::platform_specific::wayland::{ event_loop::{state::SctkSeat, state::SctkState}, sctk_event::{KeyboardEventVariant, SctkEvent, SeatEventVariant}, }; -use iced_runtime::keyboard::Modifiers; -use sctk::{ +use cctk::sctk::{ delegate_seat, reexports::client::{protocol::wl_keyboard::WlKeyboard, Proxy}, seat::{pointer::ThemeSpec, SeatHandler}, }; +use iced_runtime::keyboard::Modifiers; impl SeatHandler for SctkState { - fn seat_state(&mut self) -> &mut sctk::seat::SeatState { + fn seat_state(&mut self) -> &mut cctk::sctk::seat::SeatState { &mut self.seat_state } fn new_seat( &mut self, - _conn: &sctk::reexports::client::Connection, - qh: &sctk::reexports::client::QueueHandle, - seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + _conn: &cctk::sctk::reexports::client::Connection, + qh: &cctk::sctk::reexports::client::QueueHandle, + seat: cctk::sctk::reexports::client::protocol::wl_seat::WlSeat, ) { self.sctk_events.push(SctkEvent::SeatEvent { variant: SeatEventVariant::New, @@ -43,10 +43,10 @@ impl SeatHandler for SctkState { fn new_capability( &mut self, - _conn: &sctk::reexports::client::Connection, - qh: &sctk::reexports::client::QueueHandle, - seat: sctk::reexports::client::protocol::wl_seat::WlSeat, - capability: sctk::seat::Capability, + _conn: &cctk::sctk::reexports::client::Connection, + qh: &cctk::sctk::reexports::client::QueueHandle, + seat: cctk::sctk::reexports::client::protocol::wl_seat::WlSeat, + capability: cctk::sctk::seat::Capability, ) { let my_seat = match self.seats.iter_mut().find(|s| s.seat == seat) { Some(s) => s, @@ -71,7 +71,7 @@ impl SeatHandler for SctkState { }; // TODO data device match capability { - sctk::seat::Capability::Keyboard => { + cctk::sctk::seat::Capability::Keyboard => { let seat_clone = seat.clone(); let seat_clone_2 = seat.clone(); if let Ok(kbd) = self.seat_state.get_keyboard_with_repeat( @@ -107,7 +107,7 @@ impl SeatHandler for SctkState { _ = my_seat.kbd.replace(kbd); } } - sctk::seat::Capability::Pointer => { + cctk::sctk::seat::Capability::Pointer => { let surface = self.compositor_state.create_surface(qh); if let Ok(ptr) = self.seat_state.get_pointer_with_theme( @@ -127,7 +127,7 @@ impl SeatHandler for SctkState { _ = my_seat.ptr.replace(ptr); } } - sctk::seat::Capability::Touch => { + cctk::sctk::seat::Capability::Touch => { if let Some(touch) = self.seat_state.get_touch(qh, &seat).ok() { self.sctk_events.push(SctkEvent::SeatEvent { variant: SeatEventVariant::NewCapability( @@ -145,10 +145,10 @@ impl SeatHandler for SctkState { fn remove_capability( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - seat: sctk::reexports::client::protocol::wl_seat::WlSeat, - capability: sctk::seat::Capability, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + seat: cctk::sctk::reexports::client::protocol::wl_seat::WlSeat, + capability: cctk::sctk::seat::Capability, ) { let my_seat = match self.seats.iter_mut().find(|s| s.seat == seat) { Some(s) => s, @@ -158,7 +158,7 @@ impl SeatHandler for SctkState { // TODO data device match capability { // TODO use repeating kbd? - sctk::seat::Capability::Keyboard => { + cctk::sctk::seat::Capability::Keyboard => { if let Some(kbd) = my_seat.kbd.take() { self.sctk_events.push(SctkEvent::SeatEvent { variant: SeatEventVariant::RemoveCapability( @@ -169,7 +169,7 @@ impl SeatHandler for SctkState { }); } } - sctk::seat::Capability::Pointer => { + cctk::sctk::seat::Capability::Pointer => { if let Some(ptr) = my_seat.ptr.take() { self.sctk_events.push(SctkEvent::SeatEvent { variant: SeatEventVariant::RemoveCapability( @@ -180,7 +180,7 @@ impl SeatHandler for SctkState { }); } } - sctk::seat::Capability::Touch => { + cctk::sctk::seat::Capability::Touch => { if let Some(touch) = my_seat.touch.take() { self.sctk_events.push(SctkEvent::SeatEvent { variant: SeatEventVariant::RemoveCapability( @@ -197,9 +197,9 @@ impl SeatHandler for SctkState { fn remove_seat( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - seat: sctk::reexports::client::protocol::wl_seat::WlSeat, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + seat: cctk::sctk::reexports::client::protocol::wl_seat::WlSeat, ) { self.sctk_events.push(SctkEvent::SeatEvent { variant: SeatEventVariant::Remove, diff --git a/winit/src/platform_specific/wayland/handlers/seat/touch.rs b/winit/src/platform_specific/wayland/handlers/seat/touch.rs index 99e6cacad5..0ac17a5020 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/touch.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/touch.rs @@ -7,7 +7,7 @@ use crate::{ }, }; use iced_runtime::core::{touch, Point}; -use sctk::{ +use cctk::sctk::{ delegate_touch, reexports::client::{ protocol::{wl_surface::WlSurface, wl_touch::WlTouch}, diff --git a/winit/src/platform_specific/wayland/handlers/session_lock.rs b/winit/src/platform_specific/wayland/handlers/session_lock.rs index 27ae70b720..04945b3b35 100644 --- a/winit/src/platform_specific/wayland/handlers/session_lock.rs +++ b/winit/src/platform_specific/wayland/handlers/session_lock.rs @@ -1,7 +1,7 @@ use crate::platform_specific::wayland::{ handlers::SctkState, sctk_event::SctkEvent, }; -use sctk::{ +use cctk::sctk::{ delegate_session_lock, reexports::client::{Connection, QueueHandle}, session_lock::{ diff --git a/winit/src/platform_specific/wayland/handlers/shell/layer.rs b/winit/src/platform_specific/wayland/handlers/shell/layer.rs index d7d8b3e85e..174127eea1 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/layer.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/layer.rs @@ -2,7 +2,7 @@ use crate::platform_specific::wayland::{ event_loop::state::SctkState, sctk_event::{LayerSurfaceEventVariant, SctkEvent}, }; -use sctk::{ +use cctk::sctk::{ delegate_layer, reexports::client::Proxy, shell::{ @@ -16,9 +16,9 @@ use winit::dpi::LogicalSize; impl LayerShellHandler for SctkState { fn closed( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - layer: &sctk::shell::wlr_layer::LayerSurface, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + layer: &cctk::sctk::shell::wlr_layer::LayerSurface, ) { let layer = match self.layer_surfaces.iter().position(|s| { s.surface.wl_surface().id() == layer.wl_surface().id() @@ -36,10 +36,10 @@ impl LayerShellHandler for SctkState { fn configure( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - layer: &sctk::shell::wlr_layer::LayerSurface, - mut configure: sctk::shell::wlr_layer::LayerSurfaceConfigure, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + layer: &cctk::sctk::shell::wlr_layer::LayerSurface, + mut configure: cctk::sctk::shell::wlr_layer::LayerSurfaceConfigure, _serial: u32, ) { self.request_redraw(layer.wl_surface()); diff --git a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs index 0975dc0b9e..c48c6bceb3 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs @@ -2,7 +2,7 @@ use crate::platform_specific::wayland::{ event_loop::state::{self, PopupParent, SctkState}, sctk_event::{PopupEventVariant, SctkEvent}, }; -use sctk::{ +use cctk::sctk::{ delegate_xdg_popup, reexports::client::Proxy, shell::xdg::popup::PopupHandler, }; @@ -10,10 +10,10 @@ use sctk::{ impl PopupHandler for SctkState { fn configure( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - popup: &sctk::shell::xdg::popup::Popup, - configure: sctk::shell::xdg::popup::PopupConfigure, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + popup: &cctk::sctk::shell::xdg::popup::Popup, + configure: cctk::sctk::shell::xdg::popup::PopupConfigure, ) { self.request_redraw(popup.wl_surface()); let sctk_popup = match self.popups.iter_mut().find(|s| { @@ -43,9 +43,9 @@ impl PopupHandler for SctkState { fn done( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - popup: &sctk::shell::xdg::popup::Popup, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + popup: &cctk::sctk::shell::xdg::popup::Popup, ) { let sctk_popup = match self.popups.iter().position(|s| { s.popup.wl_surface().clone() == popup.wl_surface().clone() diff --git a/winit/src/platform_specific/wayland/handlers/shell/xdg_window.rs b/winit/src/platform_specific/wayland/handlers/shell/xdg_window.rs index 24da3cfa23..43881610f4 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/xdg_window.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/xdg_window.rs @@ -1,23 +1,23 @@ use crate::platform_specific::wayland::event_loop::state::SctkState; -use sctk::{ +use cctk::sctk::{ delegate_xdg_shell, delegate_xdg_window, shell::xdg::window::WindowHandler, }; impl WindowHandler for SctkState { fn request_close( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - _window: &sctk::shell::xdg::window::Window, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + _window: &cctk::sctk::shell::xdg::window::Window, ) { } fn configure( &mut self, - _conn: &sctk::reexports::client::Connection, - _qh: &sctk::reexports::client::QueueHandle, - _window: &sctk::shell::xdg::window::Window, - _configure: sctk::shell::xdg::window::WindowConfigure, + _conn: &cctk::sctk::reexports::client::Connection, + _qh: &cctk::sctk::reexports::client::QueueHandle, + _window: &cctk::sctk::shell::xdg::window::Window, + _configure: cctk::sctk::shell::xdg::window::WindowConfigure, _serial: u32, ) { } diff --git a/winit/src/platform_specific/wayland/handlers/subcompositor.rs b/winit/src/platform_specific/wayland/handlers/subcompositor.rs index 40f9a0db8f..4bfbfab1de 100644 --- a/winit/src/platform_specific/wayland/handlers/subcompositor.rs +++ b/winit/src/platform_specific/wayland/handlers/subcompositor.rs @@ -1,4 +1,4 @@ use crate::platform_specific::wayland::handlers::SctkState; -use sctk::delegate_subcompositor; +use cctk::sctk::delegate_subcompositor; delegate_subcompositor!(SctkState); diff --git a/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs b/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs index 00c675d52d..6d188a03aa 100644 --- a/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs +++ b/winit/src/platform_specific/wayland/handlers/wp_fractional_scaling.rs @@ -1,15 +1,15 @@ // From: https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/wayland/types/wp_fractional_scaling.rs //! Handling of the fractional scaling. -use sctk::reexports::client::globals::{BindError, GlobalList}; -use sctk::reexports::client::protocol::wl_surface::WlSurface; -use sctk::reexports::client::Dispatch; -use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle}; -use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; -use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::Event as FractionalScalingEvent; -use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; +use cctk::sctk::reexports::client::globals::{BindError, GlobalList}; +use cctk::sctk::reexports::client::protocol::wl_surface::WlSurface; +use cctk::sctk::reexports::client::Dispatch; +use cctk::sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle}; +use cctk::sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; +use cctk::sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::Event as FractionalScalingEvent; +use cctk::sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; -use sctk::globals::GlobalData; +use cctk::sctk::globals::GlobalData; use crate::platform_specific::wayland::event_loop::state::SctkState; diff --git a/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs b/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs index 8eacfbd302..5cfe28d79b 100644 --- a/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs +++ b/winit/src/platform_specific/wayland/handlers/wp_viewporter.rs @@ -1,15 +1,15 @@ //! Handling of the wp-viewporter. -use sctk::reexports::client::globals::{BindError, GlobalList}; -use sctk::reexports::client::protocol::wl_surface::WlSurface; -use sctk::reexports::client::Dispatch; -use sctk::reexports::client::{ +use cctk::sctk::reexports::client::globals::{BindError, GlobalList}; +use cctk::sctk::reexports::client::protocol::wl_surface::WlSurface; +use cctk::sctk::reexports::client::Dispatch; +use cctk::sctk::reexports::client::{ delegate_dispatch, Connection, Proxy, QueueHandle, }; -use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; -use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter; +use cctk::sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; +use cctk::sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter; -use sctk::globals::GlobalData; +use cctk::sctk::globals::GlobalData; use crate::platform_specific::wayland::event_loop::state::SctkState; diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index e4fcfd9d16..7f8c13e4c9 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -10,13 +10,13 @@ pub mod winit_window; use super::{PlatformSpecific, SurfaceIdWrapper}; use crate::program::{Control, Program, WindowManager}; +use cctk::sctk::reexports::calloop; +use cctk::sctk::reexports::client::protocol::wl_surface::WlSurface; +use cctk::sctk::seat::keyboard::Modifiers; use iced_futures::futures::channel::mpsc; use iced_graphics::Compositor; use iced_runtime::core::window; use iced_runtime::Debug; -use sctk::reexports::calloop; -use sctk::reexports::client::protocol::wl_surface::WlSurface; -use sctk::seat::keyboard::Modifiers; use sctk_event::SctkEvent; use std::{collections::HashMap, sync::Arc}; use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 3f35c3890d..a1c760f6b4 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -37,7 +37,7 @@ use iced_runtime::{ user_interface, Debug, }; -use sctk::{ +use cctk::sctk::{ output::OutputInfo, reexports::{ calloop::channel, @@ -216,7 +216,7 @@ pub enum WindowEventVariant { Configure((NonZeroU32, NonZeroU32), WindowConfigure, WlSurface, bool), Size((NonZeroU32, NonZeroU32), WlSurface, bool), /// window state changed - StateChanged(sctk::reexports::csd_frame::WindowState), + StateChanged(cctk::sctk::reexports::csd_frame::WindowState), /// Scale Factor ScaleFactorChanged(f64, Option), } diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 1f657300df..12291ee4ae 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -21,8 +21,7 @@ use std::{ }; use crate::futures::futures::channel::oneshot; -use iced_futures::core::window; -use sctk::{ +use cctk::sctk::{ compositor::SurfaceData, globals::GlobalData, reexports::client::{ @@ -39,6 +38,7 @@ use sctk::{ Connection, Dispatch, Proxy, QueueHandle, }, }; +use iced_futures::core::window; use wayland_backend::client::ObjectId; use wayland_protocols::wp::{ alpha_modifier::v1::client::{ @@ -347,7 +347,7 @@ impl SubsurfaceState { let wp_viewport = self.wp_viewporter.get_viewport( &wl_surface, &self.qh, - sctk::globals::GlobalData, + cctk::sctk::globals::GlobalData, ); let wp_alpha_modifier_surface = diff --git a/winit/src/platform_specific/wayland/winit_window.rs b/winit/src/platform_specific/wayland/winit_window.rs index 36b2d5efd6..8504535ec4 100644 --- a/winit/src/platform_specific/wayland/winit_window.rs +++ b/winit/src/platform_specific/wayland/winit_window.rs @@ -1,9 +1,9 @@ use crate::platform_specific::wayland::Action; -use raw_window_handle::HandleError; -use sctk::reexports::{ +use cctk::sctk::reexports::{ calloop::channel, client::{protocol::wl_display::WlDisplay, Proxy, QueueHandle}, }; +use raw_window_handle::HandleError; use std::sync::{Arc, Mutex}; use winit::{ dpi::LogicalSize, From 974b12c189a7d0d3e75aa4cb539226876b060d98 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 26 Nov 2024 16:09:31 -0500 Subject: [PATCH 042/116] feat: overlap notify --- core/src/event/wayland/mod.rs | 4 + core/src/event/wayland/overlap_notify.rs | 21 +++ runtime/src/platform_specific/wayland/mod.rs | 15 +- .../platform_specific/wayland/commands/mod.rs | 1 + .../wayland/commands/overlap_notify.rs | 14 ++ .../wayland/event_loop/mod.rs | 51 ++++-- .../wayland/event_loop/state.rs | 64 +++++-- .../platform_specific/wayland/handlers/mod.rs | 3 +- .../wayland/handlers/overlap.rs | 117 +++++++++++++ .../wayland/handlers/toplevel.rs | 67 ++++++++ .../platform_specific/wayland/sctk_event.rs | 158 +++++++++++++++--- 11 files changed, 453 insertions(+), 62 deletions(-) create mode 100644 core/src/event/wayland/overlap_notify.rs create mode 100644 winit/src/platform_specific/wayland/commands/overlap_notify.rs create mode 100644 winit/src/platform_specific/wayland/handlers/overlap.rs create mode 100644 winit/src/platform_specific/wayland/handlers/toplevel.rs diff --git a/core/src/event/wayland/mod.rs b/core/src/event/wayland/mod.rs index dd63fdc817..5fabc86005 100644 --- a/core/src/event/wayland/mod.rs +++ b/core/src/event/wayland/mod.rs @@ -1,5 +1,6 @@ mod layer; mod output; +mod overlap_notify; mod popup; mod seat; mod session_lock; @@ -12,6 +13,7 @@ use cctk::sctk::reexports::client::protocol::{ pub use layer::*; pub use output::*; +pub use overlap_notify::*; pub use popup::*; pub use seat::*; pub use session_lock::*; @@ -26,6 +28,8 @@ pub enum Event { Popup(PopupEvent, WlSurface, Id), /// output event Output(OutputEvent, WlOutput), + /// Overlap notify event + OverlapNotify(overlap_notify::OverlapNotifyEvent), /// window event Window(WindowEvent), /// Seat Event diff --git a/core/src/event/wayland/overlap_notify.rs b/core/src/event/wayland/overlap_notify.rs new file mode 100644 index 0000000000..5d59c29a95 --- /dev/null +++ b/core/src/event/wayland/overlap_notify.rs @@ -0,0 +1,21 @@ +use cctk::{sctk::shell::wlr_layer::Layer, wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1}; + +#[derive(Debug, Clone, PartialEq)] +pub enum OverlapNotifyEvent { + OverlapToplevelAdd { + toplevel: ExtForeignToplevelHandleV1, + logical_rect: crate::Rectangle, + }, + OverlapToplevelRemove { + toplevel: ExtForeignToplevelHandleV1, + }, + OverlapLayerAdd { + identifier: String, + exclusive: u32, + layer: Option, + logical_rect: crate::Rectangle, + }, + OverlapLayerRemove { + identifier: String, + }, +} diff --git a/runtime/src/platform_specific/wayland/mod.rs b/runtime/src/platform_specific/wayland/mod.rs index c370d38592..41b21c527e 100644 --- a/runtime/src/platform_specific/wayland/mod.rs +++ b/runtime/src/platform_specific/wayland/mod.rs @@ -2,6 +2,8 @@ use std::fmt::Debug; +use iced_core::window::Id; + /// activation Actions pub mod activation; @@ -22,21 +24,26 @@ pub enum Action { Activation(activation::Action), /// session lock SessionLock(session_lock::Action), + /// Overlap Notify + OverlapNotify(Id, bool), } impl Debug for Action { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::LayerSurface(arg0) => { + Action::LayerSurface(arg0) => { f.debug_tuple("LayerSurface").field(arg0).finish() } - Self::Popup(arg0) => f.debug_tuple("Popup").field(arg0).finish(), - Self::Activation(arg0) => { + Action::Popup(arg0) => f.debug_tuple("Popup").field(arg0).finish(), + Action::Activation(arg0) => { f.debug_tuple("Activation").field(arg0).finish() } - Self::SessionLock(arg0) => { + Action::SessionLock(arg0) => { f.debug_tuple("SessionLock").field(arg0).finish() } + Action::OverlapNotify(id, _) => { + f.debug_tuple("OverlapNotify").field(id).finish() + } } } } diff --git a/winit/src/platform_specific/wayland/commands/mod.rs b/winit/src/platform_specific/wayland/commands/mod.rs index d097333d09..85ba1000f6 100644 --- a/winit/src/platform_specific/wayland/commands/mod.rs +++ b/winit/src/platform_specific/wayland/commands/mod.rs @@ -2,5 +2,6 @@ pub mod activation; pub mod layer_surface; +pub mod overlap_notify; pub mod popup; pub mod session_lock; diff --git a/winit/src/platform_specific/wayland/commands/overlap_notify.rs b/winit/src/platform_specific/wayland/commands/overlap_notify.rs new file mode 100644 index 0000000000..860838c31b --- /dev/null +++ b/winit/src/platform_specific/wayland/commands/overlap_notify.rs @@ -0,0 +1,14 @@ +use iced_futures::core::window::Id; +use iced_runtime::{ + platform_specific::{self, wayland}, + task, Action, Task, +}; + +/// Request subscription for overlap notification events on the surface +pub fn overlap_notify(id: Id, enable: bool) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::OverlapNotify( + id, enable, + )), + )) +} diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index ecd230ebdc..cb18bc5da8 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -6,6 +6,7 @@ pub mod state; use crate::platform_specific::SurfaceIdWrapper; use crate::{ futures::futures::channel::mpsc, + handlers::overlap::OverlapNotifyV1, platform_specific::wayland::{ handlers::{ wp_fractional_scaling::FractionalScalingManager, @@ -17,25 +18,31 @@ use crate::{ subsurface_widget::SubsurfaceState, }; -use raw_window_handle::HasDisplayHandle; -use cctk::sctk::reexports::calloop_wayland_source::WaylandSource; -use cctk::sctk::{ - activation::ActivationState, - compositor::CompositorState, - globals::GlobalData, - output::OutputState, - reexports::{ - calloop::{self, EventLoop}, - client::{ - globals::registry_queue_init, ConnectError, Connection, Proxy, +use cctk::{ + sctk::reexports::calloop_wayland_source::WaylandSource, + toplevel_info::ToplevelInfoState, +}; +use cctk::{ + sctk::{ + activation::ActivationState, + compositor::CompositorState, + globals::GlobalData, + output::OutputState, + reexports::{ + calloop::{self, EventLoop}, + client::{ + globals::registry_queue_init, ConnectError, Connection, Proxy, + }, }, + registry::RegistryState, + seat::SeatState, + session_lock::SessionLockState, + shell::{wlr_layer::LayerShell, xdg::XdgShell, WaylandSurface}, + shm::Shm, }, - registry::RegistryState, - seat::SeatState, - session_lock::SessionLockState, - shell::{wlr_layer::LayerShell, xdg::XdgShell, WaylandSurface}, - shm::Shm, + toplevel_management::ToplevelManagerState, }; +use raw_window_handle::HasDisplayHandle; use state::{FrameStatus, SctkWindow}; #[cfg(feature = "a11y")] use std::sync::{Arc, Mutex}; @@ -188,7 +195,6 @@ impl SctkEventLoop { event_loop, state: SctkState { connection, - registry_state, seat_state: SeatState::new(&globals, &qh), output_state: OutputState::new(&globals, &qh), compositor_state: CompositorState::bind(&globals, &qh) @@ -201,6 +207,16 @@ impl SctkEventLoop { activation_state: ActivationState::bind(&globals, &qh).ok(), session_lock_state: SessionLockState::new(&globals, &qh), session_lock: None, + overlap_notify: OverlapNotifyV1::bind(&globals, &qh).ok(), + toplevel_info: ToplevelInfoState::try_new( + ®istry_state, + &qh, + ), + toplevel_manager: ToplevelManagerState::try_new( + ®istry_state, + &qh, + ), + registry_state, queue_handle: qh, loop_handle, @@ -228,6 +244,7 @@ impl SctkEventLoop { pending_popup: Default::default(), activation_token_ctr: 0, token_senders: HashMap::new(), + overlap_notifications: HashMap::new(), }, _features: Default::default(), }; diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 1dea4f5ad7..89a9167090 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1,5 +1,8 @@ use crate::{ - handlers::activation::IcedRequestData, + handlers::{ + activation::IcedRequestData, + overlap::{OverlapNotificationV1, OverlapNotifyV1}, + }, platform_specific::{ wayland::{ handlers::{ @@ -27,22 +30,11 @@ use winit::{ platform::wayland::WindowExtWayland, }; -use iced_runtime::{ - core::{self, touch, Point}, - keyboard::Modifiers, - platform_specific::{ - self, - wayland::{ - layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, - popup::SctkPopupSettings, - Action, - }, - }, -}; -use cctk::sctk::{ +use cctk::{cosmic_protocols::overlap_notify::v1::client::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1, sctk::{ activation::{ActivationState, RequestData}, compositor::CompositorState, error::GlobalError, + globals::GlobalData, output::OutputState, reexports::{ calloop::{timer::TimeoutAction, LoopHandle}, @@ -74,7 +66,7 @@ use cctk::sctk::{ shell::{ wlr_layer::{ Anchor, KeyboardInteractivity, Layer, LayerShell, LayerSurface, - LayerSurfaceConfigure, + LayerSurfaceConfigure, SurfaceKind, }, xdg::{ popup::{Popup, PopupConfigure}, @@ -83,6 +75,18 @@ use cctk::sctk::{ WaylandSurface, }, shm::{multi::MultiPool, Shm}, +}, toplevel_info::ToplevelInfoState, toplevel_management::ToplevelManagerState}; +use iced_runtime::{ + core::{self, touch, Point}, + keyboard::Modifiers, + platform_specific::{ + self, + wayland::{ + layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, + popup::SctkPopupSettings, + Action, + }, + }, }; use wayland_protocols::{ wp::{ @@ -326,7 +330,11 @@ pub struct SctkState { /// a memory pool pub(crate) _multipool: Option>, - // all present outputs + /// all notification objects + pub(crate) overlap_notifications: + HashMap, + + /// all present outputs pub(crate) outputs: Vec, // though (for now) only one seat will be active in an iced application at a time, all ought to be tracked // Active seat is the first seat in the list @@ -379,6 +387,9 @@ pub struct SctkState { pub(crate) to_commit: HashMap, pub(crate) destroyed: HashSet, pub(crate) pending_popup: Option<(SctkPopupSettings, usize)>, + pub(crate) overlap_notify: Option, + pub(crate) toplevel_info: Option, + pub(crate) toplevel_manager: Option, pub(crate) activation_token_ctr: u32, pub(crate) token_senders: HashMap>>, @@ -1176,6 +1187,27 @@ impl SctkState { } } } + Action::OverlapNotify(id, enabled) => { + if let Some(layer_surface) = self.layer_surfaces.iter_mut().find(|l| l.id == id) { + let Some(overlap_notify_state) = self.overlap_notify.as_ref() else { + tracing::error!("Overlap notify is not supported."); + return Ok(()); + }; + let my_id = layer_surface.surface.wl_surface().id(); + if enabled && !self.overlap_notifications.contains_key(&my_id) { + let SurfaceKind::Wlr(wlr) = &layer_surface.surface.kind() else { + tracing::error!("Overlap notify is not supported for non wlr surface."); + return Ok(()); + }; + let notification = overlap_notify_state.notify.notify_on_overlap(wlr, &self.queue_handle, OverlapNotificationV1 { surface: layer_surface.surface.wl_surface().clone() }); + _ = self.overlap_notifications.insert(my_id, notification); + } else { + _ = self.overlap_notifications.remove(&my_id); + } + } else { + tracing::error!("Overlap notify subscription cannot be created for surface. No matching layer surface found."); + } + }, }; Ok(()) } diff --git a/winit/src/platform_specific/wayland/handlers/mod.rs b/winit/src/platform_specific/wayland/handlers/mod.rs index 28264f1fd3..0079299f98 100644 --- a/winit/src/platform_specific/wayland/handlers/mod.rs +++ b/winit/src/platform_specific/wayland/handlers/mod.rs @@ -2,11 +2,12 @@ pub mod activation; pub mod compositor; pub mod output; -// pub mod overlap; +pub mod overlap; pub mod seat; pub mod session_lock; pub mod shell; pub mod subcompositor; +pub mod toplevel; pub mod wp_fractional_scaling; pub mod wp_viewporter; diff --git a/winit/src/platform_specific/wayland/handlers/overlap.rs b/winit/src/platform_specific/wayland/handlers/overlap.rs new file mode 100644 index 0000000000..15db4f50a5 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/overlap.rs @@ -0,0 +1,117 @@ +use cctk::{ + cosmic_protocols::overlap_notify::v1::client::{ + zcosmic_overlap_notification_v1::{self, ZcosmicOverlapNotificationV1}, + zcosmic_overlap_notify_v1::ZcosmicOverlapNotifyV1, + }, sctk::shell::wlr_layer::Layer, wayland_client::{ + self, event_created_child, + globals::{BindError, GlobalList}, + protocol::wl_surface::WlSurface, + Connection, Dispatch, Proxy, QueueHandle, + }, wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1 +}; +use cctk::sctk::globals::GlobalData; +use iced_futures::core::Rectangle; + +use crate::{event_loop::state::SctkState, sctk_event::SctkEvent}; + +#[derive(Debug, Clone)] +pub struct OverlapNotifyV1 { + pub(crate) notify: ZcosmicOverlapNotifyV1, +} + +impl OverlapNotifyV1 { + pub fn bind( + globals: &GlobalList, + qh: &QueueHandle, + ) -> Result { + let notify = globals.bind(qh, 1..=1, GlobalData)?; + Ok(OverlapNotifyV1 { notify }) + } +} + +impl Dispatch + for OverlapNotifyV1 +{ + fn event( + _: &mut SctkState, + _: &ZcosmicOverlapNotifyV1, + _: ::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + } +} + +pub struct OverlapNotificationV1 { + pub surface: WlSurface, +} + +impl Dispatch + for OverlapNotificationV1 +{ + fn event( + state: &mut SctkState, + _: &ZcosmicOverlapNotificationV1, + event: ::Event, + data: &OverlapNotificationV1, + _: &Connection, + _: &QueueHandle, + ) { + let surface = data.surface.clone(); + + state.sctk_events.push(match event { + zcosmic_overlap_notification_v1::Event::ToplevelEnter { + toplevel, + x, + y, + width, + height, + } => SctkEvent::OverlapToplevelAdd { + surface, + toplevel, + logical_rect: Rectangle::new( + (x as f32, y as f32).into(), + (width as f32, height as f32).into(), + ), + }, + zcosmic_overlap_notification_v1::Event::ToplevelLeave { + toplevel, + } => { + SctkEvent::OverlapToplevelRemove { surface, toplevel } + } + zcosmic_overlap_notification_v1::Event::LayerEnter { + identifier, + exclusive, + layer, + x, + y, + width, + height, + } => SctkEvent::OverlapLayerAdd { surface, identifier, exclusive, layer: match layer { + wayland_client::WEnum::Value(v) => match v { + cctk::sctk::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::Layer::Background => Some(Layer::Background), + cctk::sctk::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::Layer::Bottom => Some(Layer::Bottom), + cctk::sctk::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::Layer::Top => Some(Layer::Top), + cctk::sctk::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::Layer::Overlay => Some(Layer::Overlay), + _ => Default::default(), + }, + wayland_client::WEnum::Unknown(_) => Default::default(), + }, logical_rect: Rectangle::new( + (x as f32, y as f32).into(), + (width as f32, height as f32).into(), + ), }, + zcosmic_overlap_notification_v1::Event::LayerLeave { + identifier, + } => SctkEvent::OverlapLayerRemove { identifier, surface }, + _ => unimplemented!(), + }); + } + + event_created_child!(SctkState, ZcosmicOverlapNotifyV1, [ + 0 => (ExtForeignToplevelHandleV1, Default::default()) + ]); +} + +wayland_client::delegate_dispatch!(SctkState: [ZcosmicOverlapNotifyV1: GlobalData] => OverlapNotifyV1); +wayland_client::delegate_dispatch!(SctkState: [ZcosmicOverlapNotificationV1: OverlapNotificationV1] => OverlapNotificationV1); diff --git a/winit/src/platform_specific/wayland/handlers/toplevel.rs b/winit/src/platform_specific/wayland/handlers/toplevel.rs new file mode 100644 index 0000000000..f9146987d9 --- /dev/null +++ b/winit/src/platform_specific/wayland/handlers/toplevel.rs @@ -0,0 +1,67 @@ +use cctk::{ + cosmic_protocols::{ + toplevel_info::v1::client::zcosmic_toplevel_handle_v1, + toplevel_management::v1::client::zcosmic_toplevel_manager_v1, + }, + toplevel_info::{ToplevelInfoHandler, ToplevelInfoState}, + toplevel_management::ToplevelManagerHandler, + wayland_client::{self, WEnum}, +}; +use wayland_client::{Connection, QueueHandle}; + +use crate::event_loop::state::SctkState; + +impl ToplevelManagerHandler for SctkState { + fn toplevel_manager_state( + &mut self, + ) -> &mut cctk::toplevel_management::ToplevelManagerState { + self.toplevel_manager.as_mut().unwrap() + } + + fn capabilities( + &mut self, + _conn: &Connection, + _: &QueueHandle, + _capabilities: Vec< + WEnum, + >, + ) { + // TODO + } +} + +impl ToplevelInfoHandler for SctkState { + fn toplevel_info_state(&mut self) -> &mut ToplevelInfoState { + self.toplevel_info.as_mut().unwrap() + } + + fn new_toplevel( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + ) { + // TODO + } + + fn update_toplevel( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + ) { + // TODO + } + + fn toplevel_closed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + ) { + // TODO + } +} + +cctk::delegate_toplevel_info!(SctkState); +cctk::delegate_toplevel_manager!(SctkState); diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index a1c760f6b4..872d3d8c1b 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -18,11 +18,14 @@ use dnd::DndSurface; use iced_futures::{ core::{ event::{ - wayland::{LayerEvent, PopupEvent, SessionLockEvent}, + wayland::{ + LayerEvent, OverlapNotifyEvent, PopupEvent, SessionLockEvent, + }, PlatformSpecific, }, Clipboard as _, }, + event, futures::channel::mpsc, }; use iced_graphics::Compositor; @@ -37,30 +40,33 @@ use iced_runtime::{ user_interface, Debug, }; -use cctk::sctk::{ - output::OutputInfo, - reexports::{ - calloop::channel, - client::{ - backend::ObjectId, - protocol::{ - wl_display::WlDisplay, wl_keyboard::WlKeyboard, - wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat, - wl_surface::WlSurface, wl_touch::WlTouch, +use cctk::{ + cosmic_protocols::overlap_notify::v1::client::zcosmic_overlap_notification_v1, + sctk::{ + output::OutputInfo, + reexports::{ + calloop::channel, + client::{ + backend::ObjectId, + protocol::{ + wl_display::WlDisplay, wl_keyboard::WlKeyboard, + wl_output::WlOutput, wl_pointer::WlPointer, + wl_seat::WlSeat, wl_surface::WlSurface, wl_touch::WlTouch, + }, + Proxy, QueueHandle, }, - Proxy, QueueHandle, + csd_frame::WindowManagerCapabilities, + }, + seat::{ + keyboard::{KeyEvent, Modifiers}, + pointer::{PointerEvent, PointerEventKind}, + Capability, + }, + session_lock::SessionLockSurfaceConfigure, + shell::{ + wlr_layer::{Layer, LayerSurfaceConfigure}, + xdg::{popup::PopupConfigure, window::WindowConfigure}, }, - csd_frame::WindowManagerCapabilities, - }, - seat::{ - keyboard::{KeyEvent, Modifiers}, - pointer::{PointerEvent, PointerEventKind}, - Capability, - }, - session_lock::SessionLockSurfaceConfigure, - shell::{ - wlr_layer::LayerSurfaceConfigure, - xdg::{popup::PopupConfigure, window::WindowConfigure}, }, }; use std::{ @@ -68,7 +74,10 @@ use std::{ num::NonZeroU32, sync::{Arc, Mutex}, }; -use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport; +use wayland_protocols::{ + ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, + wp::viewporter::client::wp_viewport::WpViewport, +}; use winit::{ dpi::PhysicalSize, event::WindowEvent, event_loop::EventLoopProxy, window::WindowId, @@ -120,6 +129,26 @@ pub enum SctkEvent { variant: LayerSurfaceEventVariant, id: WlSurface, }, + OverlapToplevelAdd { + surface: WlSurface, + toplevel: ExtForeignToplevelHandleV1, + logical_rect: iced_runtime::core::Rectangle, + }, + OverlapToplevelRemove { + surface: WlSurface, + toplevel: ExtForeignToplevelHandleV1, + }, + OverlapLayerAdd { + surface: WlSurface, + identifier: String, + exclusive: u32, + layer: Option, + logical_rect: iced_runtime::core::Rectangle, + }, + OverlapLayerRemove { + surface: WlSurface, + identifier: String, + }, PopupEvent { variant: PopupEventVariant, /// this may be the Id of a window or layer surface @@ -1183,6 +1212,87 @@ impl SctkEvent { SctkEvent::Subcompositor(s) => { *subsurface_state = Some(s); } + SctkEvent::OverlapToplevelAdd { + surface, + toplevel, + logical_rect, + } => { + if let Some(id) = surface_ids.get(&surface.id()) { + events.push(( + Some(id.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::OverlapNotify( + OverlapNotifyEvent::OverlapToplevelAdd { + toplevel, + logical_rect, + }, + ), + ), + ), + )) + } + } + SctkEvent::OverlapToplevelRemove { surface, toplevel } => { + if let Some(id) = surface_ids.get(&surface.id()) { + events.push(( + Some(id.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::OverlapNotify( + OverlapNotifyEvent::OverlapToplevelRemove { + toplevel, + }, + ), + ), + ), + )) + } + } + SctkEvent::OverlapLayerAdd { + surface, + identifier, + exclusive, + layer, + logical_rect, + } => { + if let Some(id) = surface_ids.get(&surface.id()) { + events.push(( + Some(id.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::OverlapNotify( + OverlapNotifyEvent::OverlapLayerAdd { + identifier, + exclusive, + layer, + logical_rect, + }, + ), + ), + ), + )) + } + } + SctkEvent::OverlapLayerRemove { + surface, + identifier, + } => { + if let Some(id) = surface_ids.get(&surface.id()) { + events.push(( + Some(id.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::OverlapNotify( + OverlapNotifyEvent::OverlapLayerRemove { + identifier, + }, + ), + ), + ), + )) + } + } } } } From e454cbf8e778a32174d3eb3b23d387c6db3dada6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 27 Nov 2024 11:31:11 -0500 Subject: [PATCH 043/116] refactor: add namespace --- Cargo.toml | 2 +- core/src/event/wayland/overlap_notify.rs | 1 + examples/sctk_todos/Cargo.toml | 1 + examples/sctk_todos/src/main.rs | 30 ++++++++++++++----- .../wayland/handlers/overlap.rs | 3 +- .../platform_specific/wayland/sctk_event.rs | 3 ++ 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35b3c23905..a825450782 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -197,7 +197,7 @@ pulldown-cmark = "0.11" qrcode = { version = "0.13", default-features = false } raw-window-handle = "0.6" rustc-hash = "2.0" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "27d70b6" } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d218c76" } smol = "1.0" smol_str = "0.2" softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } diff --git a/core/src/event/wayland/overlap_notify.rs b/core/src/event/wayland/overlap_notify.rs index 5d59c29a95..60f790d8f0 100644 --- a/core/src/event/wayland/overlap_notify.rs +++ b/core/src/event/wayland/overlap_notify.rs @@ -11,6 +11,7 @@ pub enum OverlapNotifyEvent { }, OverlapLayerAdd { identifier: String, + namespace: String, exclusive: u32, layer: Option, logical_rect: crate::Rectangle, diff --git a/examples/sctk_todos/Cargo.toml b/examples/sctk_todos/Cargo.toml index fd14b95ed2..cfa96e7499 100644 --- a/examples/sctk_todos/Cargo.toml +++ b/examples/sctk_todos/Cargo.toml @@ -27,6 +27,7 @@ log = "0.4.17" env_logger = "0.10.0" async-std = "1.0" directories-next = "2.0.0" +tracing = "0.1" [profile.release-opt] debug = true diff --git a/examples/sctk_todos/src/main.rs b/examples/sctk_todos/src/main.rs index 7be16c835d..e2a38fdaaf 100644 --- a/examples/sctk_todos/src/main.rs +++ b/examples/sctk_todos/src/main.rs @@ -1,8 +1,9 @@ use env_logger::Env; use iced::alignment::{self, Alignment}; use iced::event::{self, listen_raw, Event}; -use iced::platform_specific::shell::commands::layer_surface::{ - get_layer_surface, Anchor, +use iced::platform_specific::shell::commands::{ + layer_surface::{get_layer_surface, Anchor}, + overlap_notify::overlap_notify, }; use iced::theme::{self, Theme}; use iced::widget::{ @@ -24,11 +25,11 @@ use std::fmt::Debug; static INPUT_ID: Lazy = Lazy::new(|| text_input::Id::unique()); pub fn main() -> iced::Result { - let env = Env::default() - .filter_or("MY_LOG_LEVEL", "info") - .write_style_or("MY_LOG_STYLE", "always"); + // let env = Env::default() + // .filter_or("MY_LOG_LEVEL", "info") + // .write_style_or("MY_LOG_STYLE", "always"); - env_logger::init_from_env(env); + // env_logger::init_from_env(env); iced::daemon(Todos::title, Todos::update, Todos::view) .subscription(Todos::subscription) .font(include_bytes!("../fonts/icons.ttf").as_slice()) @@ -89,17 +90,20 @@ impl Debug for Message { impl Todos { fn new() -> (Todos, Task) { + let id = window::Id::unique(); ( Todos::Loading, Task::batch(vec![ Task::perform(SavedState::load(), Message::Loaded), get_layer_surface(iced::platform_specific::runtime::wayland::layer_surface::SctkLayerSurfaceSettings { + id: id.clone(), size: Some((None, Some(500))), pointer_interactivity: true, keyboard_interactivity: cctk::sctk::shell::wlr_layer::KeyboardInteractivity::OnDemand, anchor: Anchor::LEFT.union(Anchor::RIGHT).union(Anchor::TOP), ..Default::default() }), + overlap_notify(id, true) ]), ) } @@ -311,12 +315,22 @@ impl Todos { }), ( Event::PlatformSpecific(event::PlatformSpecific::Wayland( - event::wayland::Event::Window(e), + event::wayland::Event::Layer(e, ..), )), _, _, ) => { - dbg!(&e); + dbg!(e); + None + } + ( + Event::PlatformSpecific(event::PlatformSpecific::Wayland( + event::wayland::Event::OverlapNotify(e), + )), + _, + _, + ) => { + dbg!(e); None } _ => None, diff --git a/winit/src/platform_specific/wayland/handlers/overlap.rs b/winit/src/platform_specific/wayland/handlers/overlap.rs index 15db4f50a5..530c6a7f79 100644 --- a/winit/src/platform_specific/wayland/handlers/overlap.rs +++ b/winit/src/platform_specific/wayland/handlers/overlap.rs @@ -82,13 +82,14 @@ impl Dispatch } zcosmic_overlap_notification_v1::Event::LayerEnter { identifier, + namespace, exclusive, layer, x, y, width, height, - } => SctkEvent::OverlapLayerAdd { surface, identifier, exclusive, layer: match layer { + } => SctkEvent::OverlapLayerAdd { surface, namespace, identifier, exclusive, layer: match layer { wayland_client::WEnum::Value(v) => match v { cctk::sctk::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::Layer::Background => Some(Layer::Background), cctk::sctk::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::Layer::Bottom => Some(Layer::Bottom), diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 872d3d8c1b..f6c2bef6a4 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -140,6 +140,7 @@ pub enum SctkEvent { }, OverlapLayerAdd { surface: WlSurface, + namespace: String, identifier: String, exclusive: u32, layer: Option, @@ -1251,6 +1252,7 @@ impl SctkEvent { } SctkEvent::OverlapLayerAdd { surface, + namespace, identifier, exclusive, layer, @@ -1264,6 +1266,7 @@ impl SctkEvent { wayland::Event::OverlapNotify( OverlapNotifyEvent::OverlapLayerAdd { identifier, + namespace, exclusive, layer, logical_rect, From 83396f3aa8fbe0f0a84dc7040463e0c642cc53ec Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 27 Nov 2024 16:48:35 -0500 Subject: [PATCH 044/116] fix: convert sctk configure events to Opened and Resized --- .../platform_specific/wayland/sctk_event.rs | 93 +++++++++++++++++-- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index f6c2bef6a4..ac655e95d3 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -23,7 +23,7 @@ use iced_futures::{ }, PlatformSpecific, }, - Clipboard as _, + Clipboard as _, Size, }, event, futures::channel::mpsc, @@ -838,9 +838,12 @@ impl SctkEvent { surface, first, ) => { - if let Some(w) = surface_ids - .get(&surface.id()) - .and_then(|id| window_manager.get_mut(id.inner())) + if let Some((id, w)) = + surface_ids.get(&surface.id()).and_then(|id| { + window_manager + .get_mut(id.inner()) + .map(|v| (id.inner(), v)) + }) { let scale = w.state.scale_factor(); let p_w = (configure.new_size.0.max(1) as f64 * scale) @@ -854,6 +857,26 @@ impl SctkEvent { )), debug, ); + if first { + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Resized( + w.state.logical_size(), + ), + ), + )) + } else { + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Opened { + size: w.state.logical_size(), + position: Default::default(), + }, + ), + )) + } } } }, @@ -1044,7 +1067,34 @@ impl SctkEvent { let _ = user_interfaces.insert(surface_id, ui); } - PopupEventVariant::Configure(_, _, _) => {} // TODO + PopupEventVariant::Configure(configure, surface, first) => { + let size = Size::new( + configure.width as f32, + configure.height as f32, + ); + if let Some(id) = + surface_ids.get(&surface.id()).map(|id| id.inner()) + { + if first { + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Resized(size), + ), + )) + } else { + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Opened { + size: size, + position: Default::default(), + }, + ), + )) + } + } + } // TODO PopupEventVariant::RepositionionedPopup { token: _ } => {} PopupEventVariant::Size(_, _) => {} PopupEventVariant::ScaleFactorChanged(..) => {} @@ -1190,7 +1240,38 @@ impl SctkEvent { ), ); } - SctkEvent::SessionLockSurfaceConfigure { .. } => {} + SctkEvent::SessionLockSurfaceConfigure { + surface, + configure, + first, + } => { + let size = Size::new( + configure.new_size.0 as f32, + configure.new_size.1 as f32, + ); + if let Some(id) = + surface_ids.get(&surface.id()).map(|id| id.inner()) + { + if first { + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Resized(size), + ), + )) + } else { + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Opened { + size: size, + position: Default::default(), + }, + ), + )) + } + } + } SctkEvent::SessionLockSurfaceDone { surface } => { if let Some(id) = surface_ids.remove(&surface.id()) { _ = window_manager.remove(id.inner()); From 98d538f50f40bd1c015ba3acda58d5d1cf550298 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 2 Dec 2024 19:19:36 -0500 Subject: [PATCH 045/116] fix: use dummy window for clipboard This avoids issues with apps like the portal that creates a screenshot, sets the clipboard, and then has the data device destroyed when the window is closed. --- winit/src/program.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index 933c643e0b..8d941b9194 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -245,6 +245,7 @@ where sender: mpsc::UnboundedSender>, receiver: mpsc::UnboundedReceiver, error: Option, + proxy: Proxy, #[cfg(target_arch = "wasm32")] is_booted: std::rc::Rc>, @@ -274,6 +275,7 @@ where sender: event_sender, receiver: control_receiver, error: None, + proxy: proxy.clone(), #[cfg(target_arch = "wasm32")] is_booted: std::rc::Rc::new(std::cell::RefCell::new(false)), @@ -378,6 +380,7 @@ where self.canvas = window.canvas(); } + let proxy = self.proxy.raw.clone(); let finish_boot = async move { let mut compositor = C::new(graphics_settings, window.clone()).await?; @@ -390,6 +393,13 @@ where .send(Boot { compositor, is_wayland, + clipboard: Clipboard::connect( + window, + crate::clipboard::ControlSender { + sender: control_sender, + proxy, + }, + ), }) .ok() .expect("Send boot event"); @@ -636,6 +646,7 @@ where struct Boot { compositor: C, is_wayland: bool, + clipboard: Clipboard, } pub(crate) enum Event { @@ -702,6 +713,7 @@ async fn run_instance<'a, P, C>( let Boot { mut compositor, is_wayland, + mut clipboard, } = boot.await.expect("Receive boot"); let mut platform_specific_handler = @@ -781,7 +793,6 @@ async fn run_instance<'a, P, C>( rustc_hash::FxBuildHasher, >, > = ManuallyDrop::new(FxHashMap::default()); - let mut clipboard = Clipboard::unconnected(); let mut cur_dnd_surface: Option = None; From b32415dde061a052902af0c91a8f4b1a944fca54 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 2 Dec 2024 20:31:56 -0500 Subject: [PATCH 046/116] fix(winit): cleanup layer surface dnd surface --- winit/src/platform_specific/wayland/sctk_event.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index ac655e95d3..c0d552b36e 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -666,16 +666,14 @@ impl SctkEvent { LayerSurfaceEventVariant::Done => { if let Some(id) = surface_ids.remove(&surface.id()) { if let Some(w) = window_manager.remove(id.inner()) { + clipboard.register_dnd_destination( + DndSurface(Arc::new(Box::new(w.raw.clone()))), + Vec::new(), + ); if clipboard .window_id() .is_some_and(|id| w.raw.id() == id) { - clipboard.register_dnd_destination( - DndSurface(Arc::new(Box::new( - w.raw.clone(), - ))), - Vec::new(), - ); *clipboard = Clipboard::unconnected(); } } From 584ffb62c0b668d0af871665ac3d6a00bda2a577 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 6 Nov 2024 14:48:30 -0800 Subject: [PATCH 047/116] subsurface_widget: Take owned `SubsurfaceBuffer` This type is an `Arc`, so there's no major benefit to using a reference, or a `Cow` to make it generic. Drag surfaces currently require a `'static` `Element`, so this is needed their. --- .../wayland/subsurface_widget.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 12291ee4ae..6267f95ddf 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -583,17 +583,16 @@ pub(crate) fn take_subsurfaces() -> Vec { } #[must_use] -pub struct Subsurface<'a> { +pub struct Subsurface { buffer_size: Size, - buffer: &'a SubsurfaceBuffer, + buffer: SubsurfaceBuffer, width: Length, height: Length, content_fit: ContentFit, alpha: f32, } -impl<'a, Message, Theme, Renderer> Widget - for Subsurface<'a> +impl Widget for Subsurface where Renderer: renderer::Renderer, { @@ -649,11 +648,11 @@ where } } -impl<'a> Subsurface<'a> { +impl Subsurface { pub fn new( buffer_width: u32, buffer_height: u32, - buffer: &'a SubsurfaceBuffer, + buffer: SubsurfaceBuffer, ) -> Self { Self { buffer_size: Size::new(buffer_width as f32, buffer_height as f32), @@ -687,13 +686,13 @@ impl<'a> Subsurface<'a> { } } -impl<'a, Message, Theme, Renderer> From> - for Element<'a, Message, Theme, Renderer> +impl From + for Element<'static, Message, Theme, Renderer> where - Message: Clone + 'a, + Message: Clone, Renderer: renderer::Renderer, { - fn from(subsurface: Subsurface<'a>) -> Self { + fn from(subsurface: Subsurface) -> Self { Self::new(subsurface) } } From 1077ba990fa30e55936701616ac23ef083bd9efd Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 6 Nov 2024 15:41:22 -0800 Subject: [PATCH 048/116] subsurface_widget: Clear subsurface list after calls to `::draw()` Otherwise the `draw` call in response to `AboutToWait` causes this list to grow, and eventually hit errors in `sendmsg` due to too many buffered file descriptors from creating subsurfaces. Probably the number of places `UserInterface::draw` are called should be cleaned up. And it shouldn't be called in `AboutToWiat` like this. Some improvements could probably be made in upstream iced. --- winit/src/platform_specific/mod.rs | 7 +++++++ winit/src/platform_specific/wayland/mod.rs | 4 ++++ winit/src/program.rs | 3 +++ 3 files changed, 14 insertions(+) diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index 2163818bb3..530f7e00fe 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -59,6 +59,13 @@ impl PlatformSpecific { } } + pub(crate) fn clear_subsurface_list(&mut self) { + #[cfg(all(feature = "wayland", target_os = "linux"))] + { + self.wayland.clear_subsurface_list(); + } + } + pub(crate) fn update_subsurfaces( &mut self, id: window::Id, diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 7f8c13e4c9..557453c393 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -181,6 +181,10 @@ impl WaylandSpecific { }; } + pub(crate) fn clear_subsurface_list(&mut self) { + let _ = crate::subsurface_widget::take_subsurfaces(); + } + pub(crate) fn update_subsurfaces( &mut self, id: window::Id, diff --git a/winit/src/program.rs b/winit/src/program.rs index 8d941b9194..879f358a88 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -970,6 +970,8 @@ async fn run_instance<'a, P, C>( }, Default::default(), ); + platform_specific_handler + .clear_subsurface_list(); let mut bytes = compositor.screenshot( &mut renderer, &mut surface, @@ -1641,6 +1643,7 @@ async fn run_instance<'a, P, C>( cursor, ) }; + platform_specific_handler.clear_subsurface_list(); if new_mouse_interaction != window.mouse_interaction { if let Some(interaction) = From 9f2280c5b29f914101334caf0dab27f3cc1f275f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 20 Nov 2024 19:06:45 -0800 Subject: [PATCH 049/116] subsurface_widget: Remove `subsurface_ids` This was previously needed, but should be redundant now that an empty input region is set for the subsurface. With that, the subsurface should never get input events, so mapping them isn't needed. --- winit/src/platform_specific/wayland/mod.rs | 5 ----- .../platform_specific/wayland/sctk_event.rs | 18 +++-------------- .../wayland/subsurface_widget.rs | 20 +------------------ 3 files changed, 4 insertions(+), 39 deletions(-) diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 557453c393..315bda5ed0 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -63,7 +63,6 @@ pub(crate) struct WaylandSpecific { modifiers: Modifiers, surface_ids: HashMap, destroyed_surface_ids: HashMap, - subsurface_ids: HashMap, subsurface_state: Option, surface_subsurfaces: HashMap>, } @@ -138,7 +137,6 @@ impl WaylandSpecific { display_handle, surface_ids, destroyed_surface_ids, - subsurface_ids, modifiers, subsurface_state, surface_subsurfaces, @@ -165,7 +163,6 @@ impl WaylandSpecific { compositor, window_manager, surface_ids, - subsurface_ids, sender, event_sender, proxy, @@ -198,8 +195,6 @@ impl WaylandSpecific { }; subsurface_state.update_subsurfaces( - id, - &mut self.subsurface_ids, wl_surface, surface_subsurfaces, &subsurfaces, diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index c0d552b36e..d354171d20 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -320,7 +320,6 @@ impl SctkEvent { compositor: &mut C, window_manager: &mut crate::program::WindowManager, surface_ids: &mut HashMap, - subsurface_ids: &mut HashMap, sctk_tx: &channel::Sender, control_sender: &mpsc::UnboundedSender, proxy: &EventLoopProxy, @@ -356,13 +355,6 @@ impl SctkEvent { iced_runtime::core::Event::Mouse(mouse::Event::CursorLeft), )), PointerEventKind::Motion { .. } => { - let offset = if let Some((x_offset, y_offset, _)) = - subsurface_ids.get(&variant.surface.id()) - { - (*x_offset, *y_offset) - } else { - (0, 0) - }; let id = surface_ids .get(&variant.surface.id()) .map(|id| id.inner()); @@ -370,11 +362,7 @@ impl SctkEvent { id.clone().and_then(|id| window_manager.get_mut(id)) { w.state.set_logical_cursor_pos( - ( - variant.position.0 + offset.0 as f64, - variant.position.1 + offset.1 as f64, - ) - .into(), + (variant.position.0, variant.position.1).into(), ) } events.push(( @@ -382,8 +370,8 @@ impl SctkEvent { iced_runtime::core::Event::Mouse( mouse::Event::CursorMoved { position: Point::new( - variant.position.0 as f32 + offset.0 as f32, - variant.position.1 as f32 + offset.1 as f32, + variant.position.0 as f32, + variant.position.1 as f32, ), }, ), diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 6267f95ddf..4e334026c4 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -368,8 +368,6 @@ impl SubsurfaceState { // Update `subsurfaces` from `view_subsurfaces` pub(crate) fn update_subsurfaces( &mut self, - parent_id: window::Id, - subsurface_ids: &mut HashMap, parent: &WlSurface, subsurfaces: &mut Vec, view_subsurfaces: &[SubsurfaceInfo], @@ -405,16 +403,7 @@ impl SubsurfaceState { for (subsurface_data, subsurface) in view_subsurfaces.iter().zip(subsurfaces.iter_mut()) { - subsurface.attach_and_commit( - parent_id, - subsurface_ids, - subsurface_data, - self, - ); - } - - if let Some(backend) = parent.backend().upgrade() { - subsurface_ids.retain(|k, _| backend.info(k.clone()).is_ok()); + subsurface.attach_and_commit(subsurface_data, self); } } @@ -478,8 +467,6 @@ impl SubsurfaceInstance { // TODO correct damage? no damage/commit if unchanged? fn attach_and_commit( &mut self, - parent_id: window::Id, - subsurface_ids: &mut HashMap, info: &SubsurfaceInfo, state: &mut SubsurfaceState, ) { @@ -542,11 +529,6 @@ impl SubsurfaceInstance { wp_alpha_modifier_surface.set_multiplier(alpha); } - _ = subsurface_ids.insert( - self.wl_surface.id(), - (info.bounds.x as i32, info.bounds.y as i32, parent_id), - ); - self.wl_buffer = Some(buffer); self.bounds = Some(info.bounds); } From 7b3b0fd67ed31187fe4008be3d1aa5befa65bed1 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 21 Nov 2024 16:26:17 -0800 Subject: [PATCH 050/116] Remove `surface` argument of `Compositor::screenshot` This argument was completely ignored by the wgpu renderer, and used only for the `clip_mask` by the `tiny_skia` renderer. I believe creating a new clip mask is correct. This way it's possible to render offscreen without needing a surface. --- graphics/src/compositor.rs | 2 -- renderer/src/fallback.rs | 41 +++++++++++++----------------- tiny_skia/src/window/compositor.rs | 9 ++++--- wgpu/src/window/compositor.rs | 1 - winit/src/program.rs | 1 - 5 files changed, 22 insertions(+), 32 deletions(-) diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index d4db344377..d406b13eb3 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -90,7 +90,6 @@ pub trait Compositor: Sized { fn screenshot>( &mut self, renderer: &mut Self::Renderer, - surface: &mut Self::Surface, viewport: &Viewport, background_color: Color, overlay: &[T], @@ -213,7 +212,6 @@ impl Compositor for () { fn screenshot>( &mut self, _renderer: &mut Self::Renderer, - _surface: &mut Self::Surface, _viewport: &Viewport, _background_color: Color, _overlay: &[T], diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 0bceaed065..9e3874c9e0 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -378,34 +378,27 @@ where fn screenshot>( &mut self, renderer: &mut Self::Renderer, - surface: &mut Self::Surface, viewport: &graphics::Viewport, background_color: Color, overlay: &[T], ) -> Vec { - match (self, renderer, surface) { - ( - Self::Primary(compositor), - Renderer::Primary(renderer), - Surface::Primary(surface), - ) => compositor.screenshot( - renderer, - surface, - viewport, - background_color, - overlay, - ), - ( - Self::Secondary(compositor), - Renderer::Secondary(renderer), - Surface::Secondary(surface), - ) => compositor.screenshot( - renderer, - surface, - viewport, - background_color, - overlay, - ), + match (self, renderer) { + (Self::Primary(compositor), Renderer::Primary(renderer)) => { + compositor.screenshot( + renderer, + viewport, + background_color, + overlay, + ) + } + (Self::Secondary(compositor), Renderer::Secondary(renderer)) => { + compositor.screenshot( + renderer, + viewport, + background_color, + overlay, + ) + } _ => unreachable!(), } } diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index cc887d04bb..432ec8e16a 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -121,12 +121,11 @@ impl crate::graphics::Compositor for Compositor { fn screenshot>( &mut self, renderer: &mut Self::Renderer, - surface: &mut Self::Surface, viewport: &Viewport, background_color: Color, overlay: &[T], ) -> Vec { - screenshot(renderer, surface, viewport, background_color, overlay) + screenshot(renderer, viewport, background_color, overlay) } } @@ -219,7 +218,6 @@ pub fn present>( pub fn screenshot>( renderer: &mut Renderer, - surface: &mut Surface, viewport: &Viewport, background_color: Color, overlay: &[T], @@ -229,6 +227,9 @@ pub fn screenshot>( let mut offscreen_buffer: Vec = vec![0; size.width as usize * size.height as usize]; + let mut clip_mask = tiny_skia::Mask::new(size.width, size.height) + .expect("Create clip mask"); + renderer.draw( &mut tiny_skia::PixmapMut::from_bytes( bytemuck::cast_slice_mut(&mut offscreen_buffer), @@ -236,7 +237,7 @@ pub fn screenshot>( size.height, ) .expect("Create offscreen pixel map"), - &mut surface.clip_mask, + &mut clip_mask, viewport, &[Rectangle::with_size(Size::new( size.width as f32, diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 1787f4e881..04982afe00 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -455,7 +455,6 @@ impl graphics::Compositor for Compositor { fn screenshot>( &mut self, renderer: &mut Self::Renderer, - _surface: &mut Self::Surface, viewport: &Viewport, background_color: Color, overlay: &[T], diff --git a/winit/src/program.rs b/winit/src/program.rs index 879f358a88..39c9145adf 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -2197,7 +2197,6 @@ where if let Some(window) = window_manager.get_mut(id) { let bytes = compositor.screenshot( &mut window.renderer, - &mut window.surface, window.state.viewport(), window.state.background_color(), &debug.overlay(), From 8404d3add64cf4fdae69b1b84e6a65b66395262f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 21 Nov 2024 16:47:37 -0800 Subject: [PATCH 051/116] Update `screenshot` call to not pass surface `.create_surface()` actually created another wgpu surface for the parent surface. Which ends up calling `vkCreateSwapchainKHR`. Looks like that technically should have errored with `VK_ERROR_NATIVE_WINDOW_IN_USE_KHR`, but Mesa doesn't produce that error in every case where it should: https://gitlab.freedesktop.org/mesa/mesa/-/issues/7467. --- winit/src/program.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index 39c9145adf..c673106af1 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -948,11 +948,6 @@ async fn run_instance<'a, P, C>( size, state.viewport().scale_factor(), ); - let mut surface = compositor.create_surface( - window.raw.clone(), - viewport.physical_width(), - viewport.physical_height(), - ); let mut ui = UserInterface::build( e, @@ -974,7 +969,6 @@ async fn run_instance<'a, P, C>( .clear_subsurface_list(); let mut bytes = compositor.screenshot( &mut renderer, - &mut surface, &viewport, core::Color::TRANSPARENT, &debug.overlay(), From a20b5b5626b8618751ae5a4701d4249330894a2e Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 2 Dec 2024 12:42:16 -0800 Subject: [PATCH 052/116] subsurface: Add `width`/`height` methods to `SubsuraceBuffer` This is potentially useful. Other properties are specific to shm/dmabuf, and not meaningful in general. --- .../platform_specific/wayland/subsurface_widget.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 4e334026c4..a3a0b88a24 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -159,6 +159,20 @@ impl SubsurfaceBuffer { (subsurface_buffer, SubsurfaceBufferRelease(receiver)) } + pub fn width(&self) -> i32 { + match &*self.0.source { + BufferSource::Dma(dma) => dma.width, + BufferSource::Shm(shm) => shm.width, + } + } + + pub fn height(&self) -> i32 { + match &*self.0.source { + BufferSource::Dma(dma) => dma.height, + BufferSource::Shm(shm) => shm.height, + } + } + // Behavior of `wl_buffer::released` is undefined if attached to multiple surfaces. To allow // things like that, create a new `wl_buffer` each time. fn create_buffer( From c2414c11abb1959934f6892431bc00223507eca3 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 2 Dec 2024 13:41:35 -0800 Subject: [PATCH 053/116] subsurface: Remove width/height arguments to `Subsurface::new` In an earlier version, this took a `WlBuffer`, so the width/height arguments were needed. But since using the same `wl_buffer` multiple times conflicted with `release` handling we instead accept `SubsurfaceBuffer`, which stores the same height/width. --- .../wayland/subsurface_widget.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index a3a0b88a24..4c19cedffa 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -580,7 +580,6 @@ pub(crate) fn take_subsurfaces() -> Vec { #[must_use] pub struct Subsurface { - buffer_size: Size, buffer: SubsurfaceBuffer, width: Length, height: Length, @@ -603,10 +602,12 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let raw_size = - limits.resolve(self.width, self.height, self.buffer_size); + let buffer_size = + Size::new(self.buffer.width() as f32, self.buffer.height() as f32); - let full_size = self.content_fit.fit(self.buffer_size, raw_size); + let raw_size = limits.resolve(self.width, self.height, buffer_size); + + let full_size = self.content_fit.fit(buffer_size, raw_size); let final_size = Size { width: match self.width { @@ -645,13 +646,8 @@ where } impl Subsurface { - pub fn new( - buffer_width: u32, - buffer_height: u32, - buffer: SubsurfaceBuffer, - ) -> Self { + pub fn new(buffer: SubsurfaceBuffer) -> Self { Self { - buffer_size: Size::new(buffer_width as f32, buffer_height as f32), buffer, // Matches defaults of image widget width: Length::Shrink, From 72589e79ad18f0038aca63bf0e469f68a27da2ff Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 2 Dec 2024 15:33:19 -0800 Subject: [PATCH 054/116] subsurface: Add option to apply buffer transform --- .../wayland/subsurface_widget.rs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 4c19cedffa..fb3ff9ac09 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -29,6 +29,7 @@ use cctk::sctk::{ protocol::{ wl_buffer::{self, WlBuffer}, wl_compositor::WlCompositor, + wl_output, wl_shm::{self, WlShm}, wl_shm_pool::{self, WlShmPool}, wl_subcompositor::WlSubcompositor, @@ -376,6 +377,7 @@ impl SubsurfaceState { wp_alpha_modifier_surface, wl_buffer: None, bounds: None, + transform: wl_output::Transform::Normal, } } @@ -475,6 +477,7 @@ pub(crate) struct SubsurfaceInstance { wp_alpha_modifier_surface: Option, wl_buffer: Option, bounds: Option>, + transform: wl_output::Transform, } impl SubsurfaceInstance { @@ -528,11 +531,15 @@ impl SubsurfaceInstance { info.bounds.height as i32, ); } + let transform_changed = self.transform != info.transform; + if transform_changed { + self.wl_surface.set_buffer_transform(info.transform); + } if buffer_changed { self.wl_surface.attach(Some(&buffer), 0, 0); self.wl_surface.damage(0, 0, i32::MAX, i32::MAX); } - if buffer_changed || bounds_changed { + if buffer_changed || bounds_changed || transform_changed { _ = self.wl_surface.frame(&state.qh, self.wl_surface.clone()); self.wl_surface.commit(); } @@ -545,6 +552,7 @@ impl SubsurfaceInstance { self.wl_buffer = Some(buffer); self.bounds = Some(info.bounds); + self.transform = info.transform; } pub fn unmap(&self) { @@ -568,6 +576,7 @@ pub(crate) struct SubsurfaceInfo { pub buffer: SubsurfaceBuffer, pub bounds: Rectangle, pub alpha: f32, + pub transform: wl_output::Transform, } thread_local! { @@ -585,6 +594,7 @@ pub struct Subsurface { height: Length, content_fit: ContentFit, alpha: f32, + transform: wl_output::Transform, } impl Widget for Subsurface @@ -602,9 +612,18 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let buffer_size = - Size::new(self.buffer.width() as f32, self.buffer.height() as f32); + let (width, height) = match self.transform { + wl_output::Transform::_90 + | wl_output::Transform::_270 + | wl_output::Transform::Flipped90 + | wl_output::Transform::Flipped270 => { + (self.buffer.height(), self.buffer.width()) + } + _ => (self.buffer.width(), self.buffer.height()), + }; + let buffer_size = Size::new(width as f32, height as f32); + // TODO apply transform let raw_size = limits.resolve(self.width, self.height, buffer_size); let full_size = self.content_fit.fit(buffer_size, raw_size); @@ -640,6 +659,7 @@ where buffer: self.buffer.clone(), bounds: layout.bounds(), alpha: self.alpha, + transform: self.transform, }) }); } @@ -654,6 +674,7 @@ impl Subsurface { height: Length::Shrink, content_fit: ContentFit::Contain, alpha: 1., + transform: wl_output::Transform::Normal, } } @@ -676,6 +697,11 @@ impl Subsurface { self.alpha = alpha; self } + + pub fn transform(mut self, transform: wl_output::Transform) -> Self { + self.transform = transform; + self + } } impl From From 5d759664b86059967fd468beb16d2e1c5cb7db04 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 11 Dec 2024 14:37:17 -0500 Subject: [PATCH 055/116] fix: use total_cmp where possible --- core/src/gradient.rs | 2 +- graphics/src/gradient.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/gradient.rs b/core/src/gradient.rs index ccae0bcef7..722c3c5c20 100644 --- a/core/src/gradient.rs +++ b/core/src/gradient.rs @@ -70,7 +70,7 @@ impl Linear { let (Ok(index) | Err(index)) = self.stops.binary_search_by(|stop| match stop { None => Ordering::Greater, - Some(stop) => stop.offset.partial_cmp(&offset).unwrap(), + Some(stop) => stop.offset.total_cmp(&offset), }); if index < 8 { diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs index 542617214a..95593c59ec 100644 --- a/graphics/src/gradient.rs +++ b/graphics/src/gradient.rs @@ -67,7 +67,7 @@ impl Linear { let (Ok(index) | Err(index)) = self.stops.binary_search_by(|stop| match stop { None => Ordering::Greater, - Some(stop) => stop.offset.partial_cmp(&offset).unwrap(), + Some(stop) => stop.offset.total_cmp(&offset), }); if index < 8 { From fb7047228e91f04ce1627914441ebb7a5651d330 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 2 Jan 2025 11:10:24 -0800 Subject: [PATCH 056/116] Remove unused `destroyed_surface_ids` --- winit/src/platform_specific/wayland/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 315bda5ed0..ef4b5a1d79 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -62,7 +62,6 @@ pub(crate) struct WaylandSpecific { display_handle: Option, modifiers: Modifiers, surface_ids: HashMap, - destroyed_surface_ids: HashMap, subsurface_state: Option, surface_subsurfaces: HashMap>, } @@ -136,7 +135,6 @@ impl WaylandSpecific { sender, display_handle, surface_ids, - destroyed_surface_ids, modifiers, subsurface_state, surface_subsurfaces, From aeaf005e96ee46b18850f29f1856cfdbe78d9665 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 17 Dec 2024 15:13:24 -0800 Subject: [PATCH 057/116] winit/wayland: Subsurfaces for drag surfaces Use `Icon::Surface` instead of `Icon::Buffer`, so we can then create subsurfaces of the `wl_surface`. Using `.screenshot()` then copying to an shm buffer is suboptimal, but this does seem overall better than with the older Iced version when a drag surface didn't appear until a Vulkan surface could be created for it. This re-uses `Connection` in the platform-specific code, instead of creating from display handle on each call. --- Cargo.toml | 7 +- winit/Cargo.toml | 2 + winit/src/platform_specific/mod.rs | 43 ++++++------ winit/src/platform_specific/wayland/mod.rs | 70 +++++++++++++++++++ .../wayland/subsurface_widget.rs | 48 ++++++++++++- winit/src/program.rs | 42 ++++++++--- 6 files changed, 174 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a825450782..f497776c1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -214,14 +214,15 @@ wasm-timer = "0.2" web-time = "1.1" wgpu = "23.0" wayland-protocols = { version = "0.32.1", features = ["staging"] } +wayland-client = { version = "0.31.5" } # web-time = "1.1" winapi = "0.3" # window_clipboard = "0.4.1" -window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-0.13" } -dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-0.13" } -mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-0.13" } +window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-0.13-2" } +dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-0.13-2" } +mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-0.13-2" } winit = { git = "https://github.com/pop-os/winit.git", tag = "iced-xdg-surface-0.13" } # winit = { path = "../../winit" } # winit = { git = "https://github.com/iced-rs/winit.git", rev = "254d6b3420ce4e674f516f7a2bd440665e05484d" } diff --git a/winit/Cargo.toml b/winit/Cargo.toml index f16d109e86..b66d4ab39a 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -62,12 +62,14 @@ cctk.workspace = true cctk.optional = true wayland-protocols.workspace = true wayland-protocols.optional = true +wayland-client.workspace = true wayland-backend = { version = "0.3.1", features = [ "client_system", ], optional = true } xkbcommon = { version = "0.7", features = ["wayland"], optional = true } xkbcommon-dl = { version = "0.4.1", optional = true } xkeysym = { version = "0.2.0", optional = true } +rustix = { version = "0.38" } [target.'cfg(target_os = "windows")'.dependencies] winapi.workspace = true diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index 530f7e00fe..5490bfac77 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use iced_graphics::Compositor; use iced_runtime::{core::window, user_interface, Debug}; +use raw_window_handle::HasWindowHandle; #[cfg(all(feature = "wayland", target_os = "linux"))] pub mod wayland; @@ -69,7 +70,7 @@ impl PlatformSpecific { pub(crate) fn update_subsurfaces( &mut self, id: window::Id, - window: &dyn winit::window::Window, + window: &dyn HasWindowHandle, ) { #[cfg(all(feature = "wayland", target_os = "linux"))] { @@ -78,31 +79,12 @@ impl PlatformSpecific { }; use wayland_backend::client::ObjectId; - let Ok(backend) = window.rwh_06_display_handle().display_handle() - else { - log::error!("No display handle"); + let Some(conn) = self.wayland.conn() else { + log::error!("No Wayland conn"); return; }; - let conn = match backend.as_raw() { - raw_window_handle::RawDisplayHandle::Wayland( - wayland_display_handle, - ) => { - let backend = unsafe { - Backend::from_foreign_display( - wayland_display_handle.display.as_ptr().cast(), - ) - }; - cctk::sctk::reexports::client::Connection::from_backend( - backend, - ) - } - _ => { - return; - } - }; - - let Ok(raw) = window.rwh_06_window_handle().window_handle() else { + let Ok(raw) = window.window_handle() else { log::error!("Invalid window handle {id:?}"); return; }; @@ -137,6 +119,21 @@ impl PlatformSpecific { self.wayland.update_subsurfaces(id, &wl_surface); } } + + pub(crate) fn create_surface(&mut self) -> Option> { + #[cfg(all(feature = "wayland", target_os = "linux"))] + { + return self.wayland.create_surface(); + } + None + } + + pub(crate) fn update_surface_shm(&mut self, surface: &dyn HasWindowHandle, width: u32, height: u32, data: &[u8]) { + #[cfg(all(feature = "wayland", target_os = "linux"))] + { + return self.wayland.update_surface_shm(surface, width, height, data); + } + } } pub type UserInterfaces<'a, P> = HashMap< diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index ef4b5a1d79..1b3b8cf5b8 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -17,10 +17,13 @@ use iced_futures::futures::channel::mpsc; use iced_graphics::Compositor; use iced_runtime::core::window; use iced_runtime::Debug; +use raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle}; +use raw_window_handle::{HasRawDisplayHandle, RawWindowHandle}; use sctk_event::SctkEvent; use std::{collections::HashMap, sync::Arc}; use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; use wayland_backend::client::ObjectId; +use wayland_client::{Connection, Proxy}; use winit::event_loop::OwnedDisplayHandle; use winit::window::CursorIcon; @@ -60,6 +63,7 @@ pub(crate) struct WaylandSpecific { proxy: Option, sender: Option>, display_handle: Option, + conn: Option, modifiers: Modifiers, surface_ids: HashMap, subsurface_state: Option, @@ -74,6 +78,28 @@ impl PlatformSpecific { display: OwnedDisplayHandle, ) -> Self { self.wayland.winit_event_sender = Some(tx); + self.wayland.conn = match display.raw_display_handle() { + Ok(raw_window_handle::RawDisplayHandle::Wayland( + wayland_display_handle, + )) => { + let backend = unsafe { + wayland_backend::client::Backend::from_foreign_display( + wayland_display_handle.display.as_ptr().cast(), + ) + }; + Some(Connection::from_backend( + backend, + )) + } + Ok(_) => { + log::error!("Non-Wayland display handle"); + None + } + Err(_) => { + log::error!("No display handle"); + None + } + }; self.wayland.display_handle = Some(display); self.wayland.proxy = Some(raw); // TODO remove this @@ -111,6 +137,10 @@ impl PlatformSpecific { } impl WaylandSpecific { + pub(crate) fn conn(&self) -> Option<&Connection> { + self.conn.as_ref() + } + pub(crate) fn handle_event<'a, P, C>( &mut self, e: SctkEvent, @@ -134,6 +164,7 @@ impl WaylandSpecific { proxy, sender, display_handle, + conn, surface_ids, modifiers, subsurface_state, @@ -198,4 +229,43 @@ impl WaylandSpecific { &subsurfaces, ); } + + pub(crate) fn create_surface(&mut self) -> Option> { + if let Some(subsurface_state) = self.subsurface_state.as_mut() { + let wl_surface = subsurface_state.create_surface(); + Some(Box::new(Window(wl_surface))) + } else { + None + } + } + + pub(crate) fn update_surface_shm(&mut self, window: &dyn HasWindowHandle, width: u32, height: u32, data: &[u8]) { + if let Some(subsurface_state) = self.subsurface_state.as_mut() { + if let RawWindowHandle::Wayland(window) = window.window_handle().unwrap().as_raw() { + let id = unsafe { ObjectId::from_ptr(WlSurface::interface(), window.surface.as_ptr().cast()).unwrap() }; + let surface = WlSurface::from_id(self.conn.as_ref().unwrap(), id).unwrap(); + subsurface_state.update_surface_shm(&surface, width, height, data); + } + } + } +} + +struct Window(WlSurface); + +impl HasWindowHandle for Window { + fn window_handle(&self) -> Result, raw_window_handle::HandleError> { + Ok(unsafe { + raw_window_handle::WindowHandle::borrow_raw(raw_window_handle::RawWindowHandle::Wayland( + raw_window_handle::WaylandWindowHandle::new( + std::ptr::NonNull::new(self.0.id().as_ptr() as *mut _).unwrap(), + ), + )) + }) + } +} + +impl Drop for Window { + fn drop(&mut self) { + self.0.destroy(); + } } diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index fb3ff9ac09..5cde0e96bb 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -23,7 +23,9 @@ use std::{ use crate::futures::futures::channel::oneshot; use cctk::sctk::{ compositor::SurfaceData, - globals::GlobalData, + error::GlobalError, + globals::{GlobalData, ProvidesBoundGlobal}, + shm::slot::SlotPool, reexports::client::{ delegate_noop, protocol::{ @@ -251,6 +253,7 @@ impl PartialEq for SubsurfaceBuffer { } } + impl Dispatch for SctkState { fn event( _: &mut SctkState, @@ -288,6 +291,23 @@ impl Dispatch for SctkState { } } +impl Dispatch for SctkState { + fn event( + _: &mut SctkState, + _: &WlBuffer, + event: wl_buffer::Event, + _: &GlobalData, + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_buffer::Event::Release => { + } + _ => unreachable!(), + } + } +} + impl Dispatch for SctkState { fn event( _: &mut SctkState, @@ -325,6 +345,15 @@ impl Hash for WeakBufferSource { } } +// Implement `ProvidesBoundGlobal` to use `SlotPool` +struct ShmGlobal<'a>(&'a WlShm); + +impl<'a> ProvidesBoundGlobal for ShmGlobal<'a> { + fn bound_global(&self) -> Result { + Ok(self.0.clone()) + } +} + // create wl_buffer from BufferSource (avoid create_immed?) // release #[derive(Debug, Clone)] @@ -342,6 +371,22 @@ pub struct SubsurfaceState { } impl SubsurfaceState { + pub fn create_surface(&self) -> WlSurface { + self + .wl_compositor + .create_surface(&self.qh, SurfaceData::new(None, 1)) + } + + pub fn update_surface_shm(&self, surface: &WlSurface, width: u32, height: u32, data: &[u8]) { + let shm = ShmGlobal(&self.wl_shm); + let mut pool = SlotPool::new(width as usize * height as usize * 4, &shm).unwrap(); + let (buffer, canvas) = pool.create_buffer(width as i32, height as i32, width as i32 * 4, wl_shm::Format::Argb8888).unwrap(); + canvas[0..width as usize * height as usize * 4].copy_from_slice(data); + surface.damage_buffer(0, 0, width as i32, height as i32); + buffer.attach_to(&surface); + surface.commit(); + } + fn create_subsurface(&self, parent: &WlSurface) -> SubsurfaceInstance { let wl_surface = self .wl_compositor @@ -572,6 +617,7 @@ impl Drop for SubsurfaceInstance { } } +#[derive(Debug)] pub(crate) struct SubsurfaceInfo { pub buffer: SubsurfaceBuffer, pub bounds: Rectangle, diff --git a/winit/src/program.rs b/winit/src/program.rs index c673106af1..f3b1ae9db7 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -796,6 +796,8 @@ async fn run_instance<'a, P, C>( let mut cur_dnd_surface: Option = None; + let mut dnd_surface: Option>> = None; + debug.startup_finished(); loop { // Empty the queue if possible @@ -964,9 +966,7 @@ async fn run_instance<'a, P, C>( scale_factor: state.scale_factor(), }, Default::default(), - ); - platform_specific_handler - .clear_subsurface_list(); + );; let mut bytes = compositor.screenshot( &mut renderer, &viewport, @@ -977,11 +977,25 @@ async fn run_instance<'a, P, C>( // rgba -> argb little endian pix.swap(0, 2); } - Icon::Buffer { - data: Arc::new(bytes), - width: viewport.physical_width(), - height: viewport.physical_height(), - transparent: true, + // update subsurfaces + if let Some(surface) = platform_specific_handler.create_surface() { + // TODO Remove id + let id = window::Id::unique(); + platform_specific_handler + .update_subsurfaces(id, &surface); + platform_specific_handler.update_surface_shm(&surface, viewport.physical_width(), viewport.physical_height(), &bytes); + let surface = Arc::new(surface); + dnd_surface = Some(surface.clone()); + Icon::Surface(dnd::DndSurface(surface)) + } else { + platform_specific_handler + .clear_subsurface_list(); + Icon::Buffer { + data: Arc::new(bytes), + width: viewport.physical_width(), + height: viewport.physical_height(), + transparent: true, + } } }, ); @@ -1191,7 +1205,7 @@ async fn run_instance<'a, P, C>( cursor, ); platform_specific_handler - .update_subsurfaces(id, window.raw.as_ref()); + .update_subsurfaces(id, window.raw.rwh_06_window_handle()); debug.draw_finished(); if new_mouse_interaction != window.mouse_interaction { @@ -1276,7 +1290,7 @@ async fn run_instance<'a, P, C>( window.state.cursor(), ); platform_specific_handler - .update_subsurfaces(id, window.raw.as_ref()); + .update_subsurfaces(id, window.raw.rwh_06_window_handle()); debug.draw_finished(); if new_mouse_interaction != window.mouse_interaction @@ -1723,7 +1737,13 @@ async fn run_instance<'a, P, C>( dnd::DndEvent::Offer(..) => { events.push((cur_dnd_surface, core::Event::Dnd(e))); } - dnd::DndEvent::Source(_) => { + dnd::DndEvent::Source(evt) => { + match evt { + dnd::SourceEvent::Finished | dnd::SourceEvent::Cancelled => { + dnd_surface = None; + } + _ => {} + } for w in window_manager.ids() { events.push((Some(w), core::Event::Dnd(e.clone()))); } From 46025551ce63d2ec23ca0bc461043ed0b71c8a7d Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 3 Jan 2025 12:55:55 -0800 Subject: [PATCH 058/116] Don't unnecessarily chain `.map()` The first `map()` call is unneded, and the second and third can be combined. This seems a bit clearer. --- winit/src/program.rs | 183 ++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 100 deletions(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index f3b1ae9db7..e4b0f12041 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -888,11 +888,7 @@ async fn run_instance<'a, P, C>( let state = &window.state; let icon_surface = icon_surface .map(|i| { - let i: Box = i; - i - }) - .map(|i| { - i.downcast::( >, core::widget::tree::State, )>>() - .unwrap() - }) - .map( - |e: Box< - Arc<( - core::Element< - 'static, - (), - P::Theme, - P::Renderer, - >, - core::widget::tree::State, - )>, - >| { - let mut renderer = compositor.create_renderer(); - - let e = Arc::into_inner(*e).unwrap(); - let (mut e, widget_state) = e; - let lim = core::layout::Limits::new( - Size::new(1., 1.), - Size::new( - state.viewport().physical_width() - as f32, - state.viewport().physical_height() - as f32, - ), - ); - - let mut tree = core::widget::Tree { - id: e.as_widget().id(), - tag: e.as_widget().tag(), - state: widget_state, - children: e.as_widget().children(), - }; - - let size = e - .as_widget() - .layout(&mut tree, &renderer, &lim); - e.as_widget_mut().diff(&mut tree); + .unwrap(); + let e = Arc::into_inner(*e).unwrap(); + let (mut e, widget_state) = e; + + let mut renderer = compositor.create_renderer(); + + let lim = core::layout::Limits::new( + Size::new(1., 1.), + Size::new( + state.viewport().physical_width() + as f32, + state.viewport().physical_height() + as f32, + ), + ); - let size = lim.resolve( - Length::Shrink, - Length::Shrink, - size.size(), - ); - let viewport = Viewport::with_logical_size( - size, - state.viewport().scale_factor(), - ); + let mut tree = core::widget::Tree { + id: e.as_widget().id(), + tag: e.as_widget().tag(), + state: widget_state, + children: e.as_widget().children(), + }; + + let size = e + .as_widget() + .layout(&mut tree, &renderer, &lim); + e.as_widget_mut().diff(&mut tree); + + let size = lim.resolve( + Length::Shrink, + Length::Shrink, + size.size(), + ); + let viewport = Viewport::with_logical_size( + size, + state.viewport().scale_factor(), + ); - let mut ui = UserInterface::build( - e, - size, - user_interface::Cache::default(), - &mut renderer, - ); - _ = ui.draw( - &mut renderer, - state.theme(), - &renderer::Style { - icon_color: state.icon_color(), - text_color: state.text_color(), - scale_factor: state.scale_factor(), - }, - Default::default(), - );; - let mut bytes = compositor.screenshot( - &mut renderer, - &viewport, - core::Color::TRANSPARENT, - &debug.overlay(), - ); - for pix in bytes.chunks_exact_mut(4) { - // rgba -> argb little endian - pix.swap(0, 2); - } - // update subsurfaces - if let Some(surface) = platform_specific_handler.create_surface() { - // TODO Remove id - let id = window::Id::unique(); - platform_specific_handler - .update_subsurfaces(id, &surface); - platform_specific_handler.update_surface_shm(&surface, viewport.physical_width(), viewport.physical_height(), &bytes); - let surface = Arc::new(surface); - dnd_surface = Some(surface.clone()); - Icon::Surface(dnd::DndSurface(surface)) - } else { - platform_specific_handler - .clear_subsurface_list(); - Icon::Buffer { - data: Arc::new(bytes), - width: viewport.physical_width(), - height: viewport.physical_height(), - transparent: true, - } + let mut ui = UserInterface::build( + e, + size, + user_interface::Cache::default(), + &mut renderer, + ); + _ = ui.draw( + &mut renderer, + state.theme(), + &renderer::Style { + icon_color: state.icon_color(), + text_color: state.text_color(), + scale_factor: state.scale_factor(), + }, + Default::default(), + );; + let mut bytes = compositor.screenshot( + &mut renderer, + &viewport, + core::Color::TRANSPARENT, + &debug.overlay(), + ); + for pix in bytes.chunks_exact_mut(4) { + // rgba -> argb little endian + pix.swap(0, 2); + } + // update subsurfaces + if let Some(surface) = platform_specific_handler.create_surface() { + // TODO Remove id + let id = window::Id::unique(); + platform_specific_handler + .update_subsurfaces(id, &surface); + platform_specific_handler.update_surface_shm(&surface, viewport.physical_width(), viewport.physical_height(), &bytes); + let surface = Arc::new(surface); + dnd_surface = Some(surface.clone()); + Icon::Surface(dnd::DndSurface(surface)) + } else { + platform_specific_handler + .clear_subsurface_list(); + Icon::Buffer { + data: Arc::new(bytes), + width: viewport.physical_width(), + height: viewport.physical_height(), + transparent: true, } - }, - ); + } + }); clipboard.start_dnd_winit( internal, From 9b52fb706deccaa3c27c064c82c737fbc374d11b Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 3 Jan 2025 13:16:13 -0800 Subject: [PATCH 059/116] Don't use `Box>` and `Arc::into_inner` unnecessarily --- core/src/clipboard.rs | 4 ++-- winit/src/program.rs | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs index 0197354f67..1ea53145e6 100644 --- a/core/src/clipboard.rs +++ b/core/src/clipboard.rs @@ -1,6 +1,6 @@ //! Access the clipboard. -use std::{any::Any, sync::Arc}; +use std::any::Any; use dnd::{DndAction, DndDestinationRectangle, DndSurface}; use mime::{self, AllowedMimeTypes, AsMimeTypes, ClipboardStoreData}; @@ -98,7 +98,7 @@ pub fn start_dnd( internal, source_surface, icon_surface.map(|i| { - let i: Box = Box::new(Arc::new(i)); + let i: Box = Box::new(i); i }), content, diff --git a/winit/src/program.rs b/winit/src/program.rs index e4b0f12041..367f550c0a 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -888,7 +888,7 @@ async fn run_instance<'a, P, C>( let state = &window.state; let icon_surface = icon_surface .map(|i| { - let e = i.downcast::( P::Renderer, >, core::widget::tree::State, - )>>() + )>() .unwrap(); - let e = Arc::into_inner(*e).unwrap(); - let (mut e, widget_state) = e; + let (mut e, widget_state) = *e; let mut renderer = compositor.create_renderer(); From b2fa62ecd416650d18679d9fae0649ef1c077dbb Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 3 Jan 2025 14:21:35 -0800 Subject: [PATCH 060/116] runtime: Remove `start_dnd` This is not currently used. And is impossible to (soundly) use, since it requires a `Send` bound, which `State` and `Element` lack. --- runtime/src/dnd.rs | 43 ------------------------------------------- winit/src/program.rs | 15 --------------- 2 files changed, 58 deletions(-) diff --git a/runtime/src/dnd.rs b/runtime/src/dnd.rs index 69c956f686..e6f2b2678a 100644 --- a/runtime/src/dnd.rs +++ b/runtime/src/dnd.rs @@ -17,19 +17,6 @@ pub enum DndAction { /// The rectangles to register. rectangles: Vec, }, - /// Start a Dnd operation. - StartDnd { - /// Whether the Dnd operation is internal. - internal: bool, - /// The source surface of the Dnd operation. - source_surface: Option, - /// The icon surface of the Dnd operation. - icon_surface: Option>, - /// The content of the Dnd operation. - content: Box, - /// The actions of the Dnd operation. - actions: dnd::DndAction, - }, /// End a Dnd operation. EndDnd, /// Peek the current Dnd operation. @@ -53,19 +40,6 @@ impl std::fmt::Debug for DndAction { .field("surface", surface) .field("rectangles", rectangles) .finish(), - Self::StartDnd { - internal, - source_surface, - icon_surface, - content: _, - actions, - } => f - .debug_struct("StartDnd") - .field("internal", internal) - .field("source_surface", source_surface) - .field("icon_surface", icon_surface) - .field("actions", actions) - .finish(), Self::EndDnd => f.write_str("EndDnd"), Self::PeekDnd(mime, _) => { f.debug_struct("PeekDnd").field("mime", mime).finish() @@ -99,23 +73,6 @@ pub fn register_dnd_destination( })) } -/// Start a Dnd operation. -pub fn start_dnd( - internal: bool, - source_surface: Option, - icon_surface: Option>, - content: Box, - actions: dnd::DndAction, -) -> Task { - task::effect(Action::Dnd(DndAction::StartDnd { - internal, - source_surface, - icon_surface, - content, - actions, - })) -} - /// End a Dnd operation. pub fn end_dnd() -> Task { task::effect(Action::Dnd(DndAction::EndDnd)) diff --git a/winit/src/program.rs b/winit/src/program.rs index 367f550c0a..28254304e9 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -2279,21 +2279,6 @@ where } => { clipboard.register_dnd_destination(surface, rectangles); } - iced_runtime::dnd::DndAction::StartDnd { - internal, - source_surface, - icon_surface, - content, - actions, - } => { - clipboard.start_dnd( - internal, - source_surface, - icon_surface.map(|d| d as Box), - content, - actions, - ); - } iced_runtime::dnd::DndAction::EndDnd => { clipboard.end_dnd(); } From 8960f88f1c90f09e5184ccd0c8a18c09c2c2ba8f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 3 Jan 2025 14:33:11 -0800 Subject: [PATCH 061/116] Define a `IconSurface` for drag and drop instead of tuple, box dyn This makes it easier to add a field, and makes the handling of upcasting/downcasting a more organized. --- core/src/clipboard.rs | 44 +++++++++++++++++++++++++++++++++++------- winit/src/clipboard.rs | 6 +++--- winit/src/program.rs | 26 ++++++++----------------- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs index 1ea53145e6..160ca46f07 100644 --- a/core/src/clipboard.rs +++ b/core/src/clipboard.rs @@ -7,6 +7,39 @@ use mime::{self, AllowedMimeTypes, AsMimeTypes, ClipboardStoreData}; use crate::{widget::tree::State, window, Element}; +#[derive(Debug)] +pub struct IconSurface { + pub element: E, + pub state: State, +} + +pub type DynIconSurface = IconSurface>; + +impl IconSurface> { + pub fn new(element: Element<'static, (), T, R>, state: State) -> Self { + Self { element, state } + } + + fn upcast(self) -> DynIconSurface { + IconSurface { + element: Box::new(self.element), + state: self.state, + } + } +} + +impl DynIconSurface { + /// Downcast `element` to concrete type `Element<(), T, R>` + /// + /// Panics if type doesn't match + pub fn downcast(self) -> IconSurface> { + IconSurface { + element: *self.element.downcast().expect("drag-and-drop icon surface has invalid element type"), + state: self.state, + } + } +} + /// A buffer for short-term storage and transfer within and between /// applications. pub trait Clipboard { @@ -53,7 +86,7 @@ pub trait Clipboard { &mut self, _internal: bool, _source_surface: Option, - _icon_surface: Option>, + _icon_surface: Option, _content: Box, _actions: DndAction, ) { @@ -86,21 +119,18 @@ pub enum Kind { /// Starts a DnD operation. /// icon surface is a tuple of the icon element and optionally the icon element state. -pub fn start_dnd( +pub fn start_dnd( clipboard: &mut dyn Clipboard, internal: bool, source_surface: Option, - icon_surface: Option<(Element<'static, M, T, R>, State)>, + icon_surface: Option>>, content: Box, actions: DndAction, ) { clipboard.start_dnd( internal, source_surface, - icon_surface.map(|i| { - let i: Box = Box::new(i); - i - }), + icon_surface.map(IconSurface::upcast), content, actions, ); diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 26381d2554..097e65ca1e 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -3,7 +3,7 @@ use std::sync::Mutex; use std::{any::Any, borrow::Cow}; -use crate::core::clipboard::DndSource; +use crate::core::clipboard::{DndSource, DynIconSurface}; use crate::core::clipboard::Kind; use std::sync::Arc; use winit::dpi::LogicalSize; @@ -26,7 +26,7 @@ pub struct Clipboard { pub(crate) struct StartDnd { pub(crate) internal: bool, pub(crate) source_surface: Option, - pub(crate) icon_surface: Option>, + pub(crate) icon_surface: Option, pub(crate) content: Box, pub(crate) actions: DndAction, } @@ -261,7 +261,7 @@ impl crate::core::Clipboard for Clipboard { &mut self, internal: bool, source_surface: Option, - icon_surface: Option>, + icon_surface: Option, content: Box, actions: DndAction, ) { diff --git a/winit/src/program.rs b/winit/src/program.rs index 28254304e9..c9de2e2bd5 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -888,17 +888,7 @@ async fn run_instance<'a, P, C>( let state = &window.state; let icon_surface = icon_surface .map(|i| { - let e = i.downcast::<( - core::Element< - 'static, - (), - P::Theme, - P::Renderer, - >, - core::widget::tree::State, - )>() - .unwrap(); - let (mut e, widget_state) = *e; + let mut icon_surface = i.downcast::(); let mut renderer = compositor.create_renderer(); @@ -913,16 +903,16 @@ async fn run_instance<'a, P, C>( ); let mut tree = core::widget::Tree { - id: e.as_widget().id(), - tag: e.as_widget().tag(), - state: widget_state, - children: e.as_widget().children(), + id: icon_surface.element.as_widget().id(), + tag: icon_surface.element.as_widget().tag(), + state: icon_surface.state, + children: icon_surface.element.as_widget().children(), }; - let size = e + let size = icon_surface.element .as_widget() .layout(&mut tree, &renderer, &lim); - e.as_widget_mut().diff(&mut tree); + icon_surface.element.as_widget_mut().diff(&mut tree); let size = lim.resolve( Length::Shrink, @@ -935,7 +925,7 @@ async fn run_instance<'a, P, C>( ); let mut ui = UserInterface::build( - e, + icon_surface.element, size, user_interface::Cache::default(), &mut renderer, From 5866623e294328e843df6ef78b5e01ea1ea1058a Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 3 Jan 2025 15:00:11 -0800 Subject: [PATCH 062/116] Add an `offset` to `IconSurface`, so icon can be offset from custor --- core/src/clipboard.rs | 9 ++++++--- winit/src/platform_specific/mod.rs | 6 +++--- winit/src/platform_specific/wayland/mod.rs | 6 +++--- winit/src/platform_specific/wayland/subsurface_widget.rs | 5 +++-- winit/src/program.rs | 8 +++++++- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs index 160ca46f07..4dc64c5842 100644 --- a/core/src/clipboard.rs +++ b/core/src/clipboard.rs @@ -5,25 +5,27 @@ use std::any::Any; use dnd::{DndAction, DndDestinationRectangle, DndSurface}; use mime::{self, AllowedMimeTypes, AsMimeTypes, ClipboardStoreData}; -use crate::{widget::tree::State, window, Element}; +use crate::{widget::tree::State, window, Element, Vector}; #[derive(Debug)] pub struct IconSurface { pub element: E, pub state: State, + pub offset: Vector, } pub type DynIconSurface = IconSurface>; impl IconSurface> { - pub fn new(element: Element<'static, (), T, R>, state: State) -> Self { - Self { element, state } + pub fn new(element: Element<'static, (), T, R>, state: State, offset: Vector) -> Self { + Self { element, state, offset } } fn upcast(self) -> DynIconSurface { IconSurface { element: Box::new(self.element), state: self.state, + offset: self.offset, } } } @@ -36,6 +38,7 @@ impl DynIconSurface { IconSurface { element: *self.element.downcast().expect("drag-and-drop icon surface has invalid element type"), state: self.state, + offset: self.offset, } } } diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index 5490bfac77..b3f516c3bd 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use iced_graphics::Compositor; -use iced_runtime::{core::window, user_interface, Debug}; +use iced_runtime::{core::{window, Vector}, user_interface, Debug}; use raw_window_handle::HasWindowHandle; #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -128,10 +128,10 @@ impl PlatformSpecific { None } - pub(crate) fn update_surface_shm(&mut self, surface: &dyn HasWindowHandle, width: u32, height: u32, data: &[u8]) { + pub(crate) fn update_surface_shm(&mut self, surface: &dyn HasWindowHandle, width: u32, height: u32, data: &[u8], offset: Vector) { #[cfg(all(feature = "wayland", target_os = "linux"))] { - return self.wayland.update_surface_shm(surface, width, height, data); + return self.wayland.update_surface_shm(surface, width, height, data, offset); } } } diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 1b3b8cf5b8..267e501a71 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -15,7 +15,7 @@ use cctk::sctk::reexports::client::protocol::wl_surface::WlSurface; use cctk::sctk::seat::keyboard::Modifiers; use iced_futures::futures::channel::mpsc; use iced_graphics::Compositor; -use iced_runtime::core::window; +use iced_runtime::core::{window, Vector}; use iced_runtime::Debug; use raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle}; use raw_window_handle::{HasRawDisplayHandle, RawWindowHandle}; @@ -239,12 +239,12 @@ impl WaylandSpecific { } } - pub(crate) fn update_surface_shm(&mut self, window: &dyn HasWindowHandle, width: u32, height: u32, data: &[u8]) { + pub(crate) fn update_surface_shm(&mut self, window: &dyn HasWindowHandle, width: u32, height: u32, data: &[u8], offset: Vector) { if let Some(subsurface_state) = self.subsurface_state.as_mut() { if let RawWindowHandle::Wayland(window) = window.window_handle().unwrap().as_raw() { let id = unsafe { ObjectId::from_ptr(WlSurface::interface(), window.surface.as_ptr().cast()).unwrap() }; let surface = WlSurface::from_id(self.conn.as_ref().unwrap(), id).unwrap(); - subsurface_state.update_surface_shm(&surface, width, height, data); + subsurface_state.update_surface_shm(&surface, width, height, data, offset); } } } diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 5cde0e96bb..18730bce58 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -4,7 +4,7 @@ use crate::core::{ layout::{self, Layout}, mouse, renderer, widget::{self, Widget}, - ContentFit, Element, Length, Rectangle, Size, + ContentFit, Element, Length, Rectangle, Size, Vector, }; use std::{ cell::RefCell, @@ -377,13 +377,14 @@ impl SubsurfaceState { .create_surface(&self.qh, SurfaceData::new(None, 1)) } - pub fn update_surface_shm(&self, surface: &WlSurface, width: u32, height: u32, data: &[u8]) { + pub fn update_surface_shm(&self, surface: &WlSurface, width: u32, height: u32, data: &[u8], offset: Vector) { let shm = ShmGlobal(&self.wl_shm); let mut pool = SlotPool::new(width as usize * height as usize * 4, &shm).unwrap(); let (buffer, canvas) = pool.create_buffer(width as i32, height as i32, width as i32 * 4, wl_shm::Format::Argb8888).unwrap(); canvas[0..width as usize * height as usize * 4].copy_from_slice(data); surface.damage_buffer(0, 0, width as i32, height as i32); buffer.attach_to(&surface); + surface.offset(offset.x as i32, offset.y as i32); surface.commit(); } diff --git a/winit/src/program.rs b/winit/src/program.rs index c9de2e2bd5..f61d32f693 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -886,6 +886,7 @@ async fn run_instance<'a, P, C>( }; let state = &window.state; + let mut dnd_buffer = None; let icon_surface = icon_surface .map(|i| { let mut icon_surface = i.downcast::(); @@ -956,9 +957,9 @@ async fn run_instance<'a, P, C>( let id = window::Id::unique(); platform_specific_handler .update_subsurfaces(id, &surface); - platform_specific_handler.update_surface_shm(&surface, viewport.physical_width(), viewport.physical_height(), &bytes); let surface = Arc::new(surface); dnd_surface = Some(surface.clone()); + dnd_buffer = Some((viewport.physical_size(), bytes, icon_surface.offset)); Icon::Surface(dnd::DndSurface(surface)) } else { platform_specific_handler @@ -979,6 +980,11 @@ async fn run_instance<'a, P, C>( content, actions, ); + + // This needs to be after `wl_data_device::start_drag` for the offset to have an effect + if let (Some(surface), Some((size, bytes, offset))) = (dnd_surface.as_ref(), dnd_buffer) { + platform_specific_handler.update_surface_shm(&surface, size.width, size.height, &bytes, offset); + } } } Event::WindowCreated { From a4fdb59f19bdb781f3abf32e118d130b2ca8e210 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 7 Jan 2025 13:52:08 -0800 Subject: [PATCH 063/116] winit/wayland: Apply scale when creating drag surface --- winit/src/platform_specific/mod.rs | 4 ++-- winit/src/platform_specific/wayland/mod.rs | 4 ++-- winit/src/platform_specific/wayland/subsurface_widget.rs | 9 ++++++++- winit/src/program.rs | 6 +++--- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index b3f516c3bd..77e851802c 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -128,10 +128,10 @@ impl PlatformSpecific { None } - pub(crate) fn update_surface_shm(&mut self, surface: &dyn HasWindowHandle, width: u32, height: u32, data: &[u8], offset: Vector) { + pub(crate) fn update_surface_shm(&mut self, surface: &dyn HasWindowHandle, width: u32, height: u32, scale: f64, data: &[u8], offset: Vector) { #[cfg(all(feature = "wayland", target_os = "linux"))] { - return self.wayland.update_surface_shm(surface, width, height, data, offset); + return self.wayland.update_surface_shm(surface, width, height, scale, data, offset); } } } diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 267e501a71..16c316c0a7 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -239,12 +239,12 @@ impl WaylandSpecific { } } - pub(crate) fn update_surface_shm(&mut self, window: &dyn HasWindowHandle, width: u32, height: u32, data: &[u8], offset: Vector) { + pub(crate) fn update_surface_shm(&mut self, window: &dyn HasWindowHandle, width: u32, height: u32, scale: f64, data: &[u8], offset: Vector) { if let Some(subsurface_state) = self.subsurface_state.as_mut() { if let RawWindowHandle::Wayland(window) = window.window_handle().unwrap().as_raw() { let id = unsafe { ObjectId::from_ptr(WlSurface::interface(), window.surface.as_ptr().cast()).unwrap() }; let surface = WlSurface::from_id(self.conn.as_ref().unwrap(), id).unwrap(); - subsurface_state.update_surface_shm(&surface, width, height, data, offset); + subsurface_state.update_surface_shm(&surface, width, height, scale, data, offset); } } } diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 18730bce58..288dbb6106 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -377,7 +377,12 @@ impl SubsurfaceState { .create_surface(&self.qh, SurfaceData::new(None, 1)) } - pub fn update_surface_shm(&self, surface: &WlSurface, width: u32, height: u32, data: &[u8], offset: Vector) { + pub fn update_surface_shm(&self, surface: &WlSurface, width: u32, height: u32, scale: f64, data: &[u8], offset: Vector) { + let wp_viewport = self.wp_viewporter.get_viewport( + &surface, + &self.qh, + cctk::sctk::globals::GlobalData, + ); let shm = ShmGlobal(&self.wl_shm); let mut pool = SlotPool::new(width as usize * height as usize * 4, &shm).unwrap(); let (buffer, canvas) = pool.create_buffer(width as i32, height as i32, width as i32 * 4, wl_shm::Format::Argb8888).unwrap(); @@ -385,7 +390,9 @@ impl SubsurfaceState { surface.damage_buffer(0, 0, width as i32, height as i32); buffer.attach_to(&surface); surface.offset(offset.x as i32, offset.y as i32); + wp_viewport.set_destination((width as f64 / scale) as i32, (height as f64 / scale) as i32); surface.commit(); + wp_viewport.destroy(); } fn create_subsurface(&self, parent: &WlSurface) -> SubsurfaceInstance { diff --git a/winit/src/program.rs b/winit/src/program.rs index f61d32f693..1640702925 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -959,7 +959,7 @@ async fn run_instance<'a, P, C>( .update_subsurfaces(id, &surface); let surface = Arc::new(surface); dnd_surface = Some(surface.clone()); - dnd_buffer = Some((viewport.physical_size(), bytes, icon_surface.offset)); + dnd_buffer = Some((viewport.physical_size(), state.scale_factor(), bytes, icon_surface.offset)); Icon::Surface(dnd::DndSurface(surface)) } else { platform_specific_handler @@ -982,8 +982,8 @@ async fn run_instance<'a, P, C>( ); // This needs to be after `wl_data_device::start_drag` for the offset to have an effect - if let (Some(surface), Some((size, bytes, offset))) = (dnd_surface.as_ref(), dnd_buffer) { - platform_specific_handler.update_surface_shm(&surface, size.width, size.height, &bytes, offset); + if let (Some(surface), Some((size, scale, bytes, offset))) = (dnd_surface.as_ref(), dnd_buffer) { + platform_specific_handler.update_surface_shm(&surface, size.width, size.height, scale, &bytes, offset); } } } From 62fda6ff1028c1f08f32bebd937fe7b801528c89 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 13 Jan 2025 09:25:31 -0800 Subject: [PATCH 064/116] winit: Fix building without `wayland` feature --- winit/src/platform_specific/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index 77e851802c..0c07ef6871 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use iced_graphics::Compositor; use iced_runtime::{core::{window, Vector}, user_interface, Debug}; -use raw_window_handle::HasWindowHandle; +use winit::raw_window_handle::HasWindowHandle; #[cfg(all(feature = "wayland", target_os = "linux"))] pub mod wayland; From 1c9a7688981d6639e2c8bb5d371cffb69d56ecd7 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 14 Jan 2025 21:19:43 +0100 Subject: [PATCH 065/116] chore: update image to 0.25 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f497776c1d..9cfd0bd54f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ a11y = [ winit = ["iced_winit", "iced_accessibility?/accesskit_winit"] -# Enables the sctk shell. +# Enables the sctk shell. wayland = ["iced_widget/wayland", "iced_core/wayland", "iced_winit/wayland"] [dependencies] iced_core.workspace = true @@ -183,7 +183,7 @@ resvg = "0.42" web-sys = "0.3.69" guillotiere = "0.6" half = "2.2" -image = { version = "0.24", default-features = false } +image = { version = "0.25", default-features = false } kamadak-exif = "0.5" kurbo = "0.10" log = "0.4" From 953e84d270f701cca7e3d47520ecb2c3bce0c4c9 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 28 Jan 2025 11:50:44 -0800 Subject: [PATCH 066/116] mouse_area: Fix `on_drag` behavior without `on_release` Mouse/touch release events should end the drag even if there's not an `on_release` callback. This was causing https://github.com/pop-os/cosmic-comp/issues/1071. --- widget/src/mouse_area.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 1ce5001858..6e894c618b 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -506,11 +506,11 @@ fn update( } } - if let Some(message) = widget.on_release.as_ref() { - if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) = event - { - state.drag_initiated = None; + if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) = event + { + state.drag_initiated = None; + if let Some(message) = widget.on_release.as_ref() { shell.publish(message.clone()); return event::Status::Captured; From 596a506b5b2861231989f3ee7bb514ba2c1c6412 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 4 Feb 2025 10:39:22 -0800 Subject: [PATCH 067/116] Update `cctk` to latest commit --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9cfd0bd54f..3576788130 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -197,7 +197,7 @@ pulldown-cmark = "0.11" qrcode = { version = "0.13", default-features = false } raw-window-handle = "0.6" rustc-hash = "2.0" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d218c76" } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "29ab323" } smol = "1.0" smol_str = "0.2" softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } From 2d129a8fbc592f76184e608819145bd9bd3dff2c Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 10 Feb 2025 14:22:06 -0800 Subject: [PATCH 068/116] winit/wayland: Update for toplevel-info cctk changes --- Cargo.toml | 2 +- winit/src/platform_specific/wayland/handlers/toplevel.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3576788130..13d80349d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -197,7 +197,7 @@ pulldown-cmark = "0.11" qrcode = { version = "0.13", default-features = false } raw-window-handle = "0.6" rustc-hash = "2.0" -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "29ab323" } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b" } smol = "1.0" smol_str = "0.2" softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } diff --git a/winit/src/platform_specific/wayland/handlers/toplevel.rs b/winit/src/platform_specific/wayland/handlers/toplevel.rs index f9146987d9..6ff1c3e0e5 100644 --- a/winit/src/platform_specific/wayland/handlers/toplevel.rs +++ b/winit/src/platform_specific/wayland/handlers/toplevel.rs @@ -1,6 +1,5 @@ use cctk::{ cosmic_protocols::{ - toplevel_info::v1::client::zcosmic_toplevel_handle_v1, toplevel_management::v1::client::zcosmic_toplevel_manager_v1, }, toplevel_info::{ToplevelInfoHandler, ToplevelInfoState}, @@ -8,6 +7,7 @@ use cctk::{ wayland_client::{self, WEnum}, }; use wayland_client::{Connection, QueueHandle}; +use wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1; use crate::event_loop::state::SctkState; @@ -39,7 +39,7 @@ impl ToplevelInfoHandler for SctkState { &mut self, _conn: &Connection, _qh: &QueueHandle, - _toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + _toplevel: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ) { // TODO } @@ -48,7 +48,7 @@ impl ToplevelInfoHandler for SctkState { &mut self, _conn: &Connection, _qh: &QueueHandle, - _toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + _toplevel: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ) { // TODO } @@ -57,7 +57,7 @@ impl ToplevelInfoHandler for SctkState { &mut self, _conn: &Connection, _qh: &QueueHandle, - _toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, + _toplevel: &ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ) { // TODO } From 170b899547fb9f8d068cf1569cd2957d61f42857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 11 Nov 2024 11:05:46 +0100 Subject: [PATCH 069/116] Add focus id to text editor --- widget/src/text_editor.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a298252a6c..c024a833cc 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -50,6 +50,7 @@ use crate::core::{ Background, Border, Color, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector, }; +use crate::runtime::{task, Action as RuntimeAction, Task}; use std::cell::RefCell; use std::fmt; @@ -58,6 +59,16 @@ use std::sync::Arc; pub use text::editor::{Action, Edit, Motion}; +/// The identifier of a [`TextEditor`]. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(widget::Id); + +impl From for Id { + fn from(value: widget::Id) -> Self { + Id(value) + } +} + /// A multi-line text input. /// /// # Example @@ -103,6 +114,7 @@ pub struct TextEditor< Theme: Catalog, Renderer: text::Renderer, { + id: Option, content: &'a Content, placeholder: Option>, font: Option, @@ -131,6 +143,7 @@ where /// Creates new [`TextEditor`] with the given [`Content`]. pub fn new(content: &'a Content) -> Self { Self { + id: None, content, placeholder: None, font: None, @@ -149,6 +162,18 @@ where }, } } + + pub fn id(mut self, id: impl Into) -> Self { + self.id = Some(id.into()); + self + } +} + +/// Produces a [`Task`] that focuses the [`TextEditor`] with the given [`Id`]. +pub fn focus(id: impl Into) -> Task { + task::effect(RuntimeAction::widget(operation::focusable::focus( + id.into().0, + ))) } impl<'a, Highlighter, Message, Theme, Renderer> @@ -256,6 +281,7 @@ where ) -> highlighter::Format, ) -> TextEditor<'a, H, Message, Theme, Renderer> { TextEditor { + id: self.id, content: self.content, placeholder: self.placeholder, font: self.font, @@ -958,7 +984,14 @@ where ) { let state = tree.state.downcast_mut::>(); - operation.focusable(state, None); + operation.focusable(state, self.id.as_ref().map(|id| &id.0)); + } + + fn id(&self) -> Option { + self.id.as_ref().map(|id| id.0.clone()) + } + fn set_id(&mut self, id: widget::Id) { + self.id = Some(Id(id)); } } From 060ddf4cf625d193e4bf4740b0fc989fb36e2792 Mon Sep 17 00:00:00 2001 From: Adam Cosner <160804448+Adam-Cosner@users.noreply.github.com> Date: Thu, 20 Feb 2025 04:34:39 -0800 Subject: [PATCH 070/116] perf: use cached alpha mode instead of calling surface.get_capabilities every time the window is redrawn --- wgpu/src/window/compositor.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 04982afe00..07399ba1d1 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -402,21 +402,6 @@ impl graphics::Compositor for Compositor { width: u32, height: u32, ) { - let caps = surface.get_capabilities(&self.adapter); - let alpha_mode = if caps - .alpha_modes - .contains(&wgpu::CompositeAlphaMode::PostMultiplied) - { - wgpu::CompositeAlphaMode::PostMultiplied - } else if caps - .alpha_modes - .contains(&wgpu::CompositeAlphaMode::PreMultiplied) - { - wgpu::CompositeAlphaMode::PreMultiplied - } else { - wgpu::CompositeAlphaMode::Auto - }; - surface.configure( &self.device, &wgpu::SurfaceConfiguration { @@ -425,7 +410,7 @@ impl graphics::Compositor for Compositor { present_mode: self.settings.present_mode, width, height, - alpha_mode, + alpha_mode: self.alpha_mode, view_formats: vec![], desired_maximum_frame_latency: 1, }, From 68c6b0387bcc2e93eae09bac22c4646e704c4479 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:16:25 -0400 Subject: [PATCH 071/116] feat: subsurfaces --- core/src/clipboard.rs | 21 +- core/src/event/wayland/mod.rs | 4 + core/src/event/wayland/subsurface.rs | 8 + examples/multi_window/src/main.rs | 1 + examples/sctk_subsurface/Cargo.toml | 2 +- examples/sctk_subsurface/src/main.rs | 143 +++-- .../src/subsurface_container.rs | 430 +++++++++++++++ examples/sctk_subsurface/src/wayland.rs | 33 +- .../wayland/layer_surface.rs | 2 +- runtime/src/platform_specific/wayland/mod.rs | 8 + .../src/platform_specific/wayland/popup.rs | 14 +- .../platform_specific/wayland/subsurface.rs | 77 +++ winit/src/clipboard.rs | 2 +- winit/src/platform_specific/mod.rs | 27 +- .../platform_specific/wayland/commands/mod.rs | 1 + .../wayland/commands/session_lock.rs | 2 +- .../wayland/commands/subsurface.rs | 28 + .../platform_specific/wayland/conversion.rs | 44 -- .../wayland/event_loop/mod.rs | 115 +++- .../wayland/event_loop/proxy.rs | 2 +- .../wayland/event_loop/state.rs | 511 ++++++++++++++++-- .../wayland/handlers/activation.rs | 2 +- .../wayland/handlers/seat/keyboard.rs | 182 ++++--- .../wayland/handlers/seat/pointer.rs | 3 + .../wayland/handlers/seat/touch.rs | 2 +- .../wayland/handlers/session_lock.rs | 21 +- .../wayland/handlers/shell/xdg_popup.rs | 4 + .../wayland/handlers/toplevel.rs | 4 +- winit/src/platform_specific/wayland/mod.rs | 66 ++- .../platform_specific/wayland/sctk_event.rs | 342 +++++++++++- .../wayland/subsurface_widget.rs | 162 +++++- .../platform_specific/wayland/winit_window.rs | 39 +- winit/src/program.rs | 255 ++++++--- winit/src/program/window_manager.rs | 22 +- 34 files changed, 2178 insertions(+), 401 deletions(-) create mode 100644 core/src/event/wayland/subsurface.rs create mode 100644 examples/sctk_subsurface/src/subsurface_container.rs create mode 100644 runtime/src/platform_specific/wayland/subsurface.rs create mode 100644 winit/src/platform_specific/wayland/commands/subsurface.rs diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs index 4dc64c5842..daa03c94d3 100644 --- a/core/src/clipboard.rs +++ b/core/src/clipboard.rs @@ -17,8 +17,16 @@ pub struct IconSurface { pub type DynIconSurface = IconSurface>; impl IconSurface> { - pub fn new(element: Element<'static, (), T, R>, state: State, offset: Vector) -> Self { - Self { element, state, offset } + pub fn new( + element: Element<'static, (), T, R>, + state: State, + offset: Vector, + ) -> Self { + Self { + element, + state, + offset, + } } fn upcast(self) -> DynIconSurface { @@ -34,9 +42,14 @@ impl DynIconSurface { /// Downcast `element` to concrete type `Element<(), T, R>` /// /// Panics if type doesn't match - pub fn downcast(self) -> IconSurface> { + pub fn downcast( + self, + ) -> IconSurface> { IconSurface { - element: *self.element.downcast().expect("drag-and-drop icon surface has invalid element type"), + element: *self + .element + .downcast() + .expect("drag-and-drop icon surface has invalid element type"), state: self.state, offset: self.offset, } diff --git a/core/src/event/wayland/mod.rs b/core/src/event/wayland/mod.rs index 5fabc86005..2bfae76ae1 100644 --- a/core/src/event/wayland/mod.rs +++ b/core/src/event/wayland/mod.rs @@ -4,6 +4,7 @@ mod overlap_notify; mod popup; mod seat; mod session_lock; +mod subsurface; mod window; use crate::{time::Instant, window::Id}; @@ -17,6 +18,7 @@ pub use overlap_notify::*; pub use popup::*; pub use seat::*; pub use session_lock::*; +pub use subsurface::*; pub use window::*; /// wayland events @@ -40,4 +42,6 @@ pub enum Event { Frame(Instant, WlSurface, Id), /// Request Resize RequestResize, + /// Subsurface + Subsurface(SubsurfaceEvent), } diff --git a/core/src/event/wayland/subsurface.rs b/core/src/event/wayland/subsurface.rs new file mode 100644 index 0000000000..ea42f541af --- /dev/null +++ b/core/src/event/wayland/subsurface.rs @@ -0,0 +1,8 @@ +/// popup events +#[derive(Debug, Clone, PartialEq)] +pub enum SubsurfaceEvent { + /// Destroyed + Destroyed, + /// repositioned, + Created, +} diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index 33fadea9db..f49a6b3d13 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -167,6 +167,7 @@ impl Window { scale_input: "1.0".to_string(), current_scale: 1.0, theme: Theme::ALL[count % Theme::ALL.len()].clone(), + input_id: text_input::Id::unique(), } } diff --git a/examples/sctk_subsurface/Cargo.toml b/examples/sctk_subsurface/Cargo.toml index 126bc28350..c2d694c9c1 100644 --- a/examples/sctk_subsurface/Cargo.toml +++ b/examples/sctk_subsurface/Cargo.toml @@ -15,6 +15,6 @@ iced = { path = "../..", default-features = false, features = [ iced_runtime = { path = "../../runtime" } env_logger = "0.10" futures-channel = "0.3.29" -calloop = "0.12.3" +calloop = "0.13" rustix = { version = "0.38.30", features = ["fs", "shm"] } cctk.workspace = true diff --git a/examples/sctk_subsurface/src/main.rs b/examples/sctk_subsurface/src/main.rs index 9d31e08a94..94380e9dca 100644 --- a/examples/sctk_subsurface/src/main.rs +++ b/examples/sctk_subsurface/src/main.rs @@ -1,14 +1,24 @@ // Shows a subsurface with a 1x1 px red buffer, stretch to window size +use cctk::sctk::reexports::{ + client::{Connection, Proxy}, + protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}, +}; + +use iced::platform_specific::shell::commands::subsurface::get_subsurface; use iced::{ event::wayland::Event as WaylandEvent, - platform_specific::shell::subsurface_widget::{self, SubsurfaceBuffer}, - widget::text, + platform_specific::{ + runtime::wayland::subsurface::SctkSubsurfaceSettings, + shell::subsurface_widget::{self, SubsurfaceBuffer}, + }, + widget::{button, column, text, text_input}, window::{self, Id, Settings}, Element, Length, Subscription, Task, }; -use cctk::sctk::reexports::client::{Connection, Proxy}; +use std::sync::{Arc, Mutex}; +mod subsurface_container; mod wayland; fn main() -> iced::Result { @@ -23,8 +33,11 @@ fn main() -> iced::Result { #[derive(Debug, Clone, Default)] struct SubsurfaceApp { + text: Arc>, + counter: Arc>, connection: Option, red_buffer: Option, + green_buffer: Option, } #[derive(Debug, Clone)] @@ -33,6 +46,8 @@ pub enum Message { Wayland(wayland::Event), Pressed(&'static str), Id(Id), + Inc, + Text(String), } impl SubsurfaceApp { @@ -55,47 +70,106 @@ impl SubsurfaceApp { fn update(&mut self, message: Message) -> Task { match message { - Message::WaylandEvent(evt) => match evt { - WaylandEvent::Output(_evt, output) => { - if self.connection.is_none() { - if let Some(backend) = output.backend().upgrade() { - self.connection = - Some(Connection::from_backend(backend)); + Message::WaylandEvent(evt) => { + dbg!(&evt); + match evt { + WaylandEvent::Output(_evt, output) => { + if self.connection.is_none() { + if let Some(backend) = output.backend().upgrade() { + self.connection = + Some(Connection::from_backend(backend)); + } } } + _ => {} } - _ => {} - }, + } Message::Wayland(evt) => match evt { wayland::Event::RedBuffer(buffer) => { self.red_buffer = Some(buffer); } + wayland::Event::GreenBuffer(buffer) => { + self.green_buffer = Some(buffer); + } }, Message::Pressed(side) => println!("{side} surface pressed"), - Message::Id(_) => {} + Message::Id(id) => { + let my_text = self.text.clone(); + let my_counter = self.counter.clone(); + return get_subsurface(SctkSubsurfaceSettings { + id: window::Id::unique(), + parent: id, + loc: iced::Point::new(100., 200.), + size: Some(iced::Size::new(100., 100.)), + z: 1000, + steal_keyboard_focus: false, + gravity: Gravity::BottomRight, + input_zone: None, + offset: (0, 0), + }); + } + Message::Inc => { + let mut guard = self.counter.lock().unwrap(); + + *guard += 1; + } + Message::Text(s) => { + let mut guard = self.text.lock().unwrap(); + *guard = s; + } } Task::none() } - fn view(&self, _id: window::Id) -> Element { - if let Some(buffer) = &self.red_buffer { - iced::widget::row![ - iced::widget::button( - subsurface_widget::Subsurface::new(1, 1, buffer) - .width(Length::Fill) - .height(Length::Fill) - ) - .width(Length::Fill) - .height(Length::Fill) - .on_press(Message::Pressed("left")), - iced::widget::button( - subsurface_widget::Subsurface::new(1, 1, buffer) - .width(Length::Fill) - .height(Length::Fill) - ) - .width(Length::Fill) - .height(Length::Fill) - .on_press(Message::Pressed("right")) + fn view(&self, id: window::Id) -> Element { + let my_text_guard = self.text.lock().unwrap(); + if let Some((red_buffer, green_buffer)) = + self.red_buffer.iter().zip(self.green_buffer.iter()).next() + { + column![ + iced::widget::row![ + iced::widget::button( + subsurface_container::SubsurfaceContainer::new() + .width(Length::Fill) + .height(Length::Fill) + .push( + subsurface_widget::Subsurface::new( + red_buffer.clone() + ) + .width(Length::Fill) + .height(Length::Fill) + .z(0) + ) + .push( + subsurface_widget::Subsurface::new( + green_buffer.clone() + ) + .width(Length::Fixed(1920.)) + .height(Length::Fixed(200.)) + .z(1) + ) + .push( + subsurface_widget::Subsurface::new( + red_buffer.clone() + ) + .width(Length::Fill) + .height(Length::Fixed(100.)) + .z(2) + ) + ) + .width(Length::Fill) + .height(Length::Fill) + .on_press(Message::Pressed("left")), + iced::widget::button( + subsurface_widget::Subsurface::new(red_buffer.clone()) + .width(Length::Fill) + .height(Length::Fill) + ) + .width(Length::Fill) + .height(Length::Fill) + .on_press(Message::Pressed("right")) + ], + text_input("asdf", &my_text_guard).on_input(Message::Text) ] .into() } else { @@ -106,10 +180,13 @@ impl SubsurfaceApp { fn subscription(&self) -> Subscription { let mut subscriptions = vec![iced::event::listen_with(|evt, _, _| { if let iced::Event::PlatformSpecific( - iced::event::PlatformSpecific::Wayland(evt), + iced::event::PlatformSpecific::Wayland(WaylandEvent::Output( + evt, + output, + )), ) = evt { - Some(Message::WaylandEvent(evt)) + Some(Message::WaylandEvent(WaylandEvent::Output(evt, output))) } else { None } diff --git a/examples/sctk_subsurface/src/subsurface_container.rs b/examples/sctk_subsurface/src/subsurface_container.rs new file mode 100644 index 0000000000..d4d5398dfe --- /dev/null +++ b/examples/sctk_subsurface/src/subsurface_container.rs @@ -0,0 +1,430 @@ +//! Distribute content vertically. +use iced::core::alignment::{self, Alignment}; +use iced::core::event::{self, Event}; +use iced::core::layout; +use iced::core::mouse; +use iced::core::overlay; +use iced::core::renderer; +use iced::core::widget::{Operation, Tree}; +use iced::core::{ + Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, + Size, Vector, Widget, +}; + +/// A container that distributes its contents vertically. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } } +/// # pub type State = (); +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// use iced::widget::{button, SubsurfaceContainer}; +/// +/// #[derive(Debug, Clone)] +/// enum Message { +/// // ... +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// SubsurfaceContainer![ +/// "I am on top!", +/// button("I am in the center!"), +/// "I am below.", +/// ].into() +/// } +/// ``` +#[allow(missing_debug_implementations)] +pub struct SubsurfaceContainer< + 'a, + Message, + Theme = iced::Theme, + Renderer = iced::Renderer, +> { + spacing: f32, + padding: Padding, + width: Length, + height: Length, + max_width: f32, + align: Alignment, + clip: bool, + children: Vec>, +} + +impl<'a, Message, Theme, Renderer> + SubsurfaceContainer<'a, Message, Theme, Renderer> +where + Renderer: iced::core::Renderer, +{ + /// Creates an empty [`SubsurfaceContainer`]. + pub fn new() -> Self { + Self::from_vec(Vec::new()) + } + + /// Creates a [`SubsurfaceContainer`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_vec(Vec::with_capacity(capacity)) + } + + /// Creates a [`SubsurfaceContainer`] with the given elements. + pub fn with_children( + children: impl IntoIterator>, + ) -> Self { + let iterator = children.into_iter(); + + Self::with_capacity(iterator.size_hint().0).extend(iterator) + } + + /// Creates a [`SubsurfaceContainer`] from an already allocated [`Vec`]. + /// + /// Keep in mind that the [`SubsurfaceContainer`] will not inspect the [`Vec`], which means + /// it won't automatically adapt to the sizing strategy of its contents. + /// + /// If any of the children have a [`Length::Fill`] strategy, you will need to + /// call [`SubsurfaceContainer::width`] or [`SubsurfaceContainer::height`] accordingly. + pub fn from_vec( + children: Vec>, + ) -> Self { + Self { + spacing: 0.0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + max_width: f32::INFINITY, + align: Alignment::Start, + clip: false, + children, + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } + + /// Sets the [`Padding`] of the [`SubsurfaceContainer`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the width of the [`SubsurfaceContainer`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`SubsurfaceContainer`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Sets the maximum width of the [`SubsurfaceContainer`]. + pub fn max_width(mut self, max_width: impl Into) -> Self { + self.max_width = max_width.into().0; + self + } + + /// Sets the horizontal alignment of the contents of the [`SubsurfaceContainer`] . + pub fn align_x(mut self, align: impl Into) -> Self { + self.align = Alignment::from(align.into()); + self + } + + /// Sets whether the contents of the [`SubsurfaceContainer`] should be clipped on + /// overflow. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self + } + + /// Adds an element to the [`SubsurfaceContainer`]. + pub fn push( + mut self, + child: impl Into>, + ) -> Self { + let child = child.into(); + let child_size = child.as_widget().size_hint(); + + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); + + self.children.push(child); + self + } + + /// Adds an element to the [`SubsurfaceContainer`], if `Some`. + pub fn push_maybe( + self, + child: Option>>, + ) -> Self { + if let Some(child) = child { + self.push(child) + } else { + self + } + } + + /// Extends the [`SubsurfaceContainer`] with the given children. + pub fn extend( + self, + children: impl IntoIterator>, + ) -> Self { + children.into_iter().fold(self, Self::push) + } +} + +impl<'a, Message, Renderer> Default + for SubsurfaceContainer<'a, Message, Renderer> +where + Renderer: iced::core::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +impl<'a, Message, Theme, Renderer: iced::core::Renderer> + FromIterator> + for SubsurfaceContainer<'a, Message, Theme, Renderer> +{ + fn from_iter< + T: IntoIterator>, + >( + iter: T, + ) -> Self { + Self::with_children(iter) + } +} + +impl<'a, Message, Theme, Renderer> Widget + for SubsurfaceContainer<'a, Message, Theme, Renderer> +where + Renderer: iced::core::Renderer, +{ + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(self.children.as_mut_slice()); + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.max_width(self.max_width); + let nodes = self + .children + .iter() + .zip(tree.children.iter_mut()) + .map(|c| { + let size = c.0.as_widget().size(); + layout::positioned( + &limits.max_width(self.max_width), + size.width, + size.height, + self.padding, + |limits| c.0.as_widget().layout(c.1, renderer, limits), + |content, size| { + content.align(self.align, Alignment::Start, size) + }, + ) + }) + .collect(); + + let size = limits.resolve(self.width, self.height, Size::ZERO); + + layout::Node::with_children(size, nodes) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container(None, layout.bounds(), &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), layout)| { + child + .as_widget() + .operate(state, layout, renderer, operation); + }); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + self.children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget().mouse_interaction( + state, layout, cursor, viewport, renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + if let Some(clipped_viewport) = layout.bounds().intersection(viewport) { + let viewport = if self.clip { + &clipped_viewport + } else { + viewport + }; + + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .filter(|(_, layout)| layout.bounds().intersects(viewport)) + { + child.as_widget().draw( + state, renderer, theme, style, layout, cursor, viewport, + ); + } + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + translation, + ) + } + + #[cfg(feature = "a11y")] + /// get the a11y nodes for the widget + fn a11y_nodes( + &self, + layout: Layout<'_>, + state: &Tree, + cursor: mouse::Cursor, + ) -> iced_accessibility::A11yTree { + use iced_accessibility::A11yTree; + A11yTree::join( + self.children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + .map(|((c, c_layout), state)| { + c.as_widget().a11y_nodes(c_layout, state, cursor) + }), + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + renderer: &Renderer, + dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles, + ) { + for ((e, layout), state) in self + .children + .iter() + .zip(layout.children()) + .zip(state.children.iter()) + { + e.as_widget().drag_destinations( + state, + layout, + renderer, + dnd_rectangles, + ); + } + } +} + +impl<'a, Message, Theme, Renderer> + From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: 'a, + Renderer: iced::core::Renderer + 'a, +{ + fn from( + SubsurfaceContainer: SubsurfaceContainer<'a, Message, Theme, Renderer>, + ) -> Self { + Self::new(SubsurfaceContainer) + } +} diff --git a/examples/sctk_subsurface/src/wayland.rs b/examples/sctk_subsurface/src/wayland.rs index 033dfc8ed2..f43a77a32f 100644 --- a/examples/sctk_subsurface/src/wayland.rs +++ b/examples/sctk_subsurface/src/wayland.rs @@ -1,11 +1,5 @@ -use futures_channel::mpsc; -use iced::{ - futures::{FutureExt, SinkExt}, - platform_specific::shell::subsurface_widget::{Shmbuf, SubsurfaceBuffer}, -}; -use iced_runtime::futures::subscription; -use rustix::{io::Errno, shm::ShmOFlags}; use cctk::sctk::{ + self, reexports::{ calloop_wayland_source::WaylandSource, client::{ @@ -18,16 +12,24 @@ use cctk::sctk::{ registry::{ProvidesRegistryState, RegistryState}, shm::{Shm, ShmHandler}, }; +use futures_channel::mpsc; +use iced::{ + futures::{FutureExt, SinkExt}, + platform_specific::shell::subsurface_widget::{Shmbuf, SubsurfaceBuffer}, +}; +use iced_runtime::futures::subscription; +use rustix::{io::Errno, shm::ShmOFlags}; use std::{ os::fd::OwnedFd, sync::Arc, thread, - time::{SystemTime, UNIX_EPOCH}, + time::{Duration, SystemTime, UNIX_EPOCH}, }; #[derive(Debug, Clone)] pub enum Event { RedBuffer(SubsurfaceBuffer), + GreenBuffer(SubsurfaceBuffer), } struct AppData { @@ -80,6 +82,20 @@ async fn start(conn: Connection) -> mpsc::Receiver { format: wl_shm::Format::Xrgb8888, }; + let buffer = SubsurfaceBuffer::new(Arc::new(shmbuf.into())).0; + let _ = sender.send(Event::GreenBuffer(buffer)).await; + + let fd = create_memfile().unwrap(); + rustix::io::write(&fd, &[0, 0, 255, 255]).unwrap(); + + let shmbuf = Shmbuf { + fd, + offset: 0, + width: 1, + height: 1, + stride: 4, + format: wl_shm::Format::Xrgb8888, + }; let buffer = SubsurfaceBuffer::new(Arc::new(shmbuf.into())).0; let _ = sender.send(Event::RedBuffer(buffer)).await; @@ -90,6 +106,7 @@ async fn start(conn: Connection) -> mpsc::Receiver { .unwrap(); loop { event_loop.dispatch(None, &mut app_data).unwrap(); + std::thread::sleep(Duration::from_millis(500)); } }); diff --git a/runtime/src/platform_specific/wayland/layer_surface.rs b/runtime/src/platform_specific/wayland/layer_surface.rs index f4419c96c0..187d31746c 100644 --- a/runtime/src/platform_specific/wayland/layer_surface.rs +++ b/runtime/src/platform_specific/wayland/layer_surface.rs @@ -1,10 +1,10 @@ use std::fmt; -use iced_core::layout::Limits; use cctk::sctk::{ reexports::client::protocol::wl_output::WlOutput, shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}, }; +use iced_core::layout::Limits; use iced_core::window::Id; diff --git a/runtime/src/platform_specific/wayland/mod.rs b/runtime/src/platform_specific/wayland/mod.rs index 41b21c527e..9e8acea268 100644 --- a/runtime/src/platform_specific/wayland/mod.rs +++ b/runtime/src/platform_specific/wayland/mod.rs @@ -14,6 +14,9 @@ pub mod popup; /// session locks pub mod session_lock; +// subsurfaces +pub mod subsurface; + /// Platform specific actions defined for wayland pub enum Action { /// LayerSurface Actions @@ -26,6 +29,8 @@ pub enum Action { SessionLock(session_lock::Action), /// Overlap Notify OverlapNotify(Id, bool), + /// Subsurfaces + Subsurface(subsurface::Action), } impl Debug for Action { @@ -44,6 +49,9 @@ impl Debug for Action { Action::OverlapNotify(id, _) => { f.debug_tuple("OverlapNotify").field(id).finish() } + Action::Subsurface(action) => { + f.debug_tuple("Subsurface").field(action).finish() + } } } } diff --git a/runtime/src/platform_specific/wayland/popup.rs b/runtime/src/platform_specific/wayland/popup.rs index 19b23e020f..46dc310a7a 100644 --- a/runtime/src/platform_specific/wayland/popup.rs +++ b/runtime/src/platform_specific/wayland/popup.rs @@ -1,12 +1,15 @@ +use std::any::Any; use std::fmt; use std::hash::{Hash, Hasher}; +use std::sync::Arc; -use iced_core::layout::Limits; -use iced_core::window::Id; -use iced_core::Rectangle; use cctk::sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{ Anchor, Gravity, }; +use iced_core::layout::Limits; +use iced_core::window::Id; +use iced_core::{Element, Rectangle}; + /// Popup creation details #[derive(Debug, Clone)] pub struct SctkPopupSettings { @@ -20,6 +23,11 @@ pub struct SctkPopupSettings { pub parent_size: Option<(u32, u32)>, /// whether a grab should be requested for the popup after creation pub grab: bool, + /// whether a popup should close when its child popups close + pub close_with_children: bool, + /// input zone + /// None results in accepting all input + pub input_zone: Option, } impl Hash for SctkPopupSettings { diff --git a/runtime/src/platform_specific/wayland/subsurface.rs b/runtime/src/platform_specific/wayland/subsurface.rs new file mode 100644 index 0000000000..0fb5130d1f --- /dev/null +++ b/runtime/src/platform_specific/wayland/subsurface.rs @@ -0,0 +1,77 @@ +use std::any::Any; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; + +use cctk::sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{ + Anchor, Gravity, +}; +use iced_core::layout::Limits; +use iced_core::window::Id; +use iced_core::{Element, Point, Rectangle, Size}; + +/// Subsurface creation details +#[derive(Debug, Clone)] +pub struct SctkSubsurfaceSettings { + /// XXX must be unique, id of the parent + pub parent: Id, + /// XXX must be unique, id of the subsurface + pub id: Id, + /// anchor position of the subsurface + pub loc: Point, + /// size of the subsurface + pub size: Option, + // pub subsurface_view: Option>, + /// Z + pub z: u32, + /// Steal Keyboard focus from parent while open. + /// Will not work on a regular window. + pub steal_keyboard_focus: bool, + + /// offset of the subsurface from the anchor + pub offset: (i32, i32), + /// the gravity of the popup + pub gravity: Gravity, + + /// input zone + /// None results in accepting all input + pub input_zone: Option, +} + +impl Hash for SctkSubsurfaceSettings { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +#[derive(Clone)] +/// Window Action +pub enum Action { + /// create a window and receive a message with its Id + Subsurface { + /// subsurface + subsurface: SctkSubsurfaceSettings, + }, + /// destroy the subsurface + Destroy { + /// id of the subsurface + id: Id, + }, +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::Subsurface { subsurface, .. } => write!( + f, + "Action::SubsurfaceAction::Subsurface {{ subsurface: {:?} }}", + subsurface + ), + Action::Destroy { id } => write!( + f, + "Action::SubsurfaceAction::Destroy {{ id: {:?} }}", + id + ), + } + } +} diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 097e65ca1e..bdc39b6233 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -3,8 +3,8 @@ use std::sync::Mutex; use std::{any::Any, borrow::Cow}; -use crate::core::clipboard::{DndSource, DynIconSurface}; use crate::core::clipboard::Kind; +use crate::core::clipboard::{DndSource, DynIconSurface}; use std::sync::Arc; use winit::dpi::LogicalSize; use winit::window::{Window, WindowId}; diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index 0c07ef6871..d0bc355e38 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -4,7 +4,10 @@ use std::collections::HashMap; use iced_graphics::Compositor; -use iced_runtime::{core::{window, Vector}, user_interface, Debug}; +use iced_runtime::{ + core::{window, Vector}, + user_interface, Debug, +}; use winit::raw_window_handle::HasWindowHandle; #[cfg(all(feature = "wayland", target_os = "linux"))] @@ -29,6 +32,7 @@ pub enum SurfaceIdWrapper { Window(window::Id), Popup(window::Id), SessionLock(window::Id), + Subsurface(window::Id), } impl SurfaceIdWrapper { pub fn inner(&self) -> window::Id { @@ -37,6 +41,7 @@ impl SurfaceIdWrapper { SurfaceIdWrapper::Window(id) => *id, SurfaceIdWrapper::Popup(id) => *id, SurfaceIdWrapper::SessionLock(id) => *id, + SurfaceIdWrapper::Subsurface(id) => *id, } } } @@ -120,7 +125,9 @@ impl PlatformSpecific { } } - pub(crate) fn create_surface(&mut self) -> Option> { + pub(crate) fn create_surface( + &mut self, + ) -> Option> { #[cfg(all(feature = "wayland", target_os = "linux"))] { return self.wayland.create_surface(); @@ -128,10 +135,20 @@ impl PlatformSpecific { None } - pub(crate) fn update_surface_shm(&mut self, surface: &dyn HasWindowHandle, width: u32, height: u32, scale: f64, data: &[u8], offset: Vector) { + pub(crate) fn update_surface_shm( + &mut self, + surface: &dyn HasWindowHandle, + width: u32, + height: u32, + scale: f64, + data: &[u8], + offset: Vector, + ) { #[cfg(all(feature = "wayland", target_os = "linux"))] { - return self.wayland.update_surface_shm(surface, width, height, scale, data, offset); + return self.wayland.update_surface_shm( + surface, width, height, scale, data, offset, + ); } } } @@ -162,7 +179,7 @@ pub(crate) fn handle_event<'a, P, C>( (u64, iced_accessibility::accesskit_winit::Adapter), >, ) where - P: Program, + P: 'static + Program, C: Compositor, { match e { diff --git a/winit/src/platform_specific/wayland/commands/mod.rs b/winit/src/platform_specific/wayland/commands/mod.rs index 85ba1000f6..38a8c2798b 100644 --- a/winit/src/platform_specific/wayland/commands/mod.rs +++ b/winit/src/platform_specific/wayland/commands/mod.rs @@ -5,3 +5,4 @@ pub mod layer_surface; pub mod overlap_notify; pub mod popup; pub mod session_lock; +pub mod subsurface; diff --git a/winit/src/platform_specific/wayland/commands/session_lock.rs b/winit/src/platform_specific/wayland/commands/session_lock.rs index d008b0d679..7f8748f7e5 100644 --- a/winit/src/platform_specific/wayland/commands/session_lock.rs +++ b/winit/src/platform_specific/wayland/commands/session_lock.rs @@ -1,10 +1,10 @@ use crate::core::window::Id as SurfaceId; +use cctk::sctk::reexports::client::protocol::wl_output::WlOutput; use iced_runtime::{ self, platform_specific::{self, wayland}, task, Action, Task, }; -use cctk::sctk::reexports::client::protocol::wl_output::WlOutput; pub fn lock() -> Task { task::effect(Action::PlatformSpecific( diff --git a/winit/src/platform_specific/wayland/commands/subsurface.rs b/winit/src/platform_specific/wayland/commands/subsurface.rs new file mode 100644 index 0000000000..d8ce825956 --- /dev/null +++ b/winit/src/platform_specific/wayland/commands/subsurface.rs @@ -0,0 +1,28 @@ +use crate::core::window::Id as SurfaceId; +pub use cctk::sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}; +use iced_runtime::{ + self, + platform_specific::{ + self, + wayland::{self, subsurface::SctkSubsurfaceSettings}, + }, + task, Action, Task, +}; + +pub fn get_subsurface( + subsurface: SctkSubsurfaceSettings, +) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Subsurface( + wayland::subsurface::Action::Subsurface { subsurface }, + )), + )) +} + +pub fn destroy_subsurface(id: SurfaceId) -> Task { + task::effect(Action::PlatformSpecific( + platform_specific::Action::Wayland(wayland::Action::Subsurface( + wayland::subsurface::Action::Destroy { id }, + )), + )) +} diff --git a/winit/src/platform_specific/wayland/conversion.rs b/winit/src/platform_specific/wayland/conversion.rs index 1db1abbb20..c2ca825d6b 100644 --- a/winit/src/platform_specific/wayland/conversion.rs +++ b/winit/src/platform_specific/wayland/conversion.rs @@ -73,47 +73,3 @@ pub fn modifiers_to_native(mods: Modifiers) -> keyboard::Modifiers { // } native_mods } - -// pub fn keysym_to_vkey(keysym: RawKeysym) -> Option { -// key_conversion.get(&keysym).cloned() -// } - -// pub(crate) fn cursor_icon(cursor: winit::window::CursorIcon) -> CursorIcon { -// match cursor { -// CursorIcon::Default => todo!(), -// CursorIcon::ContextMenu => todo!(), -// CursorIcon::Help => todo!(), -// CursorIcon::Pointer => todo!(), -// CursorIcon::Progress => todo!(), -// CursorIcon::Wait => todo!(), -// CursorIcon::Cell => todo!(), -// CursorIcon::Crosshair => todo!(), -// CursorIcon::Text => todo!(), -// CursorIcon::VerticalText => todo!(), -// CursorIcon::Alias => todo!(), -// CursorIcon::Copy => todo!(), -// CursorIcon::Move => todo!(), -// CursorIcon::NoDrop => todo!(), -// CursorIcon::NotAllowed => todo!(), -// CursorIcon::Grab => todo!(), -// CursorIcon::Grabbing => todo!(), -// CursorIcon::EResize => todo!(), -// CursorIcon::NResize => todo!(), -// CursorIcon::NeResize => todo!(), -// CursorIcon::NwResize => todo!(), -// CursorIcon::SResize => todo!(), -// CursorIcon::SeResize => todo!(), -// CursorIcon::SwResize => todo!(), -// CursorIcon::WResize => todo!(), -// CursorIcon::EwResize => todo!(), -// CursorIcon::NsResize => todo!(), -// CursorIcon::NeswResize => todo!(), -// CursorIcon::NwseResize => todo!(), -// CursorIcon::ColResize => todo!(), -// CursorIcon::RowResize => todo!(), -// CursorIcon::AllScroll => todo!(), -// CursorIcon::ZoomIn => todo!(), -// CursorIcon::ZoomOut => todo!(), -// _ => todo!(), -// } -// } diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index cb18bc5da8..09127ee11e 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -43,7 +43,7 @@ use cctk::{ toplevel_management::ToplevelManagerState, }; use raw_window_handle::HasDisplayHandle; -use state::{FrameStatus, SctkWindow}; +use state::{send_event, FrameStatus, SctkWindow}; #[cfg(feature = "a11y")] use std::sync::{Arc, Mutex}; use std::{ @@ -52,7 +52,7 @@ use std::{ }; use tracing::error; use wayland_backend::client::Backend; -use winit::event_loop::OwnedDisplayHandle; +use winit::{dpi::LogicalSize, event_loop::OwnedDisplayHandle}; use self::state::SctkState; @@ -122,7 +122,36 @@ impl SctkEventLoop { } crate::Action::RemoveWindow(id) => { // TODO clean up popups matching the window. - state.windows.retain(|window| id != window.id); + if let Some(pos) = state + .windows + .iter() + .position(|window| id == window.id) + { + let w = state.windows.remove(pos); + for subsurface_id in state + .subsurfaces + .iter() + .enumerate() + .filter_map(|(i, s)| { + (winit::window::WindowId::from( + s.instance.parent.as_ptr() + as u64, + ) == w.window.id()) + .then_some(i) + }) + .collect::>() + { + let s = state + .subsurfaces + .remove(subsurface_id); + crate::subsurface_widget::remove_iced_subsurface( + &s.instance.wl_surface, + ); + send_event(&state.events_sender, &state.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) ) + ); + } + } } crate::platform_specific::Action::SetCursor( icon, @@ -146,6 +175,57 @@ impl SctkEventLoop { crate::Action::Dropped(id) => { _ = state.destroyed.remove(&id.inner()); } + crate::Action::SubsurfaceResize(id, size) => { + // reposition the surface + if let Some(pos) = state + .subsurfaces + .iter() + .position(|window| id == window.id) + { + let subsurface = &mut state.subsurfaces[pos]; + let settings = &subsurface.settings; + let mut loc = settings.loc; + let guard = subsurface.common.lock().unwrap(); + let size: LogicalSize = size.to_logical(guard.fractional_scale.unwrap_or(1.)); + let half_w = size.width / 2.; + let half_h = size.height / 2.; + match settings.gravity { + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::None => { + // center on + loc.x -= half_w; + loc.y -= half_h; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Top => { + loc.x -= half_w; + loc.y -= size.height; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Bottom => { + loc.x -= half_w; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Left => { + loc.y -= half_h; + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Right => { + loc.y -= half_h; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft => { + loc.y -= size.height; + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft => { + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight => { + loc.y -= size.height; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight => {}, + _ => unimplemented!(), + }; + subsurface.instance.wl_subsurface.set_position(loc.x as i32, loc.y as i32); + + } + send_event(&state.events_sender, &state.proxy, SctkEvent::SubsurfaceEvent(crate::sctk_event::SubsurfaceEventVariant::Resized(id, size)))}, }, calloop::channel::Event::Closed => { log::info!("Calloop channel closed."); @@ -169,7 +249,7 @@ impl SctkEventLoop { let (viewporter_state, fractional_scaling_manager) = match FractionalScalingManager::new(&globals, &qh) { Ok(m) => { - let viewporter_state = + let viewporter_state: Option = match ViewporterState::new(&globals, &qh) { Ok(s) => Some(s), Err(e) => { @@ -229,6 +309,7 @@ impl SctkEventLoop { layer_surfaces: Vec::new(), popups: Vec::new(), lock_surfaces: Vec::new(), + subsurfaces: Vec::new(), _kbd_focus: None, touch_points: HashMap::new(), sctk_events: Vec::new(), @@ -245,6 +326,7 @@ impl SctkEventLoop { activation_token_ctr: 0, token_senders: HashMap::new(), overlap_notifications: HashMap::new(), + subsurface_state: None, }, _features: Default::default(), }; @@ -282,20 +364,23 @@ impl SctkEventLoop { if let (Ok(wl_subcompositor), Ok(wp_viewporter)) = (wl_subcompositor, wp_viewporter) { + let subsurface_state = SubsurfaceState { + wl_compositor, + wl_subcompositor, + wp_viewporter, + wl_shm, + wp_dmabuf, + wp_alpha_modifier, + qh: state.state.queue_handle.clone(), + buffers: HashMap::new(), + unmapped_subsurfaces: Vec::new(), + new_iced_subsurfaces: Vec::new(), + }; + state.state.subsurface_state = Some(subsurface_state.clone()); state::send_event( &state.state.events_sender, &state.state.proxy, - SctkEvent::Subcompositor(SubsurfaceState { - wl_compositor, - wl_subcompositor, - wp_viewporter, - wl_shm, - wp_dmabuf, - wp_alpha_modifier, - qh: state.state.queue_handle.clone(), - buffers: HashMap::new(), - unmapped_subsurfaces: Vec::new(), - }), + SctkEvent::Subcompositor(subsurface_state), ); } else { log::warn!("Subsurfaces not supported.") diff --git a/winit/src/platform_specific/wayland/event_loop/proxy.rs b/winit/src/platform_specific/wayland/event_loop/proxy.rs index 44c0949e3d..bdbc44a42f 100644 --- a/winit/src/platform_specific/wayland/event_loop/proxy.rs +++ b/winit/src/platform_specific/wayland/event_loop/proxy.rs @@ -1,9 +1,9 @@ +use cctk::sctk::reexports::calloop; use iced_futures::futures::{ channel::mpsc, task::{Context, Poll}, Sink, }; -use cctk::sctk::reexports::calloop; use std::pin::Pin; /// An event loop proxy that implements `Sink`. diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 89a9167090..32af0f5754 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -14,14 +14,21 @@ use crate::{ Event, }, program::Control, + sctk_event::KeyboardEventVariant, + subsurface_widget::SubsurfaceState, + wayland::SubsurfaceInstance, +}; +use iced_futures::{ + core::{Rectangle, Size}, + futures::channel::{mpsc, oneshot}, }; -use iced_futures::futures::channel::{mpsc, oneshot}; use raw_window_handle::HasWindowHandle; use std::{ collections::{HashMap, HashSet}, convert::Infallible, fmt::Debug, sync::{atomic::AtomicU32, Arc, Mutex}, + thread::panicking, time::Duration, }; use wayland_backend::client::ObjectId; @@ -82,9 +89,7 @@ use iced_runtime::{ platform_specific::{ self, wayland::{ - layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, - popup::SctkPopupSettings, - Action, + layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, popup::SctkPopupSettings, subsurface::{self, SctkSubsurfaceSettings}, Action }, }, }; @@ -183,6 +188,10 @@ pub enum CommonSurface { Popup(Popup, Arc), Layer(LayerSurface), Lock(SessionLockSurface), + Subsurface { + wl_surface: WlSurface, + wl_subsurface: WlSubsurface, + }, } impl CommonSurface { @@ -193,6 +202,7 @@ impl CommonSurface { CommonSurface::Lock(session_lock_surface) => { session_lock_surface.wl_surface() } + CommonSurface::Subsurface { ref wl_surface, .. } => wl_surface, }; wl_surface } @@ -241,6 +251,7 @@ pub struct SctkPopup { pub(crate) data: SctkPopupData, pub(crate) common: Arc>, pub(crate) wp_fractional_scale: Option, + pub(crate) close_with_children: bool, } impl SctkPopup { @@ -249,10 +260,19 @@ impl SctkPopup { self.popup .xdg_surface() .set_window_geometry(0, 0, w as i32, h as i32); + self.update_viewport(w, h); // update positioner self.data.positioner.set_size(w as i32, h as i32); self.popup.reposition(&self.data.positioner, token); } + + pub(crate) fn update_viewport(&mut self, w: u32, h: u32) { + let common = self.common.lock().unwrap(); + if let Some(viewport) = common.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination(w as i32, h as i32); + } + } } #[derive(Debug)] @@ -261,8 +281,27 @@ pub struct SctkLockSurface { pub(crate) session_lock_surface: SessionLockSurface, pub(crate) last_configure: Option, pub(crate) wp_fractional_scale: Option, - pub(crate) wp_viewport: Option, pub(crate) common: Arc>, + pub(crate) output: WlOutput, +} +impl SctkLockSurface { + pub(crate) fn update_viewport(&mut self, w: u32, h: u32) { + let mut common = self.common.lock().unwrap(); + + common.size = LogicalSize::new(w, h); + if let Some(viewport) = common.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination(w as i32, h as i32); + } + } +} +#[derive(Debug)] +pub struct SctkSubsurface { + pub(crate) common: Arc>, + pub(crate) steals_keyboard_focus: bool, + pub(crate) id: core::window::Id, + pub(crate) instance: SubsurfaceInstance, + pub(crate) settings: SctkSubsurfaceSettings, } #[derive(Debug)] @@ -271,6 +310,7 @@ pub struct SctkPopupData { pub(crate) parent: PopupParent, pub(crate) toplevel: WlSurface, pub(crate) positioner: Arc, + pub(crate) grab: bool, } pub struct SctkWindow { @@ -347,6 +387,7 @@ pub struct SctkState { pub(crate) windows: Vec, pub(crate) layer_surfaces: Vec, pub(crate) popups: Vec, + pub(crate) subsurfaces: Vec, pub(crate) lock_surfaces: Vec, pub(crate) _kbd_focus: Option, pub(crate) touch_points: HashMap, @@ -390,6 +431,7 @@ pub struct SctkState { pub(crate) overlap_notify: Option, pub(crate) toplevel_info: Option, pub(crate) toplevel_manager: Option, + pub(crate) subsurface_state: Option, pub(crate) activation_token_ctr: u32, pub(crate) token_senders: HashMap>>, @@ -431,6 +473,22 @@ pub enum LayerSurfaceCreationError { LayerSurfaceCreationFailed(GlobalError), } +/// An error that occurred while running an application. +#[derive(Debug, thiserror::Error)] +pub enum SubsurfaceCreationError { + /// Subsurface creation failed + #[error("Subsurface creation failed")] + CreationFailed(GlobalError), + + /// The specified parent is missing + #[error("The specified parent is missing")] + ParentMissing, + + /// Subsurfaces are unsupported + #[error("Subsurfaces are unsupported")] + Unsupported, +} + pub(crate) fn receive_frame( frame_status: &mut HashMap, s: &WlSurface, @@ -460,6 +518,18 @@ impl SctkState { ) { let mut id = None; + for subsurface in &self.subsurfaces { + if subsurface.instance.parent != surface.id() { + continue; + } + + self.sctk_events.push(SctkEvent::SurfaceScaleFactorChanged( + scale_factor, + surface.clone(), + subsurface.id, + )); + } + if let Some(popup) = self .popups .iter_mut() @@ -688,6 +758,19 @@ impl SctkState { log::error!("Can't take grab on popup. Missing serial."); } } + + if let Some(z) = settings.input_zone { + let region = self + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); + region.add( + z.x.round() as i32, + z.y.round() as i32, + z.width.round() as i32, + z.height.round() as i32, + ); + } popup.xdg_surface().set_window_geometry( 0, 0, @@ -719,11 +802,13 @@ impl SctkState { parent: parent.clone(), toplevel: toplevel.clone(), positioner: positioner.clone(), + grab: settings.grab, }, last_configure: None, _pending_requests: Default::default(), wp_fractional_scale, common: common.clone(), + close_with_children: settings.close_with_children, }); Ok(( @@ -863,15 +948,16 @@ impl SctkState { self.fractional_scaling_manager.as_ref().map(|fsm| { fsm.fractional_scaling(&wl_surface, &self.queue_handle) }); - let common = - Arc::new(Mutex::new(Common::from(LogicalSize::new(1, 1)))); + let mut common = Common::from(LogicalSize::new(1, 1)); + common.wp_viewport = wp_viewport; + let common = Arc::new(Mutex::new(common)); self.lock_surfaces.push(SctkLockSurface { id, session_lock_surface: session_lock_surface.clone(), last_configure: None, wp_fractional_scale, - wp_viewport, common: common.clone(), + output: output.clone(), }); Some((CommonSurface::Lock(session_lock_surface), common)) } else { @@ -920,6 +1006,25 @@ impl SctkState { platform_specific::wayland::layer_surface::Action::Destroy(id) => { if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) { let l = self.layer_surfaces.remove(i); + + let (removed, remaining): (Vec<_>, Vec<_>) = self + .subsurfaces + .drain(..) + .partition(|s| { + s.instance.parent == l.surface.wl_surface().id() + }); + + self.subsurfaces = remaining; + for s in removed + { + crate::subsurface_widget::remove_iced_subsurface( + &s.instance.wl_surface, + ); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) ) + ); + } + if let Some(destroyed) = self.id_map.remove(&l.surface.wl_surface().id()) { _ = self.destroyed.insert(destroyed); } @@ -976,28 +1081,77 @@ impl SctkState { }, }, Action::Popup(action) => match action { - platform_specific::wayland::popup::Action::Popup { popup, .. } => { - let parent_mismatch = self.popups.last().is_some_and(|p| { - self.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p| *p != popup.parent) + platform_specific::wayland::popup::Action::Popup { popup: settings } => { + // first check existing popup + if let Some(existing) = self.popups.iter().position(|p| p.data.id == settings.id + && ( + self.popups.iter().any(|parent| parent.popup.wl_surface() == p.data.parent.wl_surface() && parent.data.id == settings.parent) + || self.windows.iter().any(|w| w.id == settings.parent && *p.data.parent.wl_surface() == w.wl_surface(&self.connection)) + || self.layer_surfaces.iter().any(|l| l.id == settings.parent && p.data.parent.wl_surface() == l.surface.wl_surface())) + ) { + let existing = &mut self.popups[existing]; + let size = if settings.positioner.size.is_none() { + log::info!("No configured popup size"); + (1, 1) + } else { + settings.positioner.size.unwrap() + }; + let Ok(positioner) = XdgPositioner::new(&self.xdg_shell_state) + .map_err(PopupCreationError::PositionerCreationFailed) else { + log::error!("Failed to create popup positioner"); + return Ok(()); + }; + positioner.set_anchor(settings.positioner.anchor); + positioner.set_anchor_rect( + settings.positioner.anchor_rect.x, + settings.positioner.anchor_rect.y, + settings.positioner.anchor_rect.width, + settings.positioner.anchor_rect.height, + ); + if let Ok(constraint_adjustment) = + settings.positioner.constraint_adjustment.try_into() + { + positioner.set_constraint_adjustment(constraint_adjustment); + } + positioner.set_gravity(settings.positioner.gravity); + positioner.set_offset( + settings.positioner.offset.0, + settings.positioner.offset.1, + ); + if settings.positioner.reactive { + positioner.set_reactive(); + } + positioner.set_size(size.0 as i32, size.1 as i32); + existing.data.positioner = Arc::new(positioner); + existing.set_size(size.0, size.1, TOKEN_CTR.fetch_add(1, std::sync::atomic::Ordering::Relaxed)); + _ = send_event(&self.events_sender, &self.proxy, + SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(size.0, size.1), toplevel_id: existing.data.parent.wl_surface().clone(), parent_id: existing.data.parent.wl_surface().clone(), id: existing.popup.wl_surface().clone() }); + return Ok(()); + } + let parent_mismatch = self.popups.iter().rev().find(|p| { + self.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p_id|{ + *p_id != settings.parent && p.data.grab && settings.grab}) }); - if !self.destroyed.is_empty() || parent_mismatch { - if parent_mismatch { + if !self.destroyed.is_empty() || parent_mismatch.is_some() { + if parent_mismatch.is_some() { for i in 0..self.popups.len() { let id = self.id_map.get(&self.popups[i].popup.wl_surface().id()); if let Some(id) = id { - if *id != popup.parent { + if *id != settings.parent { _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy{id: *id})); } } } } - if self.pending_popup.replace((popup, 0)).is_none() { + if self.pending_popup.replace((settings, 0)).is_none() { + let timer = cctk::sctk::reexports::calloop::timer::Timer::from_duration(Duration::from_millis(30)); let queue_handle = self.queue_handle.clone(); _ = self.loop_handle.insert_source(timer, move |_, _, state| { - let Some((popup, attempt)) = state.pending_popup.take() else { + let Some((mut popup, attempt)) = state.pending_popup.take() else { return TimeoutAction::Drop; }; + if !state.destroyed.is_empty() || state.popups.last().is_some_and(|p| { state.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p| *p != popup.parent) }) { @@ -1029,7 +1183,7 @@ impl SctkState { // log::error!("Invalid popup Id {:?}", popup.id); } else { self.pending_popup = None; - match self.get_popup(popup) { + match self.get_popup(settings) { Ok((id, parent_id, toplevel_id, surface, common)) => { let wl_surface = surface.wl_surface().clone(); receive_frame(&mut self.frame_status, &wl_surface); @@ -1054,31 +1208,51 @@ impl SctkState { { Some(p) => self.popups.remove(p), None => { - log::warn!("No popup to destroy"); + log::info!("No popup to destroy"); return Ok(()); }, }; let mut to_destroy = vec![sctk_popup]; - while let Some(popup_to_destroy) = to_destroy.last() { - match popup_to_destroy.data.parent.clone() { - PopupParent::LayerSurface(_) | PopupParent::Window(_) => { - break; - } - PopupParent::Popup(popup_to_destroy_first) => { - let popup_to_destroy_first = self - .popups - .iter() - .position(|p| p.popup.wl_surface() == &popup_to_destroy_first) - .unwrap(); - let popup_to_destroy_first = self.popups.remove(popup_to_destroy_first); - to_destroy.push(popup_to_destroy_first); - } - } + // TODO optionally destroy parents if they request to be destroyed with children + while let Some(popup_to_destroy_last) = to_destroy.last().and_then(|popup| self + .popups + .iter() + .position(|p| popup.data.parent.wl_surface() == p.popup.wl_surface() && p.close_with_children)) { + let popup_to_destroy_last = self.popups.remove(popup_to_destroy_last); + to_destroy.push(popup_to_destroy_last); + } + to_destroy.reverse(); + + while let Some(popup_to_destroy_first) = to_destroy.last().and_then(|popup| self + .popups + .iter() + .position(|p| p.data.parent.wl_surface() == popup.popup.wl_surface())) { + let popup_to_destroy_first = self.popups.remove(popup_to_destroy_first); + to_destroy.push(popup_to_destroy_first); } for popup in to_destroy.into_iter().rev() { if let Some(id) = self.id_map.remove(&popup.popup.wl_surface().id()) { _ = self.destroyed.insert(id); } + + let (removed, remaining): (Vec<_>, Vec<_>) = self + .subsurfaces + .drain(..) + .partition(|s| { + s.instance.parent == popup.popup.wl_surface().id() + }); + + self.subsurfaces = remaining; + for s in removed + { + + crate::subsurface_widget::remove_iced_subsurface( + &s.instance.wl_surface, + ); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) ) + ); + } _ = send_event(&self.events_sender, &self.proxy, SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Done, toplevel_id: popup.data.toplevel.clone(), parent_id: popup.data.parent.wl_surface().clone(), id: popup.popup.wl_surface().clone() }); } @@ -1165,11 +1339,15 @@ impl SctkState { send_event(&self.events_sender, &self.proxy, SctkEvent::SessionUnlocked); } platform_specific::wayland::session_lock::Action::LockSurface { id, output } => { + // Should we panic if the id does not match? + if self.lock_surfaces.iter().any(|s| s.output == output) { + tracing::warn!("Cannot create multiple lock surfaces for a single output."); + return Ok(()); + } // TODO how to handle this when there's no lock? - if let Some((surface, common)) = self.get_lock_surface(id, &output) { + if let Some((surface, _)) = self.get_lock_surface(id, &output) { let wl_surface = surface.wl_surface(); receive_frame(&mut self.frame_status, &wl_surface); - send_event(&self.events_sender, &self.proxy, SctkEvent::SessionLockSurfaceCreated { queue_handle: self.queue_handle.clone(), surface, native_id: id, common, display: self.connection.display() }); } } platform_specific::wayland::session_lock::Action::DestroyLockSurface { id } => { @@ -1179,6 +1357,24 @@ impl SctkState { }) { let surface = self.lock_surfaces.remove(i); + let (removed, remaining): (Vec<_>, Vec<_>) = self + .subsurfaces + .drain(..) + .partition(|s| { + s.instance.parent == surface.session_lock_surface.wl_surface().id() + }); + + self.subsurfaces = remaining; + for s in removed + { + + crate::subsurface_widget::remove_iced_subsurface( + &s.instance.wl_surface, + ); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(s.instance) ) + ); + } if let Some(id) = self.id_map.remove(&surface.session_lock_surface.wl_surface().id()) { _ = self.destroyed.insert(id); } @@ -1208,9 +1404,254 @@ impl SctkState { tracing::error!("Overlap notify subscription cannot be created for surface. No matching layer surface found."); } }, + Action::Subsurface(action) => match action { + platform_specific::wayland::subsurface::Action::Subsurface { subsurface: subsurface_settings } => { + let parent_id = subsurface_settings.parent; + if let Ok((_, parent, subsurface, common_surface, common)) = self.get_subsurface(subsurface_settings.clone()) { + // TODO Ashley: all surfaces should probably have an optional title for a11y if nothing else + receive_frame(&mut self.frame_status, &subsurface); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent (crate::sctk_event::SubsurfaceEventVariant::Created{ + parent_id, + parent, + surface: subsurface, + qh: self.queue_handle.clone(), + common_surface, + surface_id: subsurface_settings.id, + common, + display: self.connection.display(), + z: subsurface_settings.z, + }) + ); + } + }, + platform_specific::wayland::subsurface::Action::Destroy { id } => { + let mut destroyed = vec![]; + if let Some(subsurface) = self.subsurfaces.iter().position(|s| s.id == id) { + let subsurface = self.subsurfaces.remove(subsurface); + destroyed.push((subsurface.instance.wl_surface.clone(), subsurface.instance.parent.clone())); + + subsurface.instance.wl_surface.attach(None, 0, 0); + subsurface.instance.wl_surface.commit(); + send_event(&self.events_sender, &self.proxy, + SctkEvent::SubsurfaceEvent( crate::sctk_event::SubsurfaceEventVariant::Destroyed(subsurface.instance) ) + ); + } + for (destroyed, parent) in destroyed { + if let Some((wl_surface, f)) = self.seats.iter_mut().find(|f| { + f.kbd_focus.as_ref().is_some_and(|f| *f == destroyed) + }).and_then(|f| WlSurface::from_id(&self.connection, parent).ok().map(|wl| (wl, &mut f.kbd_focus))) { + *f = Some(wl_surface); + } + } + }, + }, }; Ok(()) } + + pub fn get_subsurface( + &mut self, + settings: SctkSubsurfaceSettings, + ) -> Result< + ( + core::window::Id, + WlSurface, + WlSurface, + CommonSurface, + Arc>, + ), + SubsurfaceCreationError, + > { + let Some(subsurface_state) = self.subsurface_state.as_ref() else { + return Err(SubsurfaceCreationError::Unsupported); + }; + + let size = settings.size.unwrap_or(Size::new(1., 1.)); + let half_w = size.width / 2.; + let half_h = size.height / 2.; + + let mut loc = settings.loc; + match settings.gravity { + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::None => { + // center on + loc.x -= half_w; + loc.y -= half_h; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Top => { + loc.x -= half_w; + loc.y -= size.height; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Bottom => { + loc.x -= half_w; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Left => { + loc.y -= half_h; + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Right => { + loc.y -= half_h; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft => { + loc.y -= size.height; + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft => { + loc.x -= size.width; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight => { + loc.y -= size.height; + }, + wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight => {}, + _ => unimplemented!(), + }; + let bounds = Rectangle::new(loc, size); + + let parent = if let Some(parent) = + self.layer_surfaces.iter().find(|l| l.id == settings.parent) + { + PopupParent::LayerSurface(parent.surface.wl_surface().clone()) + } else if let Some(parent) = + self.windows.iter().find(|w| w.id == settings.parent) + { + PopupParent::Window(parent.wl_surface(&self.connection)) + } else if let Some(i) = self + .popups + .iter() + .position(|p| p.data.id == settings.parent) + { + let parent = &self.popups[i]; + PopupParent::Popup(parent.popup.wl_surface().clone()) + } else if let Some(i) = self + .lock_surfaces + .iter() + .position(|p| p.id == settings.parent) + { + let parent = &self.lock_surfaces[i]; + PopupParent::Popup(parent.session_lock_surface.wl_surface().clone()) + } else { + return Err(SubsurfaceCreationError::ParentMissing); + }; + + let wl_surface = + self.compositor_state.create_surface(&self.queue_handle); + _ = self.id_map.insert(wl_surface.id(), settings.id.clone()); + + for s in self.seats.iter_mut() { + if s.kbd_focus + .as_ref() + .is_some_and(|f| f == parent.wl_surface()) + { + s.kbd_focus = Some(wl_surface.clone()); + } + } + let parent_wl_surface = parent.wl_surface(); + let wl_subsurface = subsurface_state.wl_subcompositor.get_subsurface( + &wl_surface, + parent_wl_surface, + &self.queue_handle, + (), + ); + wl_subsurface.set_position(bounds.x as i32, bounds.y as i32); + _ = wl_surface.frame(&self.queue_handle, wl_surface.clone()); + if let Some(zone) = settings.input_zone { + let region = self + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); + region.add( + zone.x.round() as i32, + zone.y.round() as i32, + zone.width.round() as i32, + zone.height.round() as i32, + ); + wl_surface.set_input_region(Some(®ion)); + region.destroy(); + } + + wl_surface.commit(); + + let wp_viewport = subsurface_state.wp_viewporter.get_viewport( + &wl_surface, + &self.queue_handle, + cctk::sctk::globals::GlobalData, + ); + + let wp_alpha_modifier_surface = subsurface_state + .wp_alpha_modifier + .as_ref() + .map(|wp_alpha_modifier| { + wp_alpha_modifier.get_surface( + &wl_surface, + &self.queue_handle, + (), + ) + }); + wp_viewport.set_destination(size.width as i32, size.height as i32); + + let mut common: Common = + LogicalSize::new(size.width as u32, size.height as u32).into(); + let instance = SubsurfaceInstance { + wl_surface: wl_surface.clone(), + wl_subsurface: wl_subsurface.clone(), + wp_viewport: wp_viewport.clone(), + wp_alpha_modifier_surface: wp_alpha_modifier_surface, + + wl_buffer: None, + bounds: Some(bounds), + transform: + cctk::wayland_client::protocol::wl_output::Transform::Normal, + z: settings.z, + parent: parent_wl_surface.id(), + }; + common.wp_viewport = Some(wp_viewport); + let common = Arc::new(Mutex::new(common)); + + for focus in &mut self.seats { + if focus + .kbd_focus + .as_ref() + .is_some_and(|s| s == parent_wl_surface) + { + let id = winit::window::WindowId::from( + wl_surface.id().as_ptr() as u64, + ); + self.sctk_events.push(SctkEvent::Winit( + id, + winit::event::WindowEvent::Focused(true), + )); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Enter(wl_surface.clone()), + kbd_id: focus.kbd.clone().unwrap(), + seat_id: focus.seat.clone(), + surface: wl_surface.clone(), + }); + focus.kbd_focus = Some(wl_surface.clone()); + } + } + let id = settings.id; + self.subsurfaces.push(SctkSubsurface { + common: common.clone(), + steals_keyboard_focus: settings.steal_keyboard_focus, + id: settings.id, + instance, + settings, + }); + // XXX subsurfaces need to be sorted by z in descending order + self.subsurfaces + .sort_by(|a, b| b.instance.z.cmp(&a.instance.z)); + + Ok(( + id, + parent.wl_surface().clone(), + wl_surface.clone(), + CommonSurface::Subsurface { + wl_surface, + wl_subsurface, + }, + common, + )) + } } pub(crate) fn send_event( diff --git a/winit/src/platform_specific/wayland/handlers/activation.rs b/winit/src/platform_specific/wayland/handlers/activation.rs index 26cfbeb817..6b03a8ddc6 100644 --- a/winit/src/platform_specific/wayland/handlers/activation.rs +++ b/winit/src/platform_specific/wayland/handlers/activation.rs @@ -1,9 +1,9 @@ -use iced_futures::futures::channel::oneshot::Sender; use cctk::sctk::{ activation::{ActivationHandler, RequestData, RequestDataExt}, delegate_activation, reexports::client::protocol::{wl_seat::WlSeat, wl_surface::WlSurface}, }; +use iced_futures::futures::channel::oneshot::Sender; use crate::platform_specific::wayland::event_loop::state::SctkState; diff --git a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs index 8617f85eff..199c3b2847 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs @@ -19,7 +19,6 @@ impl KeyboardHandler for SctkState { _raw: &[u32], _keysyms: &[Keysym], ) { - self.request_redraw(surface); let (i, mut is_active, seat) = { let (i, is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { @@ -32,35 +31,48 @@ impl KeyboardHandler for SctkState { Some((i, s)) => (i, i == 0, s), None => return, }; + + let surface = if let Some(subsurface) = + self.subsurfaces.iter().find(|s| { + s.steals_keyboard_focus && s.instance.parent == surface.id() + }) { + &subsurface.instance.wl_surface + } else { + surface + }; _ = my_seat.kbd_focus.replace(surface.clone()); let seat = my_seat.seat.clone(); (i, is_active, seat) }; - // TODO Ashley: thoroughly test this - // swap the active seat to be the current seat if the current "active" seat is not focused on the application anyway if !is_active && self.seats[0].kbd_focus.is_none() { is_active = true; self.seats.swap(0, i); } + self.request_redraw(&surface); - if is_active { - let id = - winit::window::WindowId::from(surface.id().as_ptr() as u64); - if self.windows.iter().any(|w| w.window.id() == id) { - return; + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()).then(|| &s.instance.wl_surface) + }); + for surface in surfaces.chain(std::iter::once(surface)) { + if is_active { + let id = + winit::window::WindowId::from(surface.id().as_ptr() as u64); + if self.windows.iter().any(|w| w.window.id() == id) { + continue; + } + self.sctk_events.push(SctkEvent::Winit( + id, + winit::event::WindowEvent::Focused(true), + )); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Enter(surface.clone()), + kbd_id: keyboard.clone(), + seat_id: seat.clone(), + surface: surface.clone(), + }); } - self.sctk_events.push(SctkEvent::Winit( - id, - winit::event::WindowEvent::Focused(true), - )); - self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Enter(surface.clone()), - kbd_id: keyboard.clone(), - seat_id: seat, - surface: surface.clone(), - }); } } @@ -90,37 +102,42 @@ impl KeyboardHandler for SctkState { _ = my_seat.kbd_focus.take(); (is_active, seat, kbd) }; - - if is_active { - self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Leave(surface.clone()), - kbd_id: kbd, - seat_id: seat, - surface: surface.clone(), - }); - // if there is another seat with a keyboard focused on a surface make that the new active seat - if let Some(i) = - self.seats.iter().position(|s| s.kbd_focus.is_some()) - { - self.seats.swap(0, i); - let s = &self.seats[0]; - let id = - winit::window::WindowId::from(surface.id().as_ptr() as u64); - if self.windows.iter().any(|w| w.window.id() == id) { - return; - } - self.sctk_events.push(SctkEvent::Winit( - id, - winit::event::WindowEvent::Focused(true), - )); + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()).then(|| &s.instance.wl_surface) + }); + for surface in surfaces.chain(std::iter::once(surface)) { + if is_active { self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Enter( - s.kbd_focus.clone().unwrap(), - ), - kbd_id: s.kbd.clone().unwrap(), - seat_id: s.seat.clone(), + variant: KeyboardEventVariant::Leave(surface.clone()), + kbd_id: kbd.clone(), + seat_id: seat.clone(), surface: surface.clone(), - }) + }); + // if there is another seat with a keyboard focused on a surface make that the new active seat + if let Some(i) = + self.seats.iter().position(|s| s.kbd_focus.is_some()) + { + self.seats.swap(0, i); + let s = &self.seats[0]; + let id = winit::window::WindowId::from( + surface.id().as_ptr() as u64, + ); + if self.windows.iter().any(|w| w.window.id() == id) { + continue; + } + self.sctk_events.push(SctkEvent::Winit( + id, + winit::event::WindowEvent::Focused(true), + )); + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Enter( + s.kbd_focus.clone().unwrap(), + ), + kbd_id: s.kbd.clone().unwrap(), + seat_id: s.seat.clone(), + surface: surface.clone(), + }) + } } } } @@ -148,35 +165,20 @@ impl KeyboardHandler for SctkState { let kbd_id = keyboard.clone(); _ = my_seat.last_kbd_press.replace((event.clone(), serial)); if is_active { - // FIXME can't create winit key events because of private field - // if let Some(id) = id { - // let physical_key = raw_keycode_to_physicalkey(event.raw_code); - // let (logical_key, location) = - // keysym_to_vkey_location(event.keysym); - // self.sctk_events.push(SctkEvent::Winit( - // id, - // winit::event::WindowEvent::KeyboardInput { - // device_id: Default::default(), - // event: winit::event::KeyEvent { - // physical_key, - // logical_key, - // text: event.utf8.map(|s| s.into()), - // location, - // state: winit::event::ElementState::Pressed, - // repeat: false, // TODO we don't have this info... - // }, - // is_synthetic: false, - // }, - // )) - // } if let Some(surface) = my_seat.kbd_focus.clone() { self.request_redraw(&surface); - self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Press(event), - kbd_id, - seat_id, - surface, + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()) + .then(|| &s.instance.wl_surface) }); + for surface in surfaces.chain(std::iter::once(&surface)) { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Press(event.clone()), + kbd_id: kbd_id.clone(), + seat_id: seat_id.clone(), + surface: surface.clone(), + }); + } } } } @@ -206,12 +208,18 @@ impl KeyboardHandler for SctkState { if is_active { if let Some(surface) = my_seat.kbd_focus.clone() { self.request_redraw(&surface); - self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Release(event), - kbd_id, - seat_id, - surface, + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()) + .then(|| &s.instance.wl_surface) }); + for surface in surfaces.chain(std::iter::once(&surface)) { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Release(event.clone()), + kbd_id: kbd_id.clone(), + seat_id: seat_id.clone(), + surface: surface.clone(), + }); + } } } } @@ -242,12 +250,20 @@ impl KeyboardHandler for SctkState { if is_active { if let Some(surface) = my_seat.kbd_focus.clone() { self.request_redraw(&surface); - self.sctk_events.push(SctkEvent::KeyboardEvent { - variant: KeyboardEventVariant::Modifiers(modifiers), - kbd_id, - seat_id, - surface, + let surfaces = self.subsurfaces.iter().filter_map(|s| { + (s.instance.parent == surface.id()) + .then(|| &s.instance.wl_surface) }); + for surface in surfaces.chain(std::iter::once(&surface)) { + self.sctk_events.push(SctkEvent::KeyboardEvent { + variant: KeyboardEventVariant::Modifiers( + modifiers.clone(), + ), + kbd_id: kbd_id.clone(), + seat_id: seat_id.clone(), + surface: surface.clone(), + }); + } } } } diff --git a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs index b150904436..61f50692e8 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs @@ -11,6 +11,7 @@ use cctk::sctk::{ CursorIcon, PointerEvent, PointerEventKind, PointerHandler, }, }; +use iced_futures::core::Point; use winit::{ dpi::PhysicalPosition, event::{MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}, @@ -53,6 +54,7 @@ impl PointerHandler for SctkState { if self.windows.iter().any(|w| w.window.id() == id) { continue; } + let entry = self .frame_status .entry(e.surface.id()) @@ -60,6 +62,7 @@ impl PointerHandler for SctkState { if matches!(entry, FrameStatus::Received) { *entry = FrameStatus::Ready; } + if let PointerEventKind::Motion { time } = &e.kind { self.sctk_events.push(SctkEvent::PointerEvent { variant: PointerEvent { diff --git a/winit/src/platform_specific/wayland/handlers/seat/touch.rs b/winit/src/platform_specific/wayland/handlers/seat/touch.rs index 0ac17a5020..f726fd9373 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/touch.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/touch.rs @@ -6,7 +6,6 @@ use crate::{ event_loop::state::SctkState, sctk_event::SctkEvent, }, }; -use iced_runtime::core::{touch, Point}; use cctk::sctk::{ delegate_touch, reexports::client::{ @@ -15,6 +14,7 @@ use cctk::sctk::{ }, seat::touch::TouchHandler, }; +use iced_runtime::core::{touch, Point}; impl TouchHandler for SctkState { fn down( diff --git a/winit/src/platform_specific/wayland/handlers/session_lock.rs b/winit/src/platform_specific/wayland/handlers/session_lock.rs index 04945b3b35..d0eee5efd1 100644 --- a/winit/src/platform_specific/wayland/handlers/session_lock.rs +++ b/winit/src/platform_specific/wayland/handlers/session_lock.rs @@ -1,5 +1,6 @@ -use crate::platform_specific::wayland::{ - handlers::SctkState, sctk_event::SctkEvent, +use crate::{ + event_loop::state::CommonSurface, + platform_specific::wayland::{handlers::SctkState, sctk_event::SctkEvent}, }; use cctk::sctk::{ delegate_session_lock, @@ -15,8 +16,9 @@ impl SessionLockHandler for SctkState { &mut self, _conn: &Connection, _qh: &QueueHandle, - _session_lock: SessionLock, + session_lock: SessionLock, ) { + self.session_lock = Some(session_lock); self.sctk_events.push(SctkEvent::SessionLocked); } @@ -44,8 +46,21 @@ impl SessionLockHandler for SctkState { Some(l) => l, None => return, }; + lock_surface + .update_viewport(configure.new_size.0, configure.new_size.1); + let first = lock_surface.last_configure.is_none(); _ = lock_surface.last_configure.replace(configure.clone()); + + self.sctk_events.push(SctkEvent::SessionLockSurfaceCreated { + queue_handle: self.queue_handle.clone(), + surface: CommonSurface::Lock( + lock_surface.session_lock_surface.clone(), + ), + native_id: lock_surface.id, + common: lock_surface.common.clone(), + display: self.connection.display(), + }); self.sctk_events .push(SctkEvent::SessionLockSurfaceConfigure { surface: session_lock_surface.wl_surface().clone(), diff --git a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs index c48c6bceb3..2e7a58c11e 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs @@ -6,6 +6,7 @@ use cctk::sctk::{ delegate_xdg_popup, reexports::client::Proxy, shell::xdg::popup::PopupHandler, }; +use winit::dpi::LogicalSize; impl PopupHandler for SctkState { fn configure( @@ -24,6 +25,9 @@ impl PopupHandler for SctkState { }; let first = sctk_popup.last_configure.is_none(); _ = sctk_popup.last_configure.replace(configure.clone()); + let mut guard = sctk_popup.common.lock().unwrap(); + guard.size = + LogicalSize::new(configure.width as u32, configure.height as u32); self.sctk_events.push(SctkEvent::PopupEvent { variant: PopupEventVariant::Configure( diff --git a/winit/src/platform_specific/wayland/handlers/toplevel.rs b/winit/src/platform_specific/wayland/handlers/toplevel.rs index 6ff1c3e0e5..62d18687c0 100644 --- a/winit/src/platform_specific/wayland/handlers/toplevel.rs +++ b/winit/src/platform_specific/wayland/handlers/toplevel.rs @@ -1,7 +1,5 @@ use cctk::{ - cosmic_protocols::{ - toplevel_management::v1::client::zcosmic_toplevel_manager_v1, - }, + cosmic_protocols::toplevel_management::v1::client::zcosmic_toplevel_manager_v1, toplevel_info::{ToplevelInfoHandler, ToplevelInfoState}, toplevel_management::ToplevelManagerHandler, wayland_client::{self, WEnum}, diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 16c316c0a7..8e495aecd3 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -24,6 +24,7 @@ use std::{collections::HashMap, sync::Arc}; use subsurface_widget::{SubsurfaceInstance, SubsurfaceState}; use wayland_backend::client::ObjectId; use wayland_client::{Connection, Proxy}; +use winit::dpi::Size; use winit::event_loop::OwnedDisplayHandle; use winit::window::CursorIcon; @@ -34,6 +35,7 @@ pub(crate) enum Action { TrackWindow(Arc, window::Id), RemoveWindow(window::Id), Dropped(SurfaceIdWrapper), + SubsurfaceResize(window::Id, Size), } impl std::fmt::Debug for Action { @@ -53,6 +55,11 @@ impl std::fmt::Debug for Action { f.debug_tuple("RemoveWindow").field(arg0).finish() } Self::Dropped(_surface_id_wrapper) => write!(f, "Dropped"), + Self::SubsurfaceResize(id, size) => f + .debug_tuple("SubsurfaceResize") + .field(id) + .field(size) + .finish(), } } } @@ -87,9 +94,7 @@ impl PlatformSpecific { wayland_display_handle.display.as_ptr().cast(), ) }; - Some(Connection::from_backend( - backend, - )) + Some(Connection::from_backend(backend)) } Ok(_) => { log::error!("Non-Wayland display handle"); @@ -156,8 +161,9 @@ impl WaylandSpecific { (u64, iced_accessibility::accesskit_winit::Adapter), >, ) where - P: Program, + P: 'static + Program, C: Compositor, + P::Message: 'static, { let Self { winit_event_sender, @@ -230,7 +236,9 @@ impl WaylandSpecific { ); } - pub(crate) fn create_surface(&mut self) -> Option> { + pub(crate) fn create_surface( + &mut self, + ) -> Option> { if let Some(subsurface_state) = self.subsurface_state.as_mut() { let wl_surface = subsurface_state.create_surface(); Some(Box::new(Window(wl_surface))) @@ -239,12 +247,32 @@ impl WaylandSpecific { } } - pub(crate) fn update_surface_shm(&mut self, window: &dyn HasWindowHandle, width: u32, height: u32, scale: f64, data: &[u8], offset: Vector) { + pub(crate) fn update_surface_shm( + &mut self, + window: &dyn HasWindowHandle, + width: u32, + height: u32, + scale: f64, + data: &[u8], + offset: Vector, + ) { if let Some(subsurface_state) = self.subsurface_state.as_mut() { - if let RawWindowHandle::Wayland(window) = window.window_handle().unwrap().as_raw() { - let id = unsafe { ObjectId::from_ptr(WlSurface::interface(), window.surface.as_ptr().cast()).unwrap() }; - let surface = WlSurface::from_id(self.conn.as_ref().unwrap(), id).unwrap(); - subsurface_state.update_surface_shm(&surface, width, height, scale, data, offset); + if let RawWindowHandle::Wayland(window) = + window.window_handle().unwrap().as_raw() + { + let id = unsafe { + ObjectId::from_ptr( + WlSurface::interface(), + window.surface.as_ptr().cast(), + ) + .unwrap() + }; + let surface = + WlSurface::from_id(self.conn.as_ref().unwrap(), id) + .unwrap(); + subsurface_state.update_surface_shm( + &surface, width, height, scale, data, offset, + ); } } } @@ -253,13 +281,21 @@ impl WaylandSpecific { struct Window(WlSurface); impl HasWindowHandle for Window { - fn window_handle(&self) -> Result, raw_window_handle::HandleError> { + fn window_handle( + &self, + ) -> Result< + raw_window_handle::WindowHandle<'_>, + raw_window_handle::HandleError, + > { Ok(unsafe { - raw_window_handle::WindowHandle::borrow_raw(raw_window_handle::RawWindowHandle::Wayland( - raw_window_handle::WaylandWindowHandle::new( - std::ptr::NonNull::new(self.0.id().as_ptr() as *mut _).unwrap(), + raw_window_handle::WindowHandle::borrow_raw( + raw_window_handle::RawWindowHandle::Wayland( + raw_window_handle::WaylandWindowHandle::new( + std::ptr::NonNull::new(self.0.id().as_ptr() as *mut _) + .unwrap(), + ), ), - )) + ) }) } } diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index d354171d20..0a557d8395 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -68,8 +68,10 @@ use cctk::{ xdg::{popup::PopupConfigure, window::WindowConfigure}, }, }, + wayland_client::protocol::wl_subsurface::WlSubsurface, }; use std::{ + any::Any, collections::HashMap, num::NonZeroU32, sync::{Arc, Mutex}, @@ -79,7 +81,9 @@ use wayland_protocols::{ wp::viewporter::client::wp_viewport::WpViewport, }; use winit::{ - dpi::PhysicalSize, event::WindowEvent, event_loop::EventLoopProxy, + dpi::{self, PhysicalSize}, + event::WindowEvent, + event_loop::EventLoopProxy, window::WindowId, }; use xkeysym::Keysym; @@ -87,7 +91,9 @@ use xkeysym::Keysym; use super::{ event_loop::state::{Common, CommonSurface, SctkState}, keymap::raw_keycode_to_physicalkey, + subsurface_widget::remove_iced_subsurface, winit_window::SctkWinitWindow, + SubsurfaceInstance, }; #[derive(Debug, Clone)] @@ -160,6 +166,8 @@ pub enum SctkEvent { id: WlSurface, }, + SubsurfaceEvent(SubsurfaceEventVariant), + // // output events // @@ -273,6 +281,26 @@ pub enum PopupEventVariant { ScaleFactorChanged(f64, Option), } +#[derive(Debug, Clone)] +pub enum SubsurfaceEventVariant { + /// Popup Created + Created { + parent_id: window::Id, + parent: WlSurface, + surface: WlSurface, + qh: QueueHandle, + common_surface: CommonSurface, + surface_id: SurfaceId, + common: Arc>, + display: WlDisplay, + z: u32, + }, + /// Destroyed + Destroyed(SubsurfaceInstance), + /// Resized + Resized(SurfaceId, dpi::Size), +} + #[derive(Debug, Clone)] pub enum LayerSurfaceEventVariant { /// sent after creation of the layer surface @@ -333,7 +361,7 @@ impl SctkEvent { (u64, iced_accessibility::accesskit_winit::Adapter), >, ) where - P: Program, + P: 'static + Program, C: Compositor, { match self { @@ -484,6 +512,7 @@ impl SctkEvent { ), ), ), + SurfaceIdWrapper::Subsurface(id) => None, }) { events.push(( @@ -545,6 +574,7 @@ impl SctkEvent { ), ), ), + SurfaceIdWrapper::Subsurface(_) => None, } .map(|e| (Some(id.inner()), e)) }) @@ -567,7 +597,6 @@ impl SctkEvent { let physical_key = raw_keycode_to_physicalkey(ke.raw_code); let physical_key = crate::conversion::physical_key(physical_key); - events.push(( surface_ids.get(&surface.id()).map(|id| id.inner()), iced_runtime::core::Event::Keyboard( @@ -847,19 +876,19 @@ impl SctkEvent { events.push(( Some(id), iced_runtime::core::Event::Window( - window::Event::Resized( - w.state.logical_size(), - ), + window::Event::Opened { + size: w.state.logical_size(), + position: Default::default(), + }, ), )) } else { events.push(( Some(id), iced_runtime::core::Event::Window( - window::Event::Opened { - size: w.state.logical_size(), - position: Default::default(), - }, + window::Event::Resized( + w.state.logical_size(), + ), ), )) } @@ -1058,24 +1087,44 @@ impl SctkEvent { configure.width as f32, configure.height as f32, ); - if let Some(id) = - surface_ids.get(&surface.id()).map(|id| id.inner()) + + if let Some((id, w)) = + surface_ids.get(&surface.id()).and_then(|id| { + window_manager + .get_mut(id.inner()) + .map(|v| (id.inner(), v)) + }) { + let scale = w.state.scale_factor(); + let p_w = (configure.width.max(1) as f64 * scale) + .ceil() + as u32; + let p_h = (configure.height.max(1) as f64 * scale) + .ceil() + as u32; + + w.state.update( + w.raw.as_ref(), + &WindowEvent::SurfaceResized( + PhysicalSize::new(p_w, p_h), + ), + debug, + ); if first { events.push(( Some(id), iced_runtime::core::Event::Window( - window::Event::Resized(size), + window::Event::Opened { + size: size, + position: Default::default(), + }, ), )) } else { events.push(( Some(id), iced_runtime::core::Event::Window( - window::Event::Opened { - size: size, - position: Default::default(), - }, + window::Event::Resized(size), ), )) } @@ -1235,24 +1284,40 @@ impl SctkEvent { configure.new_size.0 as f32, configure.new_size.1 as f32, ); - if let Some(id) = - surface_ids.get(&surface.id()).map(|id| id.inner()) + if let Some((id, w)) = + surface_ids.get(&surface.id()).and_then(|id| { + window_manager + .get_mut(id.inner()) + .map(|v| (id.inner(), v)) + }) { + let scale = w.state.scale_factor(); + let p_w = (configure.new_size.0.max(1) as f64 * scale) + .round() as u32; + let p_h = (configure.new_size.1.max(1) as f64 * scale) + .round() as u32; + w.state.update( + w.raw.as_ref(), + &WindowEvent::SurfaceResized(PhysicalSize::new( + p_w, p_h, + )), + debug, + ); if first { events.push(( Some(id), iced_runtime::core::Event::Window( - window::Event::Resized(size), + window::Event::Opened { + size: size, + position: Default::default(), + }, ), )) } else { events.push(( Some(id), iced_runtime::core::Event::Window( - window::Event::Opened { - size: size, - position: Default::default(), - }, + window::Event::Resized(size), ), )) } @@ -1363,6 +1428,235 @@ impl SctkEvent { )) } } + SctkEvent::SubsurfaceEvent(variant) => match variant { + SubsurfaceEventVariant::Created { + parent_id, + common_surface, + common, + z, + parent, + surface: _, + qh, + surface_id, + display, + } => { + let CommonSurface::Subsurface { + wl_surface, + wl_subsurface, + } = &common_surface + else { + return; + }; + if let Some(subsurface_state) = subsurface_state.as_mut() { + subsurface_state.new_iced_subsurfaces.push(( + parent_id, + parent.id(), + surface_id, + wl_subsurface.clone(), + wl_surface.clone(), + z, + )); + } + + let wrapper = SurfaceIdWrapper::Popup(surface_id); + _ = surface_ids.insert(wl_surface.id(), wrapper.clone()); + let sctk_winit = SctkWinitWindow::new( + sctk_tx.clone(), + common, + wrapper, + common_surface, + display, + qh, + ); + #[cfg(feature = "a11y")] + { + use crate::a11y::*; + use iced_accessibility::accesskit::{ + ActivationHandler, NodeBuilder, NodeId, Role, Tree, + TreeUpdate, + }; + use iced_accessibility::accesskit_winit::Adapter; + + let node_id = iced_runtime::core::id::window_node_id(); + + let activation_handler = WinitActivationHandler { + proxy: control_sender.clone(), + title: String::new(), + }; + + let action_handler = WinitActionHandler { + id: surface_id, + proxy: control_sender.clone(), + }; + + let deactivation_handler = WinitDeactivationHandler { + proxy: control_sender.clone(), + }; + _ = adapters.insert( + surface_id, + ( + node_id, + Adapter::with_direct_handlers( + sctk_winit.as_ref(), + activation_handler, + action_handler, + deactivation_handler, + ), + ), + ); + } + + if clipboard.window_id().is_none() { + *clipboard = Clipboard::connect( + sctk_winit.clone(), + crate::clipboard::ControlSender { + sender: control_sender.clone(), + proxy: proxy.clone(), + }, + ); + } + + let window = window_manager.insert( + surface_id, sctk_winit, program, compositor, + false, // TODO do we want to get this value here? + 0, + ); + let logical_size = window.size(); + + let mut ui = crate::program::build_user_interface( + program, + user_interface::Cache::default(), + &mut window.renderer, + logical_size, + debug, + surface_id, + window.raw.clone(), + window.prev_dnd_destination_rectangles_count, + clipboard, + ); + + _ = ui.update( + &vec![iced_runtime::core::Event::PlatformSpecific( + iced_runtime::core::event::PlatformSpecific::Wayland( + iced_runtime::core::event::wayland::Event::RequestResize, + ), + )], + window.state.cursor(), + &mut window.renderer, + clipboard, + &mut Vec::new(), + ); + + if let Some(requested_size) = + clipboard.requested_logical_size.lock().unwrap().take() + { + let requested_physical_size = + winit::dpi::PhysicalSize::new( + (requested_size.width as f64 + * window.state.scale_factor()) + .ceil() as u32, + (requested_size.height as f64 + * window.state.scale_factor()) + .ceil() as u32, + ); + let physical_size = window.state.physical_size(); + if requested_physical_size.width != physical_size.width + || requested_physical_size.height + != physical_size.height + { + // FIXME what to do when we are stuck in a configure event/resize request loop + // We don't have control over how winit handles this. + window.resize_enabled = true; + + let s = winit::dpi::Size::Physical( + requested_physical_size, + ); + _ = window.raw.request_surface_size(s); + window.raw.set_min_surface_size(Some(s)); + window.raw.set_max_surface_size(Some(s)); + window.state.synchronize( + &program, + surface_id, + window.raw.as_ref(), + ); + } + } + events.push(( + Some(surface_id), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Subsurface( + wayland::SubsurfaceEvent::Created, + ), + ), + ), + )); + let _ = user_interfaces.insert(surface_id, ui); + } + SubsurfaceEventVariant::Destroyed(instance) => { + remove_iced_subsurface(&instance.wl_surface); + + if let Some(id_wrapper) = + surface_ids.remove(&instance.wl_surface.id()) + { + _ = user_interfaces.remove(&id_wrapper.inner()); + + if let Some(w) = + window_manager.remove(id_wrapper.inner()) + { + clipboard.register_dnd_destination( + DndSurface(Arc::new(Box::new(w.raw.clone()))), + Vec::new(), + ); + if clipboard + .window_id() + .is_some_and(|id| w.raw.id() == id) + { + *clipboard = Clipboard::unconnected(); + } + } + events.push(( + Some(id_wrapper.inner()), + iced_runtime::core::Event::PlatformSpecific( + PlatformSpecific::Wayland( + wayland::Event::Subsurface( + wayland::SubsurfaceEvent::Destroyed, + ), + ), + ), + )); + } + if let Some(subsurface_state) = subsurface_state.as_mut() { + subsurface_state.unmapped_subsurfaces.push(instance); + } + } + SubsurfaceEventVariant::Resized(id, size) => { + if let Some((id, w)) = + window_manager.get_mut(id).map(|v| (id, v)) + { + let scale = w.state.scale_factor(); + let physical_size = size.to_physical(scale); + w.state.update( + w.raw.as_ref(), + &WindowEvent::SurfaceResized(PhysicalSize::new( + physical_size.width, + physical_size.height, + )), + debug, + ); + + events.push(( + Some(id), + iced_runtime::core::Event::Window( + window::Event::Opened { + size: w.state.logical_size(), + position: Default::default(), + }, + ), + )) + } + } + }, } } } diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 288dbb6106..402d5d2097 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -7,6 +7,7 @@ use crate::core::{ ContentFit, Element, Length, Rectangle, Size, Vector, }; use std::{ + borrow::BorrowMut, cell::RefCell, collections::HashMap, fmt::Debug, @@ -25,7 +26,6 @@ use cctk::sctk::{ compositor::SurfaceData, error::GlobalError, globals::{GlobalData, ProvidesBoundGlobal}, - shm::slot::SlotPool, reexports::client::{ delegate_noop, protocol::{ @@ -40,6 +40,7 @@ use cctk::sctk::{ }, Connection, Dispatch, Proxy, QueueHandle, }, + shm::slot::SlotPool, }; use iced_futures::core::window; use wayland_backend::client::ObjectId; @@ -56,6 +57,7 @@ use wayland_protocols::wp::{ wp_viewport::WpViewport, wp_viewporter::WpViewporter, }, }; +use winit::window::WindowId; use crate::platform_specific::{ event_loop::state::SctkState, SurfaceIdWrapper, @@ -253,7 +255,6 @@ impl PartialEq for SubsurfaceBuffer { } } - impl Dispatch for SctkState { fn event( _: &mut SctkState, @@ -301,8 +302,7 @@ impl Dispatch for SctkState { _: &QueueHandle, ) { match event { - wl_buffer::Event::Release => { - } + wl_buffer::Event::Release => {} _ => unreachable!(), } } @@ -367,30 +367,56 @@ pub struct SubsurfaceState { pub wp_alpha_modifier: Option, pub qh: QueueHandle, pub(crate) buffers: HashMap>, - pub unmapped_subsurfaces: Vec, + pub(crate) unmapped_subsurfaces: Vec, + pub new_iced_subsurfaces: Vec<( + window::Id, + ObjectId, + window::Id, + WlSubsurface, + WlSurface, + u32, + )>, } impl SubsurfaceState { pub fn create_surface(&self) -> WlSurface { - self - .wl_compositor + self.wl_compositor .create_surface(&self.qh, SurfaceData::new(None, 1)) } - pub fn update_surface_shm(&self, surface: &WlSurface, width: u32, height: u32, scale: f64, data: &[u8], offset: Vector) { + pub fn update_surface_shm( + &self, + surface: &WlSurface, + width: u32, + height: u32, + scale: f64, + data: &[u8], + offset: Vector, + ) { let wp_viewport = self.wp_viewporter.get_viewport( &surface, &self.qh, cctk::sctk::globals::GlobalData, ); let shm = ShmGlobal(&self.wl_shm); - let mut pool = SlotPool::new(width as usize * height as usize * 4, &shm).unwrap(); - let (buffer, canvas) = pool.create_buffer(width as i32, height as i32, width as i32 * 4, wl_shm::Format::Argb8888).unwrap(); + let mut pool = + SlotPool::new(width as usize * height as usize * 4, &shm).unwrap(); + let (buffer, canvas) = pool + .create_buffer( + width as i32, + height as i32, + width as i32 * 4, + wl_shm::Format::Argb8888, + ) + .unwrap(); canvas[0..width as usize * height as usize * 4].copy_from_slice(data); surface.damage_buffer(0, 0, width as i32, height as i32); buffer.attach_to(&surface); surface.offset(offset.x as i32, offset.y as i32); - wp_viewport.set_destination((width as f64 / scale) as i32, (height as f64 / scale) as i32); + wp_viewport.set_destination( + (width as f64 / scale) as i32, + (height as f64 / scale) as i32, + ); surface.commit(); wp_viewport.destroy(); } @@ -431,6 +457,8 @@ impl SubsurfaceState { wl_buffer: None, bounds: None, transform: wl_output::Transform::Normal, + z: 0, + parent: parent.id(), } } @@ -446,6 +474,14 @@ impl SubsurfaceState { // // They should be safe to destroy by the next time `update_subsurfaces` // is run. + ICED_SUBSURFACES.with_borrow_mut(|surfaces| { + surfaces.retain(|s| { + !self + .unmapped_subsurfaces + .iter() + .any(|unmapped| unmapped.wl_surface == s.4) + }) + }); self.unmapped_subsurfaces.clear(); // Remove cached `wl_buffers` for any `BufferSource`s that no longer exist. @@ -464,11 +500,61 @@ impl SubsurfaceState { subsurface.unmap(); self.unmapped_subsurfaces.push(subsurface); } + let needs_sorting = subsurfaces.len() < view_subsurfaces.len() + || !self.new_iced_subsurfaces.is_empty(); + // Create new subsurfaces if there aren't enough. while subsurfaces.len() < view_subsurfaces.len() { subsurfaces.push(self.create_subsurface(parent)); } - // Attach buffers to subsurfaces, set viewports, and commit. + if needs_sorting { + let mut sorted_subsurfaces: Vec<_> = view_subsurfaces + .iter() + .zip(subsurfaces.iter_mut()) + .map(|(_, instance)| { + ( + instance.parent.clone(), + instance.wl_subsurface.clone(), + instance.wl_surface.clone(), + instance.z, + ) + }) + .chain(self.new_iced_subsurfaces.clone().into_iter().map( + |(_, parent, _, wl_subsurface, wl_surface, z)| { + (parent.clone(), wl_subsurface, wl_surface, z) + }, + )) + .chain(ICED_SUBSURFACES.with(|surfaces| { + let b = surfaces.borrow(); + let v: Vec<_> = b + .iter() + .map(move |s| { + (s.1.clone(), s.3.clone(), s.4.clone(), s.5) + }) + .collect(); + v.into_iter() + })) + .collect(); + + sorted_subsurfaces.sort_by(|a, b| a.3.cmp(&b.3)); + + // Attach buffers to subsurfaces, set viewports, and commit. + for i in 1..sorted_subsurfaces.len() { + let prev = &sorted_subsurfaces[0..i]; + let subsurface = &sorted_subsurfaces[i]; + for prev in prev.iter().rev() { + if prev.0 != subsurface.0 { + continue; + } + subsurface.1.place_above(&prev.2); + } + } + } + if !self.new_iced_subsurfaces.is_empty() { + ICED_SUBSURFACES.with(|surfaces| { + surfaces.borrow_mut().append(&mut self.new_iced_subsurfaces); + }) + }; for (subsurface_data, subsurface) in view_subsurfaces.iter().zip(subsurfaces.iter_mut()) { @@ -525,12 +611,14 @@ impl Drop for SubsurfaceState { #[derive(Clone, Debug)] pub(crate) struct SubsurfaceInstance { pub(crate) wl_surface: WlSurface, - wl_subsurface: WlSubsurface, - wp_viewport: WpViewport, - wp_alpha_modifier_surface: Option, - wl_buffer: Option, - bounds: Option>, - transform: wl_output::Transform, + pub(crate) wl_subsurface: WlSubsurface, + pub(crate) wp_viewport: WpViewport, + pub(crate) wp_alpha_modifier_surface: Option, + pub(crate) wl_buffer: Option, + pub(crate) bounds: Option>, + pub(crate) transform: wl_output::Transform, + pub(crate) z: u32, + pub parent: ObjectId, } impl SubsurfaceInstance { @@ -606,6 +694,7 @@ impl SubsurfaceInstance { self.wl_buffer = Some(buffer); self.bounds = Some(info.bounds); self.transform = info.transform; + self.z = info.z; } pub fn unmap(&self) { @@ -631,16 +720,45 @@ pub(crate) struct SubsurfaceInfo { pub bounds: Rectangle, pub alpha: f32, pub transform: wl_output::Transform, + pub z: u32, } thread_local! { static SUBSURFACES: RefCell> = RefCell::new(Vec::new()); + static ICED_SUBSURFACES: RefCell> = RefCell::new(Vec::new()); } pub(crate) fn take_subsurfaces() -> Vec { SUBSURFACES.with(|subsurfaces| mem::take(&mut *subsurfaces.borrow_mut())) } +pub(crate) fn subsurface_ids(parent: WindowId) -> Vec { + ICED_SUBSURFACES.with(|subsurfaces| { + subsurfaces + .borrow_mut() + .iter() + .filter_map(|s| { + if winit::window::WindowId::from(s.1.as_ptr() as u64) == parent + { + Some( + winit::window::WindowId::from(s.4.id().as_ptr() as u64), + ) + } else { + None + } + }) + .collect() + }) +} + +pub(crate) fn remove_iced_subsurface(surface: &WlSurface) { + ICED_SUBSURFACES.with(|surfaces| { + surfaces + .borrow_mut() + .retain(|(_, _, _, _, s, _)| s != surface) + }) +} + #[must_use] pub struct Subsurface { buffer: SubsurfaceBuffer, @@ -649,6 +767,7 @@ pub struct Subsurface { content_fit: ContentFit, alpha: f32, transform: wl_output::Transform, + pub z: u32, } impl Widget for Subsurface @@ -714,6 +833,7 @@ where bounds: layout.bounds(), alpha: self.alpha, transform: self.transform, + z: self.z, }) }); } @@ -729,6 +849,7 @@ impl Subsurface { content_fit: ContentFit::Contain, alpha: 1., transform: wl_output::Transform::Normal, + z: 0, } } @@ -752,6 +873,11 @@ impl Subsurface { self } + pub fn z(mut self, z: u32) -> Self { + self.z = z; + self + } + pub fn transform(mut self, transform: wl_output::Transform) -> Self { self.transform = transform; self diff --git a/winit/src/platform_specific/wayland/winit_window.rs b/winit/src/platform_specific/wayland/winit_window.rs index 8504535ec4..7c89c31896 100644 --- a/winit/src/platform_specific/wayland/winit_window.rs +++ b/winit/src/platform_specific/wayland/winit_window.rs @@ -1,6 +1,6 @@ use crate::platform_specific::wayland::Action; use cctk::sctk::reexports::{ - calloop::channel, + calloop::{channel, LoopHandle}, client::{protocol::wl_display::WlDisplay, Proxy, QueueHandle}, }; use raw_window_handle::HandleError; @@ -99,14 +99,14 @@ impl winit::window::Window for SctkWinitWindow { ) -> Option> { let mut guard = self.common.lock().unwrap(); self.request_redraw(); - let size: LogicalSize = + let logical_size: LogicalSize = size.to_logical(guard.fractional_scale.unwrap_or(1.)); match &self.surface { CommonSurface::Popup(popup, positioner) => { - if size.width == 0 || size.height == 0 { + if logical_size.width == 0 || logical_size.height == 0 { return None; } - guard.size = size; + guard.size = logical_size; guard.requested_size.0 = Some(guard.size.width); guard.requested_size.1 = Some(guard.size.height); positioner.set_size( @@ -134,16 +134,16 @@ impl winit::window::Window for SctkWinitWindow { } CommonSurface::Layer(layer_surface) => { guard.requested_size = ( - (size.width > 0).then_some(size.width), - (size.height > 0).then_some(size.height), + (logical_size.width > 0).then_some(logical_size.width), + (logical_size.height > 0).then_some(logical_size.height), ); - if size.width > 0 { - guard.size.width = size.width; + if logical_size.width > 0 { + guard.size.width = logical_size.width; } - if size.height > 0 { - guard.size.height = size.height; + if logical_size.height > 0 { + guard.size.height = logical_size.height; } - layer_surface.set_size(size.width, size.height); + layer_surface.set_size(logical_size.width, logical_size.height); if let Some(viewport) = guard.wp_viewport.as_ref() { // Set inner size without the borders. viewport.set_destination( @@ -153,6 +153,23 @@ impl winit::window::Window for SctkWinitWindow { } } CommonSurface::Lock(_) => {} + CommonSurface::Subsurface { .. } => { + guard.requested_size = ( + (logical_size.width > 0).then_some(logical_size.width), + (logical_size.height > 0).then_some(logical_size.height), + ); + guard.size = logical_size; + if let Some(viewport) = guard.wp_viewport.as_ref() { + // Set inner size without the borders. + viewport.set_destination( + guard.size.width as i32, + guard.size.height as i32, + ); + } + _ = self + .tx + .send(Action::SubsurfaceResize(self.id.inner(), size)); + } } None } diff --git a/winit/src/program.rs b/winit/src/program.rs index 1640702925..5f64352142 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -2,10 +2,13 @@ #[path = "application/drag_resize.rs"] mod drag_resize; mod state; -mod window_manager; +pub(crate) mod window_manager; +use iced_futures::core::window::Id; pub use runtime::{default, Appearance, DefaultStyle}; +use window_manager::ViewFn; use winit::dpi::PhysicalSize; +use winit::event::WindowEvent; use winit::event_loop::OwnedDisplayHandle; use crate::conversion; @@ -48,6 +51,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::mem::ManuallyDrop; use std::sync::Arc; +use std::sync::Mutex; use std::time::Duration; /// An interactive, native, cross-platform, multi-windowed application. @@ -154,6 +158,10 @@ where fn scale_factor(&self, window: window::Id) -> f64 { 1.0 } + + fn with_program(&self, f: impl Fn(&Self) -> T) -> T { + f(self) + } } /// Runs a [`Program`] with an executor, compositor, and the provided @@ -321,6 +329,29 @@ where | winit::event::WindowEvent::Moved(_) ); + #[cfg(feature = "wayland")] + { + if matches!(event, WindowEvent::RedrawRequested) { + for id in + crate::subsurface_widget::subsurface_ids(window_id) + { + _ = self.sender.start_send(Event::Winit( + id, + WindowEvent::RedrawRequested, + )); + } + } else if matches!(event, WindowEvent::RedrawRequested) { + for id in + crate::subsurface_widget::subsurface_ids(window_id) + { + _ = self.sender.start_send(Event::Winit( + id, + WindowEvent::CloseRequested, + )); + } + } + } + self.process_event( event_loop, Some(Event::Winit(window_id, event)), @@ -603,6 +634,30 @@ where .expect("Send event"); } Control::Winit(id, e) => { + #[cfg(feature = "wayland")] + { + if matches!(e, WindowEvent::RedrawRequested) + { + for id in crate::subsurface_widget::subsurface_ids(id) { + _ = self.sender + .start_send(Event::Winit( + id, + WindowEvent::RedrawRequested, + )); + } + } else if matches!( + e, + WindowEvent::RedrawRequested + ) { + for id in crate::subsurface_widget::subsurface_ids(id) { + _ = self.sender + .start_send(Event::Winit( + id, + WindowEvent::CloseRequested, + )); + } + } + } self.sender .start_send(Event::Winit(id, e)) .expect("Send event"); @@ -796,7 +851,9 @@ async fn run_instance<'a, P, C>( let mut cur_dnd_surface: Option = None; - let mut dnd_surface: Option>> = None; + let mut dnd_surface: Option< + Arc>, + > = None; debug.startup_finished(); loop { @@ -887,91 +944,99 @@ async fn run_instance<'a, P, C>( let state = &window.state; let mut dnd_buffer = None; - let icon_surface = icon_surface - .map(|i| { - let mut icon_surface = i.downcast::(); - - let mut renderer = compositor.create_renderer(); - - let lim = core::layout::Limits::new( - Size::new(1., 1.), - Size::new( - state.viewport().physical_width() - as f32, - state.viewport().physical_height() - as f32, - ), - ); + let icon_surface = icon_surface.map(|i| { + let mut icon_surface = + i.downcast::(); - let mut tree = core::widget::Tree { - id: icon_surface.element.as_widget().id(), - tag: icon_surface.element.as_widget().tag(), - state: icon_surface.state, - children: icon_surface.element.as_widget().children(), - }; + let mut renderer = compositor.create_renderer(); - let size = icon_surface.element + let lim = core::layout::Limits::new( + Size::new(1., 1.), + Size::new( + state.viewport().physical_width() as f32, + state.viewport().physical_height() as f32, + ), + ); + + let mut tree = core::widget::Tree { + id: icon_surface.element.as_widget().id(), + tag: icon_surface.element.as_widget().tag(), + state: icon_surface.state, + children: icon_surface + .element .as_widget() - .layout(&mut tree, &renderer, &lim); - icon_surface.element.as_widget_mut().diff(&mut tree); + .children(), + }; - let size = lim.resolve( - Length::Shrink, - Length::Shrink, - size.size(), - ); - let viewport = Viewport::with_logical_size( - size, - state.viewport().scale_factor(), - ); + let size = icon_surface + .element + .as_widget() + .layout(&mut tree, &renderer, &lim); + icon_surface.element.as_widget_mut().diff(&mut tree); - let mut ui = UserInterface::build( - icon_surface.element, - size, - user_interface::Cache::default(), - &mut renderer, - ); - _ = ui.draw( - &mut renderer, - state.theme(), - &renderer::Style { - icon_color: state.icon_color(), - text_color: state.text_color(), - scale_factor: state.scale_factor(), - }, - Default::default(), - );; - let mut bytes = compositor.screenshot( - &mut renderer, - &viewport, - core::Color::TRANSPARENT, - &debug.overlay(), - ); - for pix in bytes.chunks_exact_mut(4) { - // rgba -> argb little endian - pix.swap(0, 2); - } - // update subsurfaces - if let Some(surface) = platform_specific_handler.create_surface() { - // TODO Remove id - let id = window::Id::unique(); - platform_specific_handler - .update_subsurfaces(id, &surface); - let surface = Arc::new(surface); - dnd_surface = Some(surface.clone()); - dnd_buffer = Some((viewport.physical_size(), state.scale_factor(), bytes, icon_surface.offset)); - Icon::Surface(dnd::DndSurface(surface)) - } else { - platform_specific_handler - .clear_subsurface_list(); - Icon::Buffer { - data: Arc::new(bytes), - width: viewport.physical_width(), - height: viewport.physical_height(), - transparent: true, - } + let size = lim.resolve( + Length::Shrink, + Length::Shrink, + size.size(), + ); + let viewport = Viewport::with_logical_size( + size, + state.viewport().scale_factor(), + ); + + let mut ui = UserInterface::build( + icon_surface.element, + size, + user_interface::Cache::default(), + &mut renderer, + ); + _ = ui.draw( + &mut renderer, + state.theme(), + &renderer::Style { + icon_color: state.icon_color(), + text_color: state.text_color(), + scale_factor: state.scale_factor(), + }, + Default::default(), + ); + let mut bytes = compositor.screenshot( + &mut renderer, + &viewport, + core::Color::TRANSPARENT, + &debug.overlay(), + ); + for pix in bytes.chunks_exact_mut(4) { + // rgba -> argb little endian + pix.swap(0, 2); + } + // update subsurfaces + if let Some(surface) = + platform_specific_handler.create_surface() + { + // TODO Remove id + let id = window::Id::unique(); + platform_specific_handler + .update_subsurfaces(id, &surface); + let surface = Arc::new(surface); + dnd_surface = Some(surface.clone()); + dnd_buffer = Some(( + viewport.physical_size(), + state.scale_factor(), + bytes, + icon_surface.offset, + )); + Icon::Surface(dnd::DndSurface(surface)) + } else { + platform_specific_handler.clear_subsurface_list(); + Icon::Buffer { + data: Arc::new(bytes), + width: viewport.physical_width(), + height: viewport.physical_height(), + transparent: true, } - }); + } + }); clipboard.start_dnd_winit( internal, @@ -982,8 +1047,17 @@ async fn run_instance<'a, P, C>( ); // This needs to be after `wl_data_device::start_drag` for the offset to have an effect - if let (Some(surface), Some((size, scale, bytes, offset))) = (dnd_surface.as_ref(), dnd_buffer) { - platform_specific_handler.update_surface_shm(&surface, size.width, size.height, scale, &bytes, offset); + if let (Some(surface), Some((size, scale, bytes, offset))) = + (dnd_surface.as_ref(), dnd_buffer) + { + platform_specific_handler.update_surface_shm( + &surface, + size.width, + size.height, + scale, + &bytes, + offset, + ); } } } @@ -1182,8 +1256,10 @@ async fn run_instance<'a, P, C>( }, cursor, ); - platform_specific_handler - .update_subsurfaces(id, window.raw.rwh_06_window_handle()); + platform_specific_handler.update_subsurfaces( + id, + window.raw.rwh_06_window_handle(), + ); debug.draw_finished(); if new_mouse_interaction != window.mouse_interaction { @@ -1267,8 +1343,10 @@ async fn run_instance<'a, P, C>( }, window.state.cursor(), ); - platform_specific_handler - .update_subsurfaces(id, window.raw.rwh_06_window_handle()); + platform_specific_handler.update_subsurfaces( + id, + window.raw.rwh_06_window_handle(), + ); debug.draw_finished(); if new_mouse_interaction != window.mouse_interaction @@ -1717,7 +1795,8 @@ async fn run_instance<'a, P, C>( } dnd::DndEvent::Source(evt) => { match evt { - dnd::SourceEvent::Finished | dnd::SourceEvent::Cancelled => { + dnd::SourceEvent::Finished + | dnd::SourceEvent::Cancelled => { dnd_surface = None; } _ => {} diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 85d7254ff0..263af9aa32 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -4,8 +4,9 @@ use crate::core::{Point, Size}; use crate::graphics::Compositor; use crate::program::{DefaultStyle, Program, State}; +use iced_futures::core::Element; use std::collections::BTreeMap; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use winit::monitor::MonitorHandle; #[allow(missing_debug_implementations)] @@ -15,7 +16,7 @@ where C: Compositor, P::Theme: DefaultStyle, { - aliases: BTreeMap, + pub(crate) aliases: BTreeMap, entries: BTreeMap>, } @@ -144,6 +145,10 @@ pub(crate) enum Frame { Ready, } +pub(crate) type ViewFn = Arc< + Box Option> + Send + Sync + 'static>, +>; + #[allow(missing_debug_implementations)] pub struct Window where @@ -200,4 +205,17 @@ where self.raw.request_redraw(); } } + + // pub fn with_view( + // &self, + // f: impl Fn(&ViewFn) -> T, + // ) -> Option + // where + // P::Message: 'static, + // { + // self.view_fn.as_ref().and_then(|m| { + // let g = m.lock().unwrap(); + // g.downcast_ref().map(|v| f(v)) + // }) + // } } From 5b6fa8dedd70a1c1d92883376e3a2a7d2283e0bf Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 18 Mar 2025 16:56:27 -0400 Subject: [PATCH 072/116] fix: create fractional scale for sctk subsurfaces --- .../wayland/event_loop/state.rs | 21 +++++++++++++------ .../wayland/subsurface_widget.rs | 3 +++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 32af0f5754..3b5dd0cc8c 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -519,15 +519,19 @@ impl SctkState { let mut id = None; for subsurface in &self.subsurfaces { - if subsurface.instance.parent != surface.id() { + if subsurface.instance.wl_surface != *surface { continue; } + id = Some(subsurface.id); + if legacy && subsurface.instance.wp_fractional_scale.is_some() { + return; + } + let mut common = subsurface.common.lock().unwrap(); + common.fractional_scale = Some(scale_factor); + if legacy { + subsurface.instance.wl_surface.set_buffer_scale(scale_factor as _); + } - self.sctk_events.push(SctkEvent::SurfaceScaleFactorChanged( - scale_factor, - surface.clone(), - subsurface.id, - )); } if let Some(popup) = self @@ -1576,6 +1580,10 @@ impl SctkState { &self.queue_handle, cctk::sctk::globals::GlobalData, ); + let wp_fractional_scale = + self.fractional_scaling_manager.as_ref().map(|fsm| { + fsm.fractional_scaling(&wl_surface, &self.queue_handle) + }); let wp_alpha_modifier_surface = subsurface_state .wp_alpha_modifier @@ -1596,6 +1604,7 @@ impl SctkState { wl_subsurface: wl_subsurface.clone(), wp_viewport: wp_viewport.clone(), wp_alpha_modifier_surface: wp_alpha_modifier_surface, + wp_fractional_scale, wl_buffer: None, bounds: Some(bounds), diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 402d5d2097..93bbde45b2 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -49,6 +49,7 @@ use wayland_protocols::wp::{ wp_alpha_modifier_surface_v1::WpAlphaModifierSurfaceV1, wp_alpha_modifier_v1::WpAlphaModifierV1, }, + fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1, linux_dmabuf::zv1::client::{ zwp_linux_buffer_params_v1::{self, ZwpLinuxBufferParamsV1}, zwp_linux_dmabuf_v1::{self, ZwpLinuxDmabufV1}, @@ -456,6 +457,7 @@ impl SubsurfaceState { wp_alpha_modifier_surface, wl_buffer: None, bounds: None, + wp_fractional_scale: None, transform: wl_output::Transform::Normal, z: 0, parent: parent.id(), @@ -613,6 +615,7 @@ pub(crate) struct SubsurfaceInstance { pub(crate) wl_surface: WlSurface, pub(crate) wl_subsurface: WlSubsurface, pub(crate) wp_viewport: WpViewport, + pub(crate) wp_fractional_scale: Option, pub(crate) wp_alpha_modifier_surface: Option, pub(crate) wl_buffer: Option, pub(crate) bounds: Option>, From ca0c2c6b7bc335a6c4dee2befb7a4908efb814cf Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 19 Mar 2025 14:29:07 -0600 Subject: [PATCH 073/116] Fix rich text widget capturing all mouse presses --- widget/src/text/rich.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 3d2413752a..db62562bdf 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -374,9 +374,17 @@ where .downcast_mut::>(); if let Some(span) = state.paragraph.hit_span(position) { - state.span_pressed = Some(span); - - return event::Status::Captured; + if self + .spans + .as_ref() + .as_ref() + .get(span) + .map_or(false, |span| span.link.is_some()) + { + state.span_pressed = Some(span); + + return event::Status::Captured; + } } } } From ec3c23d241c13adc8fc4fdcd54575a7c78a9a558 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 19 Mar 2025 14:35:09 -0600 Subject: [PATCH 074/116] Format --- .../wayland/event_loop/state.rs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 3b5dd0cc8c..b83e70ba7b 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -529,9 +529,11 @@ impl SctkState { let mut common = subsurface.common.lock().unwrap(); common.fractional_scale = Some(scale_factor); if legacy { - subsurface.instance.wl_surface.set_buffer_scale(scale_factor as _); + subsurface + .instance + .wl_surface + .set_buffer_scale(scale_factor as _); } - } if let Some(popup) = self @@ -1010,14 +1012,14 @@ impl SctkState { platform_specific::wayland::layer_surface::Action::Destroy(id) => { if let Some(i) = self.layer_surfaces.iter().position(|l| l.id == id) { let l = self.layer_surfaces.remove(i); - + let (removed, remaining): (Vec<_>, Vec<_>) = self .subsurfaces .drain(..) .partition(|s| { s.instance.parent == l.surface.wl_surface().id() }); - + self.subsurfaces = remaining; for s in removed { @@ -1238,18 +1240,17 @@ impl SctkState { if let Some(id) = self.id_map.remove(&popup.popup.wl_surface().id()) { _ = self.destroyed.insert(id); } - + let (removed, remaining): (Vec<_>, Vec<_>) = self .subsurfaces .drain(..) .partition(|s| { s.instance.parent == popup.popup.wl_surface().id() }); - + self.subsurfaces = remaining; for s in removed { - crate::subsurface_widget::remove_iced_subsurface( &s.instance.wl_surface, ); @@ -1367,11 +1368,10 @@ impl SctkState { .partition(|s| { s.instance.parent == surface.session_lock_surface.wl_surface().id() }); - + self.subsurfaces = remaining; for s in removed { - crate::subsurface_widget::remove_iced_subsurface( &s.instance.wl_surface, ); @@ -1580,10 +1580,10 @@ impl SctkState { &self.queue_handle, cctk::sctk::globals::GlobalData, ); - let wp_fractional_scale = - self.fractional_scaling_manager.as_ref().map(|fsm| { - fsm.fractional_scaling(&wl_surface, &self.queue_handle) - }); + let wp_fractional_scale = self + .fractional_scaling_manager + .as_ref() + .map(|fsm| fsm.fractional_scaling(&wl_surface, &self.queue_handle)); let wp_alpha_modifier_surface = subsurface_state .wp_alpha_modifier From 28b23af0584ad904a299cc14a24d6134bb1921e7 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 19 Mar 2025 23:11:48 -0400 Subject: [PATCH 075/116] refactor: include virtual offset in Layout --- core/src/layout.rs | 16 ++++ core/src/overlay.rs | 11 ++- widget/src/button.rs | 33 +++++-- widget/src/column.rs | 43 ++++++--- widget/src/container.rs | 38 ++++++-- widget/src/keyed/column.rs | 37 +++++--- widget/src/pane_grid.rs | 32 ++++--- widget/src/pane_grid/title_bar.rs | 50 +++++++---- widget/src/row.rs | 43 ++++++--- widget/src/scrollable.rs | 142 +++++++++++++++++------------- 10 files changed, 302 insertions(+), 143 deletions(-) diff --git a/core/src/layout.rs b/core/src/layout.rs index 98d056029f..be5aeb4833 100644 --- a/core/src/layout.rs +++ b/core/src/layout.rs @@ -12,6 +12,9 @@ use crate::{Length, Padding, Point, Rectangle, Size, Vector}; /// The bounds of a [`Node`] and its children, using absolute coordinates. #[derive(Debug, Clone, Copy)] pub struct Layout<'a> { + /// The virtual offset of the layout. + /// May represent the scroll positions in pixels of a scrollable, for example. + virtual_offset: Vector, position: Point, node: &'a Node, } @@ -28,16 +31,29 @@ impl<'a> Layout<'a> { let bounds = node.bounds(); Self { + virtual_offset: Vector::new(0., 0.), position: Point::new(bounds.x, bounds.y) + offset, node, } } + /// Returns a new layout with the virtual offset + pub fn with_virtual_offset(mut self, virtual_offset: Vector) -> Self { + self.virtual_offset = virtual_offset; + self + } + /// Returns the position of the [`Layout`]. pub fn position(&self) -> Point { self.position } + /// The virtual offset of the layout. + /// May represent the scroll positions in pixels of a scrollable, for example. + pub fn virtual_offset(&self) -> Vector { + self.virtual_offset + } + /// Returns the bounds of the [`Layout`]. /// /// The returned [`Rectangle`] describes the position and size of a diff --git a/core/src/overlay.rs b/core/src/overlay.rs index b8ce136f56..a3759a1ba1 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -122,10 +122,13 @@ where .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .filter_map(|((child, state), layout)| { - child - .as_widget_mut() - .overlay(state, layout, renderer, translation) + .filter_map(|((child, state), c_layout)| { + child.as_widget_mut().overlay( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + translation, + ) }) .collect::>(); diff --git a/widget/src/button.rs b/widget/src/button.rs index 012904dc72..82e1e590c9 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -329,7 +329,11 @@ where operation.container(None, layout.bounds(), &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -350,7 +354,11 @@ where if let event::Status::Captured = self.content.as_widget_mut().on_event( &mut tree.children[0], event.clone(), - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -450,7 +458,11 @@ where viewport: &Rectangle, ) { let bounds = layout.bounds(); - let content_layout = layout.children().next().unwrap(); + let content_layout = layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()); let is_mouse_over = cursor.is_over(bounds); let status = if self.on_press.is_none() { @@ -534,7 +546,11 @@ where ) -> Option> { self.content.as_widget_mut().overlay( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, translation, ) @@ -557,10 +573,11 @@ where let child_layout = layout.children().next().unwrap(); let child_tree = &state.children[0]; - let child_tree = - self.content - .as_widget() - .a11y_nodes(child_layout, child_tree, p); + let child_tree = self.content.as_widget().a11y_nodes( + child_layout.with_virtual_offset(layout.virtual_offset()), + child_tree, + p, + ); let Rectangle { x, diff --git a/widget/src/column.rs b/widget/src/column.rs index ec0c059c28..acecee756d 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -250,10 +250,13 @@ where .iter() .zip(&mut tree.children) .zip(layout.children()) - .for_each(|((child, state), layout)| { - child - .as_widget() - .operate(state, layout, renderer, operation); + .for_each(|((child, state), c_layout)| { + child.as_widget().operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); }); } @@ -273,11 +276,11 @@ where .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { + .map(|((child, state), c_layout)| { child.as_widget_mut().on_event( state, event.clone(), - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -300,9 +303,13 @@ where .iter() .zip(&tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { + .map(|((child, state), c_layout)| { child.as_widget().mouse_interaction( - state, layout, cursor, viewport, renderer, + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, ) }) .max() @@ -326,7 +333,7 @@ where viewport }; - for ((child, state), layout) in self + for ((child, state), c_layout) in self .children .iter() .zip(&tree.children) @@ -334,7 +341,13 @@ where .filter(|(_, layout)| layout.bounds().intersects(viewport)) { child.as_widget().draw( - state, renderer, theme, style, layout, cursor, viewport, + state, + renderer, + theme, + style, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, ); } } @@ -371,7 +384,11 @@ where .zip(layout.children()) .zip(state.children.iter()) .map(|((c, c_layout), state)| { - c.as_widget().a11y_nodes(c_layout, state, cursor) + c.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + cursor, + ) }), ) } @@ -383,7 +400,7 @@ where renderer: &Renderer, dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, ) { - for ((e, layout), state) in self + for ((e, c_layout), state) in self .children .iter() .zip(layout.children()) @@ -391,7 +408,7 @@ where { e.as_widget().drag_destinations( state, - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); diff --git a/widget/src/container.rs b/widget/src/container.rs index dbc3e8fd82..ff0a31d6d4 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -282,7 +282,11 @@ where &mut |operation| { self.content.as_widget().operate( tree, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -304,7 +308,11 @@ where self.content.as_widget_mut().on_event( tree, event, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -323,7 +331,11 @@ where ) -> mouse::Interaction { self.content.as_widget().mouse_interaction( tree, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -359,7 +371,11 @@ where .unwrap_or(renderer_style.text_color), scale_factor: renderer_style.scale_factor, }, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), cursor, if self.clip { &clipped_viewport @@ -379,7 +395,11 @@ where ) -> Option> { self.content.as_widget_mut().overlay( tree, - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(layout.virtual_offset()), renderer, translation, ) @@ -395,7 +415,11 @@ where ) -> iced_accessibility::A11yTree { let c_layout = layout.children().next().unwrap(); - self.content.as_widget().a11y_nodes(c_layout, state, cursor) + self.content.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + cursor, + ) } fn drag_destinations( @@ -408,7 +432,7 @@ where if let Some(l) = layout.children().next() { self.content.as_widget().drag_destinations( state, - l, + l.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 6ec5ca4cf3..cdc98440c5 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -292,10 +292,13 @@ where .iter() .zip(&mut tree.children) .zip(layout.children()) - .for_each(|((child, state), layout)| { - child - .as_widget() - .operate(state, layout, renderer, operation); + .for_each(|((child, state), c_layout)| { + child.as_widget().operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); }); } @@ -315,11 +318,11 @@ where .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { + .map(|((child, state), c_layout)| { child.as_widget_mut().on_event( state, event.clone(), - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -342,9 +345,13 @@ where .iter() .zip(&tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { + .map(|((child, state), c_layout)| { child.as_widget().mouse_interaction( - state, layout, cursor, viewport, renderer, + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, ) }) .max() @@ -361,15 +368,21 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - for ((child, state), layout) in self + for ((child, state), c_layout) in self .children .iter() .zip(&tree.children) .zip(layout.children()) { - child - .as_widget() - .draw(state, renderer, theme, style, layout, cursor, viewport); + child.as_widget().draw( + state, + renderer, + theme, + style, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + ); } } diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 383957c4cc..41c56ddf8f 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -419,8 +419,13 @@ where .maximized() .map_or(true, |maximized| *pane == maximized) }) - .for_each(|(((_, content), state), layout)| { - content.operate(state, layout, renderer, operation); + .for_each(|(((_, content), state), c_layout)| { + content.operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); }); } @@ -624,13 +629,13 @@ where .maximized() .map_or(true, |maximized| *pane == maximized) }) - .map(|(((pane, content), tree), layout)| { + .map(|(((pane, content), tree), c_layout)| { let is_picked = picked_pane == Some(pane); content.on_event( tree, event.clone(), - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -700,10 +705,10 @@ where .maximized() .map_or(true, |maximized| *pane == maximized) }) - .map(|(((_, content), tree), layout)| { + .map(|(((_, content), tree), c_layout)| { content.mouse_interaction( tree, - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -820,7 +825,8 @@ where renderer, theme, defaults, - pane_layout, + pane_layout + .with_virtual_offset(layout.virtual_offset()), pane_cursor, viewport, ); @@ -851,7 +857,8 @@ where renderer, theme, defaults, - pane_layout, + pane_layout + .with_virtual_offset(layout.virtual_offset()), pane_cursor, viewport, ); @@ -948,7 +955,7 @@ where .zip(&mut self.contents) .zip(&mut tree.children) .zip(layout.children()) - .filter_map(|(((pane, content), state), layout)| { + .filter_map(|(((pane, content), state), c_layout)| { if self .internal .maximized() @@ -957,7 +964,12 @@ where return None; } - content.overlay(state, layout, renderer, translation) + content.overlay( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + translation, + ) }) .collect::>(); diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 54b6390bad..c4a2b7a007 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -191,7 +191,8 @@ where renderer, theme, &inherited_style, - compact_layout, + compact_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, ); @@ -203,7 +204,8 @@ where renderer, theme, &inherited_style, - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, ); @@ -214,7 +216,8 @@ where renderer, theme, &inherited_style, - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, ); @@ -228,7 +231,7 @@ where renderer, theme, &inherited_style, - title_layout, + title_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, ); @@ -396,7 +399,8 @@ where compact.as_widget().operate( &mut tree.children[2], - compact_layout, + compact_layout + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -405,7 +409,8 @@ where controls.full.as_widget().operate( &mut tree.children[1], - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -413,7 +418,8 @@ where } else { controls.full.as_widget().operate( &mut tree.children[1], - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), renderer, operation, ); @@ -459,7 +465,8 @@ where compact.as_widget_mut().on_event( &mut tree.children[2], event.clone(), - compact_layout, + compact_layout + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -472,7 +479,8 @@ where controls.full.as_widget_mut().on_event( &mut tree.children[1], event.clone(), - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -484,7 +492,8 @@ where controls.full.as_widget_mut().on_event( &mut tree.children[1], event.clone(), - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -500,7 +509,7 @@ where self.content.as_widget_mut().on_event( &mut tree.children[0], event, - title_layout, + title_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -530,7 +539,7 @@ where let title_interaction = self.content.as_widget().mouse_interaction( &tree.children[0], - title_layout, + title_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -541,7 +550,8 @@ where let controls_interaction = controls.full.as_widget().mouse_interaction( &tree.children[1], - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -555,7 +565,8 @@ where let compact_interaction = compact.as_widget().mouse_interaction( &tree.children[2], - compact_layout, + compact_layout + .with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, @@ -611,14 +622,18 @@ where compact.as_widget_mut().overlay( compact_state, - compact_layout, + compact_layout.with_virtual_offset( + layout.virtual_offset(), + ), renderer, translation, ) } else { controls.full.as_widget_mut().overlay( controls_state, - controls_layout, + controls_layout.with_virtual_offset( + layout.virtual_offset(), + ), renderer, translation, ) @@ -626,7 +641,8 @@ where } else { controls.full.as_widget_mut().overlay( controls_state, - controls_layout, + controls_layout + .with_virtual_offset(layout.virtual_offset()), renderer, translation, ) diff --git a/widget/src/row.rs b/widget/src/row.rs index 315243c370..5877e02544 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -246,10 +246,13 @@ where .iter() .zip(&mut tree.children) .zip(layout.children()) - .for_each(|((child, state), layout)| { - child - .as_widget() - .operate(state, layout, renderer, operation); + .for_each(|((child, state), c_layout)| { + child.as_widget().operate( + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + renderer, + operation, + ); }); }); } @@ -269,11 +272,11 @@ where .iter_mut() .zip(&mut tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { + .map(|((child, state), c_layout)| { child.as_widget_mut().on_event( state, event.clone(), - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, @@ -296,9 +299,13 @@ where .iter() .zip(&tree.children) .zip(layout.children()) - .map(|((child, state), layout)| { + .map(|((child, state), c_layout)| { child.as_widget().mouse_interaction( - state, layout, cursor, viewport, renderer, + state, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, + renderer, ) }) .max() @@ -322,7 +329,7 @@ where viewport }; - for ((child, state), layout) in self + for ((child, state), c_layout) in self .children .iter() .zip(&tree.children) @@ -330,7 +337,13 @@ where .filter(|(_, layout)| layout.bounds().intersects(viewport)) { child.as_widget().draw( - state, renderer, theme, style, layout, cursor, viewport, + state, + renderer, + theme, + style, + c_layout.with_virtual_offset(layout.virtual_offset()), + cursor, + viewport, ); } } @@ -367,7 +380,11 @@ where .zip(layout.children()) .zip(state.children.iter()) .map(|((c, c_layout), state)| { - c.as_widget().a11y_nodes(c_layout, state, cursor) + c.as_widget().a11y_nodes( + c_layout.with_virtual_offset(layout.virtual_offset()), + state, + cursor, + ) }), ) } @@ -379,7 +396,7 @@ where renderer: &Renderer, dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles, ) { - for ((e, layout), state) in self + for ((e, c_layout), state) in self .children .iter() .zip(layout.children()) @@ -387,7 +404,7 @@ where { e.as_widget().drag_destinations( state, - layout, + c_layout.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 87bcf9319c..130fe8da7a 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -549,7 +549,11 @@ where operation.container(Some(&self.id), bounds, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(translation + layout.virtual_offset()), renderer, operation, ); @@ -797,7 +801,8 @@ where self.content.as_widget_mut().on_event( &mut tree.children[0], c_event, - content, + content + .with_virtual_offset(translation + layout.virtual_offset()), cursor, renderer, clipboard, @@ -1034,7 +1039,9 @@ where renderer, theme, defaults, - content_layout, + content_layout.with_virtual_offset( + translation + layout.virtual_offset(), + ), cursor, &Rectangle { y: bounds.y + translation.y, @@ -1189,7 +1196,8 @@ where self.content.as_widget().mouse_interaction( &tree.children[0], - content_layout, + content_layout + .with_virtual_offset(translation + layout.virtual_offset()), cursor, &Rectangle { y: bounds.y + translation.y, @@ -1220,7 +1228,11 @@ where self.content.as_widget_mut().overlay( &mut tree.children[0], - layout.children().next().unwrap(), + layout + .children() + .next() + .unwrap() + .with_virtual_offset(translation + layout.virtual_offset()), renderer, translation - offset, ) @@ -1237,15 +1249,9 @@ where accesskit::{NodeBuilder, NodeId, Rect, Role}, A11yId, A11yNode, A11yTree, }; - - let child_layout = layout.children().next().unwrap(); - let child_tree = &state.children[0]; - let child_tree = self.content.as_widget().a11y_nodes( - child_layout, - &child_tree, - cursor, - ); - + if !matches!(state.state, tree::State::Some(_)) { + return A11yTree::default(); + } let window = layout.bounds(); let is_hovered = cursor.is_over(window); let Rectangle { @@ -1254,6 +1260,25 @@ where width, height, } = window; + + let my_state = state.state.downcast_ref::(); + let content = layout.children().next().unwrap(); + let content_bounds = content.bounds(); + + let translation = my_state.translation( + self.direction, + layout.bounds(), + content_bounds, + ); + + let child_layout = layout.children().next().unwrap(); + let child_tree = &state.children[0]; + let child_tree = self.content.as_widget().a11y_nodes( + child_layout + .with_virtual_offset(translation + layout.virtual_offset()), + &child_tree, + cursor, + ); let bounds = Rect::new( x as f64, y as f64, @@ -1288,53 +1313,46 @@ where node.set_labelled_by(label.clone()); } - let content = layout.children().next().unwrap(); - let content_bounds = content.bounds(); - let mut scrollbar_node = NodeBuilder::new(Role::ScrollBar); - if matches!(state.state, tree::State::Some(_)) { - let state = state.state.downcast_ref::(); - let scrollbars = Scrollbars::new( - state, - self.direction, - content_bounds, - content_bounds, + + let scrollbars = Scrollbars::new( + my_state, + self.direction, + content_bounds, + content_bounds, + ); + for (window, content, offset, scrollbar) in scrollbars + .x + .iter() + .map(|s| (window.width, content_bounds.width, my_state.offset_x, s)) + .chain(scrollbars.y.iter().map(|s| { + (window.height, content_bounds.height, my_state.offset_y, s) + })) + { + let scrollbar_bounds = scrollbar.total_bounds; + let is_hovered = cursor.is_over(scrollbar_bounds); + let Rectangle { + x, + y, + width, + height, + } = scrollbar_bounds; + let bounds = Rect::new( + x as f64, + y as f64, + (x + width) as f64, + (y + height) as f64, ); - for (window, content, offset, scrollbar) in scrollbars - .x - .iter() - .map(|s| { - (window.width, content_bounds.width, state.offset_x, s) - }) - .chain(scrollbars.y.iter().map(|s| { - (window.height, content_bounds.height, state.offset_y, s) - })) - { - let scrollbar_bounds = scrollbar.total_bounds; - let is_hovered = cursor.is_over(scrollbar_bounds); - let Rectangle { - x, - y, - width, - height, - } = scrollbar_bounds; - let bounds = Rect::new( - x as f64, - y as f64, - (x + width) as f64, - (y + height) as f64, - ); - scrollbar_node.set_bounds(bounds); - if is_hovered { - scrollbar_node.set_hovered(); - } - scrollbar_node - .set_controls(vec![A11yId::Widget(self.id.clone()).into()]); - scrollbar_node.set_numeric_value( - 100.0 * offset.absolute(window, content) as f64 - / scrollbar_bounds.height as f64, - ); + scrollbar_node.set_bounds(bounds); + if is_hovered { + scrollbar_node.set_hovered(); } + scrollbar_node + .set_controls(vec![A11yId::Widget(self.id.clone()).into()]); + scrollbar_node.set_numeric_value( + 100.0 * offset.absolute(window, content) as f64 + / scrollbar_bounds.height as f64, + ); } let child_tree = A11yTree::join( @@ -1378,9 +1396,15 @@ where layout.children().zip(tree.children.iter()).next() { let mut my_dnd_rectangles = DndDestinationRectangles::new(); + let translation = my_state.translation( + self.direction, + layout.bounds(), + c_layout.bounds(), + ); self.content.as_widget().drag_destinations( c_state, - c_layout, + c_layout + .with_virtual_offset(translation + layout.virtual_offset()), renderer, &mut my_dnd_rectangles, ); From 411a879e74206616ecb7f56cd2162cf3463398e8 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 26 Mar 2025 15:49:22 -0700 Subject: [PATCH 076/116] subsurface_widget: Fix use of `place_above` `place_above` moves the subsurface within the stack of subsurfaces, so calling it for all previous surfaces is wrong. We also need to use the `z` from `view_subsurfaces` if this is called before `attach_and_commit`, or an old value will be used. --- .../platform_specific/wayland/subsurface_widget.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 93bbde45b2..153f92beba 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -513,12 +513,14 @@ impl SubsurfaceState { let mut sorted_subsurfaces: Vec<_> = view_subsurfaces .iter() .zip(subsurfaces.iter_mut()) - .map(|(_, instance)| { + .map(|(subsurface_info, instance)| { ( instance.parent.clone(), instance.wl_subsurface.clone(), instance.wl_surface.clone(), - instance.z, + // Use from `view_subsurfaces`; not updated in `subsurfaces` + // until `attach_and_commit` + subsurface_info.z, ) }) .chain(self.new_iced_subsurfaces.clone().into_iter().map( @@ -545,10 +547,10 @@ impl SubsurfaceState { let prev = &sorted_subsurfaces[0..i]; let subsurface = &sorted_subsurfaces[i]; for prev in prev.iter().rev() { - if prev.0 != subsurface.0 { - continue; + if prev.0 == subsurface.0 { + subsurface.1.place_above(&prev.2); + break; } - subsurface.1.place_above(&prev.2); } } } From 6d6b3b46107d9ce8ee8802d091fe2c0a0a643690 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 27 Mar 2025 12:10:32 -0700 Subject: [PATCH 077/116] subsurface_widget: Sort parent `WlSurface`, not `ObjectId` --- .../src/platform_specific/wayland/event_loop/mod.rs | 2 +- .../platform_specific/wayland/event_loop/state.rs | 10 +++++----- .../wayland/handlers/seat/keyboard.rs | 12 ++++++------ winit/src/platform_specific/wayland/sctk_event.rs | 2 +- .../platform_specific/wayland/subsurface_widget.rs | 11 +++++------ 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index 09127ee11e..ce2755caf8 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -134,7 +134,7 @@ impl SctkEventLoop { .enumerate() .filter_map(|(i, s)| { (winit::window::WindowId::from( - s.instance.parent.as_ptr() + s.instance.parent.id().as_ptr() as u64, ) == w.window.id()) .then_some(i) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index b83e70ba7b..475b02b8a9 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1017,7 +1017,7 @@ impl SctkState { .subsurfaces .drain(..) .partition(|s| { - s.instance.parent == l.surface.wl_surface().id() + s.instance.parent == *l.surface.wl_surface() }); self.subsurfaces = remaining; @@ -1245,7 +1245,7 @@ impl SctkState { .subsurfaces .drain(..) .partition(|s| { - s.instance.parent == popup.popup.wl_surface().id() + s.instance.parent == *popup.popup.wl_surface() }); self.subsurfaces = remaining; @@ -1366,7 +1366,7 @@ impl SctkState { .subsurfaces .drain(..) .partition(|s| { - s.instance.parent == surface.session_lock_surface.wl_surface().id() + s.instance.parent == *surface.session_lock_surface.wl_surface() }); self.subsurfaces = remaining; @@ -1444,7 +1444,7 @@ impl SctkState { for (destroyed, parent) in destroyed { if let Some((wl_surface, f)) = self.seats.iter_mut().find(|f| { f.kbd_focus.as_ref().is_some_and(|f| *f == destroyed) - }).and_then(|f| WlSurface::from_id(&self.connection, parent).ok().map(|wl| (wl, &mut f.kbd_focus))) { + }).and_then(|f| Some((parent, &mut f.kbd_focus))) { *f = Some(wl_surface); } } @@ -1611,7 +1611,7 @@ impl SctkState { transform: cctk::wayland_client::protocol::wl_output::Transform::Normal, z: settings.z, - parent: parent_wl_surface.id(), + parent: parent_wl_surface.clone(), }; common.wp_viewport = Some(wp_viewport); let common = Arc::new(Mutex::new(common)); diff --git a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs index 199c3b2847..59043d51a9 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/keyboard.rs @@ -34,7 +34,7 @@ impl KeyboardHandler for SctkState { let surface = if let Some(subsurface) = self.subsurfaces.iter().find(|s| { - s.steals_keyboard_focus && s.instance.parent == surface.id() + s.steals_keyboard_focus && s.instance.parent == *surface }) { &subsurface.instance.wl_surface } else { @@ -53,7 +53,7 @@ impl KeyboardHandler for SctkState { self.request_redraw(&surface); let surfaces = self.subsurfaces.iter().filter_map(|s| { - (s.instance.parent == surface.id()).then(|| &s.instance.wl_surface) + (s.instance.parent == *surface).then(|| &s.instance.wl_surface) }); for surface in surfaces.chain(std::iter::once(surface)) { if is_active { @@ -103,7 +103,7 @@ impl KeyboardHandler for SctkState { (is_active, seat, kbd) }; let surfaces = self.subsurfaces.iter().filter_map(|s| { - (s.instance.parent == surface.id()).then(|| &s.instance.wl_surface) + (s.instance.parent == *surface).then(|| &s.instance.wl_surface) }); for surface in surfaces.chain(std::iter::once(surface)) { if is_active { @@ -168,7 +168,7 @@ impl KeyboardHandler for SctkState { if let Some(surface) = my_seat.kbd_focus.clone() { self.request_redraw(&surface); let surfaces = self.subsurfaces.iter().filter_map(|s| { - (s.instance.parent == surface.id()) + (s.instance.parent == surface) .then(|| &s.instance.wl_surface) }); for surface in surfaces.chain(std::iter::once(&surface)) { @@ -209,7 +209,7 @@ impl KeyboardHandler for SctkState { if let Some(surface) = my_seat.kbd_focus.clone() { self.request_redraw(&surface); let surfaces = self.subsurfaces.iter().filter_map(|s| { - (s.instance.parent == surface.id()) + (s.instance.parent == surface) .then(|| &s.instance.wl_surface) }); for surface in surfaces.chain(std::iter::once(&surface)) { @@ -251,7 +251,7 @@ impl KeyboardHandler for SctkState { if let Some(surface) = my_seat.kbd_focus.clone() { self.request_redraw(&surface); let surfaces = self.subsurfaces.iter().filter_map(|s| { - (s.instance.parent == surface.id()) + (s.instance.parent == surface) .then(|| &s.instance.wl_surface) }); for surface in surfaces.chain(std::iter::once(&surface)) { diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 0a557d8395..51d5d55583 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -1450,7 +1450,7 @@ impl SctkEvent { if let Some(subsurface_state) = subsurface_state.as_mut() { subsurface_state.new_iced_subsurfaces.push(( parent_id, - parent.id(), + parent, surface_id, wl_subsurface.clone(), wl_surface.clone(), diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 153f92beba..396f9506ef 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -43,7 +43,6 @@ use cctk::sctk::{ shm::slot::SlotPool, }; use iced_futures::core::window; -use wayland_backend::client::ObjectId; use wayland_protocols::wp::{ alpha_modifier::v1::client::{ wp_alpha_modifier_surface_v1::WpAlphaModifierSurfaceV1, @@ -371,7 +370,7 @@ pub struct SubsurfaceState { pub(crate) unmapped_subsurfaces: Vec, pub new_iced_subsurfaces: Vec<( window::Id, - ObjectId, + WlSurface, window::Id, WlSubsurface, WlSurface, @@ -460,7 +459,7 @@ impl SubsurfaceState { wp_fractional_scale: None, transform: wl_output::Transform::Normal, z: 0, - parent: parent.id(), + parent: parent.clone(), } } @@ -623,7 +622,7 @@ pub(crate) struct SubsurfaceInstance { pub(crate) bounds: Option>, pub(crate) transform: wl_output::Transform, pub(crate) z: u32, - pub parent: ObjectId, + pub parent: WlSurface, } impl SubsurfaceInstance { @@ -730,7 +729,7 @@ pub(crate) struct SubsurfaceInfo { thread_local! { static SUBSURFACES: RefCell> = RefCell::new(Vec::new()); - static ICED_SUBSURFACES: RefCell> = RefCell::new(Vec::new()); + static ICED_SUBSURFACES: RefCell> = RefCell::new(Vec::new()); } pub(crate) fn take_subsurfaces() -> Vec { @@ -743,7 +742,7 @@ pub(crate) fn subsurface_ids(parent: WindowId) -> Vec { .borrow_mut() .iter() .filter_map(|s| { - if winit::window::WindowId::from(s.1.as_ptr() as u64) == parent + if winit::window::WindowId::from(s.1.id().as_ptr() as u64) == parent { Some( winit::window::WindowId::from(s.4.id().as_ptr() as u64), From 84e7729c549a6018ac04b4afc69389c43a9891f4 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 27 Mar 2025 12:35:25 -0700 Subject: [PATCH 078/116] subsurface_widget: Use neagtive `z` to place below parent surface Now uses `i32` for `z`. --- .../platform_specific/wayland/subsurface.rs | 2 +- .../platform_specific/wayland/sctk_event.rs | 2 +- .../wayland/subsurface_widget.rs | 36 ++++++++++++------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/runtime/src/platform_specific/wayland/subsurface.rs b/runtime/src/platform_specific/wayland/subsurface.rs index 0fb5130d1f..0356d28d2f 100644 --- a/runtime/src/platform_specific/wayland/subsurface.rs +++ b/runtime/src/platform_specific/wayland/subsurface.rs @@ -23,7 +23,7 @@ pub struct SctkSubsurfaceSettings { pub size: Option, // pub subsurface_view: Option>, /// Z - pub z: u32, + pub z: i32, /// Steal Keyboard focus from parent while open. /// Will not work on a regular window. pub steal_keyboard_focus: bool, diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 51d5d55583..ac4c245a74 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -293,7 +293,7 @@ pub enum SubsurfaceEventVariant { surface_id: SurfaceId, common: Arc>, display: WlDisplay, - z: u32, + z: i32, }, /// Destroyed Destroyed(SubsurfaceInstance), diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 396f9506ef..54b0c94b45 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -374,7 +374,7 @@ pub struct SubsurfaceState { window::Id, WlSubsurface, WlSurface, - u32, + i32, )>, } @@ -542,15 +542,26 @@ impl SubsurfaceState { sorted_subsurfaces.sort_by(|a, b| a.3.cmp(&b.3)); // Attach buffers to subsurfaces, set viewports, and commit. - for i in 1..sorted_subsurfaces.len() { - let prev = &sorted_subsurfaces[0..i]; - let subsurface = &sorted_subsurfaces[i]; - for prev in prev.iter().rev() { + 'outer: for (i, subsurface) in sorted_subsurfaces.iter().enumerate() { + for prev in sorted_subsurfaces[0..i].iter().rev() { if prev.0 == subsurface.0 { - subsurface.1.place_above(&prev.2); - break; + // Fist surface that has `z` greater than 0, so place above parent, + // rather than previous subsurface. + if prev.3 < 0 && subsurface.3 >= 0 { + subsurface.1.place_above(&subsurface.0); + } else { + subsurface.1.place_above(&prev.2); + } + continue 'outer; } } + // No previous surface with same parent + if subsurface.3 < 0 { + // Place below parent if z < 0 + subsurface.1.place_below(&subsurface.0); + } else { + subsurface.1.place_above(&subsurface.0); + } } } if !self.new_iced_subsurfaces.is_empty() { @@ -621,7 +632,7 @@ pub(crate) struct SubsurfaceInstance { pub(crate) wl_buffer: Option, pub(crate) bounds: Option>, pub(crate) transform: wl_output::Transform, - pub(crate) z: u32, + pub(crate) z: i32, pub parent: WlSurface, } @@ -724,12 +735,12 @@ pub(crate) struct SubsurfaceInfo { pub bounds: Rectangle, pub alpha: f32, pub transform: wl_output::Transform, - pub z: u32, + pub z: i32, } thread_local! { static SUBSURFACES: RefCell> = RefCell::new(Vec::new()); - static ICED_SUBSURFACES: RefCell> = RefCell::new(Vec::new()); + static ICED_SUBSURFACES: RefCell> = RefCell::new(Vec::new()); } pub(crate) fn take_subsurfaces() -> Vec { @@ -771,7 +782,7 @@ pub struct Subsurface { content_fit: ContentFit, alpha: f32, transform: wl_output::Transform, - pub z: u32, + pub z: i32, } impl Widget for Subsurface @@ -877,7 +888,8 @@ impl Subsurface { self } - pub fn z(mut self, z: u32) -> Self { + /// If `z` is less than 0, it will be below the main surface + pub fn z(mut self, z: i32) -> Self { self.z = z; self } From 8e003106c263a6880ee300d242b2e461019bf6f5 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 31 Mar 2025 08:50:07 -0600 Subject: [PATCH 079/116] Adapt to new cosmic-text --- graphics/src/text/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 89bc1caee2..00d50116c8 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -586,7 +586,7 @@ impl editor::Editor for Editor { ..if let Some(font) = format.font { text::to_attributes(font) } else { - attributes.clone() // NOTE(POP-OS): Added clone due to attrslist now requiring a ref + attributes.clone() } }, ); From 669c084e5f9c4f350dbabc1542e35961f5241157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Fri, 4 Apr 2025 01:21:04 +0200 Subject: [PATCH 080/116] feat(scrollable): add padding to ends of scrollbar This adds a padding field to the scrollbar, which adds padding at the start and end of scrollbars, without affecting the scrollable. This enables the scrollable to use the full height/width of a container where it would otherwise be visually restricted by scrollbar placement (e.g. due to rounded corners). Also adds methods for changing scrollbar/scroller width, allowing to match defaults to upstream, while making required changes in libcosmic. --- widget/src/scrollable.rs | 97 +++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 130fe8da7a..3670bd1a9f 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -249,6 +249,69 @@ where self } + /// Sets the scrollbar width of the [`Scrollbar`]. + pub fn scrollbar_width(mut self, width: impl Into) -> Self { + let width = width.into().0.max(0.0); + + match &mut self.direction { + Direction::Horizontal(scrollbar) + | Direction::Vertical(scrollbar) => { + scrollbar.width = width; + } + Direction::Both { + horizontal, + vertical, + } => { + horizontal.width = width; + vertical.width = width; + } + } + + self + } + + /// Sets the scroller width of the [`Scrollbar`]. + pub fn scroller_width(mut self, width: impl Into) -> Self { + let width = width.into().0.max(0.0); + + match &mut self.direction { + Direction::Horizontal(scrollbar) + | Direction::Vertical(scrollbar) => { + scrollbar.scroller_width = width; + } + Direction::Both { + horizontal, + vertical, + } => { + horizontal.scroller_width = width; + vertical.scroller_width = width; + } + } + + self + } + + /// Sets the padding at the start and end of the [`Scrollbar`]. + pub fn scrollbar_padding(mut self, padding: impl Into) -> Self { + let padding = padding.into().0.max(0.0); + + match &mut self.direction { + Direction::Horizontal(scrollbar) + | Direction::Vertical(scrollbar) => { + scrollbar.padding = padding; + } + Direction::Both { + horizontal, + vertical, + } => { + horizontal.padding = padding; + vertical.padding = padding; + } + } + + self + } + /// Sets the style of this [`Scrollable`]. #[must_use] pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self @@ -371,16 +434,18 @@ pub struct Scrollbar { scroller_width: f32, alignment: Anchor, spacing: Option, + padding: f32, } impl Default for Scrollbar { fn default() -> Self { Self { - width: 8.0, + width: 10.0, margin: 0.0, - scroller_width: 8.0, + scroller_width: 10.0, alignment: Anchor::Start, spacing: None, + padding: 0.0, } } } @@ -424,6 +489,12 @@ impl Scrollbar { self.spacing = Some(spacing.into().0); self } + + /// Sets the padding at the start and end of the [`Scrollbar`]. + pub fn padding(mut self, padding: impl Into) -> Self { + self.padding = padding.into().0.max(0.0); + self + } } /// The anchor of the scroller of the [`Scrollable`] relative to its [`Viewport`] @@ -1822,6 +1893,7 @@ impl Scrollbars { width, margin, scroller_width, + padding, .. } = *vertical; @@ -1836,9 +1908,10 @@ impl Scrollbars { // Total bounds of the scrollbar + margin + scroller width let total_scrollbar_bounds = Rectangle { x: bounds.x + bounds.width - total_scrollbar_width, - y: bounds.y, + y: bounds.y + padding, width: total_scrollbar_width, - height: (bounds.height - x_scrollbar_height).max(0.0), + height: (bounds.height - x_scrollbar_height - 2.0 * padding) + .max(0.0), }; // Bounds of just the scrollbar @@ -1846,9 +1919,10 @@ impl Scrollbars { x: bounds.x + bounds.width - total_scrollbar_width / 2.0 - width / 2.0, - y: bounds.y, + y: bounds.y + padding, width, - height: (bounds.height - x_scrollbar_height).max(0.0), + height: (bounds.height - x_scrollbar_height - 2.0 * padding) + .max(0.0), }; let ratio = bounds.height / content_bounds.height; @@ -1892,6 +1966,7 @@ impl Scrollbars { width, margin, scroller_width, + padding, .. } = *horizontal; @@ -1905,19 +1980,21 @@ impl Scrollbars { // Total bounds of the scrollbar + margin + scroller width let total_scrollbar_bounds = Rectangle { - x: bounds.x, + x: bounds.x + padding, y: bounds.y + bounds.height - total_scrollbar_height, - width: (bounds.width - scrollbar_y_width).max(0.0), + width: (bounds.width - scrollbar_y_width - 2.0 * padding) + .max(0.0), height: total_scrollbar_height, }; // Bounds of just the scrollbar let scrollbar_bounds = Rectangle { - x: bounds.x, + x: bounds.x + padding, y: bounds.y + bounds.height - total_scrollbar_height / 2.0 - width / 2.0, - width: (bounds.width - scrollbar_y_width).max(0.0), + width: (bounds.width - scrollbar_y_width - 2.0 * padding) + .max(0.0), height: width, }; From 282d045da057f64a832453a7303b709076afb7b6 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 7 Apr 2025 09:52:02 -0700 Subject: [PATCH 081/116] wayland: Set cusor position on `Enter` events Fixes https://github.com/pop-os/cosmic-workspaces-epoch/issues/143. The way some events are converted to `WindowEvent`s in `pointer_frame` but motion is handled in `SctkEvent::process` seems a little cluttered at the moment, I guess from how this code was ported to be part of iced-winit instead of separate. But as I understand, the code handling `PointerEventKind::Enter` in `SctkEvent::enter` wasn't being called, so that can be replaced with the new code here that sets the logical cursor position. --- .../wayland/handlers/seat/pointer.rs | 12 ++++++++++++ .../platform_specific/wayland/sctk_event.rs | 18 ++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs index 61f50692e8..1551254d1e 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs @@ -63,6 +63,18 @@ impl PointerHandler for SctkState { *entry = FrameStatus::Ready; } + if let PointerEventKind::Enter { .. } = &e.kind { + self.sctk_events.push(SctkEvent::PointerEvent { + variant: PointerEvent { + surface: e.surface.clone(), + position: e.position, + kind: e.kind.clone(), + }, + ptr_id: pointer.clone(), + seat_id: my_seat.seat.clone(), + }); + } + if let PointerEventKind::Motion { time } = &e.kind { self.sctk_events.push(SctkEvent::PointerEvent { variant: PointerEvent { diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index ac4c245a74..94ccda03c7 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -369,14 +369,16 @@ impl SctkEvent { SctkEvent::SeatEvent { .. } => Default::default(), SctkEvent::PointerEvent { variant, .. } => match variant.kind { PointerEventKind::Enter { .. } => { - events.push(( - surface_ids - .get(&variant.surface.id()) - .map(|id| id.inner()), - iced_runtime::core::Event::Mouse( - mouse::Event::CursorEntered, - ), - )); + let id = surface_ids + .get(&variant.surface.id()) + .map(|id| id.inner()); + if let Some(w) = + id.clone().and_then(|id| window_manager.get_mut(id)) + { + w.state.set_logical_cursor_pos( + (variant.position.0, variant.position.1).into(), + ) + } } PointerEventKind::Leave { .. } => events.push(( surface_ids.get(&variant.surface.id()).map(|id| id.inner()), From 4070d79b0b20b6916c5cdf09181459312ab2af10 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 10 Apr 2025 13:48:50 -0400 Subject: [PATCH 082/116] fix: reset control flow if the loop is about to wait with nothing to do. --- winit/src/program.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/winit/src/program.rs b/winit/src/program.rs index 5f64352142..a00374e8b0 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -1507,6 +1507,8 @@ async fn run_instance<'a, P, C>( if skip && window_manager.iter_mut().all(|(_, w)| !w.resize_enabled) { + _ = control_sender + .start_send(Control::ChangeFlow(ControlFlow::Wait)); continue; } @@ -1515,6 +1517,8 @@ async fn run_instance<'a, P, C>( let mut resized = false; for (id, window) in window_manager.iter_mut() { if skip && !window.resize_enabled { + _ = control_sender + .start_send(Control::ChangeFlow(ControlFlow::Wait)); continue; } let mut window_events = vec![]; @@ -1583,6 +1587,8 @@ async fn run_instance<'a, P, C>( if needs_redraw { window.request_redraw(); } else { + _ = control_sender + .start_send(Control::ChangeFlow(ControlFlow::Wait)); continue; } @@ -1603,6 +1609,8 @@ async fn run_instance<'a, P, C>( } if !resized && skip { + _ = control_sender + .start_send(Control::ChangeFlow(ControlFlow::Wait)); continue; } @@ -1615,6 +1623,8 @@ async fn run_instance<'a, P, C>( | core::Event::Mouse(_) ) { + _ = control_sender + .start_send(Control::ChangeFlow(ControlFlow::Wait)); continue; } runtime.broadcast(subscription::Event::Interaction { From 16a2e8f82b06fea475910daa2916d55d128e0071 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 14 Apr 2025 19:41:42 +0200 Subject: [PATCH 083/116] wgpu: Fix explicit nvidia device selection --- wgpu/src/window/compositor.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 07399ba1d1..898a3829d7 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -68,11 +68,14 @@ impl Compositor { // HACK: // 1. If we specifically didn't select an nvidia gpu - // 2. and nobody set an adapter name, - // 3. and the user didn't request the high power pref + // 2. and the user didn't specifically request an nvidia gpu + // 3. and the user didn't set an adapter name, + // 4. and the user didn't request the high power pref // => don't load the nvidia icd, as it might power on the gpu in hybrid setups causing severe delays #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] if !matches!(ids, Some((0x10de, _))) + && std::env::var_os("__NV_PRIME_RENDER_OFFLOAD") + .is_none_or(|var| var == "0") && std::env::var_os("WGPU_ADAPTER_NAME").is_none() && std::env::var("WGPU_POWER_PREF").as_deref() != Ok("high") { From e996b9a27f5a757b647598649065dad812e289ab Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 29 Apr 2025 13:56:51 -0400 Subject: [PATCH 084/116] fix: send pointer motion event for pointer enter event --- winit/src/platform_specific/wayland/sctk_event.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 94ccda03c7..72492e7d8b 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -379,6 +379,17 @@ impl SctkEvent { (variant.position.0, variant.position.1).into(), ) } + events.push(( + id, + iced_runtime::core::Event::Mouse( + mouse::Event::CursorMoved { + position: Point::new( + variant.position.0 as f32, + variant.position.1 as f32, + ), + }, + ), + )); } PointerEventKind::Leave { .. } => events.push(( surface_ids.get(&variant.surface.id()).map(|id| id.inner()), From 742957cd617998580d2c3af9250ebcec602de1e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 4 May 2024 13:34:41 +0200 Subject: [PATCH 085/116] Infinite `List` widget from upstream feature/list-widget-reloaded branch --- examples/list/Cargo.toml | 10 + examples/list/src/main.rs | 92 +++++ widget/src/helpers.rs | 13 + widget/src/lib.rs | 3 + widget/src/list.rs | 792 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 910 insertions(+) create mode 100644 examples/list/Cargo.toml create mode 100644 examples/list/src/main.rs create mode 100644 widget/src/list.rs diff --git a/examples/list/Cargo.toml b/examples/list/Cargo.toml new file mode 100644 index 0000000000..56d5b5e9ab --- /dev/null +++ b/examples/list/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "list" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +publish = false + +[dependencies] +iced.workspace = true +iced.features = ["debug"] diff --git a/examples/list/src/main.rs b/examples/list/src/main.rs new file mode 100644 index 0000000000..488383d8ae --- /dev/null +++ b/examples/list/src/main.rs @@ -0,0 +1,92 @@ +use iced::widget::{ + button, center, column, container, horizontal_space, list, row, scrollable, + text, +}; +use iced::{Alignment, Element, Length, Theme}; + +pub fn main() -> iced::Result { + iced::program("List - Iced", List::update, List::view) + .theme(|_| Theme::TokyoNight) + .run() +} + +struct List { + content: list::Content<(usize, State)>, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Update(usize), + Remove(usize), +} + +impl List { + fn update(&mut self, message: Message) { + match message { + Message::Update(index) => { + if let Some((_id, state)) = self.content.get_mut(index) { + *state = State::Updated; + } + } + Message::Remove(index) => { + let _ = self.content.remove(index); + } + } + } + + fn view(&self) -> Element { + center( + scrollable( + container(list(&self.content, |index, (id, state)| { + row![ + match state { + State::Idle => + Element::from(text(format!("I am item {id}!"))), + State::Updated => center( + column![ + text(format!("I am item {id}!")), + text("... but different!") + ] + .spacing(20) + ) + .height(300) + .into(), + }, + horizontal_space(), + button("Update").on_press_maybe( + matches!(state, State::Idle) + .then_some(Message::Update(index)) + ), + button("Remove") + .on_press(Message::Remove(index)) + .style(button::danger) + ] + .spacing(10) + .padding(5) + .align_items(Alignment::Center) + .into() + })) + .padding(10), + ) + .width(Length::Fill), + ) + .padding(10) + .into() + } +} + +impl Default for List { + fn default() -> Self { + Self { + content: list::Content::from_iter( + (0..1_000).map(|id| (id, State::Idle)), + ), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum State { + Idle, + Updated, +} diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e1f41ec585..d6c5d3f400 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -8,6 +8,7 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::window; use crate::core::{Element, Length, Pixels, Widget}; use crate::keyed; +use crate::list::{self, List}; use crate::overlay; use crate::pane_grid::{self, PaneGrid}; use crate::pick_list::{self, PickList}; @@ -814,6 +815,18 @@ where Scrollable::new(content) } +/// Creates a new [`List`] with the provided [`Content`] and +/// closure to view an item of the [`List`]. +/// +/// [`List`]: crate::List +/// [`Content`]: crate::list::Content +pub fn list<'a, T, Message, Theme, Renderer>( + content: &'a list::Content, + view_item: impl Fn(usize, &'a T) -> Element<'a, Message, Theme, Renderer> + 'a, +) -> List<'a, T, Message, Theme, Renderer> { + List::new(content, view_item) +} + /// Creates a new [`Button`] with the provided content. /// /// # Example diff --git a/widget/src/lib.rs b/widget/src/lib.rs index a68720d692..c36de66e91 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -20,6 +20,7 @@ pub mod checkbox; pub mod combo_box; pub mod container; pub mod keyed; +pub mod list; pub mod overlay; pub mod pane_grid; pub mod pick_list; @@ -56,6 +57,8 @@ pub use combo_box::ComboBox; #[doc(no_inline)] pub use container::Container; #[doc(no_inline)] +pub use list::List; +#[doc(no_inline)] pub use mouse_area::MouseArea; #[doc(no_inline)] pub use pane_grid::PaneGrid; diff --git a/widget/src/list.rs b/widget/src/list.rs new file mode 100644 index 0000000000..253fcf0320 --- /dev/null +++ b/widget/src/list.rs @@ -0,0 +1,792 @@ +#![allow(missing_docs)] +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; +use crate::core::widget::tree::{self, Tree}; +use crate::core::window; +use crate::core::{ + self, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, + Size, Vector, Widget, +}; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::collections::VecDeque; + +#[allow(missing_debug_implementations)] +pub struct List<'a, T, Message, Theme, Renderer> { + content: &'a Content, + spacing: f32, + view_item: + Box Element<'a, Message, Theme, Renderer> + 'a>, + visible_elements: Vec>, +} + +impl<'a, T, Message, Theme, Renderer> List<'a, T, Message, Theme, Renderer> { + pub fn new( + content: &'a Content, + view_item: impl Fn(usize, &'a T) -> Element<'a, Message, Theme, Renderer> + + 'a, + ) -> Self { + Self { + content, + spacing: 0.0, + view_item: Box::new(view_item), + visible_elements: Vec::new(), + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } +} + +struct State { + last_limits: layout::Limits, + visible_layouts: Vec<(usize, layout::Node, Tree)>, + size: Size, + offsets: Vec, + widths: Vec, + task: Task, + visible_outdated: bool, +} + +enum Task { + Idle, + Computing { + current: usize, + offsets: Vec, + widths: Vec, + size: Size, + }, +} + +impl State { + fn recompute(&mut self, size: usize) { + let mut offsets = Vec::with_capacity(size + 1); + offsets.push(0.0); + + self.task = Task::Computing { + current: 0, + offsets, + widths: Vec::with_capacity(size), + size: Size::ZERO, + }; + self.visible_layouts.clear(); + } +} + +impl<'a, T, Message, Theme, Renderer> Widget + for List<'a, T, Message, Theme, Renderer> +where + Renderer: core::Renderer, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State { + last_limits: layout::Limits::NONE, + visible_layouts: Vec::new(), + size: Size::ZERO, + offsets: vec![0.0], + widths: Vec::new(), + task: Task::Idle, + visible_outdated: false, + }) + } + + fn size(&self) -> Size { + Size { + width: Length::Shrink, + height: Length::Shrink, + } + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let state = tree.state.downcast_mut::(); + let loose_limits = limits.loose(); + + if state.last_limits != loose_limits { + state.last_limits = loose_limits; + state.recompute(self.content.len()); + } + + let mut changes = self.content.changes.borrow_mut(); + + match state.task { + Task::Idle => { + while let Some(change) = changes.pop_front() { + match change { + Change::Updated { original, current } => { + let mut new_element = (self.view_item)( + current, + &self.content.items[current], + ); + + let visible_index = state + .visible_layouts + .iter_mut() + .position(|(i, _, _)| *i == original); + + let mut new_tree; + + // Update if visible + let tree = + if let Some(visible_index) = visible_index { + let (_i, _layout, tree) = &mut state + .visible_layouts[visible_index]; + + tree.diff(&mut new_element); + state.visible_outdated = true; + + tree + } else { + new_tree = Tree::new(&new_element); + + &mut new_tree + }; + + let new_layout = new_element + .as_widget_mut() + .layout(tree, renderer, &state.last_limits); + + let new_size = new_layout.size(); + + let height_difference = new_size.height + - (state.offsets[original + 1] + - state.offsets[original]); + + for offset in &mut state.offsets[original + 1..] { + *offset += height_difference; + } + + let original_width = state.widths[original]; + state.widths[original] = new_size.width; + + if let Some(visible_index) = visible_index { + state.visible_layouts[visible_index].1 = + new_layout; + + for (i, layout, _) in + &mut state.visible_layouts[visible_index..] + { + layout + .move_to_mut((0.0, state.offsets[*i])); + } + } else if let Some(first_visible) = + state.visible_layouts.first() + { + let first_visible_index = first_visible.0; + if original < first_visible_index { + for (i, layout, _) in + &mut state.visible_layouts[..] + { + layout.move_to_mut(( + 0.0, + state.offsets[*i], + )); + } + } + } + + state.size.height += height_difference; + + if original_width == state.size.width { + state.size.width = state.widths.iter().fold( + 0.0, + |current, candidate| { + current.max(*candidate) + }, + ); + } + } + Change::Removed { original, .. } => { + let height = state.offsets[original + 1] + - state.offsets[original]; + + let original_width = state.widths.remove(original); + let _ = state.offsets.remove(original + 1); + + for offset in &mut state.offsets[original + 1..] { + *offset -= height; + } + + // TODO: Smarter visible layout partial updates + state.visible_layouts.clear(); + + state.size.height -= height; + + if original_width == state.size.width { + state.size.width = state.widths.iter().fold( + 0.0, + |current, candidate| { + current.max(*candidate) + }, + ); + } + } + Change::Pushed { current, .. } => { + let mut new_element = (self.view_item)( + current, + &self.content.items[current], + ); + + let mut tree = Tree::new(&new_element); + + let layout = new_element.as_widget_mut().layout( + &mut tree, + renderer, + &state.last_limits, + ); + + let size = layout.size(); + + state.widths.push(size.width); + state.offsets.push( + state.offsets.last().unwrap() + size.height, + ); + + state.size.width = state.size.width.max(size.width); + state.size.height += size.height; + } + } + } + } + Task::Computing { .. } => { + if !changes.is_empty() { + // If changes happen during layout computation, + // we simply restart the computation + changes.clear(); + state.recompute(self.content.len()); + } + } + } + + // Recompute if new + { + let mut is_new = self.content.is_new.borrow_mut(); + + if *is_new { + state.recompute(self.content.len()); + *is_new = false; + } + } + + match &mut state.task { + Task::Idle => {} + Task::Computing { + current, + size, + widths, + offsets, + } => { + const MAX_BATCH_SIZE: usize = 50; + + let end = (*current + MAX_BATCH_SIZE).min(self.content.len()); + + let batch = &self.content.items[*current..end]; + + let mut max_width = size.width; + let mut accumulated_height = + offsets.last().copied().unwrap_or(0.0); + + for (i, item) in batch.iter().enumerate() { + let element = (self.view_item)(*current + i, item); + let mut tree = Tree::new(&element); + + let layout = element + .as_widget() + .layout(&mut tree, renderer, &state.last_limits) + .move_to((0.0, accumulated_height)); + + let bounds = layout.bounds(); + + max_width = max_width.max(bounds.width); + accumulated_height += bounds.height; + + offsets.push(accumulated_height); + widths.push(bounds.width); + } + + *size = Size::new(max_width, accumulated_height); + + if end < self.content.len() { + *current = end; + } else { + state.offsets = std::mem::take(offsets); + state.widths = std::mem::take(widths); + state.size = std::mem::take(size); + state.task = Task::Idle; + } + } + } + + let intrinsic_size = Size::new( + state.size.width, + state.size.height + + self.content.len().saturating_sub(1) as f32 * self.spacing, + ); + + let size = + limits.resolve(Length::Shrink, Length::Shrink, intrinsic_size); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let state = tree.state.downcast_mut::(); + let offset = layout.position() - Point::ORIGIN; + + let status = self + .visible_elements + .iter_mut() + .zip(&mut state.visible_layouts) + .map(|(element, (index, layout, tree))| { + element.as_widget_mut().on_event( + tree, + event.clone(), + Layout::with_offset( + offset + Vector::new(0.0, self.spacing * *index as f32), + layout, + ), + cursor, + renderer, + clipboard, + shell, + viewport, + ) + }) + .fold(event::Status::Ignored, event::Status::merge); + + if let Event::Window(window::Event::RedrawRequested(_)) = event { + match &mut state.task { + Task::Idle => {} + Task::Computing { .. } => { + shell.invalidate_layout(); + shell.request_redraw(window::RedrawRequest::NextFrame); + } + } + + let offsets = &state.offsets; + + let start = + match binary_search_with_index_by(offsets, |i, height| { + (*height + i.saturating_sub(1) as f32 * self.spacing) + .partial_cmp(&(viewport.y - offset.y)) + .unwrap_or(Ordering::Equal) + }) { + Ok(i) => i, + Err(i) => i.saturating_sub(1), + } + .min(self.content.len()); + + let end = match binary_search_with_index_by(offsets, |i, height| { + (*height + i.saturating_sub(1) as f32 * self.spacing) + .partial_cmp(&(viewport.y + viewport.height - offset.y)) + .unwrap_or(Ordering::Equal) + }) { + Ok(i) => i, + Err(i) => i, + } + .min(self.content.len()); + + if state.visible_outdated + || state.visible_layouts.len() != self.visible_elements.len() + { + self.visible_elements.clear(); + state.visible_outdated = false; + } + + // If view was recreated, we repopulate the visible elements + // out of the internal visible layouts + if self.visible_elements.is_empty() { + self.visible_elements = state + .visible_layouts + .iter() + .map(|(i, _, _)| { + (self.view_item)(*i, &self.content.items[*i]) + }) + .collect(); + } + + // Clear no longer visible elements + let top = state + .visible_layouts + .iter() + .take_while(|(i, _, _)| *i < start) + .count(); + + let bottom = state + .visible_layouts + .iter() + .rev() + .take_while(|(i, _, _)| *i >= end) + .count(); + + let _ = self.visible_elements.splice(..top, []); + let _ = state.visible_layouts.splice(..top, []); + + let _ = self + .visible_elements + .splice(self.visible_elements.len() - bottom.., []); + let _ = state + .visible_layouts + .splice(state.visible_layouts.len() - bottom.., []); + + // Prepend new visible elements + if let Some(first_visible) = + state.visible_layouts.first().map(|(i, _, _)| *i) + { + if start < first_visible { + for (i, item) in self.content.items[start..first_visible] + .iter() + .enumerate() + { + let element = (self.view_item)(start + i, item); + let mut tree = Tree::new(&element); + + let layout = element + .as_widget() + .layout(&mut tree, renderer, &state.last_limits) + .move_to(( + 0.0, + offsets[start + i] + + (start + i) as f32 * self.spacing, + )); + + state + .visible_layouts + .insert(i, (start + i, layout, tree)); + self.visible_elements.insert(i, element); + } + } + } + + // Append new visible elements + let last_visible = state + .visible_layouts + .last() + .map(|(i, _, _)| *i + 1) + .unwrap_or(start); + + if last_visible < end { + for (i, item) in + self.content.items[last_visible..end].iter().enumerate() + { + let element = (self.view_item)(last_visible + i, item); + let mut tree = Tree::new(&element); + + let layout = element + .as_widget() + .layout(&mut tree, renderer, &state.last_limits) + .move_to(( + 0.0, + offsets[last_visible + i] + + (last_visible + i) as f32 * self.spacing, + )); + + state.visible_layouts.push(( + last_visible + i, + layout, + tree, + )); + self.visible_elements.push(element); + } + } + } + + status + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + let state = tree.state.downcast_ref::(); + let offset = layout.position() - Point::ORIGIN; + + for (element, (_item, layout, tree)) in + self.visible_elements.iter().zip(&state.visible_layouts) + { + element.as_widget().draw( + tree, + renderer, + theme, + style, + Layout::with_offset(offset, layout), + cursor, + viewport, + ); + } + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let state = tree.state.downcast_ref::(); + let offset = layout.position() - Point::ORIGIN; + + self.visible_elements + .iter() + .zip(&state.visible_layouts) + .map(|(element, (_item, layout, tree))| { + element.as_widget().mouse_interaction( + tree, + Layout::with_offset(offset, layout), + cursor, + viewport, + renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + let state = tree.state.downcast_mut::(); + let offset = layout.position() - Point::ORIGIN; + + for (element, (_item, layout, tree)) in + self.visible_elements.iter().zip(&mut state.visible_layouts) + { + element.as_widget().operate( + tree, + Layout::with_offset(offset, layout), + renderer, + operation, + ); + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + let state = tree.state.downcast_mut::(); + let offset = layout.position() - Point::ORIGIN; + + let children = self + .visible_elements + .iter_mut() + .zip(&mut state.visible_layouts) + .filter_map(|(child, (_item, layout, tree))| { + child.as_widget_mut().overlay( + tree, + Layout::with_offset(offset, layout), + renderer, + translation, + ) + }) + .collect::>(); + + (!children.is_empty()) + .then(|| overlay::Group::with_children(children).overlay()) + } +} + +impl<'a, T, Message, Theme, Renderer> + From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: 'a, + Renderer: core::Renderer + 'a, +{ + fn from(list: List<'a, T, Message, Theme, Renderer>) -> Self { + Self::new(list) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Content { + items: Vec, + is_new: RefCell, + changes: RefCell>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Change { + Updated { original: usize, current: usize }, + Removed { original: usize, current: usize }, + Pushed { original: usize, current: usize }, +} + +impl Content { + pub fn new() -> Self { + Self { + items: Vec::new(), + is_new: RefCell::new(true), + changes: RefCell::new(VecDeque::new()), + } + } + + pub fn with_items(items: Vec) -> Self { + Self { + items, + is_new: RefCell::new(true), + changes: RefCell::new(VecDeque::new()), + } + } + + pub fn get(&self, index: usize) -> Option<&T> { + self.items.get(index) + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.changes.borrow_mut().push_back(Change::Updated { + original: index, + current: index, + }); + self.items.get_mut(index) + } + + pub fn push(&mut self, item: T) { + let index = self.items.len(); + + self.changes.borrow_mut().push_back(Change::Pushed { + original: index, + current: index, + }); + + self.items.push(item); + } + + pub fn remove(&mut self, index: usize) -> T { + let mut changes = self.changes.borrow_mut(); + + // Update pending changes after removal + changes.retain_mut(|change| match change { + Change::Updated { current, .. } + | Change::Removed { current, .. } + | Change::Pushed { current, .. } + if *current > index => + { + // Decrement index of later changes + *current -= 1; + + true + } + _ => true, + }); + + changes.push_back(Change::Removed { + original: index, + current: index, + }); + + self.items.remove(index) + } + + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + pub fn len(&self) -> usize { + self.items.len() + } + + pub fn into_vec(self) -> Vec { + self.items + } +} + +impl Default for Content { + fn default() -> Self { + Self::new() + } +} + +impl FromIterator for Content { + fn from_iter>(iter: I) -> Self { + Self::with_items(iter.into_iter().collect()) + } +} + +/// SAFETY: Copied from the `std` library. +#[allow(unsafe_code)] +fn binary_search_with_index_by<'a, T, F>( + slice: &'a [T], + mut f: F, +) -> Result +where + F: FnMut(usize, &'a T) -> Ordering, +{ + use std::cmp::Ordering::*; + + // INVARIANTS: + // - 0 <= left <= left + size = right <= self.len() + // - f returns Less for everything in self[..left] + // - f returns Greater for everything in self[right..] + let mut size = slice.len(); + let mut left = 0; + let mut right = size; + while left < right { + let mid = left + size / 2; + + // SAFETY: the while condition means `size` is strictly positive, so + // `size/2 < size`. Thus `left + size/2 < left + size`, which + // coupled with the `left + size <= self.len()` invariant means + // we have `left + size/2 < self.len()`, and this is in-bounds. + let cmp = f(mid, unsafe { slice.get_unchecked(mid) }); + + // This control flow produces conditional moves, which results in + // fewer branches and instructions than if/else or matching on + // cmp::Ordering. + // This is x86 asm for u8: https://rust.godbolt.org/z/698eYffTx. + left = if cmp == Less { mid + 1 } else { left }; + right = if cmp == Greater { mid } else { right }; + if cmp == Equal { + return Ok(mid); + } + + size = right - left; + } + + Err(left) +} From 3c0d8441b2c7f2389311e32e81453cc6176e829c Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 15 May 2025 14:11:17 -0600 Subject: [PATCH 086/116] support CapsLock modifiers on wayland --- core/src/keyboard/modifiers.rs | 2 ++ winit/src/platform_specific/wayland/conversion.rs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/keyboard/modifiers.rs b/core/src/keyboard/modifiers.rs index 00b3188292..43b9dbce17 100644 --- a/core/src/keyboard/modifiers.rs +++ b/core/src/keyboard/modifiers.rs @@ -24,6 +24,8 @@ bitflags! { const LOGO = 0b100 << 9; // const LLOGO = 0b010 << 9; // const RLOGO = 0b001 << 9; + /// The Caps Lock key + const CAPS_LOCK = 0b100 << 12; } } diff --git a/winit/src/platform_specific/wayland/conversion.rs b/winit/src/platform_specific/wayland/conversion.rs index c2ca825d6b..d102b8e104 100644 --- a/winit/src/platform_specific/wayland/conversion.rs +++ b/winit/src/platform_specific/wayland/conversion.rs @@ -64,10 +64,10 @@ pub fn modifiers_to_native(mods: Modifiers) -> keyboard::Modifiers { if mods.shift { native_mods = native_mods.union(keyboard::Modifiers::SHIFT); } + if mods.caps_lock { + native_mods = native_mods.union(keyboard::Modifiers::CAPS_LOCK); + } // TODO Ashley: missing modifiers as platform specific additions? - // if mods.caps_lock { - // native_mods = native_mods.union(keyboard::Modifier); - // } // if mods.num_lock { // native_mods = native_mods.union(keyboard::Modifiers::); // } From 364db8b973061e6aece4c9f668f638440fb101fd Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 15 May 2025 14:13:07 -0600 Subject: [PATCH 087/116] Format --- winit/src/platform_specific/wayland/subsurface_widget.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 54b0c94b45..c670e5217b 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -542,7 +542,8 @@ impl SubsurfaceState { sorted_subsurfaces.sort_by(|a, b| a.3.cmp(&b.3)); // Attach buffers to subsurfaces, set viewports, and commit. - 'outer: for (i, subsurface) in sorted_subsurfaces.iter().enumerate() { + 'outer: for (i, subsurface) in sorted_subsurfaces.iter().enumerate() + { for prev in sorted_subsurfaces[0..i].iter().rev() { if prev.0 == subsurface.0 { // Fist surface that has `z` greater than 0, so place above parent, @@ -753,7 +754,8 @@ pub(crate) fn subsurface_ids(parent: WindowId) -> Vec { .borrow_mut() .iter() .filter_map(|s| { - if winit::window::WindowId::from(s.1.id().as_ptr() as u64) == parent + if winit::window::WindowId::from(s.1.id().as_ptr() as u64) + == parent { Some( winit::window::WindowId::from(s.4.id().as_ptr() as u64), From a7c886d3a63d84bc2f89a3ec38e30211152352fb Mon Sep 17 00:00:00 2001 From: Jinderamarak <47433483+Jinderamarak@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:35:03 +0200 Subject: [PATCH 088/116] fix: parsing of nested markdown lists without empty line (cherry picked from commit 440918ce535abff20dae9597f592d5dd0d1ce05c) --- widget/src/markdown.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index d6bebb9b82..c0648e9e10 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -305,12 +305,21 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { None } pulldown_cmark::Tag::List(first_item) if !metadata && !table => { + let prev = if spans.is_empty() { + None + } else { + produce( + &mut lists, + Item::Paragraph(Text::new(spans.drain(..).collect())), + ) + }; + lists.push(List { start: first_item, items: Vec::new(), }); - None + prev } pulldown_cmark::Tag::Item => { lists From 35bd8184fbd2128bb75de0302d786d0d1a0e42c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Jan 2025 01:46:04 +0100 Subject: [PATCH 089/116] Fix code block merging with previous spans in `markdown` widget (cherry picked from commit cfbeb05e32914ed951b7ce4afd131ef75b3cfb32) --- widget/src/markdown.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index c0648e9e10..fe61d631a4 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -344,7 +344,16 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { })); } - None + let prev = if spans.is_empty() { + None + } else { + produce( + &mut lists, + Item::Paragraph(Text::new(spans.drain(..).collect())), + ) + }; + + prev } pulldown_cmark::Tag::MetadataBlock(_) => { metadata = true; From f6090a85c2466369e27eab8f0e7b0a8e4107323a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Jan 2025 01:46:52 +0100 Subject: [PATCH 090/116] Make `spacing` configurable in `markdown::Settings` (cherry picked from commit d49d4dc3fa58482ab0269ac678134fa6f360396a) --- widget/src/markdown.rs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index fe61d631a4..252a3e1aa5 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -527,6 +527,8 @@ pub struct Settings { pub h6_size: Pixels, /// The text size used in code blocks. pub code_size: Pixels, + /// The spacing to be used between elements. + pub spacing: Pixels, } impl Settings { @@ -547,6 +549,7 @@ impl Settings { h5_size: text_size, h6_size: text_size, code_size: text_size * 0.75, + spacing: text_size * 0.875, } } } @@ -649,10 +652,9 @@ where h5_size, h6_size, code_size, + spacing, } = settings; - let spacing = text_size * 0.625; - let blocks = items.into_iter().enumerate().map(|(i, item)| match item { Item::Heading(level, heading) => { container(rich_text(heading.spans(style)).size(match level { @@ -675,11 +677,21 @@ where } Item::List { start: None, items } => { column(items.iter().map(|items| { - row![text("•").size(text_size), view(items, settings, style)] - .spacing(spacing) - .into() + row![ + text("•").size(text_size), + view( + items, + Settings { + spacing: settings.spacing * 0.6, + ..settings + }, + style + ) + ] + .spacing(spacing) + .into() })) - .spacing(spacing) + .spacing(spacing * 0.75) .into() } Item::List { @@ -688,12 +700,19 @@ where } => column(items.iter().enumerate().map(|(i, items)| { row![ text!("{}.", i as u64 + *start).size(text_size), - view(items, settings, style) + view( + items, + Settings { + spacing: settings.spacing * 0.6, + ..settings + }, + style + ) ] .spacing(spacing) .into() })) - .spacing(spacing) + .spacing(spacing * 0.75) .into(), Item::CodeBlock(code) => container( scrollable( From 72962f21c6b10b0d944cf88d527068d1656f33ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 30 Jan 2025 01:47:10 +0100 Subject: [PATCH 091/116] Let `markdown::view` be `Shrink` when no code blocks exist (cherry picked from commit aa0f0e73aa06242b8228fd81df8acaac6f377b71) --- widget/src/markdown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 252a3e1aa5..2b7bc0fc7f 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -735,7 +735,7 @@ where .into(), }); - Element::new(column(blocks).width(Length::Fill).spacing(text_size)) + Element::new(column(blocks).spacing(spacing)) } /// The theme catalog of Markdown items. From e27849e180d73bb7c873251c61c55209b78f1f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 31 Jan 2025 17:35:38 +0100 Subject: [PATCH 092/116] Draft incremental `markdown` parsing Specially useful when dealing with long Markdown streams, like LLMs. (cherry picked from commit 128058ea948909c21a9cfd0b58cbd3a13e238e57) --- examples/markdown/Cargo.toml | 2 +- examples/markdown/src/main.rs | 96 +++++++++++++++++++++++++++++---- widget/src/markdown.rs | 99 +++++++++++++++++++++++++++++------ 3 files changed, 168 insertions(+), 29 deletions(-) diff --git a/examples/markdown/Cargo.toml b/examples/markdown/Cargo.toml index cb74b954b0..fa6ced741e 100644 --- a/examples/markdown/Cargo.toml +++ b/examples/markdown/Cargo.toml @@ -7,6 +7,6 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["markdown", "highlighter", "debug"] +iced.features = ["markdown", "highlighter", "tokio", "debug"] open = "5.3" diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 5605478fb4..a55e91d2ab 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -1,23 +1,37 @@ use iced::highlighter; -use iced::widget::{self, markdown, row, scrollable, text_editor}; -use iced::{Element, Fill, Font, Task, Theme}; +use iced::time::{self, milliseconds}; +use iced::widget::{ + self, hover, markdown, right, row, scrollable, text_editor, toggler, +}; +use iced::{Element, Fill, Font, Subscription, Task, Theme}; pub fn main() -> iced::Result { iced::application("Markdown - Iced", Markdown::update, Markdown::view) + .subscription(Markdown::subscription) .theme(Markdown::theme) .run_with(Markdown::new) } struct Markdown { content: text_editor::Content, - items: Vec, + mode: Mode, theme: Theme, } +enum Mode { + Oneshot(Vec), + Stream { + pending: String, + parsed: markdown::Content, + }, +} + #[derive(Debug, Clone)] enum Message { Edit(text_editor::Action), LinkClicked(markdown::Url), + ToggleStream(bool), + NextToken, } impl Markdown { @@ -29,7 +43,7 @@ impl Markdown { ( Self { content: text_editor::Content::with_text(INITIAL_CONTENT), - items: markdown::parse(INITIAL_CONTENT).collect(), + mode: Mode::Oneshot(markdown::parse(INITIAL_CONTENT).collect()), theme, }, widget::focus_next(), @@ -44,13 +58,48 @@ impl Markdown { self.content.perform(action); if is_edit { - self.items = - markdown::parse(&self.content.text()).collect(); + self.mode = match self.mode { + Mode::Oneshot(_) => Mode::Oneshot( + markdown::parse(&self.content.text()).collect(), + ), + Mode::Stream { .. } => Mode::Stream { + pending: self.content.text(), + parsed: markdown::Content::parse(""), + }, + } } } Message::LinkClicked(link) => { let _ = open::that_in_background(link.to_string()); } + Message::ToggleStream(enable_stream) => { + self.mode = if enable_stream { + Mode::Stream { + pending: self.content.text(), + parsed: markdown::Content::parse(""), + } + } else { + Mode::Oneshot( + markdown::parse(&self.content.text()).collect(), + ) + }; + } + Message::NextToken => match &mut self.mode { + Mode::Oneshot(_) => {} + Mode::Stream { pending, parsed } => { + if pending.is_empty() { + self.mode = Mode::Oneshot(parsed.items().to_vec()); + } else { + let mut tokens = pending.split(' '); + + if let Some(token) = tokens.next() { + parsed.push_str(&format!("{token} ")); + } + + *pending = tokens.collect::>().join(" "); + } + } + }, } } @@ -63,20 +112,45 @@ impl Markdown { .font(Font::MONOSPACE) .highlight("markdown", highlighter::Theme::Base16Ocean); + let items = match &self.mode { + Mode::Oneshot(items) => items.as_slice(), + Mode::Stream { parsed, .. } => parsed.items(), + }; + let preview = markdown( - &self.items, + items, markdown::Settings::default(), markdown::Style::from_palette(self.theme.palette()), ) .map(Message::LinkClicked); - row![editor, scrollable(preview).spacing(10).height(Fill)] - .spacing(10) - .padding(10) - .into() + row![ + editor, + hover( + scrollable(preview).spacing(10).width(Fill).height(Fill), + right( + toggler(matches!(self.mode, Mode::Stream { .. })) + .label("Stream") + .on_toggle(Message::ToggleStream) + ) + .padding([0, 20]) + ) + ] + .spacing(10) + .padding(10) + .into() } fn theme(&self) -> Theme { self.theme.clone() } + + fn subscription(&self) -> Subscription { + match self.mode { + Mode::Oneshot(_) => Subscription::none(), + Mode::Stream { .. } => { + time::every(milliseconds(20)).map(|_| Message::NextToken) + } + } + } } diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 2b7bc0fc7f..0365dee8a0 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -47,6 +47,7 @@ //! } //! } //! ``` +#![allow(missing_docs)] use crate::core::border; use crate::core::font::{self, Font}; use crate::core::padding; @@ -57,12 +58,47 @@ use crate::core::{ use crate::{column, container, rich_text, row, scrollable, span, text}; use std::cell::{Cell, RefCell}; +use std::ops::Range; use std::sync::Arc; pub use core::text::Highlight; pub use pulldown_cmark::HeadingLevel; pub use url::Url; +#[derive(Debug, Clone)] +pub struct Content { + items: Vec, + state: State, +} + +impl Content { + pub fn parse(markdown: &str) -> Self { + let mut state = State::default(); + let items = parse_with(&mut state, markdown).collect(); + + Self { items, state } + } + + pub fn push_str(&mut self, markdown: &str) { + // Append to last leftover text + let mut leftover = std::mem::take(&mut self.state.leftover); + leftover.push_str(markdown); + + // Pop the last item + let _ = self.items.pop(); + + // Re-parse last item and new text + let new_items = parse_with(&mut self.state, &leftover); + self.items.extend(new_items); + + dbg!(&self.state); + } + + pub fn items(&self) -> &[Item] { + &self.items + } +} + /// A Markdown item. #[derive(Debug, Clone)] pub enum Item { @@ -232,6 +268,24 @@ impl Span { /// } /// ``` pub fn parse(markdown: &str) -> impl Iterator + '_ { + parse_with(State::default(), markdown) +} + +#[derive(Debug, Clone, Default)] +pub struct State { + leftover: String, +} + +impl AsMut for State { + fn as_mut(&mut self) -> &mut Self { + self + } +} + +fn parse_with<'a>( + mut state: impl AsMut + 'a, + markdown: &'a str, +) -> impl Iterator + 'a { struct List { start: Option, items: Vec>, @@ -255,27 +309,31 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { | pulldown_cmark::Options::ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS | pulldown_cmark::Options::ENABLE_TABLES | pulldown_cmark::Options::ENABLE_STRIKETHROUGH, - ); - - let produce = |lists: &mut Vec, item| { - if lists.is_empty() { - Some(item) - } else { - lists - .last_mut() - .expect("list context") - .items - .last_mut() - .expect("item context") - .push(item); + ) + .into_offset_iter(); - None - } - }; + let mut produce = + move |lists: &mut Vec, item, source: Range| { + if lists.is_empty() { + state.as_mut().leftover = markdown[source.start..].to_owned(); + + Some(item) + } else { + lists + .last_mut() + .expect("list context") + .items + .last_mut() + .expect("item context") + .push(item); + + None + } + }; // We want to keep the `spans` capacity #[allow(clippy::drain_collect)] - parser.filter_map(move |event| match event { + parser.filter_map(move |(event, source)| match event { pulldown_cmark::Event::Start(tag) => match tag { pulldown_cmark::Tag::Strong if !metadata && !table => { strong = true; @@ -311,6 +369,7 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { produce( &mut lists, Item::Paragraph(Text::new(spans.drain(..).collect())), + source, ) }; @@ -350,6 +409,7 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { produce( &mut lists, Item::Paragraph(Text::new(spans.drain(..).collect())), + source, ) }; @@ -370,6 +430,7 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { produce( &mut lists, Item::Heading(level, Text::new(spans.drain(..).collect())), + source, ) } pulldown_cmark::TagEnd::Strong if !metadata && !table => { @@ -392,6 +453,7 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { produce( &mut lists, Item::Paragraph(Text::new(spans.drain(..).collect())), + source, ) } pulldown_cmark::TagEnd::Item if !metadata && !table => { @@ -401,6 +463,7 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { produce( &mut lists, Item::Paragraph(Text::new(spans.drain(..).collect())), + source, ) } } @@ -413,6 +476,7 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { start: list.start, items: list.items, }, + source, ) } pulldown_cmark::TagEnd::CodeBlock if !metadata && !table => { @@ -424,6 +488,7 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { produce( &mut lists, Item::CodeBlock(Text::new(spans.drain(..).collect())), + source, ) } pulldown_cmark::TagEnd::MetadataBlock(_) => { From 084862424e92337f71e899e497b563941bba7018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 31 Jan 2025 20:37:07 +0100 Subject: [PATCH 093/116] Implement `markdown` incremental code highlighting (cherry picked from commit 4b8fc23840e52a81f1c62c48e4e83d04b700b392) --- examples/markdown/src/main.rs | 81 +++++++++------- highlighter/src/lib.rs | 111 ++++++++++++++++----- widget/src/markdown.rs | 175 +++++++++++++++++++++++++--------- 3 files changed, 263 insertions(+), 104 deletions(-) diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index a55e91d2ab..2361b7b770 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -19,7 +19,7 @@ struct Markdown { } enum Mode { - Oneshot(Vec), + Preview(Vec), Stream { pending: String, parsed: markdown::Content, @@ -43,14 +43,14 @@ impl Markdown { ( Self { content: text_editor::Content::with_text(INITIAL_CONTENT), - mode: Mode::Oneshot(markdown::parse(INITIAL_CONTENT).collect()), + mode: Mode::Preview(markdown::parse(INITIAL_CONTENT).collect()), theme, }, widget::focus_next(), ) } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Task { match message { Message::Edit(action) => { let is_edit = action.is_edit(); @@ -58,48 +58,57 @@ impl Markdown { self.content.perform(action); if is_edit { - self.mode = match self.mode { - Mode::Oneshot(_) => Mode::Oneshot( - markdown::parse(&self.content.text()).collect(), - ), - Mode::Stream { .. } => Mode::Stream { - pending: self.content.text(), - parsed: markdown::Content::parse(""), - }, - } + self.mode = Mode::Preview( + markdown::parse(&self.content.text()).collect(), + ); } + + Task::none() } Message::LinkClicked(link) => { let _ = open::that_in_background(link.to_string()); + + Task::none() } Message::ToggleStream(enable_stream) => { - self.mode = if enable_stream { - Mode::Stream { + if enable_stream { + self.mode = Mode::Stream { pending: self.content.text(), parsed: markdown::Content::parse(""), - } + }; + + scrollable::snap_to( + "preview", + scrollable::RelativeOffset::END, + ) } else { - Mode::Oneshot( + self.mode = Mode::Preview( markdown::parse(&self.content.text()).collect(), - ) - }; + ); + + Task::none() + } } - Message::NextToken => match &mut self.mode { - Mode::Oneshot(_) => {} - Mode::Stream { pending, parsed } => { - if pending.is_empty() { - self.mode = Mode::Oneshot(parsed.items().to_vec()); - } else { - let mut tokens = pending.split(' '); - - if let Some(token) = tokens.next() { - parsed.push_str(&format!("{token} ")); + Message::NextToken => { + match &mut self.mode { + Mode::Preview(_) => {} + Mode::Stream { pending, parsed } => { + if pending.is_empty() { + self.mode = Mode::Preview(parsed.items().to_vec()); + } else { + let mut tokens = pending.split(' '); + + if let Some(token) = tokens.next() { + parsed.push_str(&format!("{token} ")); + } + + *pending = tokens.collect::>().join(" "); } - - *pending = tokens.collect::>().join(" "); } } - }, + + Task::none() + } } } @@ -113,7 +122,7 @@ impl Markdown { .highlight("markdown", highlighter::Theme::Base16Ocean); let items = match &self.mode { - Mode::Oneshot(items) => items.as_slice(), + Mode::Preview(items) => items.as_slice(), Mode::Stream { parsed, .. } => parsed.items(), }; @@ -127,7 +136,11 @@ impl Markdown { row![ editor, hover( - scrollable(preview).spacing(10).width(Fill).height(Fill), + scrollable(preview) + .spacing(10) + .width(Fill) + .height(Fill) + .id("preview"), right( toggler(matches!(self.mode, Mode::Stream { .. })) .label("Stream") @@ -147,7 +160,7 @@ impl Markdown { fn subscription(&self) -> Subscription { match self.mode { - Mode::Oneshot(_) => Subscription::none(), + Mode::Preview(_) => Subscription::none(), Mode::Stream { .. } => { time::every(milliseconds(20)).map(|_| Message::NextToken) } diff --git a/highlighter/src/lib.rs b/highlighter/src/lib.rs index 83a15cb1f0..4071a72162 100644 --- a/highlighter/src/lib.rs +++ b/highlighter/src/lib.rs @@ -104,30 +104,7 @@ impl highlighter::Highlighter for Highlighter { let ops = parser.parse_line(line, &SYNTAXES).unwrap_or_default(); - let highlighter = &self.highlighter; - - Box::new( - ScopeRangeIterator { - ops, - line_length: line.len(), - index: 0, - last_str_index: 0, - } - .filter_map(move |(range, scope)| { - let _ = stack.apply(&scope); - - if range.is_empty() { - None - } else { - Some(( - range, - Highlight( - highlighter.style_mod_for_stack(&stack.scopes), - ), - )) - } - }), - ) + Box::new(scope_iterator(ops, line, stack, &self.highlighter)) } fn current_line(&self) -> usize { @@ -135,6 +112,92 @@ impl highlighter::Highlighter for Highlighter { } } +fn scope_iterator<'a>( + ops: Vec<(usize, parsing::ScopeStackOp)>, + line: &str, + stack: &'a mut parsing::ScopeStack, + highlighter: &'a highlighting::Highlighter<'static>, +) -> impl Iterator, Highlight)> + 'a { + ScopeRangeIterator { + ops, + line_length: line.len(), + index: 0, + last_str_index: 0, + } + .filter_map(move |(range, scope)| { + let _ = stack.apply(&scope); + + if range.is_empty() { + None + } else { + Some(( + range, + Highlight(highlighter.style_mod_for_stack(&stack.scopes)), + )) + } + }) +} + +/// A streaming syntax highlighter. +/// +/// It can efficiently highlight an immutable stream of tokens. +#[derive(Debug)] +pub struct Stream { + syntax: &'static parsing::SyntaxReference, + highlighter: highlighting::Highlighter<'static>, + commit: (parsing::ParseState, parsing::ScopeStack), + state: parsing::ParseState, + stack: parsing::ScopeStack, +} + +impl Stream { + /// Creates a new [`Stream`] highlighter. + pub fn new(settings: &Settings) -> Self { + let syntax = SYNTAXES + .find_syntax_by_token(&settings.token) + .unwrap_or_else(|| SYNTAXES.find_syntax_plain_text()); + + let highlighter = highlighting::Highlighter::new( + &THEMES.themes[settings.theme.key()], + ); + + let state = parsing::ParseState::new(syntax); + let stack = parsing::ScopeStack::new(); + + Self { + syntax, + highlighter, + commit: (state.clone(), stack.clone()), + state, + stack, + } + } + + /// Highlights the given line from the last commit. + pub fn highlight_line( + &mut self, + line: &str, + ) -> impl Iterator, Highlight)> + '_ { + self.state = self.commit.0.clone(); + self.stack = self.commit.1.clone(); + + let ops = self.state.parse_line(line, &SYNTAXES).unwrap_or_default(); + scope_iterator(ops, line, &mut self.stack, &self.highlighter) + } + + /// Commits the last highlighted line. + pub fn commit(&mut self) { + self.commit = (self.state.clone(), self.stack.clone()); + } + + /// Resets the [`Stream`] highlighter. + pub fn reset(&mut self) { + self.state = parsing::ParseState::new(self.syntax); + self.stack = parsing::ScopeStack::new(); + self.commit = (self.state.clone(), self.stack.clone()); + } +} + /// The settings of a [`Highlighter`]. #[derive(Debug, Clone, PartialEq)] pub struct Settings { diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 0365dee8a0..7f6965e59c 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -57,6 +57,7 @@ use crate::core::{ }; use crate::{column, container, rich_text, row, scrollable, span, text}; +use std::borrow::BorrowMut; use std::cell::{Cell, RefCell}; use std::ops::Range; use std::sync::Arc; @@ -65,7 +66,7 @@ pub use core::text::Highlight; pub use pulldown_cmark::HeadingLevel; pub use url::Url; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Content { items: Vec, state: State, @@ -80,6 +81,10 @@ impl Content { } pub fn push_str(&mut self, markdown: &str) { + if markdown.is_empty() { + return; + } + // Append to last leftover text let mut leftover = std::mem::take(&mut self.state.leftover); leftover.push_str(markdown); @@ -90,8 +95,6 @@ impl Content { // Re-parse last item and new text let new_items = parse_with(&mut self.state, &leftover); self.items.extend(new_items); - - dbg!(&self.state); } pub fn items(&self) -> &[Item] { @@ -271,19 +274,91 @@ pub fn parse(markdown: &str) -> impl Iterator + '_ { parse_with(State::default(), markdown) } -#[derive(Debug, Clone, Default)] -pub struct State { +#[derive(Debug, Default)] +struct State { leftover: String, + #[cfg(feature = "highlighter")] + highlighter: Option, +} + +#[cfg(feature = "highlighter")] +#[derive(Debug)] +struct Highlighter { + lines: Vec<(String, Vec)>, + parser: iced_highlighter::Stream, + current: usize, } -impl AsMut for State { - fn as_mut(&mut self) -> &mut Self { - self +#[cfg(feature = "highlighter")] +impl Highlighter { + pub fn new(language: &str) -> Self { + Self { + lines: Vec::new(), + parser: iced_highlighter::Stream::new( + &iced_highlighter::Settings { + theme: iced_highlighter::Theme::Base16Ocean, + token: language.to_string(), + }, + ), + current: 0, + } + } + + pub fn prepare(&mut self) { + self.current = 0; + } + + pub fn highlight_line(&mut self, text: &str) -> &[Span] { + match self.lines.get(self.current) { + Some(line) if line.0 == text => {} + _ => { + if self.current + 1 < self.lines.len() { + println!("Resetting..."); + self.parser.reset(); + self.lines.truncate(self.current); + + for line in &self.lines { + println!("Refeeding {n} lines", n = self.lines.len()); + + let _ = self.parser.highlight_line(&line.0); + } + } + + println!("Parsing: {text}", text = text.trim_end()); + if self.current + 1 < self.lines.len() { + self.parser.commit(); + } + + let mut spans = Vec::new(); + + for (range, highlight) in self.parser.highlight_line(text) { + spans.push(Span::Highlight { + text: text[range].to_owned(), + color: highlight.color(), + font: highlight.font(), + }); + } + + if self.current + 1 == self.lines.len() { + let _ = self.lines.pop(); + } + + self.lines.push((text.to_owned(), spans)); + } + } + + self.current += 1; + + &self + .lines + .get(self.current - 1) + .expect("Line must be parsed") + .1 } } fn parse_with<'a>( - mut state: impl AsMut + 'a, + mut state: impl BorrowMut + 'a, markdown: &'a str, ) -> impl Iterator + 'a { struct List { @@ -312,24 +387,26 @@ fn parse_with<'a>( ) .into_offset_iter(); - let mut produce = - move |lists: &mut Vec, item, source: Range| { - if lists.is_empty() { - state.as_mut().leftover = markdown[source.start..].to_owned(); - - Some(item) - } else { - lists - .last_mut() - .expect("list context") - .items - .last_mut() - .expect("item context") - .push(item); + let produce = move |state: &mut State, + lists: &mut Vec, + item, + source: Range| { + if lists.is_empty() { + state.leftover = markdown[source.start..].to_owned(); + + Some(item) + } else { + lists + .last_mut() + .expect("list context") + .items + .last_mut() + .expect("item context") + .push(item); - None - } - }; + None + } + }; // We want to keep the `spans` capacity #[allow(clippy::drain_collect)] @@ -367,6 +444,7 @@ fn parse_with<'a>( None } else { produce( + state.borrow_mut(), &mut lists, Item::Paragraph(Text::new(spans.drain(..).collect())), source, @@ -393,20 +471,24 @@ fn parse_with<'a>( ) if !metadata && !table => { #[cfg(feature = "highlighter")] { - use iced_highlighter::Highlighter; - use text::Highlighter as _; - - highlighter = - Some(Highlighter::new(&iced_highlighter::Settings { - theme: iced_highlighter::Theme::Base16Ocean, - token: _language.to_string(), - })); + highlighter = Some({ + let mut highlighter = state + .borrow_mut() + .highlighter + .take() + .unwrap_or_else(|| Highlighter::new(&_language)); + + highlighter.prepare(); + + highlighter + }); } let prev = if spans.is_empty() { None } else { produce( + state.borrow_mut(), &mut lists, Item::Paragraph(Text::new(spans.drain(..).collect())), source, @@ -428,6 +510,7 @@ fn parse_with<'a>( pulldown_cmark::Event::End(tag) => match tag { pulldown_cmark::TagEnd::Heading(level) if !metadata && !table => { produce( + state.borrow_mut(), &mut lists, Item::Heading(level, Text::new(spans.drain(..).collect())), source, @@ -451,6 +534,7 @@ fn parse_with<'a>( } pulldown_cmark::TagEnd::Paragraph if !metadata && !table => { produce( + state.borrow_mut(), &mut lists, Item::Paragraph(Text::new(spans.drain(..).collect())), source, @@ -461,6 +545,7 @@ fn parse_with<'a>( None } else { produce( + state.borrow_mut(), &mut lists, Item::Paragraph(Text::new(spans.drain(..).collect())), source, @@ -471,6 +556,7 @@ fn parse_with<'a>( let list = lists.pop().expect("list context"); produce( + state.borrow_mut(), &mut lists, Item::List { start: list.start, @@ -482,10 +568,11 @@ fn parse_with<'a>( pulldown_cmark::TagEnd::CodeBlock if !metadata && !table => { #[cfg(feature = "highlighter")] { - highlighter = None; + state.borrow_mut().highlighter = highlighter.take(); } produce( + state.borrow_mut(), &mut lists, Item::CodeBlock(Text::new(spans.drain(..).collect())), source, @@ -504,20 +591,16 @@ fn parse_with<'a>( pulldown_cmark::Event::Text(text) if !metadata && !table => { #[cfg(feature = "highlighter")] if let Some(highlighter) = &mut highlighter { - use text::Highlighter as _; + let start = std::time::Instant::now(); - for (range, highlight) in - highlighter.highlight_line(text.as_ref()) - { - let span = Span::Highlight { - text: text[range].to_owned(), - color: highlight.color(), - font: highlight.font(), - }; - - spans.push(span); + for line in text.lines() { + spans.extend_from_slice( + highlighter.highlight_line(&format!("{line}\n")), + ); } + dbg!(start.elapsed()); + return None; } From 644050bb26c66707120477bb8e8985cdbb3648d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 31 Jan 2025 20:42:53 +0100 Subject: [PATCH 094/116] Replace `println` with `log` calls in `markdown` module (cherry picked from commit bc2d662af7fd9b527dc6b49f31627780e58d79c2) --- Cargo.lock | 8413 ++++++++++++++++++++++++++++++++++++++++ widget/Cargo.toml | 1 + widget/src/markdown.rs | 10 +- 3 files changed, 8421 insertions(+), 3 deletions(-) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000..8103ae0a8d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,8413 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "accesskit" +version = "0.16.0" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" + +[[package]] +name = "accesskit_atspi_common" +version = "0.9.0" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "serde", + "thiserror 1.0.69", + "zvariant 3.15.2", +] + +[[package]] +name = "accesskit_consumer" +version = "0.24.0" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +dependencies = [ + "accesskit", + "immutable-chunkmap", +] + +[[package]] +name = "accesskit_macos" +version = "0.17.0" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +dependencies = [ + "accesskit", + "accesskit_consumer", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", +] + +[[package]] +name = "accesskit_unix" +version = "0.12.0" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel 2.3.1", + "async-executor", + "async-task", + "atspi", + "futures-lite 1.13.0", + "futures-util", + "serde", + "tokio", + "tokio-stream", + "zbus 3.15.2", +] + +[[package]] +name = "accesskit_windows" +version = "0.22.0" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +dependencies = [ + "accesskit", + "accesskit_consumer", + "paste", + "static_assertions", + "windows 0.54.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.22.0" +source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle 0.6.2", + "winit", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.8.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "arc" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.0", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock 3.4.0", + "blocking", + "futures-lite 2.6.0", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io 2.4.0", + "async-lock 3.4.0", + "blocking", + "futures-lite 2.6.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock 3.4.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.0", + "parking", + "polling 3.7.4", + "rustix 0.38.44", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f" +dependencies = [ + "async-io 1.13.0", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.44", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel 2.3.1", + "async-io 2.4.0", + "async-lock 3.4.0", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.0", + "futures-lite 2.6.0", + "rustix 0.38.44", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io 2.4.0", + "async-lock 3.4.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.44", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-std" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 2.4.0", + "async-lock 3.4.0", + "async-process 2.3.0", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "async-tungstenite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tungstenite", + "webpki-roots", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_refcell" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" + +[[package]] +name = "atspi" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus 3.15.2", + "zbus_names 2.6.1", + "zvariant 3.15.2", +] + +[[package]] +name = "atspi-connection" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite 1.13.0", + "zbus 3.15.2", +] + +[[package]] +name = "atspi-proxies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +dependencies = [ + "atspi-common", + "serde", + "zbus 3.15.2", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bezier_tool" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite 2.6.0", + "piper", +] + +[[package]] +name = "built" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "calloop" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +dependencies = [ + "bitflags 2.8.0", + "log", + "polling 3.7.4", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.8.0", + "log", + "polling 3.7.4", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop 0.12.4", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "changelog" +version = "0.1.0" +dependencies = [ + "iced", + "log", + "reqwest", + "serde", + "thiserror 1.0.69", + "tokio", + "tracing-subscriber", + "webbrowser", +] + +[[package]] +name = "checkbox" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "clipboard_macos" +version = "0.1.0" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" +dependencies = [ + "objc", + "objc-foundation", + "objc_id", +] + +[[package]] +name = "clipboard_wayland" +version = "0.2.2" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" +dependencies = [ + "dnd", + "mime 0.1.0", + "smithay-clipboard", +] + +[[package]] +name = "clipboard_x11" +version = "0.4.2" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" +dependencies = [ + "thiserror 1.0.69", + "x11rb", +] + +[[package]] +name = "clock" +version = "0.1.0" +dependencies = [ + "chrono", + "iced", + "tracing-subscriber", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation 0.9.4", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_palette" +version = "0.1.0" +dependencies = [ + "iced", + "palette", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "combo_box" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cosmic-client-toolkit" +version = "0.1.0" +source = "git+https://github.com/pop-os/cosmic-protocols?rev=178eb0b#178eb0b14a0e5c192f64f6dee6c40341a8e5ee51" +dependencies = [ + "cosmic-protocols", + "libc", + "smithay-client-toolkit 0.19.2", + "wayland-client", + "wayland-protocols 0.32.5", +] + +[[package]] +name = "cosmic-protocols" +version = "0.1.0" +source = "git+https://github.com/pop-os/cosmic-protocols?rev=178eb0b#178eb0b14a0e5c192f64f6dee6c40341a8e5ee51" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.5", + "wayland-protocols-wlr 0.3.5", + "wayland-scanner", + "wayland-server", +] + +[[package]] +name = "cosmic-text" +version = "0.14.2" +source = "git+https://github.com/pop-os/cosmic-text.git#b017d7c856aed4b1a3ef0952df75d02d889a1f7b" +dependencies = [ + "bitflags 2.8.0", + "fontdb 0.23.0", + "log", + "rangemap", + "rustc-hash 1.1.0", + "rustybuzz", + "self_cell", + "smol_str", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "counter" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" + +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + +[[package]] +name = "custom_quad" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "custom_shader" +version = "0.1.0" +dependencies = [ + "bytemuck", + "glam", + "iced", + "image", + "rand 0.8.5", +] + +[[package]] +name = "custom_widget" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "d3d12" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" +dependencies = [ + "bitflags 2.8.0", + "libloading", + "winapi", +] + +[[package]] +name = "dark-light" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a76fa97167fa740dcdbfe18e8895601e1bc36525f09b044e00916e717c03a3c" +dependencies = [ + "dconf_rs", + "detect-desktop-environment", + "dirs", + "objc", + "rust-ini", + "web-sys", + "winreg", + "zbus 4.4.0", +] + +[[package]] +name = "data-encoding" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" + +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "dconf_rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7046468a81e6a002061c01e6a7c83139daf91b11c30e66795b13217c2d885c8b" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "detect-desktop-environment" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d8ad60dd5b13a4ee6bd8fa2d5d88965c597c67bce32b5fc49c94f55cb50810" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "dnd" +version = "0.1.0" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" +dependencies = [ + "bitflags 2.8.0", + "mime 0.1.0", + "raw-window-handle 0.6.2", + "smithay-client-toolkit 0.19.2", + "smithay-clipboard", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "download_progress" +version = "0.1.0" +dependencies = [ + "iced", + "reqwest", +] + +[[package]] +name = "dpi" +version = "0.1.1" +source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13#1cc02bdab141072eaabad639d74b032fd0fcc62e" + +[[package]] +name = "drm" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "rustix 0.38.44", +] + +[[package]] +name = "drm-ffi" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" +dependencies = [ + "drm-sys", + "rustix 0.38.44", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176" +dependencies = [ + "libc", + "linux-raw-sys 0.6.5", +] + +[[package]] +name = "editor" +version = "0.1.0" +dependencies = [ + "iced", + "rfd", + "tokio", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + +[[package]] +name = "events" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "exit" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "ferris" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "font-types" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa6a5e5a77b5f3f7f9e32879f484aa5b3632ddfbe568a16266c904a6f32cdaf" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.9.5", + "slotmap", + "tinyvec", + "ttf-parser 0.21.1", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.9.5", + "slotmap", + "tinyvec", + "ttf-parser 0.25.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "game_of_life" +version = "0.1.0" +dependencies = [ + "iced", + "itertools 0.12.1", + "rustc-hash 2.1.0", + "tokio", + "tracing-subscriber", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "geometry" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.8.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.8.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows 0.52.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +dependencies = [ + "bitflags 2.8.0", + "gpu-descriptor-types", + "hashbrown 0.15.2", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "gradient" +version = "0.1.0" +dependencies = [ + "iced", + "tracing-subscriber", +] + +[[package]] +name = "gstreamer" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de95703f4c8e79f4f4e42279cf1ab0e5a46b7ece4a9dfcd16424164af7be9055" +dependencies = [ + "cfg-if", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "gstreamer-sys", + "itertools 0.12.1", + "libc", + "muldiv", + "num-integer", + "num-rational", + "option-operations", + "paste", + "pin-project-lite", + "pretty-hex", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gstreamer-allocators" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c9fb20b4b33d4e5dfdddf817d47937a1a1dcc3cfa72bd6f68f6b73231214293" +dependencies = [ + "glib", + "gstreamer", + "gstreamer-allocators-sys", + "libc", +] + +[[package]] +name = "gstreamer-allocators-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1158478db5aacee46d876c7012861345a6486d1d064ce5cdceac57f3bd137af" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-app" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16bc8090a8806193237e7b6531ee429ff6e39686425f5c3eb06dfa75875390fb" +dependencies = [ + "futures-core", + "futures-sink", + "glib", + "gstreamer", + "gstreamer-app-sys", + "gstreamer-base", + "libc", +] + +[[package]] +name = "gstreamer-app-sys" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aea07f07a3f17278e6998390ecaea127e476f0af0360c2d83d96e6d3a97fb75e" +dependencies = [ + "glib-sys", + "gstreamer-base-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-base" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb150b6904a49052237fede7cc2e6479df6ced5043d95e6af8134bc141a3167f" +dependencies = [ + "atomic_refcell", + "cfg-if", + "glib", + "gstreamer", + "gstreamer-base-sys", + "libc", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ca701f9078fe115b29b24c80910b577f9cb5b039182f050dbadf5933594b64" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "564cda782b3e6eed1b81cb4798a06794db56440fb05b422505be689f34ce3bc4" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-video" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e85b2a4d1d3b7a98ae03806c3ed5c2db89d6b37a5f138780b48de015d68715e5" +dependencies = [ + "cfg-if", + "futures-channel", + "glib", + "gstreamer", + "gstreamer-base", + "gstreamer-video-sys", + "libc", + "thiserror 1.0.69", +] + +[[package]] +name = "gstreamer-video-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302318d98e6b054501e485b6bb4ee20225823218f4a8660c182f115a33b16ee" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-base-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.8.0", + "com", + "libc", + "libloading", + "thiserror 1.0.69", + "widestring", + "winapi", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime 0.3.17", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.8", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.2.0", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.21", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.1", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "socket2 0.5.8", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "iced" +version = "0.14.0-dev" +dependencies = [ + "criterion", + "dnd", + "iced_accessibility", + "iced_core", + "iced_futures", + "iced_highlighter", + "iced_renderer", + "iced_wgpu", + "iced_widget", + "iced_winit", + "image", + "mime 0.1.0", + "thiserror 1.0.69", + "window_clipboard", +] + +[[package]] +name = "iced_accessibility" +version = "0.1.0" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_windows", + "accesskit_winit", +] + +[[package]] +name = "iced_core" +version = "0.14.0-dev" +dependencies = [ + "approx", + "bitflags 2.8.0", + "bytes", + "cosmic-client-toolkit", + "dark-light", + "dnd", + "glam", + "iced_accessibility", + "log", + "mime 0.1.0", + "num-traits", + "once_cell", + "palette", + "raw-window-handle 0.6.2", + "rustc-hash 2.1.0", + "serde", + "smol_str", + "thiserror 1.0.69", + "web-time", + "window_clipboard", +] + +[[package]] +name = "iced_futures" +version = "0.14.0-dev" +dependencies = [ + "async-std", + "futures", + "iced_core", + "log", + "rustc-hash 2.1.0", + "smol", + "tokio", + "wasm-bindgen-futures", + "wasm-timer", +] + +[[package]] +name = "iced_glyphon" +version = "0.6.0" +source = "git+https://github.com/pop-os/glyphon.git?tag=iced-0.14-dev#6ef9d12a20cfd0f7bdf38136a26ded9f7459ec8b" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.1.0", + "wgpu", +] + +[[package]] +name = "iced_graphics" +version = "0.14.0-dev" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "cosmic-text", + "half", + "iced_core", + "iced_futures", + "image", + "kamadak-exif", + "log", + "lyon_path", + "once_cell", + "raw-window-handle 0.6.2", + "rustc-hash 2.1.0", + "thiserror 1.0.69", + "unicode-segmentation", +] + +[[package]] +name = "iced_highlighter" +version = "0.14.0-dev" +dependencies = [ + "iced_core", + "once_cell", + "syntect", +] + +[[package]] +name = "iced_renderer" +version = "0.14.0-dev" +dependencies = [ + "iced_graphics", + "iced_tiny_skia", + "iced_wgpu", + "log", + "thiserror 1.0.69", +] + +[[package]] +name = "iced_runtime" +version = "0.14.0-dev" +dependencies = [ + "bytes", + "cosmic-client-toolkit", + "dnd", + "iced_accessibility", + "iced_core", + "iced_futures", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", + "window_clipboard", +] + +[[package]] +name = "iced_tiny_skia" +version = "0.14.0-dev" +dependencies = [ + "bytemuck", + "cosmic-text", + "iced_graphics", + "kurbo 0.10.4", + "log", + "resvg", + "rustc-hash 2.1.0", + "softbuffer", + "tiny-skia", +] + +[[package]] +name = "iced_wgpu" +version = "0.14.0-dev" +dependencies = [ + "as-raw-xcb-connection", + "bitflags 2.8.0", + "bytemuck", + "cosmic-client-toolkit", + "futures", + "glam", + "guillotiere", + "iced_glyphon", + "iced_graphics", + "log", + "lyon", + "once_cell", + "raw-window-handle 0.6.2", + "resvg", + "rustc-hash 2.1.0", + "rustix 0.38.44", + "thiserror 1.0.69", + "tiny-xlib", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.5", + "wayland-sys", + "wgpu", + "x11rb", +] + +[[package]] +name = "iced_widget" +version = "0.14.0-dev" +dependencies = [ + "cosmic-client-toolkit", + "dnd", + "iced_accessibility", + "iced_highlighter", + "iced_renderer", + "iced_runtime", + "log", + "num-traits", + "once_cell", + "ouroboros", + "pulldown-cmark", + "qrcode", + "rustc-hash 2.1.0", + "thiserror 1.0.69", + "unicode-segmentation", + "url", + "window_clipboard", +] + +[[package]] +name = "iced_winit" +version = "0.14.0-dev" +dependencies = [ + "cosmic-client-toolkit", + "dnd", + "iced_accessibility", + "iced_futures", + "iced_graphics", + "iced_runtime", + "log", + "raw-window-handle 0.6.2", + "rustc-hash 2.1.0", + "rustix 0.38.44", + "sysinfo", + "thiserror 1.0.69", + "tracing", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.5", + "web-sys", + "winapi", + "window_clipboard", + "winit", + "xkbcommon", + "xkbcommon-dl", + "xkeysym", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + +[[package]] +name = "immutable-chunkmap" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-terminal" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kamadak-exif" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" +dependencies = [ + "mutate_once", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "kurbo" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "layout" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "lazy" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.8.0", + "libc", + "redox_syscall 0.5.8", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" + +[[package]] +name = "list" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "loading_spinners" +version = "0.1.0" +dependencies = [ + "iced", + "lyon_algorithms", + "once_cell", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +dependencies = [ + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "loupe" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" + +[[package]] +name = "lyon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markdown" +version = "0.1.0" +dependencies = [ + "iced", + "open", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "maybe_parallel_iterator" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5069b219d51d2ba2d9388623bd7eead5d4e7974bc8ff3ec9edbe36b09c0ef477" +dependencies = [ + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.8.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.1.0" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" +dependencies = [ + "smithay-clipboard", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime 0.3.17", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "modal" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "muldiv" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 0.2.12", + "httparse", + "log", + "memchr", + "mime 0.3.17", + "spin", + "version_check", +] + +[[package]] +name = "multi_window" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "multitouch" +version = "0.1.0" +dependencies = [ + "iced", + "tracing-subscriber", + "voronator", +] + +[[package]] +name = "mutate_once" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" + +[[package]] +name = "naga" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.8.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.8.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", + "memoffset 0.9.1", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.8.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.8.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.8.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "onig" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +dependencies = [ + "bitflags 1.3.2", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "option-operations" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" +dependencies = [ + "paste", +] + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser 0.25.1", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "pane_grid" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.8", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pick_list" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.3.0", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64 0.22.1", + "indexmap", + "quick-xml 0.32.0", + "serde", + "time", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pokedex" +version = "0.1.0" +dependencies = [ + "getrandom 0.2.15", + "iced", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.44", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "pretty-hex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc83ee4a840062f368f9096d80077a9841ec117e17e7f700df81958f1451254" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit 0.22.22", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "version_check", + "yansi", +] + +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +dependencies = [ + "quote", + "syn 2.0.96", +] + +[[package]] +name = "progress_bar" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "pulldown-cmark" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" +dependencies = [ + "bitflags 2.8.0", + "getopts", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "qr_code" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "qrcode" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166f136dfdb199f98186f3649cf7a0536534a61417a1a30221b492b4fb60ce3f" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +dependencies = [ + "bytes", + "cfg_aliases 0.2.1", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.0", + "rustls 0.23.21", + "socket2 0.5.8", + "thiserror 2.0.11", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" +dependencies = [ + "bytes", + "getrandom 0.3.1", + "rand 0.9.1", + "ring", + "rustc-hash 2.1.0", + "rustls 0.23.21", + "rustls-pki-types", + "slab", + "thiserror 2.0.11", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +dependencies = [ + "cfg_aliases 0.2.1", + "libc", + "once_cell", + "socket2 0.5.8", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", +] + +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "rangemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand 0.8.5", + "rand_chacha 0.3.1", + "simd_helpers", + "system-deps", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime 0.3.17", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.21", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.26.1", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "resvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rfd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d8ab342bcc5436e04d3a4c1e09e17d74958bfaddf8d5fad6f85607df0f994f" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle 0.5.2", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes 1.0.11", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.8.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screenshot" +version = "0.1.0" +dependencies = [ + "iced", + "image", + "tokio", + "tracing-subscriber", +] + +[[package]] +name = "scrollable" +version = "0.1.0" +dependencies = [ + "iced", + "iced_core", + "once_cell", +] + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2 0.9.5", + "smithay-client-toolkit 0.19.2", + "tiny-skia", +] + +[[package]] +name = "sctk_drag" +version = "0.1.0" +dependencies = [ + "cosmic-client-toolkit", + "env_logger", + "iced", + "iced_core", +] + +[[package]] +name = "sctk_lazy" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "sctk_session_lock" +version = "0.1.0" +dependencies = [ + "async-std", + "env_logger", + "iced", + "iced_runtime", + "smithay-client-toolkit 0.18.0", +] + +[[package]] +name = "sctk_subsurface" +version = "0.1.0" +dependencies = [ + "calloop 0.13.0", + "cosmic-client-toolkit", + "env_logger", + "futures-channel", + "iced", + "iced_runtime", + "rustix 0.38.44", +] + +[[package]] +name = "sctk_subsurface_gst" +version = "0.1.0" +dependencies = [ + "calloop 0.12.4", + "drm-fourcc", + "env_logger", + "futures-channel", + "gstreamer", + "gstreamer-allocators", + "gstreamer-app", + "gstreamer-video", + "iced", + "iced_runtime", + "smithay-client-toolkit 0.18.0", +] + +[[package]] +name = "sctk_todos" +version = "0.1.0" +dependencies = [ + "async-std", + "cosmic-client-toolkit", + "directories-next", + "env_logger", + "iced", + "iced_core", + "log", + "once_cell", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "self_cell" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_json" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sierpinski_triangle" +version = "0.1.0" +dependencies = [ + "iced", + "rand 0.8.5", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skrifa" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cc1aa86c26dbb1b63875a7180aa0819709b33348eb5b1491e4321fae388179d" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slider" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smithay-client-toolkit" +version = "0.18.0" +source = "git+https://github.com/smithay/client-toolkit?rev=828b1eb#828b1eb469ad022eb3ac30be02bd5645b879922f" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "calloop 0.12.4", + "calloop-wayland-source 0.2.0", + "cursor-icon", + "libc", + "log", + "memmap2 0.9.5", + "pkg-config", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols 0.31.2", + "wayland-protocols-wlr 0.2.0", + "wayland-scanner", + "xkbcommon", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2 0.9.5", + "pkg-config", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols 0.32.5", + "wayland-protocols-wlr 0.3.5", + "wayland-scanner", + "xkbcommon", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.8.0" +source = "git+https://github.com/pop-os/smithay-clipboard?tag=pop-dnd-5#5a3007def49eb678d1144850c9ee04b80707c56a" +dependencies = [ + "libc", + "raw-window-handle 0.6.2", + "smithay-client-toolkit 0.19.2", + "wayland-backend", +] + +[[package]] +name = "smol" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" +dependencies = [ + "async-channel 1.9.0", + "async-executor", + "async-fs 1.6.0", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-net", + "async-process 1.8.1", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.1" +source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#6e75b1ad7e98397d37cb187886d05969bc480995" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "cfg_aliases 0.2.1", + "cocoa", + "core-graphics", + "drm", + "fastrand 2.3.0", + "foreign-types", + "js-sys", + "log", + "memmap2 0.9.5", + "objc", + "raw-window-handle 0.6.2", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "tiny-xlib", + "wasm-bindgen", + "wayland-backend", + "wayland-client", + "wayland-sys", + "web-sys", + "windows-sys 0.52.0", + "x11rb", +] + +[[package]] +name = "solar_system" +version = "0.1.0" +dependencies = [ + "iced", + "rand 0.8.5", + "tracing-subscriber", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stopwatch" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "styling" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svg" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "svg_fmt" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" + +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo 0.11.1", + "siphasher", +] + +[[package]] +name = "swash" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fae9a562c7b46107d9c78cd78b75bbe1e991c16734c0aee8ff0ee711fb8b620a" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 1.0.69", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows 0.52.0", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +dependencies = [ + "cfg-if", + "fastrand 2.3.0", + "getrandom 0.3.1", + "once_cell", + "rustix 0.38.44", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "the_matrix" +version = "0.1.0" +dependencies = [ + "iced", + "rand 0.8.5", + "tracing-subscriber", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tiny-xlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +dependencies = [ + "as-raw-xcb-connection", + "ctor-lite", + "libloading", + "pkg-config", + "tracing", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toast" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "todos" +version = "0.1.0" +dependencies = [ + "async-std", + "directories-next", + "iced", + "iced_core", + "serde", + "serde_json", + "tracing-subscriber", + "uuid", + "wasm-timer", + "web-sys", +] + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.8", + "tokio-macros", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls 0.23.21", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.22", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.25", +] + +[[package]] +name = "tooltip" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "tour" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "console_log", + "iced", + "tracing-subscriber", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.2.0", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.22.4", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "usvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb 0.18.0", + "imagesize", + "kurbo 0.11.1", + "log", + "pico-args", + "roxmltree", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +dependencies = [ + "getrandom 0.2.15", + "rand 0.8.5", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" + +[[package]] +name = "vectorial_text" +version = "0.1.0" +dependencies = [ + "iced", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "visible_bounds" +version = "0.1.0" +dependencies = [ + "iced", + "once_cell", +] + +[[package]] +name = "voronator" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d8e6fe4b9b3b443f2e4ada327cf4b08ab5cdd27a42cd5c9f38dd29092c10ee" +dependencies = [ + "maybe_parallel_iterator", +] + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http 0.2.12", + "hyper 0.14.32", + "log", + "mime 0.3.17", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.44", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +dependencies = [ + "bitflags 2.8.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.8.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +dependencies = [ + "rustix 0.38.44", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", + "wayland-server", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.5", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.5", + "wayland-scanner", + "wayland-server", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +dependencies = [ + "proc-macro2", + "quick-xml 0.36.2", + "quote", +] + +[[package]] +name = "wayland-server" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89532cc712a2adb119eb4d09694b402576052254d0bb284f82ac1c47fb786ad" +dependencies = [ + "bitflags 2.8.0", + "downcast-rs", + "io-lifetimes 2.0.4", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-sys" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535" +dependencies = [ + "block2", + "core-foundation 0.10.0", + "home", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "websocket" +version = "1.0.0" +dependencies = [ + "async-tungstenite", + "iced", + "once_cell", + "tokio", + "warp", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "wgpu" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" +dependencies = [ + "arrayvec", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle 0.6.2", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.8.0", + "cfg_aliases 0.1.1", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle 0.6.2", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.8.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "range-alloc", + "raw-window-handle 0.6.2", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" +dependencies = [ + "bitflags 2.8.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window_clipboard" +version = "0.4.1" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" +dependencies = [ + "clipboard-win", + "clipboard_macos", + "clipboard_wayland", + "clipboard_x11", + "dnd", + "mime 0.1.0", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-implement", + "windows-interface", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "windows-interface" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.5" +source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13#1cc02bdab141072eaabad639d74b032fd0fcc62e" +dependencies = [ + "ahash 0.8.11", + "android-activity", + "atomic-waker", + "bitflags 2.8.0", + "block2", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2 0.9.5", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle 0.6.2", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.5", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xkbcommon" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" +dependencies = [ + "libc", + "memmap2 0.8.0", + "xkeysym", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.8.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "xml-rs" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast 0.5.1", + "async-executor", + "async-fs 1.6.0", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process 1.8.1", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.26.4", + "once_cell", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros 3.15.2", + "zbus_names 2.6.1", + "zvariant 3.15.2", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast 0.7.2", + "async-executor", + "async-fs 2.1.2", + "async-io 2.4.0", + "async-lock 3.4.0", + "async-process 2.3.0", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener 5.4.0", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.29.0", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils 1.0.1", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.96", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant 3.15.2", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant 4.2.0", +] + +[[package]] +name = "zeno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0de2315dc13d00e5df3cd6b8d2124a6eaec6a2d4b6a1c5f37b7efad17fcc17" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive 3.15.2", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils 1.0.1", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.96", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 7cc1f71949..02212747b7 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -39,6 +39,7 @@ cctk.workspace = true cctk.optional = true num-traits.workspace = true once_cell.workspace = true +log.workspace = true rustc-hash.workspace = true thiserror.workspace = true unicode-segmentation.workspace = true diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 7f6965e59c..77a560ecb1 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -313,18 +313,22 @@ impl Highlighter { Some(line) if line.0 == text => {} _ => { if self.current + 1 < self.lines.len() { - println!("Resetting..."); + log::debug!("Resetting highlighter..."); self.parser.reset(); self.lines.truncate(self.current); for line in &self.lines { - println!("Refeeding {n} lines", n = self.lines.len()); + log::debug!( + "Refeeding {n} lines", + n = self.lines.len() + ); let _ = self.parser.highlight_line(&line.0); } } - println!("Parsing: {text}", text = text.trim_end()); + log::trace!("Parsing: {text}", text = text.trim_end()); + if self.current + 1 < self.lines.len() { self.parser.commit(); } From eb0d7bd63efeb9aa6ebf4a7f3ec55c9f2f585318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 31 Jan 2025 20:49:25 +0100 Subject: [PATCH 095/116] Add `new` constructor for `markdown::Content` (cherry picked from commit 095859ed57e573d91ebe36dceb888ec95427b6ca) --- examples/markdown/src/main.rs | 2 +- widget/src/markdown.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 2361b7b770..5b9a3b4a54 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -74,7 +74,7 @@ impl Markdown { if enable_stream { self.mode = Mode::Stream { pending: self.content.text(), - parsed: markdown::Content::parse(""), + parsed: markdown::Content::new(), }; scrollable::snap_to( diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 77a560ecb1..b4b890958a 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -66,13 +66,17 @@ pub use core::text::Highlight; pub use pulldown_cmark::HeadingLevel; pub use url::Url; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Content { items: Vec, state: State, } impl Content { + pub fn new() -> Self { + Self::default() + } + pub fn parse(markdown: &str) -> Self { let mut state = State::default(); let items = parse_with(&mut state, markdown).collect(); @@ -595,16 +599,12 @@ fn parse_with<'a>( pulldown_cmark::Event::Text(text) if !metadata && !table => { #[cfg(feature = "highlighter")] if let Some(highlighter) = &mut highlighter { - let start = std::time::Instant::now(); - for line in text.lines() { spans.extend_from_slice( highlighter.highlight_line(&format!("{line}\n")), ); } - dbg!(start.elapsed()); - return None; } From 911bce2254f010d6704a1913ccba07f6c3a75b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 1 Feb 2025 00:33:05 +0100 Subject: [PATCH 096/116] Discard `markdown::Highlighter` if language changes (cherry picked from commit 447f5ae494da7ef93ac073600f4e5a2559c4e71c) --- widget/src/markdown.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index b4b890958a..658166ec48 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -289,6 +289,7 @@ struct State { #[derive(Debug)] struct Highlighter { lines: Vec<(String, Vec)>, + language: String, parser: iced_highlighter::Stream, current: usize, } @@ -304,6 +305,7 @@ impl Highlighter { token: language.to_string(), }, ), + language: language.to_owned(), current: 0, } } @@ -484,6 +486,9 @@ fn parse_with<'a>( .borrow_mut() .highlighter .take() + .filter(|highlighter| { + highlighter.language == _language.as_ref() + }) .unwrap_or_else(|| Highlighter::new(&_language)); highlighter.prepare(); From 169218f8c38209e702313bcb66763bb6b2da4fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 1 Feb 2025 01:07:35 +0100 Subject: [PATCH 097/116] Split code blocks into multiple `rich_text` lines This improves layout diffing considerably! (cherry picked from commit eb81679e604e2d1d45590c236fb5b2644c38f3d5) --- widget/src/markdown.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 658166ec48..bb818d194b 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -116,7 +116,7 @@ pub enum Item { /// A code block. /// /// You can enable the `highlighter` feature for syntax highlighting. - CodeBlock(Text), + CodeBlock(Vec), /// A list. List { /// The first number of the list, if it is ordered. @@ -377,6 +377,7 @@ fn parse_with<'a>( } let mut spans = Vec::new(); + let mut code = Vec::new(); let mut strong = false; let mut emphasis = false; let mut strikethrough = false; @@ -587,7 +588,7 @@ fn parse_with<'a>( produce( state.borrow_mut(), &mut lists, - Item::CodeBlock(Text::new(spans.drain(..).collect())), + Item::CodeBlock(code.drain(..).collect()), source, ) } @@ -605,9 +606,9 @@ fn parse_with<'a>( #[cfg(feature = "highlighter")] if let Some(highlighter) = &mut highlighter { for line in text.lines() { - spans.extend_from_slice( - highlighter.highlight_line(&format!("{line}\n")), - ); + code.push(Text::new( + highlighter.highlight_line(line).to_vec(), + )); } return None; @@ -871,13 +872,14 @@ where })) .spacing(spacing * 0.75) .into(), - Item::CodeBlock(code) => container( + Item::CodeBlock(lines) => container( scrollable( - container( - rich_text(code.spans(style)) + container(column(lines.iter().map(|line| { + rich_text(line.spans(style)) .font(Font::MONOSPACE) - .size(code_size), - ) + .size(code_size) + .into() + }))) .padding(spacing.0 / 2.0), ) .direction(scrollable::Direction::Horizontal( From 4b65e87b2b2c6be2d5b7d6e5ee56d069fe8d83ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 1 Feb 2025 04:33:15 +0100 Subject: [PATCH 098/116] Write documentation for `markdown::Content` (cherry picked from commit 952c47bc8a1144e8b7636b645236474ebc0fa14c) --- widget/src/markdown.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index bb818d194b..8039175ddf 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -47,7 +47,6 @@ //! } //! } //! ``` -#![allow(missing_docs)] use crate::core::border; use crate::core::font::{self, Font}; use crate::core::padding; @@ -66,6 +65,7 @@ pub use core::text::Highlight; pub use pulldown_cmark::HeadingLevel; pub use url::Url; +/// A bunch of Markdown that has been parsed. #[derive(Debug, Default)] pub struct Content { items: Vec, @@ -73,10 +73,12 @@ pub struct Content { } impl Content { + /// Creates a new empty [`Content`]. pub fn new() -> Self { Self::default() } + /// Creates some new [`Content`] by parsing the given Markdown. pub fn parse(markdown: &str) -> Self { let mut state = State::default(); let items = parse_with(&mut state, markdown).collect(); @@ -84,6 +86,10 @@ impl Content { Self { items, state } } + /// Pushes more Markdown into the [`Content`]; parsing incrementally! + /// + /// This is specially useful when you have long streams of Markdown; like + /// big files or potentially long replies. pub fn push_str(&mut self, markdown: &str) { if markdown.is_empty() { return; @@ -101,6 +107,9 @@ impl Content { self.items.extend(new_items); } + /// Returns the Markdown items, ready to be rendered. + /// + /// You can use [`view`] to turn them into an [`Element`]. pub fn items(&self) -> &[Item] { &self.items } From 52293b1a1203456092872d52c3ab1be8c2b51d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 2 Feb 2025 04:01:57 +0100 Subject: [PATCH 099/116] Fix broken references when parsing `markdown` streams (cherry picked from commit 569ef13ac96b2a77dd919c9f15fffd8179cbec28) --- Cargo.lock | 4 +- Cargo.toml | 3 +- examples/markdown/src/main.rs | 2 +- widget/src/markdown.rs | 110 ++++++++++++++++++++++++++++++---- 4 files changed, 104 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8103ae0a8d..61c2ceece5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5009,9 +5009,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.11.3" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" +checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14" dependencies = [ "bitflags 2.8.0", "getopts", diff --git a/Cargo.toml b/Cargo.toml index 13d80349d4..a4aaa96470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -193,7 +193,8 @@ num-traits = "0.2" once_cell = "1.0" ouroboros = "0.18" palette = "0.7" -pulldown-cmark = "0.11" +png = "0.17" +pulldown-cmark = "0.12" qrcode = { version = "0.13", default-features = false } raw-window-handle = "0.6" rustc-hash = "2.0" diff --git a/examples/markdown/src/main.rs b/examples/markdown/src/main.rs index 5b9a3b4a54..ba93ee1885 100644 --- a/examples/markdown/src/main.rs +++ b/examples/markdown/src/main.rs @@ -162,7 +162,7 @@ impl Markdown { match self.mode { Mode::Preview(_) => Subscription::none(), Mode::Stream { .. } => { - time::every(milliseconds(20)).map(|_| Message::NextToken) + time::every(milliseconds(10)).map(|_| Message::NextToken) } } } diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 8039175ddf..ea00d14f74 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -58,7 +58,9 @@ use crate::{column, container, rich_text, row, scrollable, span, text}; use std::borrow::BorrowMut; use std::cell::{Cell, RefCell}; +use std::collections::{HashMap, HashSet}; use std::ops::Range; +use std::rc::Rc; use std::sync::Arc; pub use core::text::Highlight; @@ -69,9 +71,16 @@ pub use url::Url; #[derive(Debug, Default)] pub struct Content { items: Vec, + incomplete: HashMap, state: State, } +#[derive(Debug)] +struct Section { + content: String, + broken_links: HashSet, +} + impl Content { /// Creates a new empty [`Content`]. pub fn new() -> Self { @@ -80,10 +89,9 @@ impl Content { /// Creates some new [`Content`] by parsing the given Markdown. pub fn parse(markdown: &str) -> Self { - let mut state = State::default(); - let items = parse_with(&mut state, markdown).collect(); - - Self { items, state } + let mut content = Self::new(); + content.push_str(markdown); + content } /// Pushes more Markdown into the [`Content`]; parsing incrementally! @@ -103,8 +111,52 @@ impl Content { let _ = self.items.pop(); // Re-parse last item and new text - let new_items = parse_with(&mut self.state, &leftover); - self.items.extend(new_items); + for (item, source, broken_links) in + parse_with(&mut self.state, &leftover) + { + if !broken_links.is_empty() { + let _ = self.incomplete.insert( + self.items.len(), + Section { + content: source.to_owned(), + broken_links, + }, + ); + } + + self.items.push(item); + } + + // Re-parse incomplete sections if new references are available + if !self.incomplete.is_empty() { + let mut state = State { + leftover: String::new(), + references: self.state.references.clone(), + highlighter: None, + }; + + self.incomplete.retain(|index, section| { + if self.items.len() <= *index { + return false; + } + + let broken_links_before = section.broken_links.len(); + + section + .broken_links + .retain(|link| !self.state.references.contains_key(link)); + + if broken_links_before != section.broken_links.len() { + if let Some((item, _source, _broken_links)) = + parse_with(&mut state, §ion.content).next() + { + self.items[*index] = item; + } + } + + !section.broken_links.is_empty() + }); + } } /// Returns the Markdown items, ready to be rendered. @@ -285,11 +337,13 @@ impl Span { /// ``` pub fn parse(markdown: &str) -> impl Iterator + '_ { parse_with(State::default(), markdown) + .map(|(item, _source, _broken_links)| item) } #[derive(Debug, Default)] struct State { leftover: String, + references: HashMap, #[cfg(feature = "highlighter")] highlighter: Option, } @@ -379,12 +433,14 @@ impl Highlighter { fn parse_with<'a>( mut state: impl BorrowMut + 'a, markdown: &'a str, -) -> impl Iterator + 'a { +) -> impl Iterator)> + 'a { struct List { start: Option, items: Vec>, } + let broken_links = Rc::new(RefCell::new(HashSet::new())); + let mut spans = Vec::new(); let mut code = Vec::new(); let mut strong = false; @@ -398,14 +454,40 @@ fn parse_with<'a>( #[cfg(feature = "highlighter")] let mut highlighter = None; - let parser = pulldown_cmark::Parser::new_ext( + let parser = pulldown_cmark::Parser::new_with_broken_link_callback( markdown, pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS | pulldown_cmark::Options::ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS | pulldown_cmark::Options::ENABLE_TABLES | pulldown_cmark::Options::ENABLE_STRIKETHROUGH, - ) - .into_offset_iter(); + { + let references = state.borrow().references.clone(); + let broken_links = broken_links.clone(); + + Some(move |broken_link: pulldown_cmark::BrokenLink<'_>| { + if let Some(reference) = + references.get(broken_link.reference.as_ref()) + { + Some(( + pulldown_cmark::CowStr::from(reference.to_owned()), + broken_link.reference.into_static(), + )) + } else { + let _ = RefCell::borrow_mut(&broken_links) + .insert(broken_link.reference.to_string()); + + None + } + }) + }, + ); + + let references = &mut state.borrow_mut().references; + + for reference in parser.reference_definitions().iter() { + let _ = references + .insert(reference.0.to_owned(), reference.1.dest.to_string()); + } let produce = move |state: &mut State, lists: &mut Vec, @@ -414,7 +496,11 @@ fn parse_with<'a>( if lists.is_empty() { state.leftover = markdown[source.start..].to_owned(); - Some(item) + Some(( + item, + &markdown[source.start..source.end], + broken_links.take(), + )) } else { lists .last_mut() @@ -428,6 +514,8 @@ fn parse_with<'a>( } }; + let parser = parser.into_offset_iter(); + // We want to keep the `spans` capacity #[allow(clippy::drain_collect)] parser.filter_map(move |(event, source)| match event { From 13191c239eec6b244bde806f9732052d039b33d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 2 Feb 2025 04:17:44 +0100 Subject: [PATCH 100/116] Avoid sharing `State` when re-parsing `markdown` sections (cherry picked from commit 599d8b560bec8036c5ddda62a7bf0a540bdec396) --- widget/src/markdown.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index ea00d14f74..858ee281f3 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -129,12 +129,6 @@ impl Content { // Re-parse incomplete sections if new references are available if !self.incomplete.is_empty() { - let mut state = State { - leftover: String::new(), - references: self.state.references.clone(), - highlighter: None, - }; - self.incomplete.retain(|index, section| { if self.items.len() <= *index { return false; @@ -147,11 +141,19 @@ impl Content { .retain(|link| !self.state.references.contains_key(link)); if broken_links_before != section.broken_links.len() { + let mut state = State { + leftover: String::new(), + references: self.state.references.clone(), + highlighter: None, + }; + if let Some((item, _source, _broken_links)) = parse_with(&mut state, §ion.content).next() { self.items[*index] = item; } + + drop(state); } !section.broken_links.is_empty() From 37f23e2c8dd563af51535c20091ac5d74bb849ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 4 Feb 2025 03:39:16 +0100 Subject: [PATCH 101/116] Introduce `view_with` customizers in `markdown` module (cherry picked from commit c02ae0c4a430994247e6fbc4318ac344ab89123c) --- widget/src/markdown.rs | 280 +++++++++++++++++++++++++++-------------- 1 file changed, 186 insertions(+), 94 deletions(-) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 858ee281f3..628a10c6ee 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -189,6 +189,130 @@ pub enum Item { }, } +impl Item { + /// Displays a Markdown [`Item`] using the default, built-in look for its children. + pub fn view<'a, 'b, Theme, Renderer>( + &'b self, + settings: Settings, + style: Style, + index: usize, + ) -> Element<'a, Url, Theme, Renderer> + where + Theme: Catalog + 'a, + Renderer: core::text::Renderer + 'a, + { + self.view_with(index, settings, style, &DefaultView) + } + + /// Displays a Markdown [`Item`] using the given [`View`] for its children. + pub fn view_with<'a, 'b, Theme, Renderer>( + &'b self, + index: usize, + settings: Settings, + style: Style, + view: &dyn View<'a, 'b, Url, Theme, Renderer>, + ) -> Element<'a, Url, Theme, Renderer> + where + Theme: Catalog + 'a, + Renderer: core::text::Renderer + 'a, + { + let Settings { + text_size, + h1_size, + h2_size, + h3_size, + h4_size, + h5_size, + h6_size, + code_size, + spacing, + } = settings; + + match self { + Item::Heading(level, heading) => { + container(rich_text(heading.spans(style)).size(match level { + pulldown_cmark::HeadingLevel::H1 => h1_size, + pulldown_cmark::HeadingLevel::H2 => h2_size, + pulldown_cmark::HeadingLevel::H3 => h3_size, + pulldown_cmark::HeadingLevel::H4 => h4_size, + pulldown_cmark::HeadingLevel::H5 => h5_size, + pulldown_cmark::HeadingLevel::H6 => h6_size, + })) + .padding(padding::top(if index > 0 { + text_size / 2.0 + } else { + Pixels::ZERO + })) + .into() + } + Item::Paragraph(paragraph) => { + rich_text(paragraph.spans(style)).size(text_size).into() + } + Item::List { start: None, items } => { + column(items.iter().map(|items| { + row![ + text("•").size(text_size), + view_with( + items, + Settings { + spacing: settings.spacing * 0.6, + ..settings + }, + style, + view + ) + ] + .spacing(spacing) + .into() + })) + .spacing(spacing * 0.75) + .into() + } + Item::List { + start: Some(start), + items, + } => column(items.iter().enumerate().map(|(i, items)| { + row![ + text!("{}.", i as u64 + *start).size(text_size), + view_with( + items, + Settings { + spacing: settings.spacing * 0.6, + ..settings + }, + style, + view + ) + ] + .spacing(spacing) + .into() + })) + .spacing(spacing * 0.75) + .into(), + Item::CodeBlock(lines) => container( + scrollable( + container(column(lines.iter().map(|line| { + rich_text(line.spans(style)) + .font(Font::MONOSPACE) + .size(code_size) + .into() + }))) + .padding(spacing.0 / 2.0), + ) + .direction(scrollable::Direction::Horizontal( + scrollable::Scrollbar::default() + .width(spacing.0 / 2.0) + .scroller_width(spacing.0 / 2.0), + )), + ) + .width(Length::Fill) + .padding(spacing.0 / 2.0) + .class(Theme::code_block()) + .into(), + } + } +} + /// A bunch of parsed Markdown text. #[derive(Debug, Clone)] pub struct Text { @@ -900,100 +1024,68 @@ where Theme: Catalog + 'a, Renderer: core::text::Renderer + 'a, { - let Settings { - text_size, - h1_size, - h2_size, - h3_size, - h4_size, - h5_size, - h6_size, - code_size, - spacing, - } = settings; - - let blocks = items.into_iter().enumerate().map(|(i, item)| match item { - Item::Heading(level, heading) => { - container(rich_text(heading.spans(style)).size(match level { - pulldown_cmark::HeadingLevel::H1 => h1_size, - pulldown_cmark::HeadingLevel::H2 => h2_size, - pulldown_cmark::HeadingLevel::H3 => h3_size, - pulldown_cmark::HeadingLevel::H4 => h4_size, - pulldown_cmark::HeadingLevel::H5 => h5_size, - pulldown_cmark::HeadingLevel::H6 => h6_size, - })) - .padding(padding::top(if i > 0 { - text_size / 2.0 - } else { - Pixels::ZERO - })) - .into() - } - Item::Paragraph(paragraph) => { - rich_text(paragraph.spans(style)).size(text_size).into() - } - Item::List { start: None, items } => { - column(items.iter().map(|items| { - row![ - text("•").size(text_size), - view( - items, - Settings { - spacing: settings.spacing * 0.6, - ..settings - }, - style - ) - ] - .spacing(spacing) - .into() - })) - .spacing(spacing * 0.75) - .into() - } - Item::List { - start: Some(start), - items, - } => column(items.iter().enumerate().map(|(i, items)| { - row![ - text!("{}.", i as u64 + *start).size(text_size), - view( - items, - Settings { - spacing: settings.spacing * 0.6, - ..settings - }, - style - ) - ] - .spacing(spacing) - .into() - })) - .spacing(spacing * 0.75) - .into(), - Item::CodeBlock(lines) => container( - scrollable( - container(column(lines.iter().map(|line| { - rich_text(line.spans(style)) - .font(Font::MONOSPACE) - .size(code_size) - .into() - }))) - .padding(spacing.0 / 2.0), - ) - .direction(scrollable::Direction::Horizontal( - scrollable::Scrollbar::default() - .width(spacing.0 / 2.0) - .scroller_width(spacing.0 / 2.0), - )), - ) - .width(Length::Fill) - .padding(spacing.0 / 2.0) - .class(Theme::code_block()) - .into(), - }); - - Element::new(column(blocks).spacing(spacing)) + view_with(items, settings, style, &DefaultView) +} + +/// Runs [`view`] but with a custom [`View`] to turn an [`Item`] into +/// an [`Element`]. +/// +/// This is useful if you want to customize the look of certain Markdown +/// elements. +/// +/// You can use [`Item::view`] and [`Item::view_with`] for the default +/// look. +pub fn view_with<'a, 'b, Message, Theme, Renderer>( + items: impl IntoIterator, + settings: Settings, + style: Style, + view: &dyn View<'a, 'b, Message, Theme, Renderer>, +) -> Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: core::text::Renderer + 'a, +{ + let blocks = items + .into_iter() + .enumerate() + .map(move |(i, item)| view.view(settings, style, item, i)); + + Element::new(column(blocks).spacing(settings.spacing)) +} + +/// A view strategy to display a Markdown [`Item`]. +pub trait View<'a, 'b, Message, Theme, Renderer> { + /// Displays a Markdown [`Item`] by projecting it into an [`Element`]. + /// + /// You can use [`Item::view`] and [`Item::view_with`] for the default + /// look. + fn view( + &self, + settings: Settings, + style: Style, + item: &'b Item, + index: usize, + ) -> Element<'a, Message, Theme, Renderer>; +} + +#[derive(Debug, Clone, Copy)] +struct DefaultView; + +impl<'a, 'b, Theme, Renderer> View<'a, 'b, Url, Theme, Renderer> for DefaultView +where + Theme: Catalog + 'a, + Renderer: core::text::Renderer + 'a, +{ + fn view( + &self, + settings: Settings, + style: Style, + item: &'b Item, + index: usize, + ) -> Element<'a, Url, Theme, Renderer> { + item.view(settings, style, index) + } } /// The theme catalog of Markdown items. From ca6abc799b595b82d6c7872fa7ce9fe08a471e62 Mon Sep 17 00:00:00 2001 From: Juniper <67175453+Ultrasquid9@users.noreply.github.com> Date: Thu, 1 May 2025 20:41:56 -0400 Subject: [PATCH 102/116] Remove Cargo.lock --- Cargo.lock | 8413 ---------------------------------------------------- 1 file changed, 8413 deletions(-) delete mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 61c2ceece5..0000000000 --- a/Cargo.lock +++ /dev/null @@ -1,8413 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ab_glyph" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" - -[[package]] -name = "accesskit" -version = "0.16.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" - -[[package]] -name = "accesskit_atspi_common" -version = "0.9.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" -dependencies = [ - "accesskit", - "accesskit_consumer", - "atspi-common", - "serde", - "thiserror 1.0.69", - "zvariant 3.15.2", -] - -[[package]] -name = "accesskit_consumer" -version = "0.24.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" -dependencies = [ - "accesskit", - "immutable-chunkmap", -] - -[[package]] -name = "accesskit_macos" -version = "0.17.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" -dependencies = [ - "accesskit", - "accesskit_consumer", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", -] - -[[package]] -name = "accesskit_unix" -version = "0.12.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" -dependencies = [ - "accesskit", - "accesskit_atspi_common", - "async-channel 2.3.1", - "async-executor", - "async-task", - "atspi", - "futures-lite 1.13.0", - "futures-util", - "serde", - "tokio", - "tokio-stream", - "zbus 3.15.2", -] - -[[package]] -name = "accesskit_windows" -version = "0.22.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" -dependencies = [ - "accesskit", - "accesskit_consumer", - "paste", - "static_assertions", - "windows 0.54.0", -] - -[[package]] -name = "accesskit_winit" -version = "0.22.0" -source = "git+https://github.com/wash2/accesskit?tag=iced-xdg-surface-0.13#956955342dadab7e588e21be726817fca39510f3" -dependencies = [ - "accesskit", - "accesskit_macos", - "accesskit_unix", - "accesskit_windows", - "raw-window-handle 0.6.2", - "winit", -] - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.15", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "getrandom 0.2.15", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - -[[package]] -name = "aligned-vec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" - -[[package]] -name = "android-activity" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" -dependencies = [ - "android-properties", - "bitflags 2.8.0", - "cc", - "cesu8", - "jni", - "jni-sys", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys 0.6.0+11769913", - "num_enum", - "thiserror 1.0.69", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anyhow" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - -[[package]] -name = "arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" - -[[package]] -name = "arc" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - -[[package]] -name = "ash" -version = "0.38.0+1.3.281" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" -dependencies = [ - "libloading", -] - -[[package]] -name = "async-broadcast" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" -dependencies = [ - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener 5.4.0", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.3.0", - "futures-lite 2.6.0", - "slab", -] - -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "blocking", - "futures-lite 1.13.0", -] - -[[package]] -name = "async-fs" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" -dependencies = [ - "async-lock 3.4.0", - "blocking", - "futures-lite 2.6.0", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.3.1", - "async-executor", - "async-io 2.4.0", - "async-lock 3.4.0", - "blocking", - "futures-lite 2.6.0", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.28", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" -dependencies = [ - "async-lock 3.4.0", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite 2.6.0", - "parking", - "polling 3.7.4", - "rustix 0.38.44", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener 5.4.0", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-net" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f" -dependencies = [ - "async-io 1.13.0", - "blocking", - "futures-lite 1.13.0", -] - -[[package]] -name = "async-process" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" -dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", - "async-signal", - "blocking", - "cfg-if", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.44", - "windows-sys 0.48.0", -] - -[[package]] -name = "async-process" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" -dependencies = [ - "async-channel 2.3.1", - "async-io 2.4.0", - "async-lock 3.4.0", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener 5.4.0", - "futures-lite 2.6.0", - "rustix 0.38.44", - "tracing", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "async-signal" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" -dependencies = [ - "async-io 2.4.0", - "async-lock 3.4.0", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 0.38.44", - "signal-hook-registry", - "slab", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-std" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io 2.4.0", - "async-lock 3.4.0", - "async-process 2.3.0", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 2.6.0", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "async-tungstenite" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" -dependencies = [ - "futures-io", - "futures-util", - "log", - "pin-project-lite", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.25.0", - "tungstenite", - "webpki-roots", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "atomic_refcell" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" - -[[package]] -name = "atspi" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" -dependencies = [ - "atspi-common", - "atspi-connection", - "atspi-proxies", -] - -[[package]] -name = "atspi-common" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" -dependencies = [ - "enumflags2", - "serde", - "static_assertions", - "zbus 3.15.2", - "zbus_names 2.6.1", - "zvariant 3.15.2", -] - -[[package]] -name = "atspi-connection" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" -dependencies = [ - "atspi-common", - "atspi-proxies", - "futures-lite 1.13.0", - "zbus 3.15.2", -] - -[[package]] -name = "atspi-proxies" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" -dependencies = [ - "atspi-common", - "serde", - "zbus 3.15.2", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "av1-grain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bezier_tool" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit-set" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" - -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" - -[[package]] -name = "bitstream-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2", -] - -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel 2.3.1", - "async-task", - "futures-io", - "futures-lite 2.6.0", - "piper", -] - -[[package]] -name = "built" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "by_address" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" - -[[package]] -name = "bytemuck" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "calloop" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" -dependencies = [ - "bitflags 2.8.0", - "log", - "polling 3.7.4", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.8.0", - "log", - "polling 3.7.4", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" -dependencies = [ - "calloop 0.12.4", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop 0.13.0", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cc" -version = "1.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" -dependencies = [ - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "changelog" -version = "0.1.0" -dependencies = [ - "iced", - "log", - "reqwest", - "serde", - "thiserror 1.0.69", - "tokio", - "tracing-subscriber", - "webbrowser", -] - -[[package]] -name = "checkbox" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "chrono" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.52.6", -] - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "clap" -version = "4.5.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" -dependencies = [ - "anstyle", - "clap_lex", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "clipboard-win" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" -dependencies = [ - "error-code", -] - -[[package]] -name = "clipboard_macos" -version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" -dependencies = [ - "objc", - "objc-foundation", - "objc_id", -] - -[[package]] -name = "clipboard_wayland" -version = "0.2.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" -dependencies = [ - "dnd", - "mime 0.1.0", - "smithay-clipboard", -] - -[[package]] -name = "clipboard_x11" -version = "0.4.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" -dependencies = [ - "thiserror 1.0.69", - "x11rb", -] - -[[package]] -name = "clock" -version = "0.1.0" -dependencies = [ - "chrono", - "iced", - "tracing-subscriber", -] - -[[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation 0.9.4", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation 0.9.4", - "core-graphics-types", - "libc", - "objc", -] - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "color_palette" -version = "0.1.0" -dependencies = [ - "iced", - "palette", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "com" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" -dependencies = [ - "com_macros", -] - -[[package]] -name = "com_macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" -dependencies = [ - "com_macros_support", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "com_macros_support" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "combo_box" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "console_log" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" -dependencies = [ - "log", - "web-sys", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "core_maths" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" -dependencies = [ - "libm", -] - -[[package]] -name = "cosmic-client-toolkit" -version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=178eb0b#178eb0b14a0e5c192f64f6dee6c40341a8e5ee51" -dependencies = [ - "cosmic-protocols", - "libc", - "smithay-client-toolkit 0.19.2", - "wayland-client", - "wayland-protocols 0.32.5", -] - -[[package]] -name = "cosmic-protocols" -version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?rev=178eb0b#178eb0b14a0e5c192f64f6dee6c40341a8e5ee51" -dependencies = [ - "bitflags 2.8.0", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.32.5", - "wayland-protocols-wlr 0.3.5", - "wayland-scanner", - "wayland-server", -] - -[[package]] -name = "cosmic-text" -version = "0.14.2" -source = "git+https://github.com/pop-os/cosmic-text.git#b017d7c856aed4b1a3ef0952df75d02d889a1f7b" -dependencies = [ - "bitflags 2.8.0", - "fontdb 0.23.0", - "log", - "rangemap", - "rustc-hash 1.1.0", - "rustybuzz", - "self_cell", - "smol_str", - "swash", - "sys-locale", - "ttf-parser 0.21.1", - "unicode-bidi", - "unicode-linebreak", - "unicode-script", - "unicode-segmentation", -] - -[[package]] -name = "counter" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctor-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" - -[[package]] -name = "cursor-icon" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" - -[[package]] -name = "custom_quad" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "custom_shader" -version = "0.1.0" -dependencies = [ - "bytemuck", - "glam", - "iced", - "image", - "rand 0.8.5", -] - -[[package]] -name = "custom_widget" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "d3d12" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" -dependencies = [ - "bitflags 2.8.0", - "libloading", - "winapi", -] - -[[package]] -name = "dark-light" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a76fa97167fa740dcdbfe18e8895601e1bc36525f09b044e00916e717c03a3c" -dependencies = [ - "dconf_rs", - "detect-desktop-environment", - "dirs", - "objc", - "rust-ini", - "web-sys", - "winreg", - "zbus 4.4.0", -] - -[[package]] -name = "data-encoding" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" - -[[package]] -name = "data-url" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" - -[[package]] -name = "dconf_rs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7046468a81e6a002061c01e6a7c83139daf91b11c30e66795b13217c2d885c8b" - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "detect-desktop-environment" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21d8ad60dd5b13a4ee6bd8fa2d5d88965c597c67bce32b5fc49c94f55cb50810" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "directories-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - -[[package]] -name = "dlv-list" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" - -[[package]] -name = "dnd" -version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" -dependencies = [ - "bitflags 2.8.0", - "mime 0.1.0", - "raw-window-handle 0.6.2", - "smithay-client-toolkit 0.19.2", - "smithay-clipboard", -] - -[[package]] -name = "document-features" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" -dependencies = [ - "litrs", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "download_progress" -version = "0.1.0" -dependencies = [ - "iced", - "reqwest", -] - -[[package]] -name = "dpi" -version = "0.1.1" -source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13#1cc02bdab141072eaabad639d74b032fd0fcc62e" - -[[package]] -name = "drm" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" -dependencies = [ - "bitflags 2.8.0", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "rustix 0.38.44", -] - -[[package]] -name = "drm-ffi" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" -dependencies = [ - "drm-sys", - "rustix 0.38.44", -] - -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" - -[[package]] -name = "drm-sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176" -dependencies = [ - "libc", - "linux-raw-sys 0.6.5", -] - -[[package]] -name = "editor" -version = "0.1.0" -dependencies = [ - "iced", - "rfd", - "tokio", -] - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "endi" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" - -[[package]] -name = "enumflags2" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "env_logger" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" -dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "error-code" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" - -[[package]] -name = "etagere" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" -dependencies = [ - "euclid", - "svg_fmt", -] - -[[package]] -name = "euclid" -version = "0.22.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" -dependencies = [ - "num-traits", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" -dependencies = [ - "event-listener 5.4.0", - "pin-project-lite", -] - -[[package]] -name = "events" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "exit" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "exr" -version = "1.73.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" -dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fast-srgb8" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "ferris" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "flate2" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" - -[[package]] -name = "float_next_after" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" - -[[package]] -name = "font-types" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa6a5e5a77b5f3f7f9e32879f484aa5b3632ddfbe568a16266c904a6f32cdaf" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "fontconfig-parser" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" -dependencies = [ - "roxmltree", -] - -[[package]] -name = "fontdb" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2 0.9.5", - "slotmap", - "tinyvec", - "ttf-parser 0.21.1", -] - -[[package]] -name = "fontdb" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2 0.9.5", - "slotmap", - "tinyvec", - "ttf-parser 0.25.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", - "num_cpus", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" -dependencies = [ - "fastrand 2.3.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "game_of_life" -version = "0.1.0" -dependencies = [ - "iced", - "itertools 0.12.1", - "rustc-hash 2.1.0", - "tokio", - "tracing-subscriber", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "geometry" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "gethostname" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" -dependencies = [ - "libc", - "windows-targets 0.48.5", -] - -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.13.3+wasi-0.2.2", - "wasm-bindgen", - "windows-targets 0.52.6", -] - -[[package]] -name = "gif" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - -[[package]] -name = "glam" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.8.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.0", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "glow" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" -dependencies = [ - "gl_generator", -] - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.8.0", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "gpu-allocator" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" -dependencies = [ - "log", - "presser", - "thiserror 1.0.69", - "winapi", - "windows 0.52.0", -] - -[[package]] -name = "gpu-descriptor" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" -dependencies = [ - "bitflags 2.8.0", - "gpu-descriptor-types", - "hashbrown 0.15.2", -] - -[[package]] -name = "gpu-descriptor-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "gradient" -version = "0.1.0" -dependencies = [ - "iced", - "tracing-subscriber", -] - -[[package]] -name = "gstreamer" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de95703f4c8e79f4f4e42279cf1ab0e5a46b7ece4a9dfcd16424164af7be9055" -dependencies = [ - "cfg-if", - "futures-channel", - "futures-core", - "futures-util", - "glib", - "gstreamer-sys", - "itertools 0.12.1", - "libc", - "muldiv", - "num-integer", - "num-rational", - "option-operations", - "paste", - "pin-project-lite", - "pretty-hex", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gstreamer-allocators" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c9fb20b4b33d4e5dfdddf817d47937a1a1dcc3cfa72bd6f68f6b73231214293" -dependencies = [ - "glib", - "gstreamer", - "gstreamer-allocators-sys", - "libc", -] - -[[package]] -name = "gstreamer-allocators-sys" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1158478db5aacee46d876c7012861345a6486d1d064ce5cdceac57f3bd137af" -dependencies = [ - "glib-sys", - "gobject-sys", - "gstreamer-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gstreamer-app" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bc8090a8806193237e7b6531ee429ff6e39686425f5c3eb06dfa75875390fb" -dependencies = [ - "futures-core", - "futures-sink", - "glib", - "gstreamer", - "gstreamer-app-sys", - "gstreamer-base", - "libc", -] - -[[package]] -name = "gstreamer-app-sys" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aea07f07a3f17278e6998390ecaea127e476f0af0360c2d83d96e6d3a97fb75e" -dependencies = [ - "glib-sys", - "gstreamer-base-sys", - "gstreamer-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gstreamer-base" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb150b6904a49052237fede7cc2e6479df6ced5043d95e6af8134bc141a3167f" -dependencies = [ - "atomic_refcell", - "cfg-if", - "glib", - "gstreamer", - "gstreamer-base-sys", - "libc", -] - -[[package]] -name = "gstreamer-base-sys" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ca701f9078fe115b29b24c80910b577f9cb5b039182f050dbadf5933594b64" -dependencies = [ - "glib-sys", - "gobject-sys", - "gstreamer-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gstreamer-sys" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "564cda782b3e6eed1b81cb4798a06794db56440fb05b422505be689f34ce3bc4" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gstreamer-video" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85b2a4d1d3b7a98ae03806c3ed5c2db89d6b37a5f138780b48de015d68715e5" -dependencies = [ - "cfg-if", - "futures-channel", - "glib", - "gstreamer", - "gstreamer-base", - "gstreamer-video-sys", - "libc", - "thiserror 1.0.69", -] - -[[package]] -name = "gstreamer-video-sys" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302318d98e6b054501e485b6bb4ee20225823218f4a8660c182f115a33b16ee" -dependencies = [ - "glib-sys", - "gobject-sys", - "gstreamer-base-sys", - "gstreamer-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "guillotiere" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" -dependencies = [ - "euclid", - "svg_fmt", -] - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hassle-rs" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" -dependencies = [ - "bitflags 2.8.0", - "com", - "libc", - "libloading", - "thiserror 1.0.69", - "widestring", - "winapi", -] - -[[package]] -name = "headers" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http 0.2.12", - "httpdate", - "mime 0.3.17", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http 0.2.12", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hexf-parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.2.0", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "humantime" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" - -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.8", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http 1.2.0", - "hyper 1.6.0", - "hyper-util", - "rustls 0.23.21", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.1", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "hyper 1.6.0", - "pin-project-lite", - "socket2 0.5.8", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.52.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "iced" -version = "0.14.0-dev" -dependencies = [ - "criterion", - "dnd", - "iced_accessibility", - "iced_core", - "iced_futures", - "iced_highlighter", - "iced_renderer", - "iced_wgpu", - "iced_widget", - "iced_winit", - "image", - "mime 0.1.0", - "thiserror 1.0.69", - "window_clipboard", -] - -[[package]] -name = "iced_accessibility" -version = "0.1.0" -dependencies = [ - "accesskit", - "accesskit_macos", - "accesskit_windows", - "accesskit_winit", -] - -[[package]] -name = "iced_core" -version = "0.14.0-dev" -dependencies = [ - "approx", - "bitflags 2.8.0", - "bytes", - "cosmic-client-toolkit", - "dark-light", - "dnd", - "glam", - "iced_accessibility", - "log", - "mime 0.1.0", - "num-traits", - "once_cell", - "palette", - "raw-window-handle 0.6.2", - "rustc-hash 2.1.0", - "serde", - "smol_str", - "thiserror 1.0.69", - "web-time", - "window_clipboard", -] - -[[package]] -name = "iced_futures" -version = "0.14.0-dev" -dependencies = [ - "async-std", - "futures", - "iced_core", - "log", - "rustc-hash 2.1.0", - "smol", - "tokio", - "wasm-bindgen-futures", - "wasm-timer", -] - -[[package]] -name = "iced_glyphon" -version = "0.6.0" -source = "git+https://github.com/pop-os/glyphon.git?tag=iced-0.14-dev#6ef9d12a20cfd0f7bdf38136a26ded9f7459ec8b" -dependencies = [ - "cosmic-text", - "etagere", - "lru", - "rustc-hash 2.1.0", - "wgpu", -] - -[[package]] -name = "iced_graphics" -version = "0.14.0-dev" -dependencies = [ - "bitflags 2.8.0", - "bytemuck", - "cosmic-text", - "half", - "iced_core", - "iced_futures", - "image", - "kamadak-exif", - "log", - "lyon_path", - "once_cell", - "raw-window-handle 0.6.2", - "rustc-hash 2.1.0", - "thiserror 1.0.69", - "unicode-segmentation", -] - -[[package]] -name = "iced_highlighter" -version = "0.14.0-dev" -dependencies = [ - "iced_core", - "once_cell", - "syntect", -] - -[[package]] -name = "iced_renderer" -version = "0.14.0-dev" -dependencies = [ - "iced_graphics", - "iced_tiny_skia", - "iced_wgpu", - "log", - "thiserror 1.0.69", -] - -[[package]] -name = "iced_runtime" -version = "0.14.0-dev" -dependencies = [ - "bytes", - "cosmic-client-toolkit", - "dnd", - "iced_accessibility", - "iced_core", - "iced_futures", - "raw-window-handle 0.6.2", - "thiserror 1.0.69", - "window_clipboard", -] - -[[package]] -name = "iced_tiny_skia" -version = "0.14.0-dev" -dependencies = [ - "bytemuck", - "cosmic-text", - "iced_graphics", - "kurbo 0.10.4", - "log", - "resvg", - "rustc-hash 2.1.0", - "softbuffer", - "tiny-skia", -] - -[[package]] -name = "iced_wgpu" -version = "0.14.0-dev" -dependencies = [ - "as-raw-xcb-connection", - "bitflags 2.8.0", - "bytemuck", - "cosmic-client-toolkit", - "futures", - "glam", - "guillotiere", - "iced_glyphon", - "iced_graphics", - "log", - "lyon", - "once_cell", - "raw-window-handle 0.6.2", - "resvg", - "rustc-hash 2.1.0", - "rustix 0.38.44", - "thiserror 1.0.69", - "tiny-xlib", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.32.5", - "wayland-sys", - "wgpu", - "x11rb", -] - -[[package]] -name = "iced_widget" -version = "0.14.0-dev" -dependencies = [ - "cosmic-client-toolkit", - "dnd", - "iced_accessibility", - "iced_highlighter", - "iced_renderer", - "iced_runtime", - "log", - "num-traits", - "once_cell", - "ouroboros", - "pulldown-cmark", - "qrcode", - "rustc-hash 2.1.0", - "thiserror 1.0.69", - "unicode-segmentation", - "url", - "window_clipboard", -] - -[[package]] -name = "iced_winit" -version = "0.14.0-dev" -dependencies = [ - "cosmic-client-toolkit", - "dnd", - "iced_accessibility", - "iced_futures", - "iced_graphics", - "iced_runtime", - "log", - "raw-window-handle 0.6.2", - "rustc-hash 2.1.0", - "rustix 0.38.44", - "sysinfo", - "thiserror 1.0.69", - "tracing", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.32.5", - "web-sys", - "winapi", - "window_clipboard", - "winit", - "xkbcommon", - "xkbcommon-dl", - "xkeysym", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "image" -version = "0.25.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "exr", - "gif", - "image-webp", - "num-traits", - "png", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imagesize" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" - -[[package]] -name = "imgref" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" - -[[package]] -name = "immutable-chunkmap" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "indexmap" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" -dependencies = [ - "equivalent", - "hashbrown 0.15.2", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "io-lifetimes" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-terminal" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" -dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "kamadak-exif" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" -dependencies = [ - "mutate_once", -] - -[[package]] -name = "khronos-egl" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" -dependencies = [ - "libc", - "libloading", - "pkg-config", -] - -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "kurbo" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" -dependencies = [ - "arrayvec", - "smallvec", -] - -[[package]] -name = "kurbo" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" -dependencies = [ - "arrayvec", - "smallvec", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - -[[package]] -name = "layout" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "lazy" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" -dependencies = [ - "arbitrary", - "cc", -] - -[[package]] -name = "libloading" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - -[[package]] -name = "libm" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.8.0", - "libc", - "redox_syscall 0.5.8", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" - -[[package]] -name = "list" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "litrs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" - -[[package]] -name = "loading_spinners" -version = "0.1.0" -dependencies = [ - "iced", - "lyon_algorithms", - "once_cell", -] - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" -dependencies = [ - "value-bag", -] - -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - -[[package]] -name = "loupe" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" - -[[package]] -name = "lyon" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" -dependencies = [ - "lyon_algorithms", - "lyon_tessellation", -] - -[[package]] -name = "lyon_algorithms" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08" -dependencies = [ - "lyon_path", - "num-traits", -] - -[[package]] -name = "lyon_geom" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570" -dependencies = [ - "arrayvec", - "euclid", - "num-traits", -] - -[[package]] -name = "lyon_path" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e" -dependencies = [ - "lyon_geom", - "num-traits", -] - -[[package]] -name = "lyon_tessellation" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c" -dependencies = [ - "float_next_after", - "lyon_path", - "num-traits", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "markdown" -version = "0.1.0" -dependencies = [ - "iced", - "open", -] - -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - -[[package]] -name = "maybe_parallel_iterator" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5069b219d51d2ba2d9388623bd7eead5d4e7974bc8ff3ec9edbe36b09c0ef477" -dependencies = [ - "rayon", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" -dependencies = [ - "libc", -] - -[[package]] -name = "memmap2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "metal" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" -dependencies = [ - "bitflags 2.8.0", - "block", - "core-graphics-types", - "foreign-types", - "log", - "objc", - "paste", -] - -[[package]] -name = "mime" -version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" -dependencies = [ - "smithay-clipboard", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime 0.3.17", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - -[[package]] -name = "modal" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "muldiv" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" - -[[package]] -name = "multer" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 0.2.12", - "httparse", - "log", - "memchr", - "mime 0.3.17", - "spin", - "version_check", -] - -[[package]] -name = "multi_window" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "multitouch" -version = "0.1.0" -dependencies = [ - "iced", - "tracing-subscriber", - "voronator", -] - -[[package]] -name = "mutate_once" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" - -[[package]] -name = "naga" -version = "22.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" -dependencies = [ - "arrayvec", - "bit-set", - "bitflags 2.8.0", - "cfg_aliases 0.1.1", - "codespan-reporting", - "hexf-parse", - "indexmap", - "log", - "rustc-hash 1.1.0", - "spirv", - "termcolor", - "thiserror 1.0.69", - "unicode-xid", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.8.0", - "jni-sys", - "log", - "ndk-sys 0.6.0+11769913", - "num_enum", - "raw-window-handle 0.6.2", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.8.0", - "cfg-if", - "cfg_aliases 0.2.1", - "libc", - "memoffset 0.9.1", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" -dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.8.0", - "block2", - "libc", - "objc2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2", - "objc2", - "objc2-contacts", - "objc2-foundation", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.8.0", - "block2", - "dispatch", - "libc", - "objc2", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "onig" -version = "6.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" -dependencies = [ - "bitflags 1.3.2", - "libc", - "once_cell", - "onig_sys", -] - -[[package]] -name = "onig_sys" -version = "69.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "oorandom" -version = "11.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" - -[[package]] -name = "open" -version = "5.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" -dependencies = [ - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "option-operations" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" -dependencies = [ - "paste", -] - -[[package]] -name = "orbclient" -version = "0.3.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" -dependencies = [ - "libredox", -] - -[[package]] -name = "ordered-multimap" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" -dependencies = [ - "dlv-list", - "hashbrown 0.12.3", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "ouroboros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "owned_ttf_parser" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" -dependencies = [ - "ttf-parser 0.25.1", -] - -[[package]] -name = "palette" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" -dependencies = [ - "approx", - "fast-srgb8", - "palette_derive", - "phf", -] - -[[package]] -name = "palette_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" -dependencies = [ - "by_address", - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "pane_grid" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.8", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pick_list" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "pin-project" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand 2.3.0", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "plist" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" -dependencies = [ - "base64 0.22.1", - "indexmap", - "quick-xml 0.32.0", - "serde", - "time", -] - -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "pokedex" -version = "0.1.0" -dependencies = [ - "getrandom 0.2.15", - "iced", - "rand 0.8.5", - "reqwest", - "serde", - "serde_json", -] - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite", - "rustix 0.38.44", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "presser" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" - -[[package]] -name = "pretty-hex" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc83ee4a840062f368f9096d80077a9841ec117e17e7f700df81958f1451254" - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" -dependencies = [ - "toml_edit 0.20.7", -] - -[[package]] -name = "proc-macro-crate" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" -dependencies = [ - "toml_edit 0.22.22", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", - "version_check", - "yansi", -] - -[[package]] -name = "profiling" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" -dependencies = [ - "quote", - "syn 2.0.96", -] - -[[package]] -name = "progress_bar" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "pulldown-cmark" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14" -dependencies = [ - "bitflags 2.8.0", - "getopts", - "memchr", - "pulldown-cmark-escape", - "unicase", -] - -[[package]] -name = "pulldown-cmark-escape" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" - -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "qr_code" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "qrcode" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166f136dfdb199f98186f3649cf7a0536534a61417a1a30221b492b4fb60ce3f" - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quick-xml" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.36.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "quinn" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" -dependencies = [ - "bytes", - "cfg_aliases 0.2.1", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash 2.1.0", - "rustls 0.23.21", - "socket2 0.5.8", - "thiserror 2.0.11", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" -dependencies = [ - "bytes", - "getrandom 0.3.1", - "rand 0.9.1", - "ring", - "rustc-hash 2.1.0", - "rustls 0.23.21", - "rustls-pki-types", - "slab", - "thiserror 2.0.11", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" -dependencies = [ - "cfg_aliases 0.2.1", - "libc", - "once_cell", - "socket2 0.5.8", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "quote" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.1", -] - -[[package]] -name = "range-alloc" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" - -[[package]] -name = "rangemap" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" - -[[package]] -name = "rav1e" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" -dependencies = [ - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools 0.12.1", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "once_cell", - "paste", - "profiling", - "rand 0.8.5", - "rand_chacha 0.3.1", - "simd_helpers", - "system-deps", - "thiserror 1.0.69", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "read-fonts" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f" -dependencies = [ - "bytemuck", - "font-types", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "renderdoc-sys" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" - -[[package]] -name = "reqwest" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.6.0", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime 0.3.17", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls 0.23.21", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls 0.26.1", - "tokio-util", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", - "windows-registry", -] - -[[package]] -name = "resvg" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051" -dependencies = [ - "gif", - "jpeg-decoder", - "log", - "pico-args", - "rgb", - "svgtypes", - "tiny-skia", - "usvg", -] - -[[package]] -name = "rfd" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d8ab342bcc5436e04d3a4c1e09e17d74958bfaddf8d5fad6f85607df0f994f" -dependencies = [ - "block", - "dispatch", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc", - "objc-foundation", - "objc_id", - "raw-window-handle 0.5.2", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "rgb" -version = "0.8.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "roxmltree" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" - -[[package]] -name = "rust-ini" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" - -[[package]] -name = "rustix" -version = "0.37.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes 1.0.11", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.8.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls" -version = "0.23.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" -dependencies = [ - "web-time", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" - -[[package]] -name = "rustybuzz" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" -dependencies = [ - "bitflags 2.8.0", - "bytemuck", - "libm", - "smallvec", - "ttf-parser 0.21.1", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-properties", - "unicode-script", -] - -[[package]] -name = "ryu" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "screenshot" -version = "0.1.0" -dependencies = [ - "iced", - "image", - "tokio", - "tracing-subscriber", -] - -[[package]] -name = "scrollable" -version = "0.1.0" -dependencies = [ - "iced", - "iced_core", - "once_cell", -] - -[[package]] -name = "sctk-adwaita" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" -dependencies = [ - "ab_glyph", - "log", - "memmap2 0.9.5", - "smithay-client-toolkit 0.19.2", - "tiny-skia", -] - -[[package]] -name = "sctk_drag" -version = "0.1.0" -dependencies = [ - "cosmic-client-toolkit", - "env_logger", - "iced", - "iced_core", -] - -[[package]] -name = "sctk_lazy" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "sctk_session_lock" -version = "0.1.0" -dependencies = [ - "async-std", - "env_logger", - "iced", - "iced_runtime", - "smithay-client-toolkit 0.18.0", -] - -[[package]] -name = "sctk_subsurface" -version = "0.1.0" -dependencies = [ - "calloop 0.13.0", - "cosmic-client-toolkit", - "env_logger", - "futures-channel", - "iced", - "iced_runtime", - "rustix 0.38.44", -] - -[[package]] -name = "sctk_subsurface_gst" -version = "0.1.0" -dependencies = [ - "calloop 0.12.4", - "drm-fourcc", - "env_logger", - "futures-channel", - "gstreamer", - "gstreamer-allocators", - "gstreamer-app", - "gstreamer-video", - "iced", - "iced_runtime", - "smithay-client-toolkit 0.18.0", -] - -[[package]] -name = "sctk_todos" -version = "0.1.0" -dependencies = [ - "async-std", - "cosmic-client-toolkit", - "directories-next", - "env_logger", - "iced", - "iced_core", - "log", - "once_cell", - "serde", - "serde_json", - "tracing", -] - -[[package]] -name = "self_cell" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" - -[[package]] -name = "serde" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "serde_json" -version = "1.0.138" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "sierpinski_triangle" -version = "0.1.0" -dependencies = [ - "iced", - "rand 0.8.5", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - -[[package]] -name = "simplecss" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" -dependencies = [ - "log", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "skrifa" -version = "0.26.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc1aa86c26dbb1b63875a7180aa0819709b33348eb5b1491e4321fae388179d" -dependencies = [ - "bytemuck", - "read-fonts", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "slider" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "slotmap" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" -dependencies = [ - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "smithay-client-toolkit" -version = "0.18.0" -source = "git+https://github.com/smithay/client-toolkit?rev=828b1eb#828b1eb469ad022eb3ac30be02bd5645b879922f" -dependencies = [ - "bitflags 2.8.0", - "bytemuck", - "calloop 0.12.4", - "calloop-wayland-source 0.2.0", - "cursor-icon", - "libc", - "log", - "memmap2 0.9.5", - "pkg-config", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols 0.31.2", - "wayland-protocols-wlr 0.2.0", - "wayland-scanner", - "xkbcommon", - "xkeysym", -] - -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.8.0", - "bytemuck", - "calloop 0.13.0", - "calloop-wayland-source 0.3.0", - "cursor-icon", - "libc", - "log", - "memmap2 0.9.5", - "pkg-config", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols 0.32.5", - "wayland-protocols-wlr 0.3.5", - "wayland-scanner", - "xkbcommon", - "xkeysym", -] - -[[package]] -name = "smithay-clipboard" -version = "0.8.0" -source = "git+https://github.com/pop-os/smithay-clipboard?tag=pop-dnd-5#5a3007def49eb678d1144850c9ee04b80707c56a" -dependencies = [ - "libc", - "raw-window-handle 0.6.2", - "smithay-client-toolkit 0.19.2", - "wayland-backend", -] - -[[package]] -name = "smol" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" -dependencies = [ - "async-channel 1.9.0", - "async-executor", - "async-fs 1.6.0", - "async-io 1.13.0", - "async-lock 2.8.0", - "async-net", - "async-process 1.8.1", - "blocking", - "futures-lite 1.13.0", -] - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "softbuffer" -version = "0.4.1" -source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#6e75b1ad7e98397d37cb187886d05969bc480995" -dependencies = [ - "as-raw-xcb-connection", - "bytemuck", - "cfg_aliases 0.2.1", - "cocoa", - "core-graphics", - "drm", - "fastrand 2.3.0", - "foreign-types", - "js-sys", - "log", - "memmap2 0.9.5", - "objc", - "raw-window-handle 0.6.2", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "tiny-xlib", - "wasm-bindgen", - "wayland-backend", - "wayland-client", - "wayland-sys", - "web-sys", - "windows-sys 0.52.0", - "x11rb", -] - -[[package]] -name = "solar_system" -version = "0.1.0" -dependencies = [ - "iced", - "rand 0.8.5", - "tracing-subscriber", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spirv" -version = "0.3.0+sdk-1.3.268.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "stopwatch" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp", -] - -[[package]] -name = "styling" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "svg" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "svg_fmt" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" - -[[package]] -name = "svgtypes" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" -dependencies = [ - "kurbo 0.11.1", - "siphasher", -] - -[[package]] -name = "swash" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fae9a562c7b46107d9c78cd78b75bbe1e991c16734c0aee8ff0ee711fb8b620a" -dependencies = [ - "skrifa", - "yazi", - "zeno", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "syntect" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" -dependencies = [ - "bincode", - "bitflags 1.3.2", - "flate2", - "fnv", - "once_cell", - "onig", - "plist", - "regex-syntax", - "serde", - "serde_derive", - "serde_json", - "thiserror 1.0.69", - "walkdir", - "yaml-rust", -] - -[[package]] -name = "sys-locale" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" -dependencies = [ - "libc", -] - -[[package]] -name = "sysinfo" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows 0.52.0", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tempfile" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" -dependencies = [ - "cfg-if", - "fastrand 2.3.0", - "getrandom 0.3.1", - "once_cell", - "rustix 0.38.44", - "windows-sys 0.59.0", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "the_matrix" -version = "0.1.0" -dependencies = [ - "iced", - "rand 0.8.5", - "tracing-subscriber", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" -dependencies = [ - "thiserror-impl 2.0.11", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tiff" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - -[[package]] -name = "time" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tiny-xlib" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" -dependencies = [ - "as-raw-xcb-connection", - "ctor-lite", - "libloading", - "pkg-config", - "tracing", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "tinyvec" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "toast" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "todos" -version = "0.1.0" -dependencies = [ - "async-std", - "directories-next", - "iced", - "iced_core", - "serde", - "serde_json", - "tracing-subscriber", - "uuid", - "wasm-timer", - "web-sys", -] - -[[package]] -name = "tokio" -version = "1.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "socket2 0.5.8", - "tokio-macros", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" -dependencies = [ - "rustls 0.23.21", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.22", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.25", -] - -[[package]] -name = "tooltip" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "tour" -version = "0.1.0" -dependencies = [ - "console_error_panic_hook", - "console_log", - "iced", - "tracing-subscriber", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "ttf-parser" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" - -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -dependencies = [ - "core_maths", -] - -[[package]] -name = "tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.2.0", - "httparse", - "log", - "rand 0.8.5", - "rustls 0.22.4", - "rustls-pki-types", - "sha1", - "thiserror 1.0.69", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset 0.9.1", - "tempfile", - "winapi", -] - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-bidi-mirroring" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" - -[[package]] -name = "unicode-ccc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" - -[[package]] -name = "unicode-ident" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" - -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - -[[package]] -name = "unicode-script" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "usvg" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" -dependencies = [ - "base64 0.22.1", - "data-url", - "flate2", - "fontdb 0.18.0", - "imagesize", - "kurbo 0.11.1", - "log", - "pico-args", - "roxmltree", - "rustybuzz", - "simplecss", - "siphasher", - "strict-num", - "svgtypes", - "tiny-skia-path", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" -dependencies = [ - "getrandom 0.2.15", - "rand 0.8.5", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "v_frame" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "value-bag" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" - -[[package]] -name = "vectorial_text" -version = "0.1.0" -dependencies = [ - "iced", -] - -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "visible_bounds" -version = "0.1.0" -dependencies = [ - "iced", - "once_cell", -] - -[[package]] -name = "voronator" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d8e6fe4b9b3b443f2e4ada327cf4b08ab5cdd27a42cd5c9f38dd29092c10ee" -dependencies = [ - "maybe_parallel_iterator", -] - -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "warp" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "headers", - "http 0.2.12", - "hyper 0.14.32", - "log", - "mime 0.3.17", - "mime_guess", - "multer", - "percent-encoding", - "pin-project", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-tungstenite", - "tokio-util", - "tower-service", - "tracing", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.96", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wayland-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" -dependencies = [ - "cc", - "downcast-rs", - "rustix 0.38.44", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" -dependencies = [ - "bitflags 2.8.0", - "rustix 0.38.44", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.8.0", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" -dependencies = [ - "rustix 0.38.44", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" -dependencies = [ - "bitflags 2.8.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" -dependencies = [ - "bitflags 2.8.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", - "wayland-server", -] - -[[package]] -name = "wayland-protocols-plasma" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" -dependencies = [ - "bitflags 2.8.0", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.32.5", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" -dependencies = [ - "bitflags 2.8.0", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.31.2", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" -dependencies = [ - "bitflags 2.8.0", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.32.5", - "wayland-scanner", - "wayland-server", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" -dependencies = [ - "proc-macro2", - "quick-xml 0.36.2", - "quote", -] - -[[package]] -name = "wayland-server" -version = "0.31.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89532cc712a2adb119eb4d09694b402576052254d0bb284f82ac1c47fb786ad" -dependencies = [ - "bitflags 2.8.0", - "downcast-rs", - "io-lifetimes 2.0.4", - "rustix 0.38.44", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-sys" -version = "0.31.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webbrowser" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535" -dependencies = [ - "block2", - "core-foundation 0.10.0", - "home", - "jni", - "log", - "ndk-context", - "objc2", - "objc2-foundation", - "url", - "web-sys", -] - -[[package]] -name = "webpki-roots" -version = "0.26.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "websocket" -version = "1.0.0" -dependencies = [ - "async-tungstenite", - "iced", - "once_cell", - "tokio", - "warp", -] - -[[package]] -name = "weezl" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" - -[[package]] -name = "wgpu" -version = "22.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" -dependencies = [ - "arrayvec", - "cfg_aliases 0.1.1", - "document-features", - "js-sys", - "log", - "naga", - "parking_lot 0.12.3", - "profiling", - "raw-window-handle 0.6.2", - "smallvec", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-core" -version = "22.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" -dependencies = [ - "arrayvec", - "bit-vec", - "bitflags 2.8.0", - "cfg_aliases 0.1.1", - "document-features", - "indexmap", - "log", - "naga", - "once_cell", - "parking_lot 0.12.3", - "profiling", - "raw-window-handle 0.6.2", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 1.0.69", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-hal" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" -dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bit-set", - "bitflags 2.8.0", - "block", - "cfg_aliases 0.1.1", - "core-graphics-types", - "d3d12", - "glow", - "glutin_wgl_sys", - "gpu-alloc", - "gpu-allocator", - "gpu-descriptor", - "hassle-rs", - "js-sys", - "khronos-egl", - "libc", - "libloading", - "log", - "metal", - "naga", - "ndk-sys 0.5.0+25.2.9519653", - "objc", - "once_cell", - "parking_lot 0.12.3", - "profiling", - "range-alloc", - "raw-window-handle 0.6.2", - "renderdoc-sys", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 1.0.69", - "wasm-bindgen", - "web-sys", - "wgpu-types", - "winapi", -] - -[[package]] -name = "wgpu-types" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" -dependencies = [ - "bitflags 2.8.0", - "js-sys", - "web-sys", -] - -[[package]] -name = "widestring" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window_clipboard" -version = "0.4.1" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" -dependencies = [ - "clipboard-win", - "clipboard_macos", - "clipboard_wayland", - "clipboard_x11", - "dnd", - "mime 0.1.0", - "raw-window-handle 0.6.2", - "thiserror 1.0.69", -] - -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" -dependencies = [ - "windows-core 0.54.0", - "windows-implement", - "windows-interface", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" -dependencies = [ - "windows-result 0.1.2", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-implement" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "windows-interface" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result 0.2.0", - "windows-strings", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winit" -version = "0.30.5" -source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13#1cc02bdab141072eaabad639d74b032fd0fcc62e" -dependencies = [ - "ahash 0.8.11", - "android-activity", - "atomic-waker", - "bitflags 2.8.0", - "block2", - "bytemuck", - "calloop 0.13.0", - "cfg_aliases 0.2.1", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics", - "cursor-icon", - "dpi", - "js-sys", - "libc", - "memmap2 0.9.5", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "orbclient", - "percent-encoding", - "pin-project", - "raw-window-handle 0.6.2", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "sctk-adwaita", - "smithay-client-toolkit 0.19.2", - "smol_str", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.32.5", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading", - "once_cell", - "rustix 0.38.44", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" - -[[package]] -name = "xcursor" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" - -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "xkbcommon" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" -dependencies = [ - "libc", - "memmap2 0.8.0", - "xkeysym", -] - -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.8.0", - "dlib", - "log", - "once_cell", - "xkeysym", -] - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "xml-rs" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" - -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yazi" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", - "synstructure", -] - -[[package]] -name = "zbus" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" -dependencies = [ - "async-broadcast 0.5.1", - "async-executor", - "async-fs 1.6.0", - "async-io 1.13.0", - "async-lock 2.8.0", - "async-process 1.8.1", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "byteorder", - "derivative", - "enumflags2", - "event-listener 2.5.3", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix 0.26.4", - "once_cell", - "ordered-stream", - "rand 0.8.5", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tokio", - "tracing", - "uds_windows", - "winapi", - "xdg-home", - "zbus_macros 3.15.2", - "zbus_names 2.6.1", - "zvariant 3.15.2", -] - -[[package]] -name = "zbus" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" -dependencies = [ - "async-broadcast 0.7.2", - "async-executor", - "async-fs 2.1.2", - "async-io 2.4.0", - "async-lock 3.4.0", - "async-process 2.3.0", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener 5.4.0", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix 0.29.0", - "ordered-stream", - "rand 0.8.5", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.52.0", - "xdg-home", - "zbus_macros 4.4.0", - "zbus_names 3.0.0", - "zvariant 4.2.0", -] - -[[package]] -name = "zbus_macros" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "regex", - "syn 1.0.109", - "zvariant_utils 1.0.1", -] - -[[package]] -name = "zbus_macros" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" -dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.96", - "zvariant_utils 2.1.0", -] - -[[package]] -name = "zbus_names" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" -dependencies = [ - "serde", - "static_assertions", - "zvariant 3.15.2", -] - -[[package]] -name = "zbus_names" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" -dependencies = [ - "serde", - "static_assertions", - "zvariant 4.2.0", -] - -[[package]] -name = "zeno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0de2315dc13d00e5df3cd6b8d2124a6eaec6a2d4b6a1c5f37b7efad17fcc17" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "zune-jpeg" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" -dependencies = [ - "zune-core", -] - -[[package]] -name = "zvariant" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" -dependencies = [ - "byteorder", - "enumflags2", - "libc", - "serde", - "static_assertions", - "zvariant_derive 3.15.2", -] - -[[package]] -name = "zvariant" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "zvariant_derive 4.2.0", -] - -[[package]] -name = "zvariant_derive" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", - "zvariant_utils 1.0.1", -] - -[[package]] -name = "zvariant_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" -dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.96", - "zvariant_utils 2.1.0", -] - -[[package]] -name = "zvariant_utils" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "zvariant_utils" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", -] From 63fcda9710606cb6a5e84b0cf1adb70e6b0ad76f Mon Sep 17 00:00:00 2001 From: Juniper <67175453+Ultrasquid9@users.noreply.github.com> Date: Thu, 1 May 2025 20:43:19 -0400 Subject: [PATCH 103/116] Remove png --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a4aaa96470..8a3f3097dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -193,7 +193,6 @@ num-traits = "0.2" once_cell = "1.0" ouroboros = "0.18" palette = "0.7" -png = "0.17" pulldown-cmark = "0.12" qrcode = { version = "0.13", default-features = false } raw-window-handle = "0.6" From e6e1071ee2bc3a19d714d3b6262add8633a18cc3 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 4 Jun 2025 11:45:55 -0700 Subject: [PATCH 104/116] fix(winit): Cleanup `surface_subsurfaces` on parent window removal --- winit/src/platform_specific/mod.rs | 10 ++++++++++ winit/src/platform_specific/wayland/mod.rs | 7 +++++++ winit/src/program.rs | 8 ++++++++ 3 files changed, 25 insertions(+) diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index d0bc355e38..cd16c432d3 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -65,6 +65,16 @@ impl PlatformSpecific { } } + pub(crate) fn retain_subsurfaces bool>( + &mut self, + keep: F, + ) { + #[cfg(all(feature = "wayland", target_os = "linux"))] + { + self.wayland.retain_subsurfaces(keep); + } + } + pub(crate) fn clear_subsurface_list(&mut self) { #[cfg(all(feature = "wayland", target_os = "linux"))] { diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index 8e495aecd3..a7ed2d4869 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -213,6 +213,13 @@ impl WaylandSpecific { }; } + pub(crate) fn retain_subsurfaces bool>( + &mut self, + keep: F, + ) { + self.surface_subsurfaces.retain(|k, v| keep(*k)) + } + pub(crate) fn clear_subsurface_list(&mut self) { let _ = crate::subsurface_widget::take_subsurfaces(); } diff --git a/winit/src/program.rs b/winit/src/program.rs index a00374e8b0..2349f286dc 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -854,6 +854,7 @@ async fn run_instance<'a, P, C>( let mut dnd_surface: Option< Arc>, > = None; + let mut dnd_surface_id: Option = None; debug.startup_finished(); loop { @@ -1020,6 +1021,7 @@ async fn run_instance<'a, P, C>( .update_subsurfaces(id, &surface); let surface = Arc::new(surface); dnd_surface = Some(surface.clone()); + dnd_surface_id = Some(id); dnd_buffer = Some(( viewport.physical_size(), state.scale_factor(), @@ -1675,6 +1677,11 @@ async fn run_instance<'a, P, C>( proxy.free_slots(actions); actions = 0; } + + platform_specific_handler.retain_subsurfaces(|id| { + window_manager.get(id).is_some() + || dnd_surface_id == Some(id) + }); } debug.draw_started(); @@ -1808,6 +1815,7 @@ async fn run_instance<'a, P, C>( dnd::SourceEvent::Finished | dnd::SourceEvent::Cancelled => { dnd_surface = None; + dnd_surface_id = None; } _ => {} } From fe41dcf253a151b826fe508bb3c8cc95a08b2c77 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 10 Jun 2025 10:36:50 -0400 Subject: [PATCH 105/116] fix: nested popup destruction --- .../wayland/event_loop/state.rs | 30 +++++++++++-------- .../wayland/handlers/shell/xdg_popup.rs | 11 +++---- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 475b02b8a9..a5b117fb29 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1134,19 +1134,25 @@ impl SctkState { SctkEvent::PopupEvent { variant: crate::sctk_event::PopupEventVariant::Size(size.0, size.1), toplevel_id: existing.data.parent.wl_surface().clone(), parent_id: existing.data.parent.wl_surface().clone(), id: existing.popup.wl_surface().clone() }); return Ok(()); } - let parent_mismatch = self.popups.iter().rev().find(|p| { - self.id_map.get(&p.popup.wl_surface().id()).map_or(true, |p_id|{ - *p_id != settings.parent && p.data.grab && settings.grab}) - }); - if !self.destroyed.is_empty() || parent_mismatch.is_some() { - if parent_mismatch.is_some() { - for i in 0..self.popups.len() { - let id = self.id_map.get(&self.popups[i].popup.wl_surface().id()); - if let Some(id) = id { - if *id != settings.parent { - _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy{id: *id})); - } + let mut found = false; + let mut parent_mismatch = false; + for p in &self.popups { + found |= p.data.id == settings.parent; + parent_mismatch |= found && p.data.id != settings.parent; + } + parent_mismatch |= !found; + if !self.destroyed.is_empty() || parent_mismatch { + if parent_mismatch { + let mut found = false; + for p in std::mem::take(&mut self.popups).into_iter().rev() { + let id = p.data.id; + self.popups.insert(0, p); + + found |= id == settings.parent; + if !found { + _ = self.handle_action(Action::Popup(platform_specific::wayland::popup::Action::Destroy{id})); } + } } if self.pending_popup.replace((settings, 0)).is_none() { diff --git a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs index 2e7a58c11e..3b8a0dd533 100644 --- a/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs +++ b/winit/src/platform_specific/wayland/handlers/shell/xdg_popup.rs @@ -65,13 +65,14 @@ impl PopupHandler for SctkState { break; } state::PopupParent::Popup(popup_to_destroy_first) => { - let popup_to_destroy_first = self - .popups - .iter() - .position(|p| { + let Some(popup_to_destroy_first) = + self.popups.iter().position(|p| { p.popup.wl_surface() == &popup_to_destroy_first }) - .unwrap(); + else { + log::warn!("could not find popup to destroy first."); + return; + }; let popup_to_destroy_first = self.popups.remove(popup_to_destroy_first); to_destroy.push(popup_to_destroy_first); From d8c3d5b16b416bb959169b0604b25809cd40e204 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 12 Jun 2025 10:42:59 -0400 Subject: [PATCH 106/116] improv(sctk): setup error handling --- .../wayland/event_loop/mod.rs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index ce2755caf8..4917a5846d 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -52,6 +52,7 @@ use std::{ }; use tracing::error; use wayland_backend::client::Backend; +use wayland_client::globals::GlobalError; use winit::{dpi::LogicalSize, event_loop::OwnedDisplayHandle}; use self::state::SctkState; @@ -67,6 +68,14 @@ pub struct SctkEventLoop { pub(crate) state: SctkState, } +pub enum Error { + Connect(ConnectError), + Calloop(calloop::Error), + Global(GlobalError), + NoDisplayHandle, + NoWaylandDisplay, +} + impl SctkEventLoop { pub(crate) fn new( winit_event_sender: mpsc::UnboundedSender, @@ -80,12 +89,13 @@ impl SctkEventLoop { let res = std::thread::spawn(move || { let Ok(dh) = display.display_handle() else { log::error!("Failed to get display handle"); - return Ok(()); + return Err(Error::NoDisplayHandle); }; let raw_window_handle::RawDisplayHandle::Wayland(wayland_dh) = dh.as_raw() else { - panic!("Invalid wayland display handle."); + log::error!("Display handle is not Wayland"); + return Err(Error::NoWaylandDisplay); }; let backend = unsafe { @@ -97,9 +107,9 @@ impl SctkEventLoop { let _display = connection.display(); let (globals, event_queue) = - registry_queue_init(&connection).unwrap(); - let event_loop = - calloop::EventLoop::::try_new().unwrap(); + registry_queue_init(&connection).map_err(Error::Global)?; + let event_loop = calloop::EventLoop::::try_new() + .map_err(Error::Calloop)?; let loop_handle = event_loop.handle(); let qh = event_queue.handle(); @@ -473,7 +483,13 @@ impl SctkEventLoop { if res.is_finished() { log::warn!("SCTK thread finished."); - res.join().map(|_: Result<(), ConnectError>| action_tx) + match res.join() { + Ok(_) => Ok(action_tx), + Err(e) => { + log::error!("SCTK thread exited with error: {e:?}"); + return Err(e); + } + } } else { Ok(action_tx) } From 5c8fac78ec6fd0c2c7c4000ec3ee870286529962 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 12 Jun 2025 13:24:39 -0400 Subject: [PATCH 107/116] chore(sctk): reduce severity of log messages --- winit/src/platform_specific/mod.rs | 2 +- winit/src/platform_specific/wayland/mod.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/winit/src/platform_specific/mod.rs b/winit/src/platform_specific/mod.rs index cd16c432d3..79d8048b6b 100644 --- a/winit/src/platform_specific/mod.rs +++ b/winit/src/platform_specific/mod.rs @@ -95,7 +95,7 @@ impl PlatformSpecific { use wayland_backend::client::ObjectId; let Some(conn) = self.wayland.conn() else { - log::error!("No Wayland conn"); + log::info!("No Wayland conn"); return; }; diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index a7ed2d4869..531073c9d9 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -97,7 +97,7 @@ impl PlatformSpecific { Some(Connection::from_backend(backend)) } Ok(_) => { - log::error!("Non-Wayland display handle"); + log::warn!("Non-Wayland display handle"); None } Err(_) => { @@ -136,7 +136,7 @@ impl PlatformSpecific { if let Some(tx) = self.wayland.sender.as_ref() { _ = tx.send(action); } else { - log::error!("Failed to process wayland Action."); + log::info!("Did not process wayland Action."); } } } @@ -180,15 +180,15 @@ impl WaylandSpecific { match e { sctk_event => { let Some(sender) = sender.as_ref() else { - log::error!("Missing calloop sender"); + log::warn!("Missing calloop sender"); return Default::default(); }; let Some(event_sender) = winit_event_sender.as_ref() else { - log::error!("Missing control sender"); + log::warn!("Missing control sender"); return Default::default(); }; let Some(proxy) = proxy.as_ref() else { - log::error!("Missing event loop proxy"); + log::warn!("Missing event loop proxy"); return Default::default(); }; From e4b7033e9f267b3f0007c287e72482c7c5c43fe6 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:11:25 +0200 Subject: [PATCH 108/116] fix: conditional feature compile error for highlighter --- widget/src/markdown.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/widget/src/markdown.rs b/widget/src/markdown.rs index 628a10c6ee..a954f5ff58 100644 --- a/widget/src/markdown.rs +++ b/widget/src/markdown.rs @@ -144,6 +144,7 @@ impl Content { let mut state = State { leftover: String::new(), references: self.state.references.clone(), + #[cfg(feature = "highlighter")] highlighter: None, }; From f9783a705ff143901ac46d265bb90b4b231ef5b4 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 8 May 2025 12:29:29 -0700 Subject: [PATCH 109/116] examples: Update `sctk_subsurface_gst` to compile again --- examples/sctk_subsurface_gst/Cargo.toml | 1 + examples/sctk_subsurface_gst/src/main.rs | 43 ++++++++++---------- examples/sctk_subsurface_gst/src/pipewire.rs | 13 +++--- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/examples/sctk_subsurface_gst/Cargo.toml b/examples/sctk_subsurface_gst/Cargo.toml index 4b8095ced7..0c9724b5e8 100644 --- a/examples/sctk_subsurface_gst/Cargo.toml +++ b/examples/sctk_subsurface_gst/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "828b1eb" } iced = { path = "../..", default-features = false, features = [ "wayland", + "winit", "debug", "a11y", ] } diff --git a/examples/sctk_subsurface_gst/src/main.rs b/examples/sctk_subsurface_gst/src/main.rs index 09119117bb..d384457f7c 100644 --- a/examples/sctk_subsurface_gst/src/main.rs +++ b/examples/sctk_subsurface_gst/src/main.rs @@ -1,28 +1,32 @@ // Shows a subsurface with a 1x1 px red buffer, stretch to window size use iced::{ - wayland::InitialSurface, widget::text, window, Application, Command, - Element, Length, Subscription, Theme, + platform_specific::shell::subsurface_widget::{self, SubsurfaceBuffer}, + widget::text, + window, Element, Length, Subscription, Task, }; -use iced_sctk::subsurface_widget::SubsurfaceBuffer; use std::{env, path::Path}; mod pipewire; -fn main() { +fn main() -> iced::Result { let args = env::args(); if args.len() != 2 { eprintln!("usage: sctk_subsurface_gst [h264 mp4 path]"); - return; + return Ok(()); } let path = args.skip(1).next().unwrap(); if !Path::new(&path).exists() { eprintln!("File `{path}` not found."); - return; + return Ok(()); } - let mut settings = iced::Settings::with_flags(path); - settings.initial_surface = InitialSurface::XdgWindow(Default::default()); - SubsurfaceApp::run(settings).unwrap(); + iced::daemon( + SubsurfaceApp::title, + SubsurfaceApp::update, + SubsurfaceApp::view, + ) + .subscription(SubsurfaceApp::subscription) + .run_with(|| SubsurfaceApp::new(path)) } #[derive(Debug, Clone, Default)] @@ -36,19 +40,14 @@ pub enum Message { Pipewire(pipewire::Event), } -impl Application for SubsurfaceApp { - type Executor = iced::executor::Default; - type Message = Message; - type Flags = String; - type Theme = Theme; - - fn new(flags: String) -> (SubsurfaceApp, Command) { +impl SubsurfaceApp { + fn new(flags: String) -> (SubsurfaceApp, Task) { ( SubsurfaceApp { path: flags, ..SubsurfaceApp::default() }, - Command::none(), + Task::none(), ) } @@ -56,7 +55,7 @@ impl Application for SubsurfaceApp { String::from("SubsurfaceApp") } - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::Pipewire(evt) => match evt { pipewire::Event::Frame(subsurface_buffer) => { @@ -64,12 +63,12 @@ impl Application for SubsurfaceApp { } }, } - Command::none() + Task::none() } - fn view(&self, _id: window::Id) -> Element { + fn view(&self, _id: window::Id) -> Element { if let Some(buffer) = &self.buffer { - iced_sctk::subsurface_widget::Subsurface::new(1, 1, buffer) + subsurface_widget::Subsurface::new(buffer.clone()) .width(Length::Fill) .height(Length::Fill) .into() @@ -78,7 +77,7 @@ impl Application for SubsurfaceApp { } } - fn subscription(&self) -> Subscription { + fn subscription(&self) -> Subscription { pipewire::subscription(&self.path).map(Message::Pipewire) } } diff --git a/examples/sctk_subsurface_gst/src/pipewire.rs b/examples/sctk_subsurface_gst/src/pipewire.rs index 9933ee099d..70537b85a3 100644 --- a/examples/sctk_subsurface_gst/src/pipewire.rs +++ b/examples/sctk_subsurface_gst/src/pipewire.rs @@ -2,7 +2,7 @@ use drm_fourcc::{DrmFourcc, DrmModifier}; use gst::glib::{self, translate::IntoGlib}; use gst::prelude::*; use iced::futures::{executor::block_on, SinkExt}; -use iced_sctk::subsurface_widget::{ +use iced::platform_specific::shell::subsurface_widget::{ BufferSource, Dmabuf, Plane, SubsurfaceBuffer, }; use std::{ffi::c_void, os::unix::io::BorrowedFd, sync::Arc, thread}; @@ -53,10 +53,13 @@ pub enum Event { pub fn subscription(path: &str) -> iced::Subscription { let path = path.to_string(); - iced::subscription::channel("pw", 16, |sender| async { - thread::spawn(move || pipewire_thread(&path, sender)); - std::future::pending().await - }) + iced::Subscription::run_with_id( + "pw", + iced::stream::channel(16, |sender| async { + thread::spawn(move || pipewire_thread(&path, sender)); + std::future::pending().await + }), + ) } fn pipewire_thread( From ce3e56f8d8a1471a716042a0c3cd6f51d99b4a78 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 8 May 2025 12:36:10 -0700 Subject: [PATCH 110/116] examples: Update gstreamer --- examples/sctk_subsurface_gst/Cargo.toml | 8 ++++---- examples/sctk_subsurface_gst/src/pipewire.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/sctk_subsurface_gst/Cargo.toml b/examples/sctk_subsurface_gst/Cargo.toml index 0c9724b5e8..bacab69903 100644 --- a/examples/sctk_subsurface_gst/Cargo.toml +++ b/examples/sctk_subsurface_gst/Cargo.toml @@ -15,8 +15,8 @@ iced_runtime = { path = "../../runtime" } env_logger = "0.10" futures-channel = "0.3.29" calloop = "0.12.3" -gst = { package = "gstreamer", version = "0.21.3" } -gst-app = { package = "gstreamer-app", version = "0.21.2" } -gst-video = { package = "gstreamer-video", version = "0.21.2" } -gst-allocators = { package = "gstreamer-allocators", version = "0.21.2" } +gst = { package = "gstreamer", version = "0.23.0" } +gst-app = { package = "gstreamer-app", version = "0.23.0" } +gst-video = { package = "gstreamer-video", version = "0.23.0" } +gst-allocators = { package = "gstreamer-allocators", version = "0.23.0" } drm-fourcc = "2.2.0" diff --git a/examples/sctk_subsurface_gst/src/pipewire.rs b/examples/sctk_subsurface_gst/src/pipewire.rs index 70537b85a3..a179bf1ff5 100644 --- a/examples/sctk_subsurface_gst/src/pipewire.rs +++ b/examples/sctk_subsurface_gst/src/pipewire.rs @@ -68,7 +68,7 @@ fn pipewire_thread( ) { gst::init().unwrap(); - let pipeline = gst::parse_launch(&format!( + let pipeline = gst::parse::launch(&format!( "filesrc name=filesrc ! qtdemux ! h264parse ! @@ -126,7 +126,7 @@ fn pipewire_thread( let planes = (0..meta.n_planes()) .map(|plane_idx| { let memory = buffer - .memory(plane_idx) + .memory(plane_idx as usize) .unwrap() .downcast_memory::() .unwrap(); From 3bd1116a151475a13c858aafbf37efb412f9b0fa Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 8 May 2025 18:51:53 -0700 Subject: [PATCH 111/116] examples: Add `sctk_subsurface_img` Test scrolling. Single color buffers in `sctk_subsurface` aren't good for comparing it, and `sctk_subsurface_gst` isn't working on latest gst with explicit sync (and AppSync isn't accepting the dmabuf when I try to modify it.) --- examples/sctk_subsurface_img/Cargo.toml | 21 ++++ examples/sctk_subsurface_img/src/main.rs | 135 +++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 examples/sctk_subsurface_img/Cargo.toml create mode 100644 examples/sctk_subsurface_img/src/main.rs diff --git a/examples/sctk_subsurface_img/Cargo.toml b/examples/sctk_subsurface_img/Cargo.toml new file mode 100644 index 0000000000..6ac531c17b --- /dev/null +++ b/examples/sctk_subsurface_img/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sctk_subsurface_img" +version = "0.1.0" +edition = "2021" + +[dependencies] +iced = { path = "../..", default-features = false, features = [ + "tokio", + "wayland", + "winit", + "debug", + "tiny-skia", + "image", +] } +iced_runtime = { path = "../../runtime" } +env_logger = "0.10" +futures-channel = "0.3.29" +calloop = "0.13" +rustix = { version = "0.38.30", features = ["fs", "shm"] } +cctk.workspace = true +image = { workspace = true, features = ["png"] } diff --git a/examples/sctk_subsurface_img/src/main.rs b/examples/sctk_subsurface_img/src/main.rs new file mode 100644 index 0000000000..feef39eb0c --- /dev/null +++ b/examples/sctk_subsurface_img/src/main.rs @@ -0,0 +1,135 @@ +use cctk::sctk::reexports::client::protocol::wl_shm; +use iced::{ + platform_specific::shell::subsurface_widget::{ + self, Shmbuf, SubsurfaceBuffer, + }, + window::{self, Id, Settings}, + Element, Length, Subscription, Task, +}; +use image::{ImageReader, Pixel}; +use rustix::{io::Errno, shm::ShmOFlags}; +use std::{ + env, + os::fd::OwnedFd, + path::Path, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; + +fn main() -> iced::Result { + let args = env::args(); + if args.len() != 2 { + eprintln!("usage: sctk_subsurface_img [image path]"); + return Ok(()); + } + let path = args.skip(1).next().unwrap(); + if !Path::new(&path).exists() { + eprintln!("File `{path}` not found."); + return Ok(()); + } + + iced::daemon( + SubsurfaceApp::title, + SubsurfaceApp::update, + SubsurfaceApp::view, + ) + .subscription(SubsurfaceApp::subscription) + .run_with(|| SubsurfaceApp::new(path)) +} + +#[derive(Debug, Clone)] +struct SubsurfaceApp { + path: String, + buffer: SubsurfaceBuffer, +} + +#[derive(Debug, Clone)] +pub enum Message { + Id(Id), +} + +impl SubsurfaceApp { + fn new(path: String) -> (SubsurfaceApp, Task) { + let img = ImageReader::open(&path) + .unwrap() + .decode() + .unwrap() + .to_rgba8(); + let fd = create_memfile().unwrap(); + for pixel in img.pixels() { + let [r, g, b, a] = <[u8; 4]>::try_from(pixel.channels()).unwrap(); + rustix::io::write(&fd, &[b, g, r, a]).unwrap(); + } + let shmbuf = Shmbuf { + fd, + offset: 0, + width: img.width() as i32, + height: img.height() as i32, + stride: img.width() as i32 * 4, + format: wl_shm::Format::Xrgb8888, + }; + let buffer = SubsurfaceBuffer::new(Arc::new(shmbuf.into())).0; + + ( + SubsurfaceApp { path, buffer }, + iced::window::open(Settings { + ..Default::default() + }) + .1 + .map(Message::Id), + ) + } + + fn title(&self, _id: window::Id) -> String { + String::from("SubsurfaceApp") + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::Id(_) => {} + } + Task::none() + } + + fn view(&self, _id: window::Id) -> Element { + // TODO compare side-by-side with image widget; key bind to toggle? + let image = subsurface_widget::Subsurface::new(self.buffer.clone()) + .content_fit(iced::ContentFit::None); + /* + let image = iced::widget::image::Image::new(&self.path) + .content_fit(iced::ContentFit::None); + */ + iced::widget::scrollable(image).into() + } + + fn subscription(&self) -> Subscription { + Subscription::none() + } +} + +fn create_memfile() -> rustix::io::Result { + loop { + let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; + + let time = SystemTime::now(); + let name = format!( + "/iced-sctk-{}", + time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() + ); + + match rustix::io::retry_on_intr(|| { + rustix::shm::shm_open(&name, flags, 0600.into()) + }) { + Ok(fd) => match rustix::shm::shm_unlink(&name) { + Ok(_) => return Ok(fd), + Err(errno) => { + return Err(errno.into()); + } + }, + Err(Errno::EXIST) => { + continue; + } + Err(err) => return Err(err.into()), + } + } +} From 1dd19d51502c997648a9e6cdd1856aaaaec86d9a Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 9 May 2025 15:44:11 -0700 Subject: [PATCH 112/116] subsurface_widget: Handle `viewport` and set source rectangle This is needed to crop subsurface when needed, instead of rendering overlapping other things. And to offset while scrolling. --- .../wayland/event_loop/state.rs | 3 +- .../wayland/subsurface_widget.rs | 55 +++++++++++++++---- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index a5b117fb29..0880ed47fd 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1613,7 +1613,8 @@ impl SctkState { wp_fractional_scale, wl_buffer: None, - bounds: Some(bounds), + source: None, + destination: Some(bounds), transform: cctk::wayland_client::protocol::wl_output::Transform::Normal, z: settings.z, diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index c670e5217b..8d7e0b3f05 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -455,7 +455,8 @@ impl SubsurfaceState { wp_viewport, wp_alpha_modifier_surface, wl_buffer: None, - bounds: None, + source: None, + destination: None, wp_fractional_scale: None, transform: wl_output::Transform::Normal, z: 0, @@ -631,7 +632,8 @@ pub(crate) struct SubsurfaceInstance { pub(crate) wp_fractional_scale: Option, pub(crate) wp_alpha_modifier_surface: Option, pub(crate) wl_buffer: Option, - pub(crate) bounds: Option>, + pub(crate) source: Option>, + pub(crate) destination: Option>, pub(crate) transform: wl_output::Transform, pub(crate) z: i32, pub parent: WlSurface, @@ -678,14 +680,21 @@ impl SubsurfaceInstance { }; // XXX scale factor? - let bounds_changed = self.bounds != Some(info.bounds); + let source_changed = self.source != Some(info.source); + let destination_changed = self.destination != Some(info.destination); // wlroots seems to have issues changing buffer without running this - if bounds_changed || buffer_changed { + if source_changed || destination_changed || buffer_changed { + self.wp_viewport.set_source( + info.source.x.into(), + info.source.y.into(), + info.source.width.into(), + info.source.height.into(), + ); self.wl_subsurface - .set_position(info.bounds.x as i32, info.bounds.y as i32); + .set_position(info.destination.x as i32, info.destination.y as i32); self.wp_viewport.set_destination( - info.bounds.width as i32, - info.bounds.height as i32, + info.destination.width as i32, + info.destination.height as i32, ); } let transform_changed = self.transform != info.transform; @@ -696,7 +705,7 @@ impl SubsurfaceInstance { self.wl_surface.attach(Some(&buffer), 0, 0); self.wl_surface.damage(0, 0, i32::MAX, i32::MAX); } - if buffer_changed || bounds_changed || transform_changed { + if buffer_changed || source_changed || destination_changed || transform_changed { _ = self.wl_surface.frame(&state.qh, self.wl_surface.clone()); self.wl_surface.commit(); } @@ -708,7 +717,8 @@ impl SubsurfaceInstance { } self.wl_buffer = Some(buffer); - self.bounds = Some(info.bounds); + self.source = Some(info.source); + self.destination = Some(info.destination); self.transform = info.transform; self.z = info.z; } @@ -733,7 +743,8 @@ impl Drop for SubsurfaceInstance { #[derive(Debug)] pub(crate) struct SubsurfaceInfo { pub buffer: SubsurfaceBuffer, - pub bounds: Rectangle, + pub source: Rectangle, + pub destination: Rectangle, pub alpha: f32, pub transform: wl_output::Transform, pub z: i32, @@ -840,14 +851,34 @@ where _style: &renderer::Style, layout: Layout<'_>, _cursor: mouse::Cursor, - _viewport: &Rectangle, + viewport: &Rectangle, ) { + let buffer_size = + Size::new(self.buffer.width() as f32, self.buffer.height() as f32); + let full_size = + self.content_fit.fit(buffer_size, layout.bounds().size()); + + let source = Rectangle { + x: viewport.x, + y: viewport.y, + width: viewport.width.min(self.buffer.width() as f32), + height: viewport.height.min(self.buffer.height() as f32), + }; + + let destination = Rectangle { + x: layout.bounds().x, + y: layout.bounds().y, + width: layout.bounds().width.min(viewport.width), + height: layout.bounds().height.min(viewport.height), + }; + // Instead of using renderer, we need to add surface to a list that is // read by the iced-sctk shell. SUBSURFACES.with(|subsurfaces| { subsurfaces.borrow_mut().push(SubsurfaceInfo { buffer: self.buffer.clone(), - bounds: layout.bounds(), + source, + destination, alpha: self.alpha, transform: self.transform, z: self.z, From ccbbc0978bfd282a35231606c9b52f6d5a986348 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 9 May 2025 15:55:50 -0700 Subject: [PATCH 113/116] examples/sctk_subsurface_img: Toggle image/subsurface with key --- examples/sctk_subsurface_img/src/main.rs | 47 +++++++++++++++++++----- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/examples/sctk_subsurface_img/src/main.rs b/examples/sctk_subsurface_img/src/main.rs index feef39eb0c..36b68f7e25 100644 --- a/examples/sctk_subsurface_img/src/main.rs +++ b/examples/sctk_subsurface_img/src/main.rs @@ -4,7 +4,7 @@ use iced::{ self, Shmbuf, SubsurfaceBuffer, }, window::{self, Id, Settings}, - Element, Length, Subscription, Task, + Element, Subscription, Task, }; use image::{ImageReader, Pixel}; use rustix::{io::Errno, shm::ShmOFlags}; @@ -41,11 +41,13 @@ fn main() -> iced::Result { struct SubsurfaceApp { path: String, buffer: SubsurfaceBuffer, + use_subsurface: bool, } #[derive(Debug, Clone)] pub enum Message { Id(Id), + Toggle, } impl SubsurfaceApp { @@ -71,7 +73,11 @@ impl SubsurfaceApp { let buffer = SubsurfaceBuffer::new(Arc::new(shmbuf.into())).0; ( - SubsurfaceApp { path, buffer }, + SubsurfaceApp { + path, + buffer, + use_subsurface: true, + }, iced::window::open(Settings { ..Default::default() }) @@ -87,23 +93,44 @@ impl SubsurfaceApp { fn update(&mut self, message: Message) -> Task { match message { Message::Id(_) => {} + Message::Toggle => { + self.use_subsurface = !self.use_subsurface; + if self.use_subsurface { + println!("Using subsurface"); + } else { + println!("Using image widget"); + } + } } Task::none() } fn view(&self, _id: window::Id) -> Element { - // TODO compare side-by-side with image widget; key bind to toggle? - let image = subsurface_widget::Subsurface::new(self.buffer.clone()) - .content_fit(iced::ContentFit::None); - /* - let image = iced::widget::image::Image::new(&self.path) - .content_fit(iced::ContentFit::None); - */ + let image: Element<_> = if self.use_subsurface { + subsurface_widget::Subsurface::new(self.buffer.clone()) + .content_fit(iced::ContentFit::None) + .into() + } else { + iced::widget::image::Image::new(&self.path) + .content_fit(iced::ContentFit::None) + .into() + }; iced::widget::scrollable(image).into() } fn subscription(&self) -> Subscription { - Subscription::none() + iced::event::listen_with(|evt, _status, _id| match evt { + iced::Event::Keyboard(iced::keyboard::Event::KeyReleased { + key, + .. + }) => match key { + iced::keyboard::Key::Named( + iced::keyboard::key::Named::Space, + ) => Some(Message::Toggle), + _ => None, + }, + _ => None, + }) } } From 992f3b20093e358dde5395d5a885bc672f018d74 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 21 Jul 2025 13:39:18 -0700 Subject: [PATCH 114/116] Revert "subsurface_widget: Handle `viewport` and set source rectangle" This reverts commit 15547dec8f83af1ea6dbed1964302400fc17c257 from https://github.com/pop-os/iced/pull/227. This is causing issues in `cosmic-workspaces`. It seems I didn't test it properly outside the examples here... and this misinterpreted exactly what the (undocumented) `viewport` argument means (Iced's `image` widget also needed a fix to handle viewports properly since our fork: https://github.com/iced-rs/iced/pull/2752). It should be possible to fix, but revert for now. --- .../wayland/event_loop/state.rs | 3 +- .../wayland/subsurface_widget.rs | 55 ++++--------------- 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index 0880ed47fd..a5b117fb29 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -1613,8 +1613,7 @@ impl SctkState { wp_fractional_scale, wl_buffer: None, - source: None, - destination: Some(bounds), + bounds: Some(bounds), transform: cctk::wayland_client::protocol::wl_output::Transform::Normal, z: settings.z, diff --git a/winit/src/platform_specific/wayland/subsurface_widget.rs b/winit/src/platform_specific/wayland/subsurface_widget.rs index 8d7e0b3f05..c670e5217b 100644 --- a/winit/src/platform_specific/wayland/subsurface_widget.rs +++ b/winit/src/platform_specific/wayland/subsurface_widget.rs @@ -455,8 +455,7 @@ impl SubsurfaceState { wp_viewport, wp_alpha_modifier_surface, wl_buffer: None, - source: None, - destination: None, + bounds: None, wp_fractional_scale: None, transform: wl_output::Transform::Normal, z: 0, @@ -632,8 +631,7 @@ pub(crate) struct SubsurfaceInstance { pub(crate) wp_fractional_scale: Option, pub(crate) wp_alpha_modifier_surface: Option, pub(crate) wl_buffer: Option, - pub(crate) source: Option>, - pub(crate) destination: Option>, + pub(crate) bounds: Option>, pub(crate) transform: wl_output::Transform, pub(crate) z: i32, pub parent: WlSurface, @@ -680,21 +678,14 @@ impl SubsurfaceInstance { }; // XXX scale factor? - let source_changed = self.source != Some(info.source); - let destination_changed = self.destination != Some(info.destination); + let bounds_changed = self.bounds != Some(info.bounds); // wlroots seems to have issues changing buffer without running this - if source_changed || destination_changed || buffer_changed { - self.wp_viewport.set_source( - info.source.x.into(), - info.source.y.into(), - info.source.width.into(), - info.source.height.into(), - ); + if bounds_changed || buffer_changed { self.wl_subsurface - .set_position(info.destination.x as i32, info.destination.y as i32); + .set_position(info.bounds.x as i32, info.bounds.y as i32); self.wp_viewport.set_destination( - info.destination.width as i32, - info.destination.height as i32, + info.bounds.width as i32, + info.bounds.height as i32, ); } let transform_changed = self.transform != info.transform; @@ -705,7 +696,7 @@ impl SubsurfaceInstance { self.wl_surface.attach(Some(&buffer), 0, 0); self.wl_surface.damage(0, 0, i32::MAX, i32::MAX); } - if buffer_changed || source_changed || destination_changed || transform_changed { + if buffer_changed || bounds_changed || transform_changed { _ = self.wl_surface.frame(&state.qh, self.wl_surface.clone()); self.wl_surface.commit(); } @@ -717,8 +708,7 @@ impl SubsurfaceInstance { } self.wl_buffer = Some(buffer); - self.source = Some(info.source); - self.destination = Some(info.destination); + self.bounds = Some(info.bounds); self.transform = info.transform; self.z = info.z; } @@ -743,8 +733,7 @@ impl Drop for SubsurfaceInstance { #[derive(Debug)] pub(crate) struct SubsurfaceInfo { pub buffer: SubsurfaceBuffer, - pub source: Rectangle, - pub destination: Rectangle, + pub bounds: Rectangle, pub alpha: f32, pub transform: wl_output::Transform, pub z: i32, @@ -851,34 +840,14 @@ where _style: &renderer::Style, layout: Layout<'_>, _cursor: mouse::Cursor, - viewport: &Rectangle, + _viewport: &Rectangle, ) { - let buffer_size = - Size::new(self.buffer.width() as f32, self.buffer.height() as f32); - let full_size = - self.content_fit.fit(buffer_size, layout.bounds().size()); - - let source = Rectangle { - x: viewport.x, - y: viewport.y, - width: viewport.width.min(self.buffer.width() as f32), - height: viewport.height.min(self.buffer.height() as f32), - }; - - let destination = Rectangle { - x: layout.bounds().x, - y: layout.bounds().y, - width: layout.bounds().width.min(viewport.width), - height: layout.bounds().height.min(viewport.height), - }; - // Instead of using renderer, we need to add surface to a list that is // read by the iced-sctk shell. SUBSURFACES.with(|subsurfaces| { subsurfaces.borrow_mut().push(SubsurfaceInfo { buffer: self.buffer.clone(), - source, - destination, + bounds: layout.bounds(), alpha: self.alpha, transform: self.transform, z: self.z, From 08caffa34d2df5479865f11f30a999fcff8f255f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 30 Jul 2025 13:12:05 -0700 Subject: [PATCH 115/116] winit/sctk: Update cursor position on touch event Ideally pointer should be seperate from touch, but this should match how Iced handles input in normal winit windows. https://github.com/iced-rs/iced/blob/d475ae5b454b82cb549d6dd961ae158eb5ac7f19/winit/src/window/state.rs#L165-L170 With this, touch input for applets seems to work as expected in general. --- .../platform_specific/wayland/sctk_event.rs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/winit/src/platform_specific/wayland/sctk_event.rs b/winit/src/platform_specific/wayland/sctk_event.rs index 72492e7d8b..132792e054 100755 --- a/winit/src/platform_specific/wayland/sctk_event.rs +++ b/winit/src/platform_specific/wayland/sctk_event.rs @@ -684,10 +684,23 @@ impl SctkEvent { touch_id: _, seat_id: _, surface, - } => events.push(( - surface_ids.get(&surface.id()).map(|id| id.inner()), - iced_runtime::core::Event::Touch(variant), - )), + } => { + let position = match variant { + touch::Event::FingerPressed { position, .. } => position, + touch::Event::FingerMoved { position, .. } => position, + touch::Event::FingerLifted { position, .. } => position, + touch::Event::FingerLost { position, .. } => position, + }; + let id = surface_ids.get(&surface.id()).map(|id| id.inner()); + if let Some(w) = + id.clone().and_then(|id| window_manager.get_mut(id)) + { + w.state.set_logical_cursor_pos( + (position.x, position.y).into(), + ); + } + events.push((id, iced_runtime::core::Event::Touch(variant))) + } SctkEvent::WindowEvent { .. } => {} SctkEvent::LayerSurfaceEvent { variant, From 6c9055d001bcc9cc4d3e401b54201cda065243e4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 31 Jul 2025 11:54:09 -0400 Subject: [PATCH 116/116] fix(wayland popup): apply input region --- .../wayland/event_loop/state.rs | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index a5b117fb29..94c971e634 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -680,7 +680,19 @@ impl SctkState { let wl_surface = self.compositor_state.create_surface(&self.queue_handle); _ = self.id_map.insert(wl_surface.id(), settings.id.clone()); - + if let Some(z) = settings.input_zone { + let region = self + .compositor_state + .wl_compositor() + .create_region(&self.queue_handle, ()); + region.add( + z.x.round() as i32, + z.y.round() as i32, + z.width.round() as i32, + z.height.round() as i32, + ); + wl_surface.set_input_region(Some(®ion)); + } let (toplevel, popup) = match &parent { PopupParent::LayerSurface(parent) => { let Some(parent_layer_surface) = self @@ -742,6 +754,14 @@ impl SctkState { ) } }; + + popup.xdg_surface().set_window_geometry( + 0, + 0, + size.0 as i32, + size.1 as i32, + ); + if grab { if let Some(s) = self.seats.first() { let ptr_data = s @@ -765,24 +785,7 @@ impl SctkState { } } - if let Some(z) = settings.input_zone { - let region = self - .compositor_state - .wl_compositor() - .create_region(&self.queue_handle, ()); - region.add( - z.x.round() as i32, - z.y.round() as i32, - z.width.round() as i32, - z.height.round() as i32, - ); - } - popup.xdg_surface().set_window_geometry( - 0, - 0, - size.0 as i32, - size.1 as i32, - ); + _ = wl_surface.frame(&self.queue_handle, wl_surface.clone()); wl_surface.commit(); @@ -1484,7 +1487,7 @@ impl SctkState { let mut loc = settings.loc; match settings.gravity { wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::None => { - // center on + // center on loc.x -= half_w; loc.y -= half_h; },