summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md204
-rw-r--r--Cargo.toml24
-rw-r--r--core/src/widget.rs10
-rw-r--r--core/src/window/id.rs12
-rw-r--r--examples/changelog/Cargo.toml24
-rw-r--r--examples/changelog/src/changelog.rs386
-rw-r--r--examples/changelog/src/icon.rs10
-rw-r--r--examples/changelog/src/main.rs379
-rw-r--r--examples/custom_shader/Cargo.toml2
-rw-r--r--examples/multi_window/src/main.rs6
-rw-r--r--examples/todos/Cargo.toml1
-rw-r--r--examples/todos/src/main.rs7
-rw-r--r--futures/src/subscription.rs8
-rw-r--r--runtime/src/lib.rs2
-rw-r--r--runtime/src/user_interface.rs2
-rw-r--r--src/application.rs20
-rw-r--r--src/daemon.rs17
-rw-r--r--src/program.rs74
-rw-r--r--widget/src/pane_grid.rs2
-rw-r--r--widget/src/scrollable.rs20
-rw-r--r--widget/src/text_input.rs39
-rw-r--r--winit/src/conversion.rs2
-rw-r--r--winit/src/lib.rs2
23 files changed, 1187 insertions, 66 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16a69a7a..6addcc69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,12 +5,211 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+
+## [0.13.0] - 2024-09-18
### Added
+- Introductory chapters to the [official guide book](https://book.iced.rs/).
+- [Pocket guide](https://docs.rs/iced/0.13.0/iced/#the-pocket-guide) in API reference.
+- `Program` API. [#2331](https://github.com/iced-rs/iced/pull/2331)
+- `Task` API. [#2463](https://github.com/iced-rs/iced/pull/2463)
+- `Daemon` API and Shell Runtime Unification. [#2469](https://github.com/iced-rs/iced/pull/2469)
+- `rich_text` and `markdown` widgets. [#2508](https://github.com/iced-rs/iced/pull/2508)
+- `stack` widget. [#2405](https://github.com/iced-rs/iced/pull/2405)
+- `hover` widget. [#2408](https://github.com/iced-rs/iced/pull/2408)
+- `row::Wrapping` widget. [#2539](https://github.com/iced-rs/iced/pull/2539)
+- `text` macro helper. [#2338](https://github.com/iced-rs/iced/pull/2338)
+- `text::Wrapping` support. [#2279](https://github.com/iced-rs/iced/pull/2279)
+- Functional widget styling. [#2312](https://github.com/iced-rs/iced/pull/2312)
+- Closure-based widget styling. [#2326](https://github.com/iced-rs/iced/pull/2326)
+- Class-based Theming. [#2350](https://github.com/iced-rs/iced/pull/2350)
+- Type-Driven Renderer Fallback. [#2351](https://github.com/iced-rs/iced/pull/2351)
+- Background styling to `rich_text` widget. [#2516](https://github.com/iced-rs/iced/pull/2516)
+- Underline support for `rich_text`. [#2526](https://github.com/iced-rs/iced/pull/2526)
+- Strikethrough support for `rich_text`. [#2528](https://github.com/iced-rs/iced/pull/2528)
+- Abortable `Task`. [#2496](https://github.com/iced-rs/iced/pull/2496)
+- `abort_on_drop` to `task::Handle`. [#2503](https://github.com/iced-rs/iced/pull/2503)
+- `Ferra` theme. [#2329](https://github.com/iced-rs/iced/pull/2329)
+- `auto-detect-theme` feature. [#2343](https://github.com/iced-rs/iced/pull/2343)
+- Custom key binding support for `text_editor`. [#2522](https://github.com/iced-rs/iced/pull/2522)
+- `align_x` for `text_input` widget. [#2535](https://github.com/iced-rs/iced/pull/2535)
+- `center` widget helper. [#2423](https://github.com/iced-rs/iced/pull/2423)
+- Rotation support for `image` and `svg` widgets. [#2334](https://github.com/iced-rs/iced/pull/2334)
+- Dynamic `opacity` support for `image` and `svg`. [#2424](https://github.com/iced-rs/iced/pull/2424)
+- Scroll transactions for `scrollable` widget. [#2401](https://github.com/iced-rs/iced/pull/2401)
+- `physical_key` and `modified_key` to `keyboard::Event`. [#2576](https://github.com/iced-rs/iced/pull/2576)
- `fetch_position` command in `window` module. [#2280](https://github.com/iced-rs/iced/pull/2280)
+- `filter_method` property for `image::Viewer` widget. [#2324](https://github.com/iced-rs/iced/pull/2324)
+- Support for pre-multiplied alpha `wgpu` composite mode. [#2341](https://github.com/iced-rs/iced/pull/2341)
+- `text_size` and `line_height` properties for `text_editor` widget. [#2358](https://github.com/iced-rs/iced/pull/2358)
+- `is_focused` method for `text_editor::State`. [#2386](https://github.com/iced-rs/iced/pull/2386)
+- `canvas::Cache` Grouping. [#2415](https://github.com/iced-rs/iced/pull/2415)
+- `ICED_PRESENT_MODE` env var to pick a `wgpu::PresentMode`. [#2428](https://github.com/iced-rs/iced/pull/2428)
+- `SpecificWith` variant to `window::Position`. [#2435](https://github.com/iced-rs/iced/pull/2435)
+- `scale_factor` field to `window::Screenshot`. [#2449](https://github.com/iced-rs/iced/pull/2449)
+- Styling support for `overlay::Menu` of `pick_list` widget. [#2457](https://github.com/iced-rs/iced/pull/2457)
+- `window::Id` in `Event` subscriptions. [#2456](https://github.com/iced-rs/iced/pull/2456)
+- `FromIterator` implementation for `row` and `column`. [#2460](https://github.com/iced-rs/iced/pull/2460)
+- `content_fit` for `image::viewer` widget. [#2330](https://github.com/iced-rs/iced/pull/2330)
+- `Display` implementation for `Radians`. [#2446](https://github.com/iced-rs/iced/pull/2446)
+- Helper methods for `window::Settings` in `Application`. [#2470](https://github.com/iced-rs/iced/pull/2470)
+- `Copy` implementation for `canvas::Fill` and `canvas::Stroke`. [#2475](https://github.com/iced-rs/iced/pull/2475)
+- Clarification of `Border` alignment for `Quad`. [#2485](https://github.com/iced-rs/iced/pull/2485)
+- "Select All" functionality on `Ctrl+A` to `text_editor`. [#2321](https://github.com/iced-rs/iced/pull/2321)
+- `stream::try_channel` helper. [#2497](https://github.com/iced-rs/iced/pull/2497)
+- `iced` widget helper to display the iced logo :comet:. [#2498](https://github.com/iced-rs/iced/pull/2498)
+- `align_x` and `align_y` helpers to `scrollable`. [#2499](https://github.com/iced-rs/iced/pull/2499)
+- Built-in text styles for each `Palette` color. [#2500](https://github.com/iced-rs/iced/pull/2500)
+- Embedded `Scrollbar` support for `scrollable`. [#2269](https://github.com/iced-rs/iced/pull/2269)
+- `on_press_with` method for `button`. [#2502](https://github.com/iced-rs/iced/pull/2502)
+- `resize_events` subscription to `window` module. [#2505](https://github.com/iced-rs/iced/pull/2505)
+- `Link` support to `rich_text` widget. [#2512](https://github.com/iced-rs/iced/pull/2512)
+- `image` and `svg` support for `canvas` widget. [#2537](https://github.com/iced-rs/iced/pull/2537)
+- `Compact` variant for `pane_grid::Controls`. [#2555](https://github.com/iced-rs/iced/pull/2555)
+- `image-without-codecs` feature flag. [#2244](https://github.com/iced-rs/iced/pull/2244)
+- `container::background` styling helper. [#2261](https://github.com/iced-rs/iced/pull/2261)
+- `undecorated_shadow` window setting for Windows. [#2285](https://github.com/iced-rs/iced/pull/2285)
+- Tasks for setting mouse passthrough. [#2284](https://github.com/iced-rs/iced/pull/2284)
+- `*_maybe` helpers for `text_input` widget. [#2390](https://github.com/iced-rs/iced/pull/2390)
+- Wasm support for `download_progress` example. [#2419](https://github.com/iced-rs/iced/pull/2419)
+- `scrollable::scroll_by` widget operation. [#2436](https://github.com/iced-rs/iced/pull/2436)
+- Enhancements to `slider` widget styling. [#2444](https://github.com/iced-rs/iced/pull/2444)
+- `on_scroll` handler to `mouse_area` widget. [#2450](https://github.com/iced-rs/iced/pull/2450)
+- `stroke_rectangle` method to `canvas::Frame`. [#2473](https://github.com/iced-rs/iced/pull/2473)
+- `override_redirect` setting for X11 windows. [#2476](https://github.com/iced-rs/iced/pull/2476)
+- Disabled state support for `toggler` widget. [#2478](https://github.com/iced-rs/iced/pull/2478)
+- `Color::parse` helper for parsing color strings. [#2486](https://github.com/iced-rs/iced/pull/2486)
+- `rounded_rectangle` method to `canvas::Path`. [#2491](https://github.com/iced-rs/iced/pull/2491)
+- `width` method to `text_editor` widget. [#2513](https://github.com/iced-rs/iced/pull/2513)
+- `on_open` handler to `combo_box` widget. [#2534](https://github.com/iced-rs/iced/pull/2534)
+- Additional `mouse::Interaction` cursors. [#2551](https://github.com/iced-rs/iced/pull/2551)
+- Scroll wheel handling in `slider` widget. [#2565](https://github.com/iced-rs/iced/pull/2565)
-Many thanks to...
+### Changed
+- Use a `StagingBelt` in `iced_wgpu` for regular buffer uploads. [#2357](https://github.com/iced-rs/iced/pull/2357)
+- Use generic `Content` in `Text` to avoid reallocation in `fill_text`. [#2360](https://github.com/iced-rs/iced/pull/2360)
+- Use `Iterator::size_hint` to initialize `Column` and `Row` capacity. [#2362](https://github.com/iced-rs/iced/pull/2362)
+- Specialize `widget::text` helper. [#2363](https://github.com/iced-rs/iced/pull/2363)
+- Use built-in `[lints]` table in `Cargo.toml`. [#2377](https://github.com/iced-rs/iced/pull/2377)
+- Target `#iced` container by default on Wasm. [#2342](https://github.com/iced-rs/iced/pull/2342)
+- Improved architecture for `iced_wgpu` and `iced_tiny_skia`. [#2382](https://github.com/iced-rs/iced/pull/2382)
+- Make image `Cache` eviction strategy less aggressive in `iced_wgpu`. [#2403](https://github.com/iced-rs/iced/pull/2403)
+- Retain caches in `iced_wgpu` as long as `Rc` values are alive. [#2409](https://github.com/iced-rs/iced/pull/2409)
+- Use `bytes` crate for `image` widget. [#2356](https://github.com/iced-rs/iced/pull/2356)
+- Update `winit` to `0.30`. [#2427](https://github.com/iced-rs/iced/pull/2427)
+- Reuse `glyphon::Pipeline` state in `iced_wgpu`. [#2430](https://github.com/iced-rs/iced/pull/2430)
+- Ask for explicit `Length` in `center_*` methods. [#2441](https://github.com/iced-rs/iced/pull/2441)
+- Hide internal `Task` constructors. [#2492](https://github.com/iced-rs/iced/pull/2492)
+- Hide `Subscription` internals. [#2493](https://github.com/iced-rs/iced/pull/2493)
+- Improved `view` ergonomics. [#2504](https://github.com/iced-rs/iced/pull/2504)
+- Update `cosmic-text` and `resvg`. [#2416](https://github.com/iced-rs/iced/pull/2416)
+- Snap `Quad` lines to the pixel grid in `iced_wgpu`. [#2531](https://github.com/iced-rs/iced/pull/2531)
+- Update `web-sys` to `0.3.69`. [#2507](https://github.com/iced-rs/iced/pull/2507)
+- Allow disabled `TextInput` to still be interacted with. [#2262](https://github.com/iced-rs/iced/pull/2262)
+- Enable horizontal scrolling without shift modifier for `srollable` widget. [#2392](https://github.com/iced-rs/iced/pull/2392)
+- Add `mouse::Button` to `mouse::Click`. [#2414](https://github.com/iced-rs/iced/pull/2414)
+- Notify `scrollable::Viewport` changes. [#2438](https://github.com/iced-rs/iced/pull/2438)
+- Improved documentation of `Component` state management. [#2556](https://github.com/iced-rs/iced/pull/2556)
+
+### Fixed
+- Fix `block_on` in `iced_wgpu` hanging Wasm builds. [#2313](https://github.com/iced-rs/iced/pull/2313)
+- Private `PaneGrid` style fields. [#2316](https://github.com/iced-rs/iced/pull/2316)
+- Some documentation typos. [#2317](https://github.com/iced-rs/iced/pull/2317)
+- Blurry input caret with non-integral scaling. [#2320](https://github.com/iced-rs/iced/pull/2320)
+- Scrollbar stuck in a `scrollable` under some circumstances. [#2322](https://github.com/iced-rs/iced/pull/2322)
+- Broken `wgpu` examples link in issue template. [#2327](https://github.com/iced-rs/iced/pull/2327)
+- Empty `wgpu` draw calls in `image` pipeline. [#2344](https://github.com/iced-rs/iced/pull/2344)
+- Layout invalidation for `Responsive` widget. [#2345](https://github.com/iced-rs/iced/pull/2345)
+- Incorrect shadows on quads with rounded corners. [#2354](https://github.com/iced-rs/iced/pull/2354)
+- Empty menu overlay on `combo_box`. [#2364](https://github.com/iced-rs/iced/pull/2364)
+- Copy / cut vulnerability in a secure `TextInput`. [#2366](https://github.com/iced-rs/iced/pull/2366)
+- Inadequate readability / contrast for built-in themes. [#2376](https://github.com/iced-rs/iced/pull/2376)
+- Fix `pkg-config` typo in `DEPENDENCIES.md`. [#2379](https://github.com/iced-rs/iced/pull/2379)
+- Unbounded memory consumption by `iced_winit::Proxy`. [#2389](https://github.com/iced-rs/iced/pull/2389)
+- Typo in `icon::Error` message. [#2393](https://github.com/iced-rs/iced/pull/2393)
+- Nested scrollables capturing all scroll events. [#2397](https://github.com/iced-rs/iced/pull/2397)
+- Content capturing scrollbar events in a `scrollable`. [#2406](https://github.com/iced-rs/iced/pull/2406)
+- Out of bounds caret and overflow when scrolling in `text_editor`. [#2407](https://github.com/iced-rs/iced/pull/2407)
+- Missing `derive(Default)` in overview code snippet. [#2412](https://github.com/iced-rs/iced/pull/2412)
+- `image::Viewer` triggering grab from outside the widget. [#2420](https://github.com/iced-rs/iced/pull/2420)
+- Different windows fighting over shared `image::Cache`. [#2425](https://github.com/iced-rs/iced/pull/2425)
+- Images not aligned to the (logical) pixel grid in `iced_wgpu`. [#2440](https://github.com/iced-rs/iced/pull/2440)
+- Incorrect local time in `clock` example under Unix systems. [#2421](https://github.com/iced-rs/iced/pull/2421)
+- `⌘ + ←` and `⌘ + →` behavior for `text_input` on macOS. [#2315](https://github.com/iced-rs/iced/pull/2315)
+- Wayland packages in `DEPENDENCIES.md`. [#2465](https://github.com/iced-rs/iced/pull/2465)
+- Typo in documentation. [#2487](https://github.com/iced-rs/iced/pull/2487)
+- Extraneous comment in `scrollable` module. [#2488](https://github.com/iced-rs/iced/pull/2488)
+- Top layer in `hover` widget hiding when focused. [#2544](https://github.com/iced-rs/iced/pull/2544)
+- Out of bounds text in `text_editor` widget. [#2536](https://github.com/iced-rs/iced/pull/2536)
+- Segfault on Wayland when closing the app. [#2547](https://github.com/iced-rs/iced/pull/2547)
+- `lazy` feature flag sometimes not present in documentation. [#2289](https://github.com/iced-rs/iced/pull/2289)
+- Border of `progress_bar` widget being rendered below the active bar. [#2443](https://github.com/iced-rs/iced/pull/2443)
+- `radii` typo in `iced_wgpu` shader. [#2484](https://github.com/iced-rs/iced/pull/2484)
+- Incorrect priority of `Binding::Delete` in `text_editor`. [#2514](https://github.com/iced-rs/iced/pull/2514)
+- Division by zero in `multitouch` example. [#2517](https://github.com/iced-rs/iced/pull/2517)
+- Invisible text in `svg` widget. [#2560](https://github.com/iced-rs/iced/pull/2560)
+- `wasm32` deployments not displaying anything. [#2574](https://github.com/iced-rs/iced/pull/2574)
+- Unnecessary COM initialization on Windows. [#2578](https://github.com/iced-rs/iced/pull/2578)
+### Removed
+- Unnecessary struct from `download_progress` example. [#2380](https://github.com/iced-rs/iced/pull/2380)
+- Out of date comment from `custom_widget` example. [#2549](https://github.com/iced-rs/iced/pull/2549)
+- `Clone` bound for `graphics::Cache::clear`. [#2575](https://github.com/iced-rs/iced/pull/2575)
+
+Many thanks to...
+- @Aaron-McGuire
+- @airstrike
+- @alex-ds13
+- @alliby
+- @Andrew-Schwartz
+- @ayeniswe
+- @B0ney
+- @Bajix
+- @blazra
+- @Brady-Simon
+- @breynard0
+- @bungoboingo
+- @casperstorm
+- @Davidster
+- @derezzedex
+- @DKolter
+- @dtoniolo
+- @dtzxporter
+- @fenhl
+- @Gigas002
+- @gintsgints
+- @henrispriet
+- @IsaacMarovitz
+- @ivanceras
+- @Jinderamarak
+- @JL710
+- @jquesada2016
+- @JustSoup312
+- @kiedtl
+- @kmoon2437
+- @Koranir
+- @lufte
+- @LuisLiraC
+- @m4rch3n1ng
+- @meithecatte
+- @mtkennerly
+- @myuujiku
- @n1ght-hunter
+- @nrjais
+- @PgBiel
+- @PolyMeilex
+- @rustrover
+- @ryankopf
+- @saihaze
+- @shartrec
+- @skygrango
+- @SolidStateDj
+- @sundaram123krishnan
+- @tarkah
+- @vladh
+- @WailAbou
+- @wiiznokes
+- @woelfman
+- @Zaubentrucker
## [0.12.1] - 2024-02-22
### Added
@@ -772,7 +971,8 @@ Many thanks to...
### Added
- First release! :tada:
-[Unreleased]: https://github.com/iced-rs/iced/compare/0.12.1...HEAD
+[Unreleased]: https://github.com/iced-rs/iced/compare/0.13.0...HEAD
+[0.13.0]: https://github.com/iced-rs/iced/compare/0.12.1...0.13.0
[0.12.1]: https://github.com/iced-rs/iced/compare/0.12.0...0.12.1
[0.12.0]: https://github.com/iced-rs/iced/compare/0.10.0...0.12.0
[0.10.0]: https://github.com/iced-rs/iced/compare/0.9.0...0.10.0
diff --git a/Cargo.toml b/Cargo.toml
index 04ac35ed..df087fdf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -117,7 +117,7 @@ members = [
]
[workspace.package]
-version = "0.13.0-dev"
+version = "0.13.0"
authors = ["Héctor Ramón Jiménez <hector@hecrj.dev>"]
edition = "2021"
license = "MIT"
@@ -128,17 +128,17 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
rust-version = "1.80"
[workspace.dependencies]
-iced = { version = "0.13.0-dev", path = "." }
-iced_core = { version = "0.13.0-dev", path = "core" }
-iced_futures = { version = "0.13.0-dev", path = "futures" }
-iced_graphics = { version = "0.13.0-dev", path = "graphics" }
-iced_highlighter = { version = "0.13.0-dev", path = "highlighter" }
-iced_renderer = { version = "0.13.0-dev", path = "renderer" }
-iced_runtime = { version = "0.13.0-dev", path = "runtime" }
-iced_tiny_skia = { version = "0.13.0-dev", path = "tiny_skia" }
-iced_wgpu = { version = "0.13.0-dev", path = "wgpu" }
-iced_widget = { version = "0.13.0-dev", path = "widget" }
-iced_winit = { version = "0.13.0-dev", path = "winit" }
+iced = { version = "0.13.0", path = "." }
+iced_core = { version = "0.13.0", path = "core" }
+iced_futures = { version = "0.13.0", path = "futures" }
+iced_graphics = { version = "0.13.0", path = "graphics" }
+iced_highlighter = { version = "0.13.0", path = "highlighter" }
+iced_renderer = { version = "0.13.0", path = "renderer" }
+iced_runtime = { version = "0.13.0", path = "runtime" }
+iced_tiny_skia = { version = "0.13.0", path = "tiny_skia" }
+iced_wgpu = { version = "0.13.0", path = "wgpu" }
+iced_widget = { version = "0.13.0", path = "widget" }
+iced_winit = { version = "0.13.0", path = "winit" }
async-std = "1.0"
bitflags = "2.0"
diff --git a/core/src/widget.rs b/core/src/widget.rs
index c5beea54..9cfff83d 100644
--- a/core/src/widget.rs
+++ b/core/src/widget.rs
@@ -33,12 +33,12 @@ use crate::{Clipboard, Length, Rectangle, Shell, Size, Vector};
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
///
-/// [examples]: https://github.com/iced-rs/iced/tree/0.12/examples
-/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.12/examples/bezier_tool
-/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.12/examples/custom_widget
-/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.12/examples/geometry
+/// [examples]: https://github.com/iced-rs/iced/tree/0.13/examples
+/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.13/examples/bezier_tool
+/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.13/examples/custom_widget
+/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.13/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
-/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.12/wgpu
+/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.13/wgpu
pub trait Widget<Message, Theme, Renderer>
where
Renderer: crate::Renderer,
diff --git a/core/src/window/id.rs b/core/src/window/id.rs
index 31ea92f3..5d5a817e 100644
--- a/core/src/window/id.rs
+++ b/core/src/window/id.rs
@@ -1,11 +1,9 @@
+use std::fmt;
use std::hash::Hash;
-
use std::sync::atomic::{self, AtomicU64};
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
/// The id of the window.
-///
-/// Internally Iced reserves `window::Id::MAIN` for the first window spawned.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Id(u64);
static COUNT: AtomicU64 = AtomicU64::new(1);
@@ -16,3 +14,9 @@ impl Id {
Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed))
}
}
+
+impl fmt::Display for Id {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
diff --git a/examples/changelog/Cargo.toml b/examples/changelog/Cargo.toml
new file mode 100644
index 00000000..eb942235
--- /dev/null
+++ b/examples/changelog/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "changelog"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced.workspace = true
+iced.features = ["tokio", "markdown", "highlighter", "debug"]
+
+log.workspace = true
+thiserror.workspace = true
+tokio.features = ["fs", "process"]
+tokio.workspace = true
+
+serde = "1"
+webbrowser = "1"
+tracing-subscriber = "0.3"
+
+[dependencies.reqwest]
+version = "0.12"
+default-features = false
+features = ["json", "rustls-tls"]
diff --git a/examples/changelog/src/changelog.rs b/examples/changelog/src/changelog.rs
new file mode 100644
index 00000000..354f0bc8
--- /dev/null
+++ b/examples/changelog/src/changelog.rs
@@ -0,0 +1,386 @@
+use serde::Deserialize;
+use tokio::fs;
+use tokio::process;
+
+use std::collections::BTreeSet;
+use std::env;
+use std::fmt;
+use std::io;
+use std::sync::Arc;
+
+#[derive(Debug, Clone)]
+pub struct Changelog {
+ ids: Vec<u64>,
+ added: Vec<String>,
+ changed: Vec<String>,
+ fixed: Vec<String>,
+ removed: Vec<String>,
+ authors: Vec<String>,
+}
+
+impl Changelog {
+ pub fn new() -> Self {
+ Self {
+ ids: Vec::new(),
+ added: Vec::new(),
+ changed: Vec::new(),
+ fixed: Vec::new(),
+ removed: Vec::new(),
+ authors: Vec::new(),
+ }
+ }
+
+ pub async fn list() -> Result<(Self, Vec<Contribution>), Error> {
+ let mut changelog = Self::new();
+
+ {
+ let markdown = fs::read_to_string("CHANGELOG.md").await?;
+
+ if let Some(unreleased) = markdown.split("\n## ").nth(1) {
+ let sections = unreleased.split("\n\n");
+
+ for section in sections {
+ if section.starts_with("Many thanks to...") {
+ for author in section.lines().skip(1) {
+ let author = author.trim_start_matches("- @");
+
+ if author.is_empty() {
+ continue;
+ }
+
+ changelog.authors.push(author.to_owned());
+ }
+
+ continue;
+ }
+
+ let Some((_, rest)) = section.split_once("### ") else {
+ continue;
+ };
+
+ let Some((name, rest)) = rest.split_once("\n") else {
+ continue;
+ };
+
+ let category = match name {
+ "Added" => Category::Added,
+ "Fixed" => Category::Fixed,
+ "Changed" => Category::Changed,
+ "Removed" => Category::Removed,
+ _ => continue,
+ };
+
+ for entry in rest.lines() {
+ let Some((_, id)) = entry.split_once("[#") else {
+ continue;
+ };
+
+ let Some((id, _)) = id.split_once(']') else {
+ continue;
+ };
+
+ let Ok(id): Result<u64, _> = id.parse() else {
+ continue;
+ };
+
+ changelog.ids.push(id);
+
+ let target = match category {
+ Category::Added => &mut changelog.added,
+ Category::Changed => &mut changelog.changed,
+ Category::Fixed => &mut changelog.fixed,
+ Category::Removed => &mut changelog.removed,
+ };
+
+ target.push(entry.to_owned());
+ }
+ }
+ }
+ }
+
+ let mut candidates = Contribution::list().await?;
+
+ for reviewed_entry in changelog.entries() {
+ candidates.retain(|candidate| candidate.id != reviewed_entry);
+ }
+
+ Ok((changelog, candidates))
+ }
+
+ pub async fn save(self) -> Result<(), Error> {
+ let markdown = fs::read_to_string("CHANGELOG.md").await?;
+
+ let Some((header, rest)) = markdown.split_once("\n## ") else {
+ return Err(Error::InvalidFormat);
+ };
+
+ let Some((_unreleased, rest)) = rest.split_once("\n## ") else {
+ return Err(Error::InvalidFormat);
+ };
+
+ let unreleased = format!("\n## [Unreleased]\n{self}");
+
+ let rest = format!("\n## {rest}");
+
+ let changelog = [header, &unreleased, &rest].concat();
+ fs::write("CHANGELOG.md", changelog).await?;
+
+ Ok(())
+ }
+
+ pub fn len(&self) -> usize {
+ self.ids.len()
+ }
+
+ pub fn entries(&self) -> impl Iterator<Item = u64> + '_ {
+ self.ids.iter().copied()
+ }
+
+ pub fn push(&mut self, entry: Entry) {
+ self.ids.push(entry.id);
+
+ let item = format!(
+ "- {title}. [#{id}](https://github.com/iced-rs/iced/pull/{id})",
+ title = entry.title,
+ id = entry.id
+ );
+
+ let target = match entry.category {
+ Category::Added => &mut self.added,
+ Category::Changed => &mut self.changed,
+ Category::Fixed => &mut self.fixed,
+ Category::Removed => &mut self.removed,
+ };
+
+ target.push(item);
+
+ if entry.author != "hecrj" && !self.authors.contains(&entry.author) {
+ self.authors.push(entry.author);
+ self.authors.sort_by_key(|author| author.to_lowercase());
+ }
+ }
+}
+
+impl fmt::Display for Changelog {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fn section(category: Category, entries: &[String]) -> String {
+ if entries.is_empty() {
+ return String::new();
+ }
+
+ format!("### {category}\n{list}\n", list = entries.join("\n"))
+ }
+
+ fn thank_you<'a>(authors: impl IntoIterator<Item = &'a str>) -> String {
+ let mut list = String::new();
+
+ for author in authors {
+ list.push_str(&format!("- @{author}\n"));
+ }
+
+ format!("Many thanks to...\n{list}")
+ }
+
+ let changelog = [
+ section(Category::Added, &self.added),
+ section(Category::Changed, &self.changed),
+ section(Category::Fixed, &self.fixed),
+ section(Category::Removed, &self.removed),
+ thank_you(self.authors.iter().map(String::as_str)),
+ ]
+ .into_iter()
+ .filter(|section| !section.is_empty())
+ .collect::<Vec<String>>()
+ .join("\n");
+
+ f.write_str(&changelog)
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Entry {
+ pub id: u64,
+ pub title: String,
+ pub category: Category,
+ pub author: String,
+}
+
+impl Entry {
+ pub fn new(
+ title: &str,
+ category: Category,
+ pull_request: &PullRequest,
+ ) -> Option<Self> {
+ let title = title.strip_suffix(".").unwrap_or(title);
+
+ if title.is_empty() {
+ return None;
+ };
+
+ Some(Self {
+ id: pull_request.id,
+ title: title.to_owned(),
+ category,
+ author: pull_request.author.clone(),
+ })
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Category {
+ Added,
+ Changed,
+ Fixed,
+ Removed,
+}
+
+impl Category {
+ pub const ALL: &'static [Self] =
+ &[Self::Added, Self::Changed, Self::Fixed, Self::Removed];
+
+ pub fn guess(label: &str) -> Option<Self> {
+ Some(match label {
+ "feature" | "addition" => Self::Added,
+ "change" => Self::Changed,
+ "bug" | "fix" => Self::Fixed,
+ _ => None?,
+ })
+ }
+}
+
+impl fmt::Display for Category {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match self {
+ Category::Added => "Added",
+ Category::Changed => "Changed",
+ Category::Fixed => "Fixed",
+ Category::Removed => "Removed",
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Contribution {
+ pub id: u64,
+}
+
+impl Contribution {
+ pub async fn list() -> Result<Vec<Contribution>, Error> {
+ let output = process::Command::new("git")
+ .args([
+ "log",
+ "--oneline",
+ "--grep",
+ "#[0-9]*",
+ "origin/latest..HEAD",
+ ])
+ .output()
+ .await?;
+
+ let log = String::from_utf8_lossy(&output.stdout);
+
+ let mut contributions: Vec<_> = log
+ .lines()
+ .filter(|title| !title.is_empty())
+ .filter_map(|title| {
+ let (_, pull_request) = title.split_once("#")?;
+ let (pull_request, _) = pull_request.split_once([')', ' '])?;
+
+ Some(Contribution {
+ id: pull_request.parse().ok()?,
+ })
+ })
+ .collect();
+
+ let mut unique = BTreeSet::from_iter(contributions.clone());
+ contributions.retain_mut(|contribution| unique.remove(contribution));
+
+ Ok(contributions)
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct PullRequest {
+ pub id: u64,
+ pub title: String,
+ pub description: Option<String>,
+ pub labels: Vec<String>,
+ pub author: String,
+}
+
+impl PullRequest {
+ pub async fn fetch(contribution: Contribution) -> Result<Self, Error> {
+ let request = reqwest::Client::new()
+ .request(
+ reqwest::Method::GET,
+ format!(
+ "https://api.github.com/repos/iced-rs/iced/pulls/{}",
+ contribution.id
+ ),
+ )
+ .header("User-Agent", "iced changelog generator")
+ .header(
+ "Authorization",
+ format!(
+ "Bearer {}",
+ env::var("GITHUB_TOKEN")
+ .map_err(|_| Error::GitHubTokenNotFound)?
+ ),
+ );
+
+ #[derive(Deserialize)]
+ struct Schema {
+ title: String,
+ body: Option<String>,
+ user: User,
+ labels: Vec<Label>,
+ }
+
+ #[derive(Deserialize)]
+ struct User {
+ login: String,
+ }
+
+ #[derive(Deserialize)]
+ struct Label {
+ name: String,
+ }
+
+ let schema: Schema = request.send().await?.json().await?;
+
+ Ok(Self {
+ id: contribution.id,
+ title: schema.title,
+ description: schema.body,
+ labels: schema.labels.into_iter().map(|label| label.name).collect(),
+ author: schema.user.login,
+ })
+ }
+}
+
+#[derive(Debug, Clone, thiserror::Error)]
+pub enum Error {
+ #[error("io operation failed: {0}")]
+ IOFailed(Arc<io::Error>),
+
+ #[error("http request failed: {0}")]
+ RequestFailed(Arc<reqwest::Error>),
+
+ #[error("no GITHUB_TOKEN variable was set")]
+ GitHubTokenNotFound,
+
+ #[error("the changelog format is not valid")]
+ InvalidFormat,
+}
+
+impl From<io::Error> for Error {
+ fn from(error: io::Error) -> Self {
+ Error::IOFailed(Arc::new(error))
+ }
+}
+
+impl From<reqwest::Error> for Error {
+ fn from(error: reqwest::Error) -> Self {
+ Error::RequestFailed(Arc::new(error))
+ }
+}
diff --git a/examples/changelog/src/icon.rs b/examples/changelog/src/icon.rs
new file mode 100644
index 00000000..dd82e5b9
--- /dev/null
+++ b/examples/changelog/src/icon.rs
@@ -0,0 +1,10 @@
+use iced::widget::{text, Text};
+use iced::Font;
+
+pub const FONT_BYTES: &[u8] = include_bytes!("../fonts/changelog-icons.ttf");
+
+const FONT: Font = Font::with_name("changelog-icons");
+
+pub fn copy() -> Text<'static> {
+ text('\u{e800}').font(FONT)
+}
diff --git a/examples/changelog/src/main.rs b/examples/changelog/src/main.rs
new file mode 100644
index 00000000..f889e757
--- /dev/null
+++ b/examples/changelog/src/main.rs
@@ -0,0 +1,379 @@
+mod changelog;
+
+use crate::changelog::Changelog;
+
+use iced::font;
+use iced::widget::{
+ button, center, column, container, markdown, pick_list, progress_bar,
+ rich_text, row, scrollable, span, stack, text, text_input,
+};
+use iced::{Center, Element, Fill, FillPortion, Font, Task, Theme};
+
+pub fn main() -> iced::Result {
+ tracing_subscriber::fmt::init();
+
+ iced::application("Changelog Generator", Generator::update, Generator::view)
+ .theme(Generator::theme)
+ .run_with(Generator::new)
+}
+
+enum Generator {
+ Loading,
+ Reviewing {
+ changelog: Changelog,
+ pending: Vec<changelog::Contribution>,
+ state: State,
+ preview: Vec<markdown::Item>,
+ },
+ Done,
+}
+
+enum State {
+ Loading(changelog::Contribution),
+ Loaded {
+ pull_request: changelog::PullRequest,
+ description: Vec<markdown::Item>,
+ title: String,
+ category: changelog::Category,
+ },
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ ChangelogListed(
+ Result<(Changelog, Vec<changelog::Contribution>), changelog::Error>,
+ ),
+ PullRequestFetched(Result<changelog::PullRequest, changelog::Error>),
+ UrlClicked(markdown::Url),
+ TitleChanged(String),
+ CategorySelected(changelog::Category),
+ Next,
+ OpenPullRequest(u64),
+ ChangelogSaved(Result<(), changelog::Error>),
+ Quit,
+}
+
+impl Generator {
+ fn new() -> (Self, Task<Message>) {
+ (
+ Self::Loading,
+ Task::perform(Changelog::list(), Message::ChangelogListed),
+ )
+ }
+
+ fn update(&mut self, message: Message) -> Task<Message> {
+ match message {
+ Message::ChangelogListed(Ok((changelog, mut pending))) => {
+ if let Some(contribution) = pending.pop() {
+ let preview =
+ markdown::parse(&changelog.to_string()).collect();
+
+ *self = Self::Reviewing {
+ changelog,
+ pending,
+ state: State::Loading(contribution.clone()),
+ preview,
+ };
+
+ Task::perform(
+ changelog::PullRequest::fetch(contribution),
+ Message::PullRequestFetched,
+ )
+ } else {
+ *self = Self::Done;
+
+ Task::none()
+ }
+ }
+ Message::PullRequestFetched(Ok(pull_request)) => {
+ let Self::Reviewing { state, .. } = self else {
+ return Task::none();
+ };
+
+ let description = markdown::parse(
+ pull_request
+ .description
+ .as_deref()
+ .unwrap_or("*No description provided*"),
+ )
+ .collect();
+
+ *state = State::Loaded {
+ title: pull_request.title.clone(),
+ category: pull_request
+ .labels
+ .iter()
+ .map(String::as_str)
+ .filter_map(changelog::Category::guess)
+ .next()
+ .unwrap_or(changelog::Category::Added),
+ pull_request,
+ description,
+ };
+
+ Task::none()
+ }
+ Message::UrlClicked(url) => {
+ let _ = webbrowser::open(url.as_str());
+
+ Task::none()
+ }
+ Message::TitleChanged(new_title) => {
+ let Self::Reviewing { state, .. } = self else {
+ return Task::none();
+ };
+
+ let State::Loaded { title, .. } = state else {
+ return Task::none();
+ };
+
+ *title = new_title;
+
+ Task::none()
+ }
+ Message::CategorySelected(new_category) => {
+ let Self::Reviewing { state, .. } = self else {
+ return Task::none();
+ };
+
+ let State::Loaded { category, .. } = state else {
+ return Task::none();
+ };
+
+ *category = new_category;
+
+ Task::none()
+ }
+ Message::Next => {
+ let Self::Reviewing {
+ changelog,
+ pending,
+ state,
+ preview,
+ ..
+ } = self
+ else {
+ return Task::none();
+ };
+
+ let State::Loaded {
+ title,
+ category,
+ pull_request,
+ ..
+ } = state
+ else {
+ return Task::none();
+ };
+
+ if let Some(entry) =
+ changelog::Entry::new(title, *category, pull_request)
+ {
+ changelog.push(entry);
+
+ let save = Task::perform(
+ changelog.clone().save(),
+ Message::ChangelogSaved,
+ );
+
+ *preview =
+ markdown::parse(&changelog.to_string()).collect();
+
+ if let Some(contribution) = pending.pop() {
+ *state = State::Loading(contribution.clone());
+
+ Task::batch([
+ save,
+ Task::perform(
+ changelog::PullRequest::fetch(contribution),
+ Message::PullRequestFetched,
+ ),
+ ])
+ } else {
+ *self = Self::Done;
+ save
+ }
+ } else {
+ Task::none()
+ }
+ }
+ Message::OpenPullRequest(id) => {
+ let _ = webbrowser::open(&format!(
+ "https://github.com/iced-rs/iced/pull/{id}"
+ ));
+
+ Task::none()
+ }
+ Message::ChangelogSaved(Ok(())) => Task::none(),
+
+ Message::ChangelogListed(Err(error))
+ | Message::PullRequestFetched(Err(error))
+ | Message::ChangelogSaved(Err(error)) => {
+ log::error!("{error}");
+
+ Task::none()
+ }
+ Message::Quit => iced::exit(),
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ match self {
+ Self::Loading => center("Loading...").into(),
+ Self::Done => center(
+ column![
+ text("Changelog is up-to-date! 🎉")
+ .shaping(text::Shaping::Advanced),
+ button("Quit").on_press(Message::Quit),
+ ]
+ .spacing(10)
+ .align_x(Center),
+ )
+ .into(),
+ Self::Reviewing {
+ changelog,
+ pending,
+ state,
+ preview,
+ } => {
+ let progress = {
+ let total = pending.len() + changelog.len();
+
+ let bar = progress_bar(
+ 0.0..=1.0,
+ changelog.len() as f32 / total as f32,
+ )
+ .style(progress_bar::secondary);
+
+ let label = text!(
+ "{amount_reviewed} / {total}",
+ amount_reviewed = changelog.len()
+ )
+ .font(Font::MONOSPACE)
+ .size(12);
+
+ stack![bar, center(label)]
+ };
+
+ let form: Element<_> = match state {
+ State::Loading(contribution) => {
+ text!("Loading #{}...", contribution.id).into()
+ }
+ State::Loaded {
+ pull_request,
+ description,
+ title,
+ category,
+ } => {
+ let details = {
+ let title = rich_text![
+ span(&pull_request.title).size(24).link(
+ Message::OpenPullRequest(pull_request.id)
+ ),
+ span(format!(" by {}", pull_request.author))
+ .font(Font {
+ style: font::Style::Italic,
+ ..Font::default()
+ }),
+ ]
+ .font(Font::MONOSPACE);
+
+ let description = markdown::view(
+ description,
+ markdown::Settings::default(),
+ markdown::Style::from_palette(
+ self.theme().palette(),
+ ),
+ )
+ .map(Message::UrlClicked);
+
+ let labels =
+ row(pull_request.labels.iter().map(|label| {
+ container(
+ text(label)
+ .size(10)
+ .font(Font::MONOSPACE),
+ )
+ .padding(5)
+ .style(container::rounded_box)
+ .into()
+ }))
+ .spacing(10)
+ .wrap();
+
+ column![
+ title,
+ labels,
+ scrollable(description)
+ .spacing(10)
+ .width(Fill)
+ .height(Fill)
+ ]
+ .spacing(10)
+ };
+
+ let title = text_input(
+ "Type a changelog entry title...",
+ title,
+ )
+ .on_input(Message::TitleChanged);
+
+ let category = pick_list(
+ changelog::Category::ALL,
+ Some(category),
+ Message::CategorySelected,
+ );
+
+ let next = button("Next →")
+ .on_press(Message::Next)
+ .style(button::success);
+
+ column![
+ details,
+ row![title, category, next].spacing(10)
+ ]
+ .spacing(10)
+ .into()
+ }
+ };
+
+ let preview = if preview.is_empty() {
+ center(
+ container(
+ text("The changelog is empty... so far!").size(12),
+ )
+ .padding(10)
+ .style(container::rounded_box),
+ )
+ } else {
+ container(
+ scrollable(
+ markdown::view(
+ preview,
+ markdown::Settings::with_text_size(12),
+ markdown::Style::from_palette(
+ self.theme().palette(),
+ ),
+ )
+ .map(Message::UrlClicked),
+ )
+ .spacing(10),
+ )
+ .width(Fill)
+ .padding(10)
+ .style(container::rounded_box)
+ };
+
+ let review = column![container(form).height(Fill), progress]
+ .spacing(10)
+ .width(FillPortion(2));
+
+ row![review, preview].spacing(10).padding(10).into()
+ }
+ }
+ }
+
+ fn theme(&self) -> Theme {
+ Theme::TokyoNightStorm
+ }
+}
diff --git a/examples/custom_shader/Cargo.toml b/examples/custom_shader/Cargo.toml
index b602f98d..7f14b8c1 100644
--- a/examples/custom_shader/Cargo.toml
+++ b/examples/custom_shader/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
iced.workspace = true
-iced.features = ["debug", "advanced"]
+iced.features = ["debug", "image", "advanced"]
image.workspace = true
bytemuck.workspace = true
diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs
index ab09116e..b43a627a 100644
--- a/examples/multi_window/src/main.rs
+++ b/examples/multi_window/src/main.rs
@@ -25,7 +25,6 @@ struct Window {
scale_input: String,
current_scale: f64,
theme: Theme,
- input_id: iced::widget::text_input::Id,
}
#[derive(Debug, Clone)]
@@ -86,7 +85,7 @@ impl Example {
}
Message::WindowOpened(id) => {
let window = Window::new(self.windows.len() + 1);
- let focus_input = text_input::focus(window.input_id.clone());
+ let focus_input = text_input::focus(format!("input-{id}"));
self.windows.insert(id, window);
@@ -163,7 +162,6 @@ 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(),
}
}
@@ -182,7 +180,7 @@ impl Window {
text("Window title:"),
text_input("Window Title", &self.title)
.on_input(move |msg| { Message::TitleChanged(id, msg) })
- .id(self.input_id.clone())
+ .id(format!("input-{id}"))
];
let new_window_button =
diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml
index 3c62bfbc..0d72be86 100644
--- a/examples/todos/Cargo.toml
+++ b/examples/todos/Cargo.toml
@@ -9,7 +9,6 @@ publish = false
iced.workspace = true
iced.features = ["async-std", "debug"]
-once_cell.workspace = true
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.0", features = ["v4", "fast-rng", "serde"] }
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index a5f7b36a..25e3ead2 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -6,12 +6,9 @@ use iced::widget::{
use iced::window;
use iced::{Center, Element, Fill, Font, Subscription, Task as Command};
-use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
-static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
-
pub fn main() -> iced::Result {
#[cfg(not(target_arch = "wasm32"))]
tracing_subscriber::fmt::init();
@@ -85,7 +82,7 @@ impl Todos {
_ => {}
}
- text_input::focus(INPUT_ID.clone())
+ text_input::focus("new-task")
}
Todos::Loaded(state) => {
let mut saved = false;
@@ -198,7 +195,7 @@ impl Todos {
.align_x(Center);
let input = text_input("What needs to be done?", input_value)
- .id(INPUT_ID.clone())
+ .id("new-task")
.on_input(Message::InputChanged)
.on_submit(Message::CreateTask)
.padding(15)
diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs
index d2a0c3f8..946995d8 100644
--- a/futures/src/subscription.rs
+++ b/futures/src/subscription.rs
@@ -190,7 +190,7 @@ impl<T> Subscription<T> {
/// Check out the [`websocket`] example, which showcases this pattern to maintain a `WebSocket`
/// connection open.
///
- /// [`websocket`]: https://github.com/iced-rs/iced/tree/0.12/examples/websocket
+ /// [`websocket`]: https://github.com/iced-rs/iced/tree/0.13/examples/websocket
pub fn run<S>(builder: fn() -> S) -> Self
where
S: Stream<Item = T> + MaybeSend + 'static,
@@ -317,9 +317,9 @@ impl<T> std::fmt::Debug for Subscription<T> {
/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how
/// to listen to time.
///
-/// [examples]: https://github.com/iced-rs/iced/tree/0.12/examples
-/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.12/examples/download_progress
-/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.12/examples/stopwatch
+/// [examples]: https://github.com/iced-rs/iced/tree/0.13/examples
+/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.13/examples/download_progress
+/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.13/examples/stopwatch
pub trait Recipe {
/// The events that will be produced by a [`Subscription`] with this
/// [`Recipe`].
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index 7230fc73..ae6d1dce 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -4,7 +4,7 @@
//!
//! `iced_runtime` takes [`iced_core`] and builds a native runtime on top of it.
//!
-//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.12/core
+//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.13/core
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs
index 11ebb381..8dfc97a7 100644
--- a/runtime/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -19,7 +19,7 @@ use crate::overlay;
/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an
/// existing graphical application.
///
-/// [`integration`]: https://github.com/iced-rs/iced/tree/0.12/examples/integration
+/// [`integration`]: https://github.com/iced-rs/iced/tree/0.13/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Theme, Renderer> {
root: Element<'a, Message, Theme, Renderer>,
diff --git a/src/application.rs b/src/application.rs
index d0f77304..2ba764be 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -32,7 +32,9 @@
//! ```
use crate::program::{self, Program};
use crate::window;
-use crate::{Element, Font, Result, Settings, Size, Subscription, Task};
+use crate::{
+ Element, Executor, Font, Result, Settings, Size, Subscription, Task,
+};
use std::borrow::Cow;
@@ -376,6 +378,22 @@ impl<P: Program> Application<P> {
window: self.window,
}
}
+
+ /// Sets the executor of the [`Application`].
+ pub fn executor<E>(
+ self,
+ ) -> Application<
+ impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
+ >
+ where
+ E: Executor,
+ {
+ Application {
+ raw: program::with_executor::<P, E>(self.raw),
+ settings: self.settings,
+ window: self.window,
+ }
+ }
}
/// The title logic of some [`Application`].
diff --git a/src/daemon.rs b/src/daemon.rs
index 6a6ad133..81254bf9 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -2,7 +2,7 @@
use crate::application;
use crate::program::{self, Program};
use crate::window;
-use crate::{Element, Font, Result, Settings, Subscription, Task};
+use crate::{Element, Executor, Font, Result, Settings, Subscription, Task};
use std::borrow::Cow;
@@ -223,6 +223,21 @@ impl<P: Program> Daemon<P> {
settings: self.settings,
}
}
+
+ /// Sets the executor of the [`Daemon`].
+ pub fn executor<E>(
+ self,
+ ) -> Daemon<
+ impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
+ >
+ where
+ E: Executor,
+ {
+ Daemon {
+ raw: program::with_executor::<P, E>(self.raw),
+ settings: self.settings,
+ }
+ }
}
/// The title logic of some [`Daemon`].
diff --git a/src/program.rs b/src/program.rs
index 2b697fbe..94cb9a7d 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -550,6 +550,80 @@ pub fn with_scale_factor<P: Program>(
}
}
+pub fn with_executor<P: Program, E: Executor>(
+ program: P,
+) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
+ use std::marker::PhantomData;
+
+ struct WithExecutor<P, E> {
+ program: P,
+ executor: PhantomData<E>,
+ }
+
+ impl<P: Program, E> Program for WithExecutor<P, E>
+ where
+ E: Executor,
+ {
+ type State = P::State;
+ type Message = P::Message;
+ type Theme = P::Theme;
+ type Renderer = P::Renderer;
+ type Executor = E;
+
+ fn title(&self, state: &Self::State, window: window::Id) -> String {
+ self.program.title(state, window)
+ }
+
+ fn update(
+ &self,
+ state: &mut Self::State,
+ message: Self::Message,
+ ) -> Task<Self::Message> {
+ self.program.update(state, message)
+ }
+
+ fn view<'a>(
+ &self,
+ state: &'a Self::State,
+ window: window::Id,
+ ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
+ self.program.view(state, window)
+ }
+
+ fn subscription(
+ &self,
+ state: &Self::State,
+ ) -> Subscription<Self::Message> {
+ self.program.subscription(state)
+ }
+
+ fn theme(
+ &self,
+ state: &Self::State,
+ window: window::Id,
+ ) -> Self::Theme {
+ self.program.theme(state, window)
+ }
+
+ fn style(
+ &self,
+ state: &Self::State,
+ theme: &Self::Theme,
+ ) -> Appearance {
+ self.program.style(state, theme)
+ }
+
+ fn scale_factor(&self, state: &Self::State, window: window::Id) -> f64 {
+ self.program.scale_factor(state, window)
+ }
+ }
+
+ WithExecutor {
+ program,
+ executor: PhantomData::<E>,
+ }
+}
+
/// The renderer of some [`Program`].
pub trait Renderer: text::Renderer + compositor::Default {}
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index 710a5443..4473119d 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -6,7 +6,7 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.12/examples/pane_grid
+//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.13/examples/pane_grid
mod axis;
mod configuration;
mod content;
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index af6a3945..00a6b556 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -751,7 +751,7 @@ where
// TODO: Configurable speed/friction (?)
-movement * 60.0
}
- mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y),
+ mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y),
};
state.scroll(
@@ -760,13 +760,17 @@ where
content_bounds,
);
- if notify_scroll(
+ let has_scrolled = notify_scroll(
state,
&self.on_scroll,
bounds,
content_bounds,
shell,
- ) {
+ );
+
+ let in_transaction = state.last_scrolled.is_some();
+
+ if has_scrolled || in_transaction {
event::Status::Captured
} else {
event::Status::Ignored
@@ -1194,11 +1198,6 @@ fn notify_viewport<Message>(
return false;
}
- let Some(on_scroll) = on_scroll else {
- state.last_notified = None;
- return false;
- };
-
let viewport = Viewport {
offset_x: state.offset_x,
offset_y: state.offset_y,
@@ -1229,9 +1228,12 @@ fn notify_viewport<Message>(
}
}
- shell.publish(on_scroll(viewport));
state.last_notified = Some(viewport);
+ if let Some(on_scroll) = on_scroll {
+ shell.publish(on_scroll(viewport));
+ }
+
true
}
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index d5ede524..3032dd13 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -114,8 +114,8 @@ where
}
/// Sets the [`Id`] of the [`TextInput`].
- pub fn id(mut self, id: Id) -> Self {
- self.id = Some(id);
+ pub fn id(mut self, id: impl Into<Id>) -> Self {
+ self.id = Some(id.into());
self
}
@@ -1226,38 +1226,53 @@ impl From<Id> for widget::Id {
}
}
+impl From<&'static str> for Id {
+ fn from(id: &'static str) -> Self {
+ Self::new(id)
+ }
+}
+
+impl From<String> for Id {
+ fn from(id: String) -> Self {
+ Self::new(id)
+ }
+}
+
/// Produces a [`Task`] that focuses the [`TextInput`] with the given [`Id`].
-pub fn focus<T>(id: Id) -> Task<T> {
- task::effect(Action::widget(operation::focusable::focus(id.0)))
+pub fn focus<T>(id: impl Into<Id>) -> Task<T> {
+ task::effect(Action::widget(operation::focusable::focus(id.into().0)))
}
/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// end.
-pub fn move_cursor_to_end<T>(id: Id) -> Task<T> {
+pub fn move_cursor_to_end<T>(id: impl Into<Id>) -> Task<T> {
task::effect(Action::widget(operation::text_input::move_cursor_to_end(
- id.0,
+ id.into().0,
)))
}
/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// front.
-pub fn move_cursor_to_front<T>(id: Id) -> Task<T> {
+pub fn move_cursor_to_front<T>(id: impl Into<Id>) -> Task<T> {
task::effect(Action::widget(operation::text_input::move_cursor_to_front(
- id.0,
+ id.into().0,
)))
}
/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
/// provided position.
-pub fn move_cursor_to<T>(id: Id, position: usize) -> Task<T> {
+pub fn move_cursor_to<T>(id: impl Into<Id>, position: usize) -> Task<T> {
task::effect(Action::widget(operation::text_input::move_cursor_to(
- id.0, position,
+ id.into().0,
+ position,
)))
}
/// Produces a [`Task`] that selects all the content of the [`TextInput`] with the given [`Id`].
-pub fn select_all<T>(id: Id) -> Task<T> {
- task::effect(Action::widget(operation::text_input::select_all(id.0)))
+pub fn select_all<T>(id: impl Into<Id>) -> Task<T> {
+ task::effect(Action::widget(operation::text_input::select_all(
+ id.into().0,
+ )))
}
/// The state of a [`TextInput`].
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index 68f15b1a..43e1848b 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -1,7 +1,7 @@
//! Convert [`winit`] types into [`iced_runtime`] types, and viceversa.
//!
//! [`winit`]: https://github.com/rust-windowing/winit
-//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.12/runtime
+//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime
use crate::core::keyboard;
use crate::core::mouse;
use crate::core::touch;
diff --git a/winit/src/lib.rs b/winit/src/lib.rs
index 3c11b72a..ed2910d6 100644
--- a/winit/src/lib.rs
+++ b/winit/src/lib.rs
@@ -11,7 +11,7 @@
//! Additionally, a [`conversion`] module is available for users that decide to
//! implement a custom event loop.
//!
-//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.12/runtime
+//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime
//! [`winit`]: https://github.com/rust-windowing/winit
//! [`conversion`]: crate::conversion
#![doc(