diff options
-rw-r--r-- | CHANGELOG.md | 204 | ||||
-rw-r--r-- | Cargo.toml | 24 | ||||
-rw-r--r-- | core/src/widget.rs | 10 | ||||
-rw-r--r-- | core/src/window/id.rs | 12 | ||||
-rw-r--r-- | examples/changelog/Cargo.toml | 24 | ||||
-rw-r--r-- | examples/changelog/src/changelog.rs | 386 | ||||
-rw-r--r-- | examples/changelog/src/icon.rs | 10 | ||||
-rw-r--r-- | examples/changelog/src/main.rs | 379 | ||||
-rw-r--r-- | examples/custom_shader/Cargo.toml | 2 | ||||
-rw-r--r-- | examples/multi_window/src/main.rs | 6 | ||||
-rw-r--r-- | examples/todos/Cargo.toml | 1 | ||||
-rw-r--r-- | examples/todos/src/main.rs | 7 | ||||
-rw-r--r-- | futures/src/subscription.rs | 8 | ||||
-rw-r--r-- | runtime/src/lib.rs | 2 | ||||
-rw-r--r-- | runtime/src/user_interface.rs | 2 | ||||
-rw-r--r-- | src/application.rs | 20 | ||||
-rw-r--r-- | src/daemon.rs | 17 | ||||
-rw-r--r-- | src/program.rs | 74 | ||||
-rw-r--r-- | widget/src/pane_grid.rs | 2 | ||||
-rw-r--r-- | widget/src/scrollable.rs | 20 | ||||
-rw-r--r-- | widget/src/text_input.rs | 39 | ||||
-rw-r--r-- | winit/src/conversion.rs | 2 | ||||
-rw-r--r-- | winit/src/lib.rs | 2 |
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 @@ -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( |