Merge branch 'master' into feat/multi-window-support

This commit is contained in:
Héctor Ramón Jiménez 2023-11-29 22:28:31 +01:00
commit e09b4e24dd
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
331 changed files with 12085 additions and 3976 deletions

53
.cargo/config.toml Normal file
View file

@ -0,0 +1,53 @@
[alias]
lint = """
clippy --workspace --no-deps -- \
-D warnings \
-A clippy::type_complexity \
-D clippy::semicolon_if_nothing_returned \
-D clippy::trivially-copy-pass-by-ref \
-D clippy::default_trait_access \
-D clippy::match-wildcard-for-single-variants \
-D clippy::redundant-closure-for-method-calls \
-D clippy::filter_map_next \
-D clippy::manual_let_else \
-D clippy::unused_async \
-D clippy::from_over_into \
-D clippy::needless_borrow \
-D clippy::new_without_default \
-D clippy::useless_conversion
"""
nitpick = """
clippy --workspace --no-deps -- \
-D warnings \
-D clippy::pedantic \
-A clippy::type_complexity \
-A clippy::must_use_candidate \
-A clippy::return_self_not_must_use \
-A clippy::needless_pass_by_value \
-A clippy::cast_precision_loss \
-A clippy::cast_sign_loss \
-A clippy::cast_possible_truncation \
-A clippy::match_same_arms \
-A clippy::missing-errors-doc \
-A clippy::missing-panics-doc \
-A clippy::cast_lossless \
-A clippy::doc_markdown \
-A clippy::items_after_statements \
-A clippy::too_many_lines \
-A clippy::module_name_repetitions \
-A clippy::if_not_else \
-A clippy::redundant_else \
-A clippy::used_underscore_binding \
-A clippy::cast_possible_wrap \
-A clippy::unnecessary_wraps \
-A clippy::struct-excessive-bools \
-A clippy::float-cmp \
-A clippy::single_match_else \
-A clippy::unreadable_literal \
-A clippy::explicit_deref_methods \
-A clippy::map_unwrap_or \
-A clippy::unnested_or_patterns \
-A clippy::similar_names \
-A clippy::unused_self
"""

View file

@ -1,11 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: I have a question
url: https://github.com/iced-rs/iced/discussions/new?category=q-a
about: Open a discussion with a Q&A format.
url: https://discourse.iced.rs/c/learn/6
about: Ask and learn from others in the Discourse forum.
- name: I want to start a discussion
url: https://github.com/iced-rs/iced/discussions/new
about: Open a new discussion if you have any suggestions, ideas, feature requests, or simply want to show off something you've made.
url: https://discourse.iced.rs/c/request-feedback/7
about: Share your idea and gather feedback in the Discourse forum.
- name: I want to chat with other users of the library
url: https://discord.com/invite/3xZJ65GAhd
about: Join the Discord Server and get involved with the community!
about: Join the Discord server and get involved with the community!

3
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,3 @@
The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to reduce the likelihood of this!
Read the contributing guidelines for more details: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md

View file

@ -1,12 +1,30 @@
name: Audit
on: [push]
on:
push: {}
pull_request: {}
schedule:
- cron: '0 0 * * *'
jobs:
dependencies:
vulnerabilities:
runs-on: ubuntu-latest
steps:
- uses: hecrj/setup-rust-action@v1
- name: Install cargo-audit
run: cargo install cargo-audit
- uses: actions/checkout@master
- name: Audit dependencies
- name: Resolve dependencies
run: cargo update
- name: Audit vulnerabilities
run: cargo audit
artifacts:
runs-on: ubuntu-latest
steps:
- uses: hecrj/setup-rust-action@v1
- name: Install cargo-outdated
run: cargo install cargo-outdated
- uses: actions/checkout@master
- name: Delete `web-sys` dependency from `integration` example
run: sed -i '$d' examples/integration/Cargo.toml
- name: Find outdated dependencies
run: cargo outdated --workspace --exit-code 1

View file

@ -40,7 +40,6 @@ jobs:
- uses: actions/checkout@master
- name: Enable static CRT linkage
run: |
mkdir .cargo
echo '[target.x86_64-pc-windows-msvc]' >> .cargo/config
echo 'rustflags = ["-Ctarget-feature=+crt-static"]' >> .cargo/config
- name: Run the application without starting the shell

29
.github/workflows/check.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: Check
on: [push, pull_request]
jobs:
widget:
runs-on: ubuntu-latest
steps:
- uses: hecrj/setup-rust-action@v1
- uses: actions/checkout@master
- name: Check standalone `iced_widget` crate
run: cargo check --package iced_widget --features image,svg,canvas
wasm:
runs-on: ubuntu-latest
env:
RUSTFLAGS: --cfg=web_sys_unstable_apis
steps:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
targets: wasm32-unknown-unknown
- uses: actions/checkout@master
- name: Run checks
run: cargo check --package iced --target wasm32-unknown-unknown
- name: Check compilation of `tour` example
run: cargo build --package tour --target wasm32-unknown-unknown
- name: Check compilation of `todos` example
run: cargo build --package todos --target wasm32-unknown-unknown
- name: Check compilation of `integration` example
run: cargo build --package integration --target wasm32-unknown-unknown

View file

@ -1,8 +1,5 @@
name: Document
on:
push:
branches:
- master
on: [push, pull_request]
jobs:
all:
runs-on: ubuntu-20.04
@ -18,6 +15,7 @@ jobs:
RUSTDOCFLAGS="--cfg docsrs" \
cargo doc --no-deps --all-features \
-p iced_core \
-p iced_highlighter \
-p iced_style \
-p iced_futures \
-p iced_runtime \
@ -31,6 +29,7 @@ jobs:
- name: Write CNAME file
run: echo 'docs.iced.rs' > ./target/doc/CNAME
- name: Publish documentation
if: github.ref == 'refs/heads/master'
uses: peaceiris/actions-gh-pages@v3
with:
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}

View file

@ -2,11 +2,11 @@ name: Lint
on: [push, pull_request]
jobs:
all:
runs-on: ubuntu-latest
runs-on: macOS-latest
steps:
- uses: hecrj/setup-rust-action@v1
with:
components: clippy
- uses: actions/checkout@master
- name: Check lints
run: cargo clippy --workspace --all-features --all-targets --no-deps -- -D warnings
run: cargo lint

View file

@ -1,8 +1,10 @@
name: Test
on: [push, pull_request]
jobs:
native:
all:
runs-on: ${{ matrix.os }}
env:
RUSTFLAGS: --deny warnings
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
@ -17,25 +19,8 @@ jobs:
run: |
export DEBIAN_FRONTED=noninteractive
sudo apt-get -qq update
sudo apt-get install -y libxkbcommon-dev
sudo apt-get install -y libxkbcommon-dev libgtk-3-dev
- name: Run tests
run: |
cargo test --verbose --workspace
cargo test --verbose --workspace --all-features
web:
runs-on: ubuntu-latest
steps:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
targets: wasm32-unknown-unknown
- uses: actions/checkout@master
- name: Run checks
run: cargo check --package iced --target wasm32-unknown-unknown
- name: Check compilation of `tour` example
run: cargo build --package tour --target wasm32-unknown-unknown
- name: Check compilation of `todos` example
run: cargo build --package todos --target wasm32-unknown-unknown
- name: Check compilation of `integration` example
run: cargo build --package integration --target wasm32-unknown-unknown

1
.gitignore vendored
View file

@ -2,6 +2,5 @@ target/
pkg/
**/*.rs.bk
Cargo.lock
.cargo/
dist/
traces/

View file

@ -5,6 +5,135 @@ 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]
### Added
- Explicit text caching. [#2058](https://github.com/iced-rs/iced/pull/2058)
- `Theme::Custom::with_fn` for custom extended palette generation. [#2067](https://github.com/iced-rs/iced/pull/2067)
### Changed
- Updated `wgpu` to `0.17`. [#2065](https://github.com/iced-rs/iced/pull/2065)
- Changed `Button::style` to take an `impl Into<...>` for consistency. [#2046](https://github.com/iced-rs/iced/pull/2046)
### Fixed
- Missing `width` attribute in `styling` example. [#2062](https://github.com/iced-rs/iced/pull/2062)
Many thanks to...
- @akshayr-mecha
- @dtzxporter
## [0.10.0] - 2023-07-28
### Added
- Text shaping, font fallback, and `iced_wgpu` overhaul. [#1697](https://github.com/iced-rs/iced/pull/1697)
- Software renderer, runtime renderer fallback, and core consolidation. [#1748](https://github.com/iced-rs/iced/pull/1748)
- Incremental rendering for `iced_tiny_skia`. [#1811](https://github.com/iced-rs/iced/pull/1811)
- Configurable `LineHeight` support for text widgets. [#1828](https://github.com/iced-rs/iced/pull/1828)
- `text::Shaping` strategy selection. [#1822](https://github.com/iced-rs/iced/pull/1822)
- Subpixel glyph positioning and layout linearity. [#1921](https://github.com/iced-rs/iced/pull/1921)
- Background gradients. [#1846](https://github.com/iced-rs/iced/pull/1846)
- Offscreen rendering and screenshots. [#1845](https://github.com/iced-rs/iced/pull/1845)
- Nested overlays. [#1719](https://github.com/iced-rs/iced/pull/1719)
- Cursor availability. [#1904](https://github.com/iced-rs/iced/pull/1904)
- Backend-specific primitives. [#1932](https://github.com/iced-rs/iced/pull/1932)
- `ComboBox` widget. [#1954](https://github.com/iced-rs/iced/pull/1954)
- `web-colors` feature flag to enable "sRGB linear" blending. [#1888](https://github.com/iced-rs/iced/pull/1888)
- `PaneGrid` logic to split panes by drag & drop. [#1856](https://github.com/iced-rs/iced/pull/1856)
- `PaneGrid` logic to drag & drop panes to the edges. [#1865](https://github.com/iced-rs/iced/pull/1865)
- Type-safe `Scrollable` direction. [#1878](https://github.com/iced-rs/iced/pull/1878)
- `Scrollable` alignment. [#1912](https://github.com/iced-rs/iced/pull/1912)
- Helpers to change viewport alignment of a `Scrollable`. [#1953](https://github.com/iced-rs/iced/pull/1953)
- `scroll_to` widget operation. [#1796](https://github.com/iced-rs/iced/pull/1796)
- `scroll_to` helper. [#1804](https://github.com/iced-rs/iced/pull/1804)
- `visible_bounds` widget operation for `Container`. [#1971](https://github.com/iced-rs/iced/pull/1971)
- Command to fetch window size. [#1927](https://github.com/iced-rs/iced/pull/1927)
- Conversion support from `Fn` trait to custom theme. [#1861](https://github.com/iced-rs/iced/pull/1861)
- Configurable border radii on relevant widgets. [#1869](https://github.com/iced-rs/iced/pull/1869)
- `border_radius` styling to `Slider` rail. [#1892](https://github.com/iced-rs/iced/pull/1892)
- `application_id` in `PlatformSpecific` settings for Linux. [#1963](https://github.com/iced-rs/iced/pull/1963)
- Aliased entries in `text::Cache`. [#1934](https://github.com/iced-rs/iced/pull/1934)
- Text cache modes. [#1938](https://github.com/iced-rs/iced/pull/1938)
- `operate` method for `program::State`. [#1913](https://github.com/iced-rs/iced/pull/1913)
- `Viewport` argument to `Widget::on_event`. [#1956](https://github.com/iced-rs/iced/pull/1956)
- Nix instructions to `DEPENDENCIES.md`. [#1859](https://github.com/iced-rs/iced/pull/1859)
- Loading spinners example. [#1902](https://github.com/iced-rs/iced/pull/1902)
- Workflow that verifies `CHANGELOG` is always up-to-date. [#1970](https://github.com/iced-rs/iced/pull/1970)
- Outdated mentions of `iced_native` in `README`. [#1979](https://github.com/iced-rs/iced/pull/1979)
### Changed
- Updated `wgpu` to `0.16`. [#1807](https://github.com/iced-rs/iced/pull/1807)
- Updated `glam` to `0.24`. [#1840](https://github.com/iced-rs/iced/pull/1840)
- Updated `winit` to `0.28`. [#1738](https://github.com/iced-rs/iced/pull/1738)
- Updated `palette` to `0.7`. [#1875](https://github.com/iced-rs/iced/pull/1875)
- Updated `ouroboros` to `0.17`. [#1925](https://github.com/iced-rs/iced/pull/1925)
- Updated `resvg` to `0.35` and `tiny-skia` to `0.10`. [#1907](https://github.com/iced-rs/iced/pull/1907)
- Changed `mouse::Button::Other` to take `u16` instead of `u8`. [#1797](https://github.com/iced-rs/iced/pull/1797)
- Changed `subscription::channel` to take a `FnOnce` non-`Sync` closure. [#1917](https://github.com/iced-rs/iced/pull/1917)
- Removed `Copy` requirement for text `StyleSheet::Style`. [#1814](https://github.com/iced-rs/iced/pull/1814)
- Removed `min_width` of 1 from scrollbar & scroller for `Scrollable`. [#1844](https://github.com/iced-rs/iced/pull/1844)
- Used `Widget::overlay` for `Tooltip`. [#1692](https://github.com/iced-rs/iced/pull/1692)
### Fixed
- `Responsive` layout not invalidated when shell layout is invalidated. [#1799](https://github.com/iced-rs/iced/pull/1799)
- `Responsive` layout not invalidated when size changes without a `view` call. [#1890](https://github.com/iced-rs/iced/pull/1890)
- Broken link in `ROADMAP.md`. [#1815](https://github.com/iced-rs/iced/pull/1815)
- `bounds` of selected option background in `Menu`. [#1831](https://github.com/iced-rs/iced/pull/1831)
- Border radius logic in `iced_tiny_skia`. [#1842](https://github.com/iced-rs/iced/pull/1842)
- `Svg` filtered color not premultiplied. [#1841](https://github.com/iced-rs/iced/pull/1841)
- Race condition when growing an `image::Atlas`. [#1847](https://github.com/iced-rs/iced/pull/1847)
- Clearing damaged surface with background color in `iced_tiny_skia`. [#1854](https://github.com/iced-rs/iced/pull/1854)
- Private gradient pack logic for `iced_graphics::Gradient`. [#1871](https://github.com/iced-rs/iced/pull/1871)
- Unordered quads of different background types. [#1873](https://github.com/iced-rs/iced/pull/1873)
- Panic in `glyphon` when glyphs are missing. [#1883](https://github.com/iced-rs/iced/pull/1883)
- Empty scissor rectangle in `iced_wgpu::triangle` pipeline. [#1893](https://github.com/iced-rs/iced/pull/1893)
- `Scrollable` scrolling when mouse not over it. [#1910](https://github.com/iced-rs/iced/pull/1910)
- `translation` in `layout` of `Nested` overlay. [#1924](https://github.com/iced-rs/iced/pull/1924)
- Build when using vendored dependencies. [#1928](https://github.com/iced-rs/iced/pull/1928)
- Minor grammar mistake. [#1931](https://github.com/iced-rs/iced/pull/1931)
- Quad rendering including border only inside of the bounds. [#1843](https://github.com/iced-rs/iced/pull/1843)
- Redraw requests not being forwarded for `Component` overlays. [#1949](https://github.com/iced-rs/iced/pull/1949)
- Blinking input cursor when window loses focus. [#1955](https://github.com/iced-rs/iced/pull/1955)
- `BorderRadius` not exposed in root crate. [#1972](https://github.com/iced-rs/iced/pull/1972)
- Outdated `ROADMAP`. [#1958](https://github.com/iced-rs/iced/pull/1958)
### Patched
- Keybinds to cycle `ComboBox` options. [#1991](https://github.com/iced-rs/iced/pull/1991)
- `Tooltip` overlay position inside `Scrollable`. [#1978](https://github.com/iced-rs/iced/pull/1978)
- `iced_wgpu` freezing on empty layers. [#1996](https://github.com/iced-rs/iced/pull/1996)
- `image::Viewer` reacting to any scroll event. [#1998](https://github.com/iced-rs/iced/pull/1998)
- `TextInput` pasting text when `Alt` key is pressed. [#2006](https://github.com/iced-rs/iced/pull/2006)
- Broken link to old `iced_native` crate in `README`. [#2024](https://github.com/iced-rs/iced/pull/2024)
- `Rectangle::contains` being non-exclusive. [#2017](https://github.com/iced-rs/iced/pull/2017)
- Documentation for `Arc` and `arc::Elliptical`. [#2008](https://github.com/iced-rs/iced/pull/2008)
Many thanks to...
- @a1phyr
- @alec-deason
- @AustinMReppert
- @bbb651
- @bungoboingo
- @casperstorm
- @clarkmoody
- @Davidster
- @Drakulix
- @genusistimelord
- @GyulyVGC
- @ids1024
- @jhff
- @JonathanLindsey
- @kr105
- @marienz
- @malramsay64
- @nicksenger
- @nicoburns
- @NyxAlexandra
- @Redhawk18
- @RGBCube
- @rs017991
- @tarkah
- @thunderstorm010
- @ua-kxie
- @wash2
- @wiiznokes
## [0.9.0] - 2023-04-13
### Added
@ -467,7 +596,8 @@ Many thanks to...
### Added
- First release! :tada:
[Unreleased]: https://github.com/iced-rs/iced/compare/0.9.0...HEAD
[Unreleased]: https://github.com/iced-rs/iced/compare/0.10.0...HEAD
[0.10.0]: https://github.com/iced-rs/iced/compare/0.9.0...0.10.0
[0.9.0]: https://github.com/iced-rs/iced/compare/0.8.0...0.9.0
[0.8.0]: https://github.com/iced-rs/iced/compare/0.7.0...0.8.0
[0.7.0]: https://github.com/iced-rs/iced/compare/0.6.0...0.7.0

View file

@ -2,32 +2,21 @@
Thank you for considering contributing to Iced! Feel free to read [the ecosystem overview] and [the roadmap] to get an idea of the current state of the library.
The main advice for new contributors is to share your ideas with the community. Introduce yourself over our [Discord server] or [start a discussion in an issue](https://github.com/iced-rs/iced/issues) explaining what you have in mind (do not be afraid of duplicated issues!). If you want to talk directly to me (@hecrj), you can also find me on Discord (`lone_scientist#9554`).
The core team is busy and does not have time to mentor nor babysit new contributors. If a member of the core team thinks that reviewing and understanding your work will take more time and effort than writing it from scratch by themselves, your contribution will be dismissed. It is your responsibility to communicate and figure out how to reduce the likelihood of this!
This is a very important step. It helps to coordinate work, get on the same page, and start building trust. Please, do not skip it! Remember that [Code is the Easy Part] and also [The Hard Parts of Open Source]!
The general advice for new contributors is to share your ideas with the community. You can share your ideas and gather feedback in [our Discourse forum]. This is a very important step. It helps to coordinate work, get on the same page, and start building trust. Remember that [Code is the Easy Part] and also [The Hard Parts of Open Source]!
Provided you get in touch first, all kinds of contributions are welcome! Here are a few interesting ideas:
- New widgets: toggle, table, grid, color picker, video...
- New renderers: `iced_piet` (already in the works!), `iced_skia`, `iced_raqote`, `iced_pathfinder`...
- New shells: `iced_sdl` could be useful for gamedev!
- Better style generation for `iced_web`
- Optimizations for `iced_wgpu`: tiling, incremental rendering...
- Alternative to [`wgpu_glyph`] for proper (shaped), efficient text rendering
- Time travelling debugger built on top of Iced itself
- Testing library
- Cool website to serve on https://iced.rs
Once you have started a channel of communication, you must wait until someone from the core team chimes in. If the core team is busy, this can take a long time (maybe months!). Your idea may need a bunch of iteration, or it may turn into something completely different, or it may be completely discarded! You will have to be patient and humble. Remember that open-source is a gift.
Besides directly writing code, there are many other different ways you can contribute. To name a few:
- Writing tutorials or blog posts
- Improving the documentation
- Submitting bug reports and use cases
- Sharing, discussing, researching and exploring new ideas
- Sharing, discussing, researching and exploring new ideas or crates
[the ecosystem overview]: ECOSYSTEM.md
[the roadmap]: ROADMAP.md
[Discord server]: https://discord.gg/3xZJ65GAhd
[our Discourse forum]: https://discourse.iced.rs/
[Code is the Easy Part]: https://youtu.be/DSjbTC-hvqQ?t=1138
[The Hard Parts of Open Source]: https://www.youtube.com/watch?v=o_4EX4dPppA
[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph

View file

@ -1,22 +1,28 @@
[package]
name = "iced"
version = "0.9.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A cross-platform GUI library inspired by Elm"
license = "MIT"
repository = "https://github.com/iced-rs/iced"
documentation = "https://docs.rs/iced"
readme = "README.md"
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"]
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[badges]
maintenance = { status = "actively-developed" }
[features]
default = ["wgpu"]
# Enable the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu"]
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
# Enables the `Image` widget
image = ["iced_widget/image", "image_rs"]
image = ["iced_widget/image", "dep:image"]
# Enables the `Svg` widget
svg = ["iced_widget/svg"]
# Enables the `Canvas` widget
@ -39,45 +45,30 @@ palette = ["iced_core/palette"]
system = ["iced_winit/system"]
# Enables broken "sRGB linear" blending to reproduce color management of the Web
web-colors = ["iced_renderer/web-colors"]
# Enables the WebGL backend, replacing WebGPU
webgl = ["iced_renderer/webgl"]
# Enables the syntax `highlighter` module
highlighter = ["iced_highlighter"]
# Enables the advanced module
advanced = []
# Enables experimental multi-window support.
multi-window = ["iced_winit/multi-window"]
[badges]
maintenance = { status = "actively-developed" }
[workspace]
members = [
"core",
"futures",
"graphics",
"runtime",
"renderer",
"style",
"tiny_skia",
"wgpu",
"widget",
"winit",
"examples/*",
]
[dependencies]
iced_core = { version = "0.9", path = "core" }
iced_futures = { version = "0.6", path = "futures" }
iced_renderer = { version = "0.1", path = "renderer" }
iced_widget = { version = "0.1", path = "widget" }
iced_winit = { version = "0.9", path = "winit", features = ["application"] }
thiserror = "1"
iced_core.workspace = true
iced_futures.workspace = true
iced_renderer.workspace = true
iced_widget.workspace = true
iced_winit.features = ["application"]
iced_winit.workspace = true
[dependencies.image_rs]
version = "0.24"
package = "image"
optional = true
iced_highlighter.workspace = true
iced_highlighter.optional = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
features = ["image", "svg", "canvas", "qr_code"]
thiserror.workspace = true
image.workspace = true
image.optional = true
[profile.release-opt]
inherits = "release"
@ -88,3 +79,85 @@ incremental = false
opt-level = 3
overflow-checks = false
strip = "debuginfo"
[workspace]
members = [
"core",
"futures",
"graphics",
"highlighter",
"renderer",
"runtime",
"style",
"tiny_skia",
"wgpu",
"widget",
"winit",
"examples/*",
]
[workspace.package]
version = "0.12.0"
authors = ["Héctor Ramón Jiménez <hector@hecrj.dev>"]
edition = "2021"
license = "MIT"
repository = "https://github.com/iced-rs/iced"
homepage = "https://iced.rs"
categories = ["gui"]
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
[workspace.dependencies]
iced = { version = "0.12", path = "." }
iced_core = { version = "0.12", path = "core" }
iced_futures = { version = "0.12", path = "futures" }
iced_graphics = { version = "0.12", path = "graphics" }
iced_highlighter = { version = "0.12", path = "highlighter" }
iced_renderer = { version = "0.12", path = "renderer" }
iced_runtime = { version = "0.12", path = "runtime" }
iced_style = { version = "0.12", path = "style" }
iced_tiny_skia = { version = "0.12", path = "tiny_skia" }
iced_wgpu = { version = "0.12", path = "wgpu" }
iced_widget = { version = "0.12", path = "widget" }
iced_winit = { version = "0.12", path = "winit" }
async-std = "1.0"
bitflags = "1.0"
bytemuck = { version = "1.0", features = ["derive"] }
cosmic-text = "0.10"
futures = "0.3"
glam = "0.24"
glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "2caa9fc5e5923c1d827d177c3619cab7e9885b85" }
guillotiere = "0.6"
half = "2.2"
image = "0.24"
instant = "0.1"
kamadak-exif = "0.5"
kurbo = "0.9"
log = "0.4"
lyon = "1.0"
lyon_path = "1.0"
num-traits = "0.2"
once_cell = "1.0"
ouroboros = "0.17"
palette = "0.7"
qrcode = { version = "0.12", default-features = false }
raw-window-handle = "0.5"
resvg = "0.36"
rustc-hash = "1.0"
smol = "1.0"
softbuffer = "0.2"
syntect = "5.1"
sysinfo = "0.28"
thiserror = "1.0"
tiny-skia = "0.11"
tokio = "1.0"
tracing = "0.1"
xxhash-rust = { version = "0.8", features = ["xxh3"] }
unicode-segmentation = "1.0"
wasm-bindgen-futures = "0.4"
wasm-timer = "0.2"
web-sys = "0.3"
wgpu = "0.18"
winapi = "0.3"
window_clipboard = "0.3"
winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e", default-features = false }

View file

@ -9,16 +9,17 @@
[![License](https://img.shields.io/crates/l/iced.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE)
[![Downloads](https://img.shields.io/crates/d/iced.svg)](https://crates.io/crates/iced)
[![Test Status](https://img.shields.io/github/actions/workflow/status/iced-rs/iced/test.yml?branch=master&event=push&label=test)](https://github.com/iced-rs/iced/actions)
[![Discourse](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscourse.iced.rs%2Fsite%2Fstatistics.json&query=%24.users_count&suffix=%20users&label=discourse&color=5e7ce2)](https://discourse.iced.rs/)
[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd)
A cross-platform GUI library for Rust focused on simplicity and type-safety.
Inspired by [Elm].
<a href="https://gfycat.com/littlesanehalicore">
<img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" width="275px">
<a href="https://iced.rs/examples/todos.mp4">
<img src="https://iced.rs/examples/todos.gif" width="275px">
</a>
<a href="https://gfycat.com/politeadorableiberianmole">
<img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif" width="273px">
<a href="https://iced.rs/examples/tour.mp4">
<img src="https://iced.rs/examples/tour.gif" width="273px">
</a>
</div>
@ -46,11 +47,11 @@ __Iced is currently experimental software.__ [Take a look at the roadmap],
[Cross-platform support]: https://raw.githubusercontent.com/iced-rs/iced/master/docs/images/todos_desktop.jpg
[the Web]: https://github.com/iced-rs/iced_web
[text inputs]: https://gfycat.com/alertcalmcrow-rust-gui
[scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui
[Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee
[text inputs]: https://iced.rs/examples/text_input.mp4
[scrollables]: https://iced.rs/examples/scrollable.mp4
[Debug overlay with performance metrics]: https://iced.rs/examples/debug.mp4
[Modular ecosystem]: ECOSYSTEM.md
[renderer-agnostic native runtime]: native/
[renderer-agnostic native runtime]: runtime/
[`wgpu`]: https://github.com/gfx-rs/wgpu
[`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
[`iced_wgpu`]: wgpu/
@ -68,7 +69,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap],
Add `iced` as a dependency in your `Cargo.toml`:
```toml
iced = "0.9"
iced = "0.10"
```
If your project is using a Rust edition older than 2021, then you will need to
@ -201,10 +202,8 @@ end-user-oriented GUI library, while keeping [the ecosystem] modular:
Contributions are greatly appreciated! If you want to contribute, please
read our [contributing guidelines] for more details.
Feedback is also welcome! You can open an issue or, if you want to talk,
come chat to our [Discord server]. Moreover, you can find me (and a bunch of
awesome folks) over the `#games-and-graphics` and `#gui-and-ui` channels in
the [Rust Community Discord]. I go by `lone_scientist#9554` there.
Feedback is also welcome! You can create a new topic in [our Discourse forum] or
come chat to [our Discord server].
## Sponsors
@ -217,7 +216,7 @@ The development of Iced is sponsored by the [Cryptowatch] team at [Kraken.com]
[The Elm Architecture]: https://guide.elm-lang.org/architecture/
[the current issues]: https://github.com/iced-rs/iced/issues
[contributing guidelines]: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md
[Discord server]: https://discord.gg/3xZJ65GAhd
[Rust Community Discord]: https://bit.ly/rust-community
[our Discourse forum]: https://discourse.iced.rs/
[our Discord server]: https://discord.gg/3xZJ65GAhd
[Cryptowatch]: https://cryptowat.ch/charts
[Kraken.com]: https://kraken.com/

View file

@ -1,120 +1,6 @@
# Roadmap
This document describes the current state of Iced and some of the most important next steps we should take before it can become a production-ready GUI library. This list keeps the short term new features in sight in order to coordinate work and discussion. Therefore, it is not meant to be exhaustive.
We have [a detailed graphical roadmap now](https://whimsical.com/roadmap-iced-7vhq6R35Lp3TmYH4WeYwLM)!
Before diving into the roadmap, check out [the ecosystem overview] to get an idea of the current state of the library.
[the ecosystem overview]: ECOSYSTEM.md
## Next steps
Most of the work related to these features needs to happen in the __native__ path of the ecosystem, as the web already supports many of them.
Once a step is completed, it is collapsed and added to this list:
* [x] Scrollables / Clippables ([#24])
* [x] Text input widget ([#25])
* [x] TodoMVC example ([#26])
* [x] Async actions ([#28])
* [x] Custom layout engine ([#52])
* [x] Event subscriptions ([#122])
* [x] Custom styling ([#146])
* [x] Canvas for 2D graphics ([#193])
* [x] Basic overlay support ([#444])
* [x] Animations [#31]
[#24]: https://github.com/iced-rs/iced/issues/24
[#25]: https://github.com/iced-rs/iced/issues/25
[#26]: https://github.com/iced-rs/iced/issues/26
[#28]: https://github.com/iced-rs/iced/issues/28
[#52]: https://github.com/iced-rs/iced/pull/52
[#122]: https://github.com/iced-rs/iced/pull/122
[#146]: https://github.com/iced-rs/iced/pull/146
[#193]: https://github.com/iced-rs/iced/pull/193
[#444]: https://github.com/iced-rs/iced/pull/444
[#31]: https://github.com/iced-rs/iced/issues/31
### Multi-window support ([#27])
Open and control multiple windows at runtime.
I think this could be achieved by implementing an additional trait in `iced_winit` similar to `Application` but with a slightly different `view` method, allowing users to control what is shown in each window.
This approach should also allow us to perform custom optimizations for this particular use case.
[#27]: https://github.com/iced-rs/iced/issues/27
### Canvas widget for 3D graphics (~~[#32]~~ [#343])
A widget to draw freely in 3D. It could be used to draw charts, implement a Paint clone, a CAD application, etc.
As a first approach, we could expose the underlying renderer directly here, and couple this widget with it ([`wgpu`] for now). Once [`wgpu`] gets WebGL or WebGPU support, this widget will be able to run on the web too. The renderer primitive could be a simple texture that the widget draws to.
In the long run, we could expose a renderer-agnostic abstraction to perform the drawing.
[#32]: https://github.com/iced-rs/iced/issues/32
[#343]: https://github.com/iced-rs/iced/issues/343
### Text shaping and font fallback ([#33])
[`wgpu_glyph`] uses [`glyph_brush`], which in turn uses [`rusttype`]. While the current implementation is able to layout text quite nicely, it does not perform any [text shaping].
[Text shaping] with font fallback is a necessary feature for any serious GUI toolkit. It unlocks support to truly localize your application, supporting many different scripts.
The only available library that does a great job at shaping is [HarfBuzz], which is implemented in C++. [`skribo`] seems to be a nice [HarfBuzz] wrapper for Rust.
This feature will probably imply rewriting [`wgpu_glyph`] entirely, as caching will be more complicated and the API will probably need to ask for more data.
[#33]: https://github.com/iced-rs/iced/issues/33
[`rusttype`]: https://github.com/redox-os/rusttype
[text shaping]: https://en.wikipedia.org/wiki/Complex_text_layout
[HarfBuzz]: https://github.com/harfbuzz/harfbuzz
[`skribo`]: https://github.com/linebender/skribo
### Grid layout and text layout ([#34])
Currently, `iced_native` only supports flexbox items. For instance, it is not possible to create a grid of items or make text float around an image.
We will need to enhance the layouting engine to support different strategies and improve the way we measure text to lay it out in a more flexible way.
[#34]: https://github.com/iced-rs/iced/issues/34
## Ideas that may be worth exploring
### Reuse existing 2D renderers
While I believe [`wgpu`] has a great future ahead of it, implementing `iced_wgpu` and making it performant will definitely be a challenge.
We should keep an eye on existing 2D graphic libraries, like [`piet`] or [`pathfinder`], and give them a try once/if they mature a bit more.
The good news here is that most of Iced is renderer-agnostic, so changing the rendering strategy, if we deem it worth it, should be really easy. Also, a 2D graphics library will expose a higher-level API than [`wgpu`], so implementing a new renderer on top of it should be fairly straightforward.
[`piet`]: https://github.com/linebender/piet
[`pathfinder`]: https://github.com/servo/pathfinder
### Remove explicit state handling and lifetimes
Currently, `iced_native` forces users to provide the local state of each widget. While this could be considered a really pure form of describing a GUI, it makes some optimizations harder because of the borrow checker.
The current borrow checker is not able to detect a drop was performed before reassigning a value to a mutable variable. Thus, keeping the generated widgets in `Application::view` alive between iterations of the event loop is not possible, which forces us to call this method quite often. `unsafe` could be used to workaround this, but it would feel fishy.
We could take a different approach, and keep some kind of state tree decoupled from the actual widget definitions. This would force us to perform diffing of nodes, as the widgets will represent the desired state and not the whole state.
Once the state lifetime of widgets is removed, we could keep them alive between iterations and even make `Application::view` take a non-mutable reference. This would also improve the end-user API, as it will not be necessary to constantly provide mutable state to widgets.
This is a big undertaking and introduces a new set of problems. We should research and consider the implications of this approach in detail before going for it.
### Try a different font rasterizer
[`wgpu_glyph`] depends indirectly on [`rusttype`]. We may be able to gain performance by using a different font rasterizer. [`fontdue`], for instance, has reported noticeable speedups.
[`fontdue`]: https://github.com/mooman219/fontdue
### Connect `iced_web` with `web-view`
It may be interesting to try to connect `iced_web` with [`web-view`]. It would give users a feature-complete renderer for free, and applications would still be leaner than with Electron.
[`web-view`]: https://github.com/Boscop/web-view
### Implement a lazy widget
Once we remove state lifetimes from widgets, we should be able to implement a widget storing a function that generates additional widgets. The runtime would then be able to control when to call this function and cache the generated widgets while some given value does not change.
This could be very useful to build very performant user interfaces with a lot of different items.
[Elm does it very well!](https://guide.elm-lang.org/optimization/lazy.html)
[Elm]: https://elm-lang.org/
[`winit`]: https://github.com/rust-windowing/winit
[`wgpu`]: https://github.com/gfx-rs/wgpu
[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
[`glyph_brush`]: https://github.com/alexheretic/glyph-brush

View file

@ -1 +1,2 @@
too-many-arguments-threshold = 20
enum-variant-name-threshold = 10

View file

@ -1,24 +1,27 @@
[package]
name = "iced_core"
version = "0.9.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "The essential concepts of Iced"
license = "MIT"
repository = "https://github.com/iced-rs/iced"
description = "The essential ideas of iced"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[dependencies]
bitflags = "1.2"
thiserror = "1"
log = "0.4.17"
twox-hash = { version = "1.5", default-features = false }
bitflags.workspace = true
log.workspace = true
thiserror.workspace = true
xxhash-rust.workspace = true
num-traits.workspace = true
[dependencies.palette]
version = "0.7"
optional = true
palette.workspace = true
palette.optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant = "0.1"
instant.workspace = true
[target.'cfg(windows)'.dependencies.raw-window-handle]
version = "0.5.2"

View file

@ -1,32 +1,72 @@
use crate::{Point, Rectangle, Vector};
use std::f32::consts::PI;
#[derive(Debug, Copy, Clone, PartialEq)]
use std::f32::consts::{FRAC_PI_2, PI};
use std::ops::RangeInclusive;
/// Degrees
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Degrees(pub f32);
#[derive(Debug, Copy, Clone, PartialEq)]
/// Radians
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Radians(pub f32);
impl Radians {
/// The range of radians of a circle.
pub const RANGE: RangeInclusive<Radians> = Radians(0.0)..=Radians(2.0 * PI);
}
impl From<Degrees> for Radians {
fn from(degrees: Degrees) -> Self {
Radians(degrees.0 * PI / 180.0)
Self(degrees.0 * PI / 180.0)
}
}
impl From<f32> for Radians {
fn from(radians: f32) -> Self {
Self(radians)
}
}
impl From<u8> for Radians {
fn from(radians: u8) -> Self {
Self(f32::from(radians))
}
}
impl From<Radians> for f64 {
fn from(radians: Radians) -> Self {
Self::from(radians.0)
}
}
impl num_traits::FromPrimitive for Radians {
fn from_i64(n: i64) -> Option<Self> {
Some(Self(n as f32))
}
fn from_u64(n: u64) -> Option<Self> {
Some(Self(n as f32))
}
fn from_f64(n: f64) -> Option<Self> {
Some(Self(n as f32))
}
}
impl Radians {
/// Calculates the line in which the [`Angle`] intercepts the `bounds`.
/// Calculates the line in which the angle intercepts the `bounds`.
pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) {
let v1 = Vector::new(f32::cos(self.0), f32::sin(self.0));
let angle = self.0 - FRAC_PI_2;
let r = Vector::new(f32::cos(angle), f32::sin(angle));
let distance_to_rect = f32::min(
f32::abs((bounds.y - bounds.center().y) / v1.y),
f32::abs(((bounds.x + bounds.width) - bounds.center().x) / v1.x),
let distance_to_rect = f32::max(
f32::abs(r.x * bounds.width / 2.0),
f32::abs(r.y * bounds.height / 2.0),
);
let start = bounds.center() + v1 * distance_to_rect;
let end = bounds.center() - v1 * distance_to_rect;
let start = bounds.center() - r * distance_to_rect;
let end = bounds.center() + r * distance_to_rect;
(start, end)
}

View file

@ -1,7 +1,7 @@
#[cfg(feature = "palette")]
use palette::rgb::{Srgb, Srgba};
/// A color in the sRGB color space.
/// A color in the `sRGB` color space.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Color {
/// Red component, 0.0 - 1.0
@ -89,6 +89,26 @@ impl Color {
}
}
/// Creates a [`Color`] from its linear RGBA components.
pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
// As described in:
// https://en.wikipedia.org/wiki/SRGB
fn gamma_component(u: f32) -> f32 {
if u < 0.0031308 {
12.92 * u
} else {
1.055 * u.powf(1.0 / 2.4) - 0.055
}
}
Self {
r: gamma_component(r),
g: gamma_component(g),
b: gamma_component(b),
a,
}
}
/// Converts the [`Color`] into its RGBA8 equivalent.
#[must_use]
pub fn into_rgba8(self) -> [u8; 4] {

View file

@ -5,7 +5,9 @@ use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::widget::tree::{self, Tree};
use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget};
use crate::{
Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget,
};
use std::any::Any;
use std::borrow::Borrow;
@ -291,7 +293,7 @@ where
}
fn diff(&self, tree: &mut Tree) {
self.widget.diff(tree)
self.widget.diff(tree);
}
fn width(&self) -> Length {
@ -304,10 +306,11 @@ where
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.widget.layout(renderer, limits)
self.widget.layout(tree, renderer, limits)
}
fn operate(
@ -325,11 +328,12 @@ where
fn container(
&mut self,
id: Option<&widget::Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(
&mut dyn widget::Operation<T>,
),
) {
self.operation.container(id, &mut |operation| {
self.operation.container(id, bounds, &mut |operation| {
operate_on_children(&mut MapOperation { operation });
});
}
@ -346,8 +350,10 @@ where
&mut self,
state: &mut dyn widget::operation::Scrollable,
id: Option<&widget::Id>,
bounds: Rectangle,
translation: Vector,
) {
self.operation.scrollable(state, id);
self.operation.scrollable(state, id, bounds, translation);
}
fn text_input(
@ -380,6 +386,7 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
viewport: &Rectangle,
) -> event::Status {
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
@ -392,6 +399,7 @@ where
renderer,
clipboard,
&mut local_shell,
viewport,
);
shell.merge(local_shell, &self.mapper);
@ -410,7 +418,7 @@ where
viewport: &Rectangle,
) {
self.widget
.draw(tree, renderer, theme, style, layout, cursor, viewport)
.draw(tree, renderer, theme, style, layout, cursor, viewport);
}
fn mouse_interaction(
@ -484,10 +492,11 @@ where
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.element.widget.layout(renderer, limits)
self.element.widget.layout(tree, renderer, limits)
}
fn operate(
@ -499,7 +508,7 @@ where
) {
self.element
.widget
.operate(state, layout, renderer, operation)
.operate(state, layout, renderer, operation);
}
fn on_event(
@ -511,10 +520,11 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
self.element
.widget
.on_event(state, event, layout, cursor, renderer, clipboard, shell)
self.element.widget.on_event(
state, event, layout, cursor, renderer, clipboard, shell, viewport,
)
}
fn draw(

View file

@ -10,8 +10,8 @@ pub struct Font {
pub weight: Weight,
/// The [`Stretch`] of the [`Font`].
pub stretch: Stretch,
/// Whether if the [`Font`] is monospaced or not.
pub monospaced: bool,
/// The [`Style`] of the [`Font`].
pub style: Style,
}
impl Font {
@ -20,13 +20,12 @@ impl Font {
family: Family::SansSerif,
weight: Weight::Normal,
stretch: Stretch::Normal,
monospaced: false,
style: Style::Normal,
};
/// A monospaced font with normal [`Weight`].
pub const MONOSPACE: Font = Font {
family: Family::Monospace,
monospaced: true,
..Self::DEFAULT
};
@ -100,3 +99,13 @@ pub enum Stretch {
ExtraExpanded,
UltraExpanded,
}
/// The style of some text.
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Style {
#[default]
Normal,
Italic,
Oblique,
}

View file

@ -6,10 +6,8 @@ use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq)]
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
/// or conically (TBD).
///
/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`].
pub enum Gradient {
/// A linear gradient interpolates colors along a direction at a specific [`Angle`].
/// A linear gradient interpolates colors along a direction at a specific angle.
Linear(Linear),
}
@ -96,8 +94,8 @@ impl Linear {
mut self,
stops: impl IntoIterator<Item = ColorStop>,
) -> Self {
for stop in stops.into_iter() {
self = self.add_stop(stop.offset, stop.color)
for stop in stops {
self = self.add_stop(stop.offset, stop.color);
}
self

View file

@ -1,10 +1,11 @@
/// The hasher used to compare layouts.
#[derive(Debug, Default)]
pub struct Hasher(twox_hash::XxHash64);
#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways.
#[derive(Default)]
pub struct Hasher(xxhash_rust::xxh3::Xxh3);
impl core::hash::Hasher for Hasher {
fn write(&mut self, bytes: &[u8]) {
self.0.write(bytes)
self.0.write(bytes);
}
fn finish(&self) -> u64 {

View file

@ -164,6 +164,16 @@ impl std::fmt::Debug for Data {
}
}
/// Image filtering strategy.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FilterMethod {
/// Bilinear interpolation.
#[default]
Linear,
/// Nearest neighbor.
Nearest,
}
/// A [`Renderer`] that can render raster graphics.
///
/// [renderer]: crate::renderer
@ -178,5 +188,10 @@ pub trait Renderer: crate::Renderer {
/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
fn draw(&mut self, handle: Self::Handle, bounds: Rectangle);
fn draw(
&mut self,
handle: Self::Handle,
filter_method: FilterMethod,
bounds: Rectangle,
);
}

View file

@ -7,7 +7,7 @@ pub mod flex;
pub use limits::Limits;
pub use node::Node;
use crate::{Point, Rectangle, Vector};
use crate::{Point, Rectangle, Size, Vector};
/// The bounds of a [`Node`] and its children, using absolute coordinates.
#[derive(Debug, Clone, Copy)]
@ -63,3 +63,36 @@ impl<'a> Layout<'a> {
})
}
}
/// Produces a [`Node`] with two children nodes one right next to each other.
pub fn next_to_each_other(
limits: &Limits,
spacing: f32,
left: impl FnOnce(&Limits) -> Node,
right: impl FnOnce(&Limits) -> Node,
) -> Node {
let mut left_node = left(limits);
let left_size = left_node.size();
let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0));
let mut right_node = right(&right_limits);
let right_size = right_node.size();
let (left_y, right_y) = if left_size.height > right_size.height {
(0.0, (left_size.height - right_size.height) / 2.0)
} else {
((right_size.height - left_size.height) / 2.0, 0.0)
};
left_node.move_to(Point::new(0.0, left_y));
right_node.move_to(Point::new(left_size.width + spacing, right_y));
Node::with_children(
Size::new(
left_size.width + spacing + right_size.width,
left_size.height.max(right_size.height),
),
vec![left_node, right_node],
)
}

View file

@ -19,6 +19,7 @@
use crate::Element;
use crate::layout::{Limits, Node};
use crate::widget;
use crate::{Alignment, Padding, Point, Size};
/// The main axis of a flex layout.
@ -66,6 +67,7 @@ pub fn resolve<Message, Renderer>(
spacing: f32,
align_items: Alignment,
items: &[Element<'_, Message, Renderer>],
trees: &mut [widget::Tree],
) -> Node
where
Renderer: crate::Renderer,
@ -81,7 +83,7 @@ where
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
nodes.resize(items.len(), Node::default());
for (i, child) in items.iter().enumerate() {
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
let fill_factor = match axis {
Axis::Horizontal => child.as_widget().width(),
Axis::Vertical => child.as_widget().height(),
@ -94,7 +96,8 @@ where
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let layout = child.as_widget().layout(renderer, &child_limits);
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
let size = layout.size();
available -= axis.main(size);
@ -108,7 +111,7 @@ where
let remaining = available.max(0.0);
for (i, child) in items.iter().enumerate() {
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
let fill_factor = match axis {
Axis::Horizontal => child.as_widget().width(),
Axis::Vertical => child.as_widget().height(),
@ -133,7 +136,8 @@ where
Size::new(max_width, max_height),
);
let layout = child.as_widget().layout(renderer, &child_limits);
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
cross = cross.max(axis.cross(layout.size()));
nodes[i] = layout;

View file

@ -2,7 +2,7 @@
use crate::{Length, Padding, Size};
/// A set of size constraints for layouting.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Limits {
min: Size,
max: Size,

View file

@ -1,29 +1,21 @@
//! The core library of [Iced].
//!
//! This library holds basic types that can be reused and re-exported in
//! different runtime implementations. For instance, both [`iced_native`] and
//! [`iced_web`] are built on top of `iced_core`.
//! different runtime implementations.
//!
//! ![The foundations of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true)
//!
//! [Iced]: https://github.com/iced-rs/iced
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
//! [`iced_web`]: https://github.com/iced-rs/iced_web
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
unused_results,
clippy::extra_unused_lifetimes,
clippy::from_over_into,
clippy::needless_borrow,
clippy::new_without_default,
clippy::useless_conversion
rustdoc::broken_intra_doc_links
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
pub mod alignment;
pub mod clipboard;
pub mod event;

View file

@ -24,7 +24,7 @@ pub enum Kind {
}
impl Kind {
fn next(&self) -> Kind {
fn next(self) -> Kind {
match self {
Kind::Single => Kind::Double,
Kind::Double => Kind::Triple,
@ -61,6 +61,11 @@ impl Click {
self.kind
}
/// Returns the position of the [`Click`].
pub fn position(&self) -> Point {
self.position
}
fn is_consecutive(&self, new_position: Point, time: Instant) -> bool {
let duration = if time > self.time {
Some(time - self.time)

View file

@ -11,7 +11,7 @@ use crate::mouse;
use crate::renderer;
use crate::widget;
use crate::widget::Tree;
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector};
/// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Renderer>
@ -25,10 +25,11 @@ where
///
/// [`Node`]: layout::Node
fn layout(
&self,
&mut self,
renderer: &Renderer,
bounds: Size,
position: Point,
translation: Vector,
) -> layout::Node;
/// Draws the [`Overlay`] using the associated `Renderer`.

View file

@ -13,6 +13,7 @@ use std::any::Any;
#[allow(missing_debug_implementations)]
pub struct Element<'a, Message, Renderer> {
position: Point,
translation: Vector,
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
}
@ -25,7 +26,11 @@ where
position: Point,
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
) -> Self {
Self { position, overlay }
Self {
position,
overlay,
translation: Vector::ZERO,
}
}
/// Returns the position of the [`Element`].
@ -36,6 +41,7 @@ where
/// Translates the [`Element`].
pub fn translate(mut self, translation: Vector) -> Self {
self.position = self.position + translation;
self.translation = self.translation + translation;
self
}
@ -48,19 +54,24 @@ where
{
Element {
position: self.position,
translation: self.translation,
overlay: Box::new(Map::new(self.overlay, f)),
}
}
/// Computes the layout of the [`Element`] in the given bounds.
pub fn layout(
&self,
&mut self,
renderer: &Renderer,
bounds: Size,
translation: Vector,
) -> layout::Node {
self.overlay
.layout(renderer, bounds, self.position + translation)
self.overlay.layout(
renderer,
bounds,
self.position + translation,
self.translation + translation,
)
}
/// Processes a runtime [`Event`].
@ -98,7 +109,7 @@ where
layout: Layout<'_>,
cursor: mouse::Cursor,
) {
self.overlay.draw(renderer, theme, style, layout, cursor)
self.overlay.draw(renderer, theme, style, layout, cursor);
}
/// Applies a [`widget::Operation`] to the [`Element`].
@ -150,12 +161,13 @@ where
Renderer: crate::Renderer,
{
fn layout(
&self,
&mut self,
renderer: &Renderer,
bounds: Size,
position: Point,
translation: Vector,
) -> layout::Node {
self.content.layout(renderer, bounds, position)
self.content.layout(renderer, bounds, position, translation)
}
fn operate(
@ -172,11 +184,12 @@ where
fn container(
&mut self,
id: Option<&widget::Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(
&mut dyn widget::Operation<T>,
),
) {
self.operation.container(id, &mut |operation| {
self.operation.container(id, bounds, &mut |operation| {
operate_on_children(&mut MapOperation { operation });
});
}
@ -193,8 +206,10 @@ where
&mut self,
state: &mut dyn widget::operation::Scrollable,
id: Option<&widget::Id>,
bounds: Rectangle,
translation: Vector,
) {
self.operation.scrollable(state, id);
self.operation.scrollable(state, id, bounds, translation);
}
fn text_input(
@ -202,7 +217,7 @@ where
state: &mut dyn widget::operation::TextInput,
id: Option<&widget::Id>,
) {
self.operation.text_input(state, id)
self.operation.text_input(state, id);
}
fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) {
@ -259,7 +274,7 @@ where
layout: Layout<'_>,
cursor: mouse::Cursor,
) {
self.content.draw(renderer, theme, style, layout, cursor)
self.content.draw(renderer, theme, style, layout, cursor);
}
fn is_over(

View file

@ -4,7 +4,9 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
use crate::{
Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size, Vector,
};
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
/// children.
@ -61,17 +63,16 @@ where
Renderer: crate::Renderer,
{
fn layout(
&self,
&mut self,
renderer: &Renderer,
bounds: Size,
position: Point,
_position: Point,
translation: Vector,
) -> layout::Node {
let translation = position - Point::ORIGIN;
layout::Node::with_children(
bounds,
self.children
.iter()
.iter_mut()
.map(|child| child.layout(renderer, bounds, translation))
.collect(),
)
@ -138,12 +139,12 @@ where
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
operation.container(None, &mut |operation| {
operation.container(None, layout.bounds(), &mut |operation| {
self.children.iter_mut().zip(layout.children()).for_each(
|(child, layout)| {
child.operate(layout, renderer, operation);
},
)
);
});
}

View file

@ -74,9 +74,9 @@ impl Rectangle<f32> {
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
pub fn contains(&self, point: Point) -> bool {
self.x <= point.x
&& point.x <= self.x + self.width
&& point.x < self.x + self.width
&& self.y <= point.y
&& point.y <= self.y + self.height
&& point.y < self.y + self.height
}
/// Returns true if the current [`Rectangle`] is completely within the given
@ -197,3 +197,18 @@ where
}
}
}
impl<T> std::ops::Sub<Vector<T>> for Rectangle<T>
where
T: std::ops::Sub<Output = T>,
{
type Output = Rectangle<T>;
fn sub(self, translation: Vector<T>) -> Self {
Rectangle {
x: self.x - translation.x,
y: self.y - translation.y,
..self
}
}
}

View file

@ -5,26 +5,13 @@ mod null;
#[cfg(debug_assertions)]
pub use null::Null;
use crate::layout;
use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};
use crate::{Background, BorderRadius, Color, Rectangle, Vector};
/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer: Sized {
/// The supported theme of the [`Renderer`].
type Theme;
/// Lays out the elements of a user interface.
///
/// You should override this if you need to perform any operations before or
/// after layouting. For instance, trimming the measurements cache.
fn layout<Message>(
&mut self,
element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
element.as_widget().layout(self, limits)
}
/// Draws the primitives recorded in the given closure in a new layer.
///
/// The layer will clip its contents to the provided `bounds`.

View file

@ -1,6 +1,7 @@
use crate::alignment;
use crate::renderer::{self, Renderer};
use crate::text::{self, Text};
use crate::{Background, Font, Point, Rectangle, Size, Vector};
use crate::{Background, Color, Font, Pixels, Point, Rectangle, Size, Vector};
use std::borrow::Cow;
@ -41,6 +42,8 @@ impl Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
type Paragraph = ();
type Editor = ();
const ICON_FONT: Font = Font::DEFAULT;
const CHECKMARK_ICON: char = '0';
@ -50,37 +53,117 @@ impl text::Renderer for Null {
Font::default()
}
fn default_size(&self) -> f32 {
16.0
fn default_size(&self) -> Pixels {
Pixels(16.0)
}
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
fn measure(
&self,
_content: &str,
_size: f32,
_line_height: text::LineHeight,
_font: Font,
_bounds: Size,
_shaping: text::Shaping,
) -> Size {
Size::new(0.0, 20.0)
fn fill_paragraph(
&mut self,
_paragraph: &Self::Paragraph,
_position: Point,
_color: Color,
) {
}
fn hit_test(
&self,
_contents: &str,
_size: f32,
_line_height: text::LineHeight,
_font: Self::Font,
_bounds: Size,
_shaping: text::Shaping,
_point: Point,
_nearest_only: bool,
) -> Option<text::Hit> {
fn fill_editor(
&mut self,
_editor: &Self::Editor,
_position: Point,
_color: Color,
) {
}
fn fill_text(
&mut self,
_paragraph: Text<'_, Self::Font>,
_position: Point,
_color: Color,
) {
}
}
impl text::Paragraph for () {
type Font = Font;
fn with_text(_text: Text<'_, Self::Font>) -> Self {}
fn resize(&mut self, _new_bounds: Size) {}
fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference {
text::Difference::None
}
fn horizontal_alignment(&self) -> alignment::Horizontal {
alignment::Horizontal::Left
}
fn vertical_alignment(&self) -> alignment::Vertical {
alignment::Vertical::Top
}
fn grapheme_position(&self, _line: usize, _index: usize) -> Option<Point> {
None
}
fn fill_text(&mut self, _text: Text<'_, Self::Font>) {}
fn min_bounds(&self) -> Size {
Size::ZERO
}
fn hit_test(&self, _point: Point) -> Option<text::Hit> {
None
}
}
impl text::Editor for () {
type Font = Font;
fn with_text(_text: &str) -> Self {}
fn cursor(&self) -> text::editor::Cursor {
text::editor::Cursor::Caret(Point::ORIGIN)
}
fn cursor_position(&self) -> (usize, usize) {
(0, 0)
}
fn selection(&self) -> Option<String> {
None
}
fn line(&self, _index: usize) -> Option<&str> {
None
}
fn line_count(&self) -> usize {
0
}
fn perform(&mut self, _action: text::editor::Action) {}
fn bounds(&self) -> Size {
Size::ZERO
}
fn update(
&mut self,
_new_bounds: Size,
_new_font: Self::Font,
_new_size: Pixels,
_new_line_height: text::LineHeight,
_new_highlighter: &mut impl text::Highlighter,
) {
}
fn highlight<H: text::Highlighter>(
&mut self,
_font: Self::Font,
_highlighter: &mut H,
_format_highlight: impl Fn(
&H::Highlight,
) -> text::highlighter::Format<Self::Font>,
) {
}
}

View file

@ -35,7 +35,7 @@ impl<'a, Message> Shell<'a, Message> {
self.messages.push(message);
}
/// Requests a new frame to be drawn at the given [`Instant`].
/// Requests a new frame to be drawn.
pub fn request_redraw(&mut self, request: window::RedrawRequest) {
match self.redraw_request {
None => {
@ -48,7 +48,7 @@ impl<'a, Message> Shell<'a, Message> {
}
}
/// Returns the requested [`Instant`] a redraw should happen, if any.
/// Returns the request a redraw should happen, if any.
pub fn redraw_request(&self) -> Option<window::RedrawRequest> {
self.redraw_request
}
@ -71,7 +71,7 @@ impl<'a, Message> Shell<'a, Message> {
if self.is_layout_invalid {
self.is_layout_invalid = false;
f()
f();
}
}

View file

@ -1,6 +1,15 @@
//! Draw and interact with text.
mod paragraph;
pub mod editor;
pub mod highlighter;
pub use editor::Editor;
pub use highlighter::Highlighter;
pub use paragraph::Paragraph;
use crate::alignment;
use crate::{Color, Pixels, Point, Rectangle, Size};
use crate::{Color, Pixels, Point, Size};
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
@ -12,17 +21,14 @@ pub struct Text<'a, Font> {
pub content: &'a str,
/// The bounds of the paragraph.
pub bounds: Rectangle,
pub bounds: Size,
/// The size of the [`Text`] in logical pixels.
pub size: f32,
pub size: Pixels,
/// The line height of the [`Text`].
pub line_height: LineHeight,
/// The color of the [`Text`].
pub color: Color,
/// The font of the [`Text`].
pub font: Font,
@ -129,10 +135,43 @@ impl Hit {
}
}
/// The difference detected in some text.
///
/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some
/// [`Text`].
///
/// [`compare`]: Paragraph::compare
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Difference {
/// No difference.
///
/// The text can be reused as it is!
None,
/// A bounds difference.
///
/// This normally means a relayout is necessary, but the shape of the text can
/// be reused.
Bounds,
/// A shape difference.
///
/// The contents, alignment, sizes, fonts, or any other essential attributes
/// of the shape of the text have changed. A complete reshape and relayout of
/// the text is necessary.
Shape,
}
/// A renderer capable of measuring and drawing [`Text`].
pub trait Renderer: crate::Renderer {
/// The font type used.
type Font: Copy;
type Font: Copy + PartialEq;
/// The [`Paragraph`] of this [`Renderer`].
type Paragraph: Paragraph<Font = Self::Font> + 'static;
/// The [`Editor`] of this [`Renderer`].
type Editor: Editor<Font = Self::Font> + 'static;
/// The icon font of the backend.
const ICON_FONT: Self::Font;
@ -151,62 +190,35 @@ pub trait Renderer: crate::Renderer {
fn default_font(&self) -> Self::Font;
/// Returns the default size of [`Text`].
fn default_size(&self) -> f32;
/// Measures the text in the given bounds and returns the minimum boundaries
/// that can fit the contents.
fn measure(
&self,
content: &str,
size: f32,
line_height: LineHeight,
font: Self::Font,
bounds: Size,
shaping: Shaping,
) -> Size;
/// Measures the width of the text as if it were laid out in a single line.
fn measure_width(
&self,
content: &str,
size: f32,
font: Self::Font,
shaping: Shaping,
) -> f32 {
let bounds = self.measure(
content,
size,
LineHeight::Absolute(Pixels(size)),
font,
Size::INFINITY,
shaping,
);
bounds.width
}
/// Tests whether the provided point is within the boundaries of text
/// laid out with the given parameters, returning information about
/// the nearest character.
///
/// If `nearest_only` is true, the hit test does not consider whether the
/// the point is interior to any glyph bounds, returning only the character
/// with the nearest centeroid.
fn hit_test(
&self,
contents: &str,
size: f32,
line_height: LineHeight,
font: Self::Font,
bounds: Size,
shaping: Shaping,
point: Point,
nearest_only: bool,
) -> Option<Hit>;
fn default_size(&self) -> Pixels;
/// Loads a [`Self::Font`] from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>);
/// Draws the given [`Text`].
fn fill_text(&mut self, text: Text<'_, Self::Font>);
/// Draws the given [`Paragraph`] at the given position and with the given
/// [`Color`].
fn fill_paragraph(
&mut self,
text: &Self::Paragraph,
position: Point,
color: Color,
);
/// Draws the given [`Editor`] at the given position and with the given
/// [`Color`].
fn fill_editor(
&mut self,
editor: &Self::Editor,
position: Point,
color: Color,
);
/// Draws the given [`Text`] at the given position and with the given
/// [`Color`].
fn fill_text(
&mut self,
text: Text<'_, Self::Font>,
position: Point,
color: Color,
);
}

181
core/src/text/editor.rs Normal file
View file

@ -0,0 +1,181 @@
//! Edit text.
use crate::text::highlighter::{self, Highlighter};
use crate::text::LineHeight;
use crate::{Pixels, Point, Rectangle, Size};
use std::sync::Arc;
/// A component that can be used by widgets to edit multi-line text.
pub trait Editor: Sized + Default {
/// The font of the [`Editor`].
type Font: Copy + PartialEq + Default;
/// Creates a new [`Editor`] laid out with the given text.
fn with_text(text: &str) -> Self;
/// Returns the current [`Cursor`] of the [`Editor`].
fn cursor(&self) -> Cursor;
/// Returns the current cursor position of the [`Editor`].
///
/// Line and column, respectively.
fn cursor_position(&self) -> (usize, usize);
/// Returns the current selected text of the [`Editor`].
fn selection(&self) -> Option<String>;
/// Returns the text of the given line in the [`Editor`], if it exists.
fn line(&self, index: usize) -> Option<&str>;
/// Returns the amount of lines in the [`Editor`].
fn line_count(&self) -> usize;
/// Performs an [`Action`] on the [`Editor`].
fn perform(&mut self, action: Action);
/// Returns the current boundaries of the [`Editor`].
fn bounds(&self) -> Size;
/// Updates the [`Editor`] with some new attributes.
fn update(
&mut self,
new_bounds: Size,
new_font: Self::Font,
new_size: Pixels,
new_line_height: LineHeight,
new_highlighter: &mut impl Highlighter,
);
/// Runs a text [`Highlighter`] in the [`Editor`].
fn highlight<H: Highlighter>(
&mut self,
font: Self::Font,
highlighter: &mut H,
format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
);
}
/// An interaction with an [`Editor`].
#[derive(Debug, Clone, PartialEq)]
pub enum Action {
/// Apply a [`Motion`].
Move(Motion),
/// Select text with a given [`Motion`].
Select(Motion),
/// Select the word at the current cursor.
SelectWord,
/// Select the line at the current cursor.
SelectLine,
/// Perform an [`Edit`].
Edit(Edit),
/// Click the [`Editor`] at the given [`Point`].
Click(Point),
/// Drag the mouse on the [`Editor`] to the given [`Point`].
Drag(Point),
/// Scroll the [`Editor`] a certain amount of lines.
Scroll {
/// The amount of lines to scroll.
lines: i32,
},
}
impl Action {
/// Returns whether the [`Action`] is an editing action.
pub fn is_edit(&self) -> bool {
matches!(self, Self::Edit(_))
}
}
/// An action that edits text.
#[derive(Debug, Clone, PartialEq)]
pub enum Edit {
/// Insert the given character.
Insert(char),
/// Paste the given text.
Paste(Arc<String>),
/// Break the current line.
Enter,
/// Delete the previous character.
Backspace,
/// Delete the next character.
Delete,
}
/// A cursor movement.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Motion {
/// Move left.
Left,
/// Move right.
Right,
/// Move up.
Up,
/// Move down.
Down,
/// Move to the left boundary of a word.
WordLeft,
/// Move to the right boundary of a word.
WordRight,
/// Move to the start of the line.
Home,
/// Move to the end of the line.
End,
/// Move to the start of the previous window.
PageUp,
/// Move to the start of the next window.
PageDown,
/// Move to the start of the text.
DocumentStart,
/// Move to the end of the text.
DocumentEnd,
}
impl Motion {
/// Widens the [`Motion`], if possible.
pub fn widen(self) -> Self {
match self {
Self::Left => Self::WordLeft,
Self::Right => Self::WordRight,
Self::Home => Self::DocumentStart,
Self::End => Self::DocumentEnd,
_ => self,
}
}
/// Returns the [`Direction`] of the [`Motion`].
pub fn direction(&self) -> Direction {
match self {
Self::Left
| Self::Up
| Self::WordLeft
| Self::Home
| Self::PageUp
| Self::DocumentStart => Direction::Left,
Self::Right
| Self::Down
| Self::WordRight
| Self::End
| Self::PageDown
| Self::DocumentEnd => Direction::Right,
}
}
}
/// A direction in some text.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
/// <-
Left,
/// ->
Right,
}
/// The cursor of an [`Editor`].
#[derive(Debug, Clone)]
pub enum Cursor {
/// Cursor without a selection
Caret(Point),
/// Cursor selecting a range of text
Selection(Vec<Rectangle>),
}

View file

@ -0,0 +1,88 @@
//! Highlight text.
use crate::Color;
use std::ops::Range;
/// A type capable of highlighting text.
///
/// A [`Highlighter`] highlights lines in sequence. When a line changes,
/// it must be notified and the lines after the changed one must be fed
/// again to the [`Highlighter`].
pub trait Highlighter: 'static {
/// The settings to configure the [`Highlighter`].
type Settings: PartialEq + Clone;
/// The output of the [`Highlighter`].
type Highlight;
/// The highlight iterator type.
type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)>
where
Self: 'a;
/// Creates a new [`Highlighter`] from its [`Self::Settings`].
fn new(settings: &Self::Settings) -> Self;
/// Updates the [`Highlighter`] with some new [`Self::Settings`].
fn update(&mut self, new_settings: &Self::Settings);
/// Notifies the [`Highlighter`] that the line at the given index has changed.
fn change_line(&mut self, line: usize);
/// Highlights the given line.
///
/// If a line changed prior to this, the first line provided here will be the
/// line that changed.
fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>;
/// Returns the current line of the [`Highlighter`].
///
/// If `change_line` has been called, this will normally be the least index
/// that changed.
fn current_line(&self) -> usize;
}
/// A highlighter that highlights nothing.
#[derive(Debug, Clone, Copy)]
pub struct PlainText;
impl Highlighter for PlainText {
type Settings = ();
type Highlight = ();
type Iterator<'a> = std::iter::Empty<(Range<usize>, Self::Highlight)>;
fn new(_settings: &Self::Settings) -> Self {
Self
}
fn update(&mut self, _new_settings: &Self::Settings) {}
fn change_line(&mut self, _line: usize) {}
fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> {
std::iter::empty()
}
fn current_line(&self) -> usize {
usize::MAX
}
}
/// The format of some text.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Format<Font> {
/// The [`Color`] of the text.
pub color: Option<Color>,
/// The `Font` of the text.
pub font: Option<Font>,
}
impl<Font> Default for Format<Font> {
fn default() -> Self {
Self {
color: None,
font: None,
}
}
}

View file

@ -0,0 +1,59 @@
use crate::alignment;
use crate::text::{Difference, Hit, Text};
use crate::{Point, Size};
/// A text paragraph.
pub trait Paragraph: Sized + Default {
/// The font of this [`Paragraph`].
type Font: Copy + PartialEq;
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
fn with_text(text: Text<'_, Self::Font>) -> Self;
/// Lays out the [`Paragraph`] with some new boundaries.
fn resize(&mut self, new_bounds: Size);
/// Compares the [`Paragraph`] with some desired [`Text`] and returns the
/// [`Difference`].
fn compare(&self, text: Text<'_, Self::Font>) -> Difference;
/// Returns the horizontal alignment of the [`Paragraph`].
fn horizontal_alignment(&self) -> alignment::Horizontal;
/// Returns the vertical alignment of the [`Paragraph`].
fn vertical_alignment(&self) -> alignment::Vertical;
/// Returns the minimum boundaries that can fit the contents of the
/// [`Paragraph`].
fn min_bounds(&self) -> Size;
/// Tests whether the provided point is within the boundaries of the
/// [`Paragraph`], returning information about the nearest character.
fn hit_test(&self, point: Point) -> Option<Hit>;
/// Returns the distance to the given grapheme index in the [`Paragraph`].
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
/// Updates the [`Paragraph`] to match the given [`Text`], if needed.
fn update(&mut self, text: Text<'_, Self::Font>) {
match self.compare(text) {
Difference::None => {}
Difference::Bounds => {
self.resize(text.bounds);
}
Difference::Shape => {
*self = Self::with_text(text);
}
}
}
/// Returns the minimum width that can fit the contents of the [`Paragraph`].
fn min_width(&self) -> f32 {
self.min_bounds().width
}
/// Returns the minimum height that can fit the contents of the [`Paragraph`].
fn min_height(&self) -> f32 {
self.min_bounds().height
}
}

View file

@ -33,12 +33,12 @@ use crate::{Clipboard, Length, Rectangle, Shell};
/// - [`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.9/examples
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry
/// [examples]: https://github.com/iced-rs/iced/tree/0.10/examples
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.10/examples/bezier_tool
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.10/examples/custom_widget
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.10/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.10/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
@ -55,6 +55,7 @@ where
/// user interface.
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node;
@ -62,7 +63,7 @@ where
/// Draws the [`Widget`] using the associated `Renderer`.
fn draw(
&self,
state: &Tree,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -115,6 +116,7 @@ where
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
event::Status::Ignored
}

View file

@ -8,6 +8,7 @@ pub use scrollable::Scrollable;
pub use text_input::TextInput;
use crate::widget::Id;
use crate::{Rectangle, Vector};
use std::any::Any;
use std::fmt;
@ -23,6 +24,7 @@ pub trait Operation<T> {
fn container(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
);
@ -30,7 +32,14 @@ pub trait Operation<T> {
fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
/// Operates on a widget that can be scrolled.
fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
fn scrollable(
&mut self,
_state: &mut dyn Scrollable,
_id: Option<&Id>,
_bounds: Rectangle,
_translation: Vector,
) {
}
/// Operates on a widget that has text input.
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
@ -92,6 +101,7 @@ where
fn container(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
struct MapRef<'a, A> {
@ -102,11 +112,12 @@ where
fn container(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
let Self { operation, .. } = self;
operation.container(id, &mut |operation| {
operation.container(id, bounds, &mut |operation| {
operate_on_children(&mut MapRef { operation });
});
}
@ -115,8 +126,10 @@ where
&mut self,
state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
translation: Vector,
) {
self.operation.scrollable(state, id);
self.operation.scrollable(state, id, bounds, translation);
}
fn focusable(
@ -145,15 +158,21 @@ where
MapRef {
operation: operation.as_mut(),
}
.container(id, operate_on_children);
.container(id, bounds, operate_on_children);
}
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
self.operation.focusable(state, id);
}
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
self.operation.scrollable(state, id);
fn scrollable(
&mut self,
state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
translation: Vector,
) {
self.operation.scrollable(state, id, bounds, translation);
}
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
@ -197,6 +216,7 @@ pub fn scope<T: 'static>(
fn container(
&mut self,
id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
) {
if id == Some(&self.target) {

View file

@ -1,6 +1,7 @@
//! Operate on widgets that can be focused.
use crate::widget::operation::{Operation, Outcome};
use crate::widget::Id;
use crate::Rectangle;
/// The internal state of a widget that can be focused.
pub trait Focusable {
@ -45,9 +46,10 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
}
@ -80,9 +82,10 @@ where
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
fn finish(&self) -> Outcome<T> {
@ -126,9 +129,10 @@ pub fn focus_previous<T>() -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
}
@ -159,9 +163,10 @@ pub fn focus_next<T>() -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
}
@ -185,9 +190,10 @@ pub fn find_focused() -> impl Operation<Id> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),
) {
operate_on_children(self)
operate_on_children(self);
}
fn finish(&self) -> Outcome<Id> {

View file

@ -1,5 +1,6 @@
//! Operate on widgets that can be scrolled.
use crate::widget::{Id, Operation};
use crate::{Rectangle, Vector};
/// The internal state of a widget that can be scrolled.
pub trait Scrollable {
@ -22,12 +23,19 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
fn scrollable(
&mut self,
state: &mut dyn Scrollable,
id: Option<&Id>,
_bounds: Rectangle,
_translation: Vector,
) {
if Some(&self.target) == id {
state.snap_to(self.offset);
}
@ -49,12 +57,19 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
fn scrollable(
&mut self,
state: &mut dyn Scrollable,
id: Option<&Id>,
_bounds: Rectangle,
_translation: Vector,
) {
if Some(&self.target) == id {
state.scroll_to(self.offset);
}

View file

@ -1,6 +1,7 @@
//! Operate on widgets that have text input.
use crate::widget::operation::Operation;
use crate::widget::Id;
use crate::Rectangle;
/// The internal state of a widget that has text input.
pub trait TextInput {
@ -34,9 +35,10 @@ pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
}
@ -63,9 +65,10 @@ pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
}
@ -93,9 +96,10 @@ pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
}
@ -121,9 +125,10 @@ pub fn select_all<T>(target: Id) -> impl Operation<T> {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
operate_on_children(self);
}
}

View file

@ -3,9 +3,9 @@ use crate::alignment;
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::text;
use crate::widget::Tree;
use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget};
use crate::text::{self, Paragraph};
use crate::widget::tree::{self, Tree};
use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget};
use std::borrow::Cow;
@ -19,7 +19,7 @@ where
Renderer::Theme: StyleSheet,
{
content: Cow<'a, str>,
size: Option<f32>,
size: Option<Pixels>,
line_height: LineHeight,
width: Length,
height: Length,
@ -53,7 +53,7 @@ where
/// Sets the size of the [`Text`].
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
self.size = Some(size.into().0);
self.size = Some(size.into());
self
}
@ -117,11 +117,23 @@ where
}
}
/// The internal state of a [`Text`] widget.
#[derive(Debug, Default)]
pub struct State<P: Paragraph>(P);
impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State<Renderer::Paragraph>>()
}
fn state(&self) -> tree::State {
tree::State::new(State(Renderer::Paragraph::default()))
}
fn width(&self) -> Length {
self.width
}
@ -132,30 +144,29 @@ where
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = self.size.unwrap_or_else(|| renderer.default_size());
let bounds = renderer.measure(
layout(
tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
renderer,
limits,
self.width,
self.height,
&self.content,
size,
self.line_height,
self.font.unwrap_or_else(|| renderer.default_font()),
limits.max(),
self.size,
self.font,
self.horizontal_alignment,
self.vertical_alignment,
self.shaping,
);
let size = limits.resolve(bounds);
layout::Node::new(size)
)
}
fn draw(
&self,
_state: &Tree,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
@ -163,22 +174,60 @@ where
_cursor_position: mouse::Cursor,
_viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
draw(
renderer,
style,
layout,
&self.content,
self.size,
self.line_height,
self.font,
state,
theme.appearance(self.style.clone()),
self.horizontal_alignment,
self.vertical_alignment,
self.shaping,
);
}
}
/// Produces the [`layout::Node`] of a [`Text`] widget.
pub fn layout<Renderer>(
state: &mut State<Renderer::Paragraph>,
renderer: &Renderer,
limits: &layout::Limits,
width: Length,
height: Length,
content: &str,
line_height: LineHeight,
size: Option<Pixels>,
font: Option<Renderer::Font>,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
) -> layout::Node
where
Renderer: text::Renderer,
{
let limits = limits.width(width).height(height);
let bounds = limits.max();
let size = size.unwrap_or_else(|| renderer.default_size());
let font = font.unwrap_or_else(|| renderer.default_font());
let State(ref mut paragraph) = state;
paragraph.update(text::Text {
content,
bounds,
size,
line_height,
font,
horizontal_alignment,
vertical_alignment,
shaping,
});
let size = limits.resolve(paragraph.min_bounds());
layout::Node::new(size)
}
/// Draws text using the same logic as the [`Text`] widget.
///
/// Specifically:
@ -193,44 +242,31 @@ pub fn draw<Renderer>(
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
content: &str,
size: Option<f32>,
line_height: LineHeight,
font: Option<Renderer::Font>,
state: &State<Renderer::Paragraph>,
appearance: Appearance,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
) where
Renderer: text::Renderer,
{
let State(ref paragraph) = state;
let bounds = layout.bounds();
let x = match horizontal_alignment {
let x = match paragraph.horizontal_alignment() {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => bounds.center_x(),
alignment::Horizontal::Right => bounds.x + bounds.width,
};
let y = match vertical_alignment {
let y = match paragraph.vertical_alignment() {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => bounds.center_y(),
alignment::Vertical::Bottom => bounds.y + bounds.height,
};
let size = size.unwrap_or_else(|| renderer.default_size());
renderer.fill_text(crate::Text {
content,
size,
line_height,
bounds: Rectangle { x, y, ..bounds },
color: appearance.color.unwrap_or(style.text_color),
font: font.unwrap_or_else(|| renderer.default_font()),
horizontal_alignment,
vertical_alignment,
shaping,
});
renderer.fill_paragraph(
paragraph,
Point::new(x, y),
appearance.color.unwrap_or(style.text_color),
);
}
impl<'a, Message, Renderer> From<Text<'a, Renderer>>

View file

@ -61,7 +61,7 @@ impl Tree {
Renderer: crate::Renderer,
{
if self.tag == new.borrow().tag() {
new.borrow().diff(self)
new.borrow().diff(self);
} else {
*self = Self::new(new);
}
@ -78,7 +78,7 @@ impl Tree {
new_children,
|tree, widget| tree.diff(widget.borrow()),
|widget| Self::new(widget.borrow()),
)
);
}
/// Reconciliates the children of the tree with the provided list of widgets using custom
@ -107,6 +107,88 @@ impl Tree {
}
}
/// Reconciliates the `current_children` with the provided list of widgets using
/// custom logic both for diffing and creating new widget state.
///
/// The algorithm will try to minimize the impact of diffing by querying the
/// `maybe_changed` closure.
pub fn diff_children_custom_with_search<T>(
current_children: &mut Vec<Tree>,
new_children: &[T],
diff: impl Fn(&mut Tree, &T),
maybe_changed: impl Fn(usize) -> bool,
new_state: impl Fn(&T) -> Tree,
) {
if new_children.is_empty() {
current_children.clear();
return;
}
if current_children.is_empty() {
current_children.extend(new_children.iter().map(new_state));
return;
}
let first_maybe_changed = maybe_changed(0);
let last_maybe_changed = maybe_changed(current_children.len() - 1);
if current_children.len() > new_children.len() {
if !first_maybe_changed && last_maybe_changed {
current_children.truncate(new_children.len());
} else {
let difference_index = if first_maybe_changed {
0
} else {
(1..current_children.len())
.find(|&i| maybe_changed(i))
.unwrap_or(0)
};
let _ = current_children.splice(
difference_index
..difference_index
+ (current_children.len() - new_children.len()),
std::iter::empty(),
);
}
}
if current_children.len() < new_children.len() {
let first_maybe_changed = maybe_changed(0);
let last_maybe_changed = maybe_changed(current_children.len() - 1);
if !first_maybe_changed && last_maybe_changed {
current_children.extend(
new_children[current_children.len()..].iter().map(new_state),
);
} else {
let difference_index = if first_maybe_changed {
0
} else {
(1..current_children.len())
.find(|&i| maybe_changed(i))
.unwrap_or(0)
};
let _ = current_children.splice(
difference_index..difference_index,
new_children[difference_index
..difference_index
+ (new_children.len() - current_children.len())]
.iter()
.map(new_state),
);
}
}
// TODO: Merge loop with extend logic (?)
for (child_state, new) in
current_children.iter_mut().zip(new_children.iter())
{
diff(child_state, new);
}
}
/// The identifier of some widget state.
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Tag(any::TypeId);

View file

@ -1,5 +1,6 @@
//! Build window-based GUI applications.
pub mod icon;
pub mod settings;
mod event;
mod id;
@ -7,7 +8,6 @@ mod level;
mod mode;
mod position;
mod redraw_request;
mod settings;
mod user_attention;
pub use event::Event;

View file

@ -3,7 +3,7 @@ use crate::Size;
use std::mem;
/// Builds an [`Icon`] from its RGBA pixels in the sRGB color space.
/// Builds an [`Icon`] from its RGBA pixels in the `sRGB` color space.
pub fn from_rgba(
rgba: Vec<u8>,
width: u32,
@ -49,7 +49,7 @@ impl Icon {
}
#[derive(Debug, thiserror::Error)]
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
/// An error produced when using [`from_rgba`] with invalid arguments.
pub enum Error {
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels.

View file

@ -13,7 +13,7 @@ pub enum RedrawRequest {
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
use crate::time::{Duration, Instant};
#[test]
fn ordering() {

View file

@ -1,5 +1,4 @@
use crate::window::{Icon, Level, Position};
//! Configure your windows.
#[cfg(target_os = "windows")]
#[path = "settings/windows.rs"]
mod platform;
@ -8,6 +7,10 @@ mod platform;
#[path = "settings/macos.rs"]
mod platform;
#[cfg(target_os = "linux")]
#[path = "settings/linux.rs"]
mod platform;
#[cfg(target_arch = "wasm32")]
#[path = "settings/wasm.rs"]
mod platform;
@ -15,13 +18,15 @@ mod platform;
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_arch = "wasm32"
)))]
#[path = "settings/other.rs"]
mod platform;
pub use platform::PlatformSpecific;
use crate::window::{Icon, Level, Position};
pub use platform::PlatformSpecific;
/// The window settings of an application.
#[derive(Debug, Clone)]
pub struct Settings {
@ -70,8 +75,8 @@ pub struct Settings {
}
impl Default for Settings {
fn default() -> Settings {
Settings {
fn default() -> Self {
Self {
size: (1024, 768),
position: Position::default(),
min_size: None,
@ -82,8 +87,8 @@ impl Default for Settings {
transparent: false,
level: Level::default(),
icon: None,
platform_specific: Default::default(),
exit_on_close_request: true,
platform_specific: PlatformSpecific::default(),
}
}
}

View file

@ -0,0 +1,11 @@
//! Platform specific settings for Linux.
/// The platform specific window settings of an application.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PlatformSpecific {
/// Sets the application id of the window.
///
/// As a best practice, it is suggested to select an application id that match
/// the basename of the applications .desktop file.
pub application_id: String,
}

View file

@ -13,7 +13,7 @@ PR_COMMIT_REGEX = re.compile(r"(?i)Merge pull request #(\d+).*")
def get_merged_prs_since_release(repo: str, previous_release_branch: str) -> List[Tuple[str, int, str, str]]:
prs = []
compare_url = f"https://api.github.com/repos/{repo}/compare/{previous_release_branch}...master"
compare_url = f"https://api.github.com/repos/{repo}/compare/{previous_release_branch}...master?per_page=1000"
compare_response = requests.get(compare_url, headers=HEADERS)
if compare_response.status_code == 200:
@ -23,7 +23,10 @@ def get_merged_prs_since_release(repo: str, previous_release_branch: str) -> Lis
if match:
pr_number = int(match.group(1))
pr_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
print(f"Querying PR {pr_number}")
pr_response = requests.get(pr_url, headers=HEADERS)
if pr_response.status_code == 200:
pr_data = pr_response.json()
prs.append((pr_data["title"], pr_number, pr_data["html_url"], pr_data["user"]["login"]))

View file

@ -10,8 +10,8 @@ A simple UI tour that can run both on native platforms and the web! It showcases
The __[`main`](tour/src/main.rs)__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__.
<div align="center">
<a href="https://gfycat.com/politeadorableiberianmole">
<img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif">
<a href="https://iced.rs/examples/tour.mp4">
<img src="https://iced.rs/examples/tour.gif">
</a>
</div>
@ -33,8 +33,8 @@ A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input,
The example code is located in the __[`main`](todos/src/main.rs)__ file.
<div align="center">
<a href="https://gfycat.com/littlesanehalicore">
<img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" height="400px">
<a href="https://iced.rs/examples/todos.mp4">
<img src="https://iced.rs/examples/todos.gif" height="400px">
</a>
</div>
@ -53,9 +53,7 @@ It runs a simulation in a background thread while allowing interaction with a `C
The relevant code is located in the __[`main`](game_of_life/src/main.rs)__ file.
<div align="center">
<a href="https://gfycat.com/briefaccurateaardvark">
<img src="https://thumbs.gfycat.com/BriefAccurateAardvark-size_restricted.gif">
</a>
<img src="https://iced.rs/examples/game_of_life.gif">
</div>
You can run it with `cargo run`:
@ -72,9 +70,7 @@ An example showcasing custom styling with a light and dark theme.
The example code is located in the __[`main`](styling/src/main.rs)__ file.
<div align="center">
<a href="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif">
<img src="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif" height="400px">
</a>
<img src="https://iced.rs/examples/styling.gif">
</div>
You can run it with `cargo run`:
@ -120,9 +116,7 @@ Since [Iced was born in May 2019], it has been powering the user interfaces in
<div align="center">
<a href="https://gfycat.com/gloomyweakhammerheadshark">
<img src="https://thumbs.gfycat.com/GloomyWeakHammerheadshark-small.gif">
</a>
<img src="https://iced.rs/examples/coffee.gif">
</div>
[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35

View file

@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
iced.workspace = true
iced.features = ["canvas", "tokio", "debug"]

View file

@ -37,7 +37,7 @@ impl Application for Arc {
(
Arc {
start: Instant::now(),
cache: Default::default(),
cache: Cache::default(),
},
Command::none(),
)

View file

@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["canvas"] }
iced.workspace = true
iced.features = ["canvas"]

View file

@ -5,9 +5,7 @@ A Paint-like tool for drawing Bézier curves using the `Canvas` widget.
The __[`main`]__ file contains all the code of the example.
<div align="center">
<a href="https://gfycat.com/soulfulinfiniteantbear">
<img src="https://thumbs.gfycat.com/SoulfulInfiniteAntbear-small.gif">
</a>
<img src="https://iced.rs/examples/bezier_tool.gif">
</div>
You can run it with `cargo run`:

View file

@ -81,7 +81,7 @@ mod bezier {
}
pub fn request_redraw(&mut self) {
self.cache.clear()
self.cache.clear();
}
}
@ -100,10 +100,7 @@ mod bezier {
bounds: Rectangle,
cursor: mouse::Cursor,
) -> (event::Status, Option<Curve>) {
let cursor_position =
if let Some(position) = cursor.position_in(bounds) {
position
} else {
let Some(cursor_position) = cursor.position_in(bounds) else {
return (event::Status::Ignored, None);
};

View file

@ -6,4 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced.workspace = true

View file

@ -6,5 +6,7 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
time = { version = "0.3.5", features = ["local-offset"] }
iced.workspace = true
iced.features = ["canvas", "tokio", "debug"]
time = { version = "0.3", features = ["local-offset"] }

View file

@ -35,7 +35,7 @@ impl Application for Clock {
Clock {
now: time::OffsetDateTime::now_local()
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
clock: Default::default(),
clock: Cache::default(),
},
Command::none(),
)
@ -141,7 +141,7 @@ impl<Message> canvas::Program<Message, Renderer> for Clock {
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.second(), 60));
frame.stroke(&long_hand, thin_stroke());
})
});
});
vec![clock]

View file

@ -6,5 +6,7 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "palette"] }
palette = "0.7.0"
iced.workspace = true
iced.features = ["canvas", "palette"]
palette.workspace = true

View file

@ -3,13 +3,11 @@
A color palette generator, based on a user-defined root color.
<div align="center">
<a href="https://gfycat.com/dirtylonebighornsheep">
<img src="screenshot.png">
</a>
</div>
You can run it with `cargo run`:
```
cargo run --package pure_color_palette
cargo run --package color_palette
```

View file

@ -3,8 +3,8 @@ use iced::mouse;
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
use iced::widget::{column, row, text, Slider};
use iced::{
Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings,
Size, Vector,
Color, Element, Length, Pixels, Point, Rectangle, Renderer, Sandbox,
Settings, Size, Vector,
};
use palette::{
self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
@ -168,7 +168,7 @@ impl Theme {
let mut text = canvas::Text {
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Top,
size: 15.0,
size: Pixels(15.0),
..canvas::Text::default()
};

View file

@ -0,0 +1,10 @@
[package]
name = "combo_box"
version = "0.1.0"
authors = ["Joao Freitas <jhff.15@gmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced.workspace = true
iced.features = ["debug"]

View file

@ -0,0 +1,18 @@
## Combo-Box
A dropdown list of searchable and selectable options.
It displays and positions an overlay based on the window position of the widget.
The __[`main`]__ file contains all the code of the example.
<div align="center">
<img src="combobox.gif">
</div>
You can run it with `cargo run`:
```
cargo run --package combo_box
```
[`main`]: src/main.rs

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View file

@ -0,0 +1,142 @@
use iced::widget::{
column, combo_box, container, scrollable, text, vertical_space,
};
use iced::{Alignment, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Example::run(Settings::default())
}
struct Example {
languages: combo_box::State<Language>,
selected_language: Option<Language>,
text: String,
}
#[derive(Debug, Clone, Copy)]
enum Message {
Selected(Language),
OptionHovered(Language),
Closed,
}
impl Sandbox for Example {
type Message = Message;
fn new() -> Self {
Self {
languages: combo_box::State::new(Language::ALL.to_vec()),
selected_language: None,
text: String::new(),
}
}
fn title(&self) -> String {
String::from("Combo box - Iced")
}
fn update(&mut self, message: Message) {
match message {
Message::Selected(language) => {
self.selected_language = Some(language);
self.text = language.hello().to_string();
}
Message::OptionHovered(language) => {
self.text = language.hello().to_string();
}
Message::Closed => {
self.text = self
.selected_language
.map(|language| language.hello().to_string())
.unwrap_or_default();
}
}
}
fn view(&self) -> Element<Message> {
let combo_box = combo_box(
&self.languages,
"Type a language...",
self.selected_language.as_ref(),
Message::Selected,
)
.on_option_hovered(Message::OptionHovered)
.on_close(Message::Closed)
.width(250);
let content = column![
text(&self.text),
"What is your language?",
combo_box,
vertical_space(150),
]
.width(Length::Fill)
.align_items(Alignment::Center)
.spacing(10);
container(scrollable(content))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Language {
Danish,
#[default]
English,
French,
German,
Italian,
Portuguese,
Spanish,
Other,
}
impl Language {
const ALL: [Language; 8] = [
Language::Danish,
Language::English,
Language::French,
Language::German,
Language::Italian,
Language::Portuguese,
Language::Spanish,
Language::Other,
];
fn hello(&self) -> &str {
match self {
Language::Danish => "Halloy!",
Language::English => "Hello!",
Language::French => "Salut!",
Language::German => "Hallo!",
Language::Italian => "Ciao!",
Language::Portuguese => "Olá!",
Language::Spanish => "¡Hola!",
Language::Other => "... hello?",
}
}
}
impl std::fmt::Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Language::Danish => "Danish",
Language::English => "English",
Language::French => "French",
Language::German => "German",
Language::Italian => "Italian",
Language::Portuguese => "Portuguese",
Language::Spanish => "Spanish",
Language::Other => "Some other language",
}
)
}
}

View file

@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug", "lazy"] }
iced.workspace = true
iced.features = ["debug", "lazy"]

View file

@ -6,4 +6,8 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced.workspace = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
iced.workspace = true
iced.features = ["webgl"]

View file

@ -5,9 +5,7 @@ The classic counter example explained in the [`README`](../../README.md).
The __[`main`]__ file contains all the code of the example.
<div align="center">
<a href="https://gfycat.com/fairdeadcatbird">
<img src="https://thumbs.gfycat.com/FairDeadCatbird-small.gif">
</a>
<img src="https://iced.rs/examples/counter.gif">
</div>
You can run it with `cargo run`:
@ -15,4 +13,12 @@ You can run it with `cargo run`:
cargo run --package counter
```
The web version can be run with [`trunk`]:
```
cd examples/counter
trunk serve
```
[`main`]: src/main.rs
[`trunk`]: https://trunkrs.dev/

View file

@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["advanced"] }
iced.workspace = true
iced.features = ["advanced"]

View file

@ -36,6 +36,7 @@ mod quad {
fn layout(
&self,
_tree: &mut widget::Tree,
_renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {

View file

@ -0,0 +1,17 @@
[package]
name = "custom_shader"
version = "0.1.0"
authors = ["Bingus <shankern@protonmail.com>"]
edition = "2021"
[dependencies]
iced.workspace = true
iced.features = ["debug", "advanced"]
image.workspace = true
bytemuck.workspace = true
glam.workspace = true
glam.features = ["bytemuck"]
rand = "0.8.5"

View file

@ -0,0 +1,163 @@
mod scene;
use scene::Scene;
use iced::executor;
use iced::time::Instant;
use iced::widget::shader::wgpu;
use iced::widget::{checkbox, column, container, row, shader, slider, text};
use iced::window;
use iced::{
Alignment, Application, Color, Command, Element, Length, Renderer,
Subscription, Theme,
};
fn main() -> iced::Result {
IcedCubes::run(iced::Settings::default())
}
struct IcedCubes {
start: Instant,
scene: Scene,
}
#[derive(Debug, Clone)]
enum Message {
CubeAmountChanged(u32),
CubeSizeChanged(f32),
Tick(Instant),
ShowDepthBuffer(bool),
LightColorChanged(Color),
}
impl Application for IcedCubes {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
(
Self {
start: Instant::now(),
scene: Scene::new(),
},
Command::none(),
)
}
fn title(&self) -> String {
"Iced Cubes".to_string()
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
match message {
Message::CubeAmountChanged(amount) => {
self.scene.change_amount(amount);
}
Message::CubeSizeChanged(size) => {
self.scene.size = size;
}
Message::Tick(time) => {
self.scene.update(time - self.start);
}
Message::ShowDepthBuffer(show) => {
self.scene.show_depth_buffer = show;
}
Message::LightColorChanged(color) => {
self.scene.light_color = color;
}
}
Command::none()
}
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
let top_controls = row![
control(
"Amount",
slider(
1..=scene::MAX,
self.scene.cubes.len() as u32,
Message::CubeAmountChanged
)
.width(100)
),
control(
"Size",
slider(0.1..=0.25, self.scene.size, Message::CubeSizeChanged)
.step(0.01)
.width(100),
),
checkbox(
"Show Depth Buffer",
self.scene.show_depth_buffer,
Message::ShowDepthBuffer
),
]
.spacing(40);
let bottom_controls = row![
control(
"R",
slider(0.0..=1.0, self.scene.light_color.r, move |r| {
Message::LightColorChanged(Color {
r,
..self.scene.light_color
})
})
.step(0.01)
.width(100)
),
control(
"G",
slider(0.0..=1.0, self.scene.light_color.g, move |g| {
Message::LightColorChanged(Color {
g,
..self.scene.light_color
})
})
.step(0.01)
.width(100)
),
control(
"B",
slider(0.0..=1.0, self.scene.light_color.b, move |b| {
Message::LightColorChanged(Color {
b,
..self.scene.light_color
})
})
.step(0.01)
.width(100)
)
]
.spacing(40);
let controls = column![top_controls, bottom_controls,]
.spacing(10)
.padding(20)
.align_items(Alignment::Center);
let shader =
shader(&self.scene).width(Length::Fill).height(Length::Fill);
container(column![shader, controls].align_items(Alignment::Center))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
fn subscription(&self) -> Subscription<Self::Message> {
window::frames().map(Message::Tick)
}
}
fn control<'a>(
label: &'static str,
control: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
row![text(label), control.into()].spacing(10).into()
}

View file

@ -0,0 +1,186 @@
mod camera;
mod pipeline;
use camera::Camera;
use pipeline::Pipeline;
use crate::wgpu;
use pipeline::cube::{self, Cube};
use iced::mouse;
use iced::time::Duration;
use iced::widget::shader;
use iced::{Color, Rectangle, Size};
use glam::Vec3;
use rand::Rng;
use std::cmp::Ordering;
use std::iter;
pub const MAX: u32 = 500;
#[derive(Clone)]
pub struct Scene {
pub size: f32,
pub cubes: Vec<Cube>,
pub camera: Camera,
pub show_depth_buffer: bool,
pub light_color: Color,
}
impl Scene {
pub fn new() -> Self {
let mut scene = Self {
size: 0.2,
cubes: vec![],
camera: Camera::default(),
show_depth_buffer: false,
light_color: Color::WHITE,
};
scene.change_amount(MAX);
scene
}
pub fn update(&mut self, time: Duration) {
for cube in self.cubes.iter_mut() {
cube.update(self.size, time.as_secs_f32());
}
}
pub fn change_amount(&mut self, amount: u32) {
let curr_cubes = self.cubes.len() as u32;
match amount.cmp(&curr_cubes) {
Ordering::Greater => {
// spawn
let cubes_2_spawn = (amount - curr_cubes) as usize;
let mut cubes = 0;
self.cubes.extend(iter::from_fn(|| {
if cubes < cubes_2_spawn {
cubes += 1;
Some(Cube::new(self.size, rnd_origin()))
} else {
None
}
}));
}
Ordering::Less => {
// chop
let cubes_2_cut = curr_cubes - amount;
let new_len = self.cubes.len() - cubes_2_cut as usize;
self.cubes.truncate(new_len);
}
Ordering::Equal => {}
}
}
}
impl<Message> shader::Program<Message> for Scene {
type State = ();
type Primitive = Primitive;
fn draw(
&self,
_state: &Self::State,
_cursor: mouse::Cursor,
bounds: Rectangle,
) -> Self::Primitive {
Primitive::new(
&self.cubes,
&self.camera,
bounds,
self.show_depth_buffer,
self.light_color,
)
}
}
/// A collection of `Cube`s that can be rendered.
#[derive(Debug)]
pub struct Primitive {
cubes: Vec<cube::Raw>,
uniforms: pipeline::Uniforms,
show_depth_buffer: bool,
}
impl Primitive {
pub fn new(
cubes: &[Cube],
camera: &Camera,
bounds: Rectangle,
show_depth_buffer: bool,
light_color: Color,
) -> Self {
let uniforms = pipeline::Uniforms::new(camera, bounds, light_color);
Self {
cubes: cubes
.iter()
.map(cube::Raw::from_cube)
.collect::<Vec<cube::Raw>>(),
uniforms,
show_depth_buffer,
}
}
}
impl shader::Primitive for Primitive {
fn prepare(
&self,
format: wgpu::TextureFormat,
device: &wgpu::Device,
queue: &wgpu::Queue,
_bounds: Rectangle,
target_size: Size<u32>,
_scale_factor: f32,
storage: &mut shader::Storage,
) {
if !storage.has::<Pipeline>() {
storage.store(Pipeline::new(device, queue, format, target_size));
}
let pipeline = storage.get_mut::<Pipeline>().unwrap();
//upload data to GPU
pipeline.update(
device,
queue,
target_size,
&self.uniforms,
self.cubes.len(),
&self.cubes,
);
}
fn render(
&self,
storage: &shader::Storage,
target: &wgpu::TextureView,
_target_size: Size<u32>,
viewport: Rectangle<u32>,
encoder: &mut wgpu::CommandEncoder,
) {
//at this point our pipeline should always be initialized
let pipeline = storage.get::<Pipeline>().unwrap();
//render primitive
pipeline.render(
target,
encoder,
viewport,
self.cubes.len() as u32,
self.show_depth_buffer,
);
}
}
fn rnd_origin() -> Vec3 {
Vec3::new(
rand::thread_rng().gen_range(-4.0..4.0),
rand::thread_rng().gen_range(-4.0..4.0),
rand::thread_rng().gen_range(-4.0..2.0),
)
}

View file

@ -0,0 +1,53 @@
use glam::{mat4, vec3, vec4};
use iced::Rectangle;
#[derive(Copy, Clone)]
pub struct Camera {
eye: glam::Vec3,
target: glam::Vec3,
up: glam::Vec3,
fov_y: f32,
near: f32,
far: f32,
}
impl Default for Camera {
fn default() -> Self {
Self {
eye: vec3(0.0, 2.0, 3.0),
target: glam::Vec3::ZERO,
up: glam::Vec3::Y,
fov_y: 45.0,
near: 0.1,
far: 100.0,
}
}
}
pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = mat4(
vec4(1.0, 0.0, 0.0, 0.0),
vec4(0.0, 1.0, 0.0, 0.0),
vec4(0.0, 0.0, 0.5, 0.0),
vec4(0.0, 0.0, 0.5, 1.0),
);
impl Camera {
pub fn build_view_proj_matrix(&self, bounds: Rectangle) -> glam::Mat4 {
//TODO looks distorted without padding; base on surface texture size instead?
let aspect_ratio = bounds.width / (bounds.height + 150.0);
let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up);
let proj = glam::Mat4::perspective_rh(
self.fov_y,
aspect_ratio,
self.near,
self.far,
);
OPENGL_TO_WGPU_MATRIX * proj * view
}
pub fn position(&self) -> glam::Vec4 {
glam::Vec4::from((self.eye, 0.0))
}
}

View file

@ -0,0 +1,619 @@
pub mod cube;
mod buffer;
mod uniforms;
mod vertex;
pub use uniforms::Uniforms;
use buffer::Buffer;
use vertex::Vertex;
use crate::wgpu;
use crate::wgpu::util::DeviceExt;
use iced::{Rectangle, Size};
const SKY_TEXTURE_SIZE: u32 = 128;
pub struct Pipeline {
pipeline: wgpu::RenderPipeline,
vertices: wgpu::Buffer,
cubes: Buffer,
uniforms: wgpu::Buffer,
uniform_bind_group: wgpu::BindGroup,
depth_texture_size: Size<u32>,
depth_view: wgpu::TextureView,
depth_pipeline: DepthPipeline,
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
target_size: Size<u32>,
) -> Self {
//vertices of one cube
let vertices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("cubes vertex buffer"),
contents: bytemuck::cast_slice(&cube::Raw::vertices()),
usage: wgpu::BufferUsages::VERTEX,
});
//cube instance data
let cubes_buffer = Buffer::new(
device,
"cubes instance buffer",
std::mem::size_of::<cube::Raw>() as u64,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
//uniforms for all cubes
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("cubes uniform buffer"),
size: std::mem::size_of::<Uniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
//depth buffer
let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("cubes depth texture"),
size: wgpu::Extent3d {
width: target_size.width,
height: target_size.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let depth_view =
depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
let normal_map_data = load_normal_map_data();
//normal map
let normal_texture = device.create_texture_with_data(
queue,
&wgpu::TextureDescriptor {
label: Some("cubes normal map texture"),
size: wgpu::Extent3d {
width: 1024,
height: 1024,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
&normal_map_data,
);
let normal_view =
normal_texture.create_view(&wgpu::TextureViewDescriptor::default());
//skybox texture for reflection/refraction
let skybox_data = load_skybox_data();
let skybox_texture = device.create_texture_with_data(
queue,
&wgpu::TextureDescriptor {
label: Some("cubes skybox texture"),
size: wgpu::Extent3d {
width: SKY_TEXTURE_SIZE,
height: SKY_TEXTURE_SIZE,
depth_or_array_layers: 6, //one for each face of the cube
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
&skybox_data,
);
let sky_view =
skybox_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("cubes skybox texture view"),
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("cubes skybox sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("cubes uniform bind group layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float {
filterable: true,
},
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(
wgpu::SamplerBindingType::Filtering,
),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float {
filterable: true,
},
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let uniform_bind_group =
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("cubes uniform bind group"),
layout: &uniform_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniforms.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&sky_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&sky_sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(
&normal_view,
),
},
],
});
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("cubes pipeline layout"),
bind_group_layouts: &[&uniform_bind_group_layout],
push_constant_ranges: &[],
});
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("cubes shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("../shaders/cubes.wgsl"),
)),
});
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("cubes pipeline"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::desc(), cube::Raw::desc()],
},
primitive: wgpu::PrimitiveState::default(),
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Max,
},
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
multiview: None,
});
let depth_pipeline = DepthPipeline::new(
device,
format,
depth_texture.create_view(&wgpu::TextureViewDescriptor::default()),
);
Self {
pipeline,
cubes: cubes_buffer,
uniforms,
uniform_bind_group,
vertices,
depth_texture_size: target_size,
depth_view,
depth_pipeline,
}
}
fn update_depth_texture(&mut self, device: &wgpu::Device, size: Size<u32>) {
if self.depth_texture_size.height != size.height
|| self.depth_texture_size.width != size.width
{
let text = device.create_texture(&wgpu::TextureDescriptor {
label: Some("cubes depth texture"),
size: wgpu::Extent3d {
width: size.width,
height: size.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
self.depth_view =
text.create_view(&wgpu::TextureViewDescriptor::default());
self.depth_texture_size = size;
self.depth_pipeline.update(device, &text);
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
target_size: Size<u32>,
uniforms: &Uniforms,
num_cubes: usize,
cubes: &[cube::Raw],
) {
//recreate depth texture if surface texture size has changed
self.update_depth_texture(device, target_size);
// update uniforms
queue.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms));
//resize cubes vertex buffer if cubes amount changed
let new_size = num_cubes * std::mem::size_of::<cube::Raw>();
self.cubes.resize(device, new_size as u64);
//always write new cube data since they are constantly rotating
queue.write_buffer(&self.cubes.raw, 0, bytemuck::cast_slice(cubes));
}
pub fn render(
&self,
target: &wgpu::TextureView,
encoder: &mut wgpu::CommandEncoder,
viewport: Rectangle<u32>,
num_cubes: u32,
show_depth: bool,
) {
{
let mut pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("cubes.pipeline.pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: Some(
wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
},
),
timestamp_writes: None,
occlusion_query_set: None,
});
pass.set_scissor_rect(
viewport.x,
viewport.y,
viewport.width,
viewport.height,
);
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
pass.set_vertex_buffer(0, self.vertices.slice(..));
pass.set_vertex_buffer(1, self.cubes.raw.slice(..));
pass.draw(0..36, 0..num_cubes);
}
if show_depth {
self.depth_pipeline.render(encoder, target, viewport);
}
}
}
struct DepthPipeline {
pipeline: wgpu::RenderPipeline,
bind_group_layout: wgpu::BindGroupLayout,
bind_group: wgpu::BindGroup,
sampler: wgpu::Sampler,
depth_view: wgpu::TextureView,
}
impl DepthPipeline {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
depth_texture: wgpu::TextureView,
) -> Self {
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("cubes.depth_pipeline.sampler"),
..Default::default()
});
let bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("cubes.depth_pipeline.bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(
wgpu::SamplerBindingType::NonFiltering,
),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float {
filterable: false,
},
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("cubes.depth_pipeline.bind_group"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(
&depth_texture,
),
},
],
});
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("cubes.depth_pipeline.layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("cubes.depth_pipeline.shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("../shaders/depth.wgsl"),
)),
});
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("cubes.depth_pipeline.pipeline"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
},
primitive: wgpu::PrimitiveState::default(),
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
multiview: None,
});
Self {
pipeline,
bind_group_layout,
bind_group,
sampler,
depth_view: depth_texture,
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
depth_texture: &wgpu::Texture,
) {
self.depth_view =
depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
self.bind_group =
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("cubes.depth_pipeline.bind_group"),
layout: &self.bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(
&self.depth_view,
),
},
],
});
}
pub fn render(
&self,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
viewport: Rectangle<u32>,
) {
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("cubes.pipeline.depth_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(
wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_view,
depth_ops: None,
stencil_ops: None,
},
),
timestamp_writes: None,
occlusion_query_set: None,
});
pass.set_scissor_rect(
viewport.x,
viewport.y,
viewport.width,
viewport.height,
);
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.bind_group, &[]);
pass.draw(0..6, 0..1);
}
}
fn load_skybox_data() -> Vec<u8> {
let pos_x: &[u8] = include_bytes!("../../textures/skybox/pos_x.jpg");
let neg_x: &[u8] = include_bytes!("../../textures/skybox/neg_x.jpg");
let pos_y: &[u8] = include_bytes!("../../textures/skybox/pos_y.jpg");
let neg_y: &[u8] = include_bytes!("../../textures/skybox/neg_y.jpg");
let pos_z: &[u8] = include_bytes!("../../textures/skybox/pos_z.jpg");
let neg_z: &[u8] = include_bytes!("../../textures/skybox/neg_z.jpg");
let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z];
data.iter().fold(vec![], |mut acc, bytes| {
let i = image::load_from_memory_with_format(
bytes,
image::ImageFormat::Jpeg,
)
.unwrap()
.to_rgba8()
.into_raw();
acc.extend(i);
acc
})
}
fn load_normal_map_data() -> Vec<u8> {
let bytes: &[u8] = include_bytes!("../../textures/ice_cube_normal_map.png");
image::load_from_memory_with_format(bytes, image::ImageFormat::Png)
.unwrap()
.to_rgba8()
.into_raw()
}

View file

@ -0,0 +1,41 @@
use crate::wgpu;
// A custom buffer container for dynamic resizing.
pub struct Buffer {
pub raw: wgpu::Buffer,
label: &'static str,
size: u64,
usage: wgpu::BufferUsages,
}
impl Buffer {
pub fn new(
device: &wgpu::Device,
label: &'static str,
size: u64,
usage: wgpu::BufferUsages,
) -> Self {
Self {
raw: device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size,
usage,
mapped_at_creation: false,
}),
label,
size,
usage,
}
}
pub fn resize(&mut self, device: &wgpu::Device, new_size: u64) {
if new_size > self.size {
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(self.label),
size: new_size,
usage: self.usage,
mapped_at_creation: false,
});
}
}
}

View file

@ -0,0 +1,326 @@
use crate::scene::pipeline::Vertex;
use crate::wgpu;
use glam::{vec2, vec3, Vec3};
use rand::{thread_rng, Rng};
/// A single instance of a cube.
#[derive(Debug, Clone)]
pub struct Cube {
pub rotation: glam::Quat,
pub position: Vec3,
pub size: f32,
rotation_dir: f32,
rotation_axis: glam::Vec3,
}
impl Default for Cube {
fn default() -> Self {
Self {
rotation: glam::Quat::IDENTITY,
position: glam::Vec3::ZERO,
size: 0.1,
rotation_dir: 1.0,
rotation_axis: glam::Vec3::Y,
}
}
}
impl Cube {
pub fn new(size: f32, origin: Vec3) -> Self {
let rnd = thread_rng().gen_range(0.0..=1.0f32);
Self {
rotation: glam::Quat::IDENTITY,
position: origin + Vec3::new(0.1, 0.1, 0.1),
size,
rotation_dir: if rnd <= 0.5 { -1.0 } else { 1.0 },
rotation_axis: if rnd <= 0.33 {
glam::Vec3::Y
} else if rnd <= 0.66 {
glam::Vec3::X
} else {
glam::Vec3::Z
},
}
}
pub fn update(&mut self, size: f32, time: f32) {
self.rotation = glam::Quat::from_axis_angle(
self.rotation_axis,
time / 2.0 * self.rotation_dir,
);
self.size = size;
}
}
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)]
#[repr(C)]
pub struct Raw {
transformation: glam::Mat4,
normal: glam::Mat3,
_padding: [f32; 3],
}
impl Raw {
const ATTRIBS: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![
//cube transformation matrix
4 => Float32x4,
5 => Float32x4,
6 => Float32x4,
7 => Float32x4,
//normal rotation matrix
8 => Float32x3,
9 => Float32x3,
10 => Float32x3,
];
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &Self::ATTRIBS,
}
}
}
impl Raw {
pub fn from_cube(cube: &Cube) -> Raw {
Raw {
transformation: glam::Mat4::from_scale_rotation_translation(
glam::vec3(cube.size, cube.size, cube.size),
cube.rotation,
cube.position,
),
normal: glam::Mat3::from_quat(cube.rotation),
_padding: [0.0; 3],
}
}
pub fn vertices() -> [Vertex; 36] {
[
//face 1
Vertex {
pos: vec3(-0.5, -0.5, -0.5),
normal: vec3(0.0, 0.0, -1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 1.0),
},
Vertex {
pos: vec3(0.5, -0.5, -0.5),
normal: vec3(0.0, 0.0, -1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 1.0),
},
Vertex {
pos: vec3(0.5, 0.5, -0.5),
normal: vec3(0.0, 0.0, -1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(0.5, 0.5, -0.5),
normal: vec3(0.0, 0.0, -1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(-0.5, 0.5, -0.5),
normal: vec3(0.0, 0.0, -1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 0.0),
},
Vertex {
pos: vec3(-0.5, -0.5, -0.5),
normal: vec3(0.0, 0.0, -1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 1.0),
},
//face 2
Vertex {
pos: vec3(-0.5, -0.5, 0.5),
normal: vec3(0.0, 0.0, 1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 1.0),
},
Vertex {
pos: vec3(0.5, -0.5, 0.5),
normal: vec3(0.0, 0.0, 1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 1.0),
},
Vertex {
pos: vec3(0.5, 0.5, 0.5),
normal: vec3(0.0, 0.0, 1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(0.5, 0.5, 0.5),
normal: vec3(0.0, 0.0, 1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(-0.5, 0.5, 0.5),
normal: vec3(0.0, 0.0, 1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 0.0),
},
Vertex {
pos: vec3(-0.5, -0.5, 0.5),
normal: vec3(0.0, 0.0, 1.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 1.0),
},
//face 3
Vertex {
pos: vec3(-0.5, 0.5, 0.5),
normal: vec3(-1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(0.0, 1.0),
},
Vertex {
pos: vec3(-0.5, 0.5, -0.5),
normal: vec3(-1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(1.0, 1.0),
},
Vertex {
pos: vec3(-0.5, -0.5, -0.5),
normal: vec3(-1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(-0.5, -0.5, -0.5),
normal: vec3(-1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(-0.5, -0.5, 0.5),
normal: vec3(-1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(0.0, 0.0),
},
Vertex {
pos: vec3(-0.5, 0.5, 0.5),
normal: vec3(-1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(0.0, 1.0),
},
//face 4
Vertex {
pos: vec3(0.5, 0.5, 0.5),
normal: vec3(1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(0.0, 1.0),
},
Vertex {
pos: vec3(0.5, 0.5, -0.5),
normal: vec3(1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(1.0, 1.0),
},
Vertex {
pos: vec3(0.5, -0.5, -0.5),
normal: vec3(1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(0.5, -0.5, -0.5),
normal: vec3(1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(0.5, -0.5, 0.5),
normal: vec3(1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(0.0, 0.0),
},
Vertex {
pos: vec3(0.5, 0.5, 0.5),
normal: vec3(1.0, 0.0, 0.0),
tangent: vec3(0.0, 0.0, -1.0),
uv: vec2(0.0, 1.0),
},
//face 5
Vertex {
pos: vec3(-0.5, -0.5, -0.5),
normal: vec3(0.0, -1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 1.0),
},
Vertex {
pos: vec3(0.5, -0.5, -0.5),
normal: vec3(0.0, -1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 1.0),
},
Vertex {
pos: vec3(0.5, -0.5, 0.5),
normal: vec3(0.0, -1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(0.5, -0.5, 0.5),
normal: vec3(0.0, -1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(-0.5, -0.5, 0.5),
normal: vec3(0.0, -1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 0.0),
},
Vertex {
pos: vec3(-0.5, -0.5, -0.5),
normal: vec3(0.0, -1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 1.0),
},
//face 6
Vertex {
pos: vec3(-0.5, 0.5, -0.5),
normal: vec3(0.0, 1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 1.0),
},
Vertex {
pos: vec3(0.5, 0.5, -0.5),
normal: vec3(0.0, 1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 1.0),
},
Vertex {
pos: vec3(0.5, 0.5, 0.5),
normal: vec3(0.0, 1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(0.5, 0.5, 0.5),
normal: vec3(0.0, 1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(1.0, 0.0),
},
Vertex {
pos: vec3(-0.5, 0.5, 0.5),
normal: vec3(0.0, 1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 0.0),
},
Vertex {
pos: vec3(-0.5, 0.5, -0.5),
normal: vec3(0.0, 1.0, 0.0),
tangent: vec3(1.0, 0.0, 0.0),
uv: vec2(0.0, 1.0),
},
]
}
}

View file

@ -0,0 +1,23 @@
use crate::scene::Camera;
use iced::{Color, Rectangle};
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct Uniforms {
camera_proj: glam::Mat4,
camera_pos: glam::Vec4,
light_color: glam::Vec4,
}
impl Uniforms {
pub fn new(camera: &Camera, bounds: Rectangle, light_color: Color) -> Self {
let camera_proj = camera.build_view_proj_matrix(bounds);
Self {
camera_proj,
camera_pos: camera.position(),
light_color: glam::Vec4::from(light_color.into_linear()),
}
}
}

View file

@ -0,0 +1,31 @@
use crate::wgpu;
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct Vertex {
pub pos: glam::Vec3,
pub normal: glam::Vec3,
pub tangent: glam::Vec3,
pub uv: glam::Vec2,
}
impl Vertex {
const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
//position
0 => Float32x3,
//normal
1 => Float32x3,
//tangent
2 => Float32x3,
//uv
3 => Float32x2,
];
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &Self::ATTRIBS,
}
}
}

View file

@ -0,0 +1,123 @@
struct Uniforms {
projection: mat4x4<f32>,
camera_pos: vec4<f32>,
light_color: vec4<f32>,
}
const LIGHT_POS: vec3<f32> = vec3<f32>(0.0, 3.0, 3.0);
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(0) @binding(1) var sky_texture: texture_cube<f32>;
@group(0) @binding(2) var tex_sampler: sampler;
@group(0) @binding(3) var normal_texture: texture_2d<f32>;
struct Vertex {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) tangent: vec3<f32>,
@location(3) uv: vec2<f32>,
}
struct Cube {
@location(4) matrix_0: vec4<f32>,
@location(5) matrix_1: vec4<f32>,
@location(6) matrix_2: vec4<f32>,
@location(7) matrix_3: vec4<f32>,
@location(8) normal_matrix_0: vec3<f32>,
@location(9) normal_matrix_1: vec3<f32>,
@location(10) normal_matrix_2: vec3<f32>,
}
struct Output {
@builtin(position) clip_pos: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) tangent_pos: vec3<f32>,
@location(2) tangent_camera_pos: vec3<f32>,
@location(3) tangent_light_pos: vec3<f32>,
}
@vertex
fn vs_main(vertex: Vertex, cube: Cube) -> Output {
let cube_matrix = mat4x4<f32>(
cube.matrix_0,
cube.matrix_1,
cube.matrix_2,
cube.matrix_3,
);
let normal_matrix = mat3x3<f32>(
cube.normal_matrix_0,
cube.normal_matrix_1,
cube.normal_matrix_2,
);
//convert to tangent space to calculate lighting in same coordinate space as normal map sample
let tangent = normalize(normal_matrix * vertex.tangent);
let normal = normalize(normal_matrix * vertex.normal);
let bitangent = cross(tangent, normal);
//shift everything into tangent space
let tbn = transpose(mat3x3<f32>(tangent, bitangent, normal));
let world_pos = cube_matrix * vec4<f32>(vertex.position, 1.0);
var out: Output;
out.clip_pos = uniforms.projection * world_pos;
out.uv = vertex.uv;
out.tangent_pos = tbn * world_pos.xyz;
out.tangent_camera_pos = tbn * uniforms.camera_pos.xyz;
out.tangent_light_pos = tbn * LIGHT_POS;
return out;
}
//cube properties
const CUBE_BASE_COLOR: vec4<f32> = vec4<f32>(0.294118, 0.462745, 0.611765, 0.6);
const SHINE_DAMPER: f32 = 1.0;
const REFLECTIVITY: f32 = 0.8;
const REFRACTION_INDEX: f32 = 1.31;
//fog, for the ~* cinematic effect *~
const FOG_DENSITY: f32 = 0.15;
const FOG_GRADIENT: f32 = 8.0;
const FOG_COLOR: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 1.0);
@fragment
fn fs_main(in: Output) -> @location(0) vec4<f32> {
let to_camera = in.tangent_camera_pos - in.tangent_pos;
//normal sample from texture
var normal = textureSample(normal_texture, tex_sampler, in.uv).xyz;
normal = normal * 2.0 - 1.0;
//diffuse
let dir_to_light: vec3<f32> = normalize(in.tangent_light_pos - in.tangent_pos);
let brightness = max(dot(normal, dir_to_light), 0.0);
let diffuse: vec3<f32> = brightness * uniforms.light_color.xyz;
//specular
let dir_to_camera = normalize(to_camera);
let light_dir = -dir_to_light;
let reflected_light_dir = reflect(light_dir, normal);
let specular_factor = max(dot(reflected_light_dir, dir_to_camera), 0.0);
let damped_factor = pow(specular_factor, SHINE_DAMPER);
let specular: vec3<f32> = damped_factor * uniforms.light_color.xyz * REFLECTIVITY;
//fog
let distance = length(to_camera);
let visibility = clamp(exp(-pow((distance * FOG_DENSITY), FOG_GRADIENT)), 0.0, 1.0);
//reflection
let reflection_dir = reflect(dir_to_camera, normal);
let reflection_color = textureSample(sky_texture, tex_sampler, reflection_dir);
let refraction_dir = refract(dir_to_camera, normal, REFRACTION_INDEX);
let refraction_color = textureSample(sky_texture, tex_sampler, refraction_dir);
let final_reflect_color = mix(reflection_color, refraction_color, 0.5);
//mix it all together!
var color = vec4<f32>(CUBE_BASE_COLOR.xyz * diffuse + specular, CUBE_BASE_COLOR.w);
color = mix(color, final_reflect_color, 0.8);
color = mix(FOG_COLOR, color, visibility);
return color;
}

View file

@ -0,0 +1,48 @@
var<private> positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
vec2<f32>(-1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(-1.0, 1.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(1.0, -1.0)
);
var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
vec2<f32>(0.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.0, 0.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0)
);
@group(0) @binding(0) var depth_sampler: sampler;
@group(0) @binding(1) var depth_texture: texture_2d<f32>;
struct Output {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
}
@vertex
fn vs_main(@builtin(vertex_index) v_index: u32) -> Output {
var out: Output;
out.position = vec4<f32>(positions[v_index], 0.0, 1.0);
out.uv = uvs[v_index];
return out;
}
@fragment
fn fs_main(input: Output) -> @location(0) vec4<f32> {
let depth = textureSample(depth_texture, depth_sampler, input.uv).r;
if (depth > .9999) {
discard;
}
let c = 1.0 - depth;
return vec4<f32>(c, c, c, 1.0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["advanced"] }
iced.workspace = true
iced.features = ["advanced"]

View file

@ -5,9 +5,7 @@ A demonstration of how to build a custom widget that draws a circle.
The __[`main`]__ file contains all the code of the example.
<div align="center">
<a href="https://gfycat.com/jealouscornyhomalocephale">
<img src="https://thumbs.gfycat.com/JealousCornyHomalocephale-small.gif">
</a>
<img src="https://iced.rs/examples/custom_widget.gif">
</div>
You can run it with `cargo run`:

View file

@ -43,6 +43,7 @@ mod circle {
fn layout(
&self,
_tree: &mut widget::Tree,
_renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {

View file

@ -6,7 +6,8 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["tokio"] }
iced.workspace = true
iced.features = ["tokio"]
[dependencies.reqwest]
version = "0.11"

View file

@ -5,9 +5,7 @@ A basic application that asynchronously downloads multiple dummy files of 100 MB
The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress.
<div align="center">
<a href="https://gfycat.com/wildearlyafricanwilddog">
<img src="https://thumbs.gfycat.com/WildEarlyAfricanwilddog-small.gif">
</a>
<img src="https://iced.rs/examples/download_progress.gif">
</div>
You can run it with `cargo run`:

View file

@ -123,7 +123,7 @@ impl Download {
| State::Errored { .. } => {
self.state = State::Downloading { progress: 0.0 };
}
_ => {}
State::Downloading { .. } => {}
}
}

View file

@ -0,0 +1,15 @@
[package]
name = "editor"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector@hecrj.dev>"]
edition = "2021"
publish = false
[dependencies]
iced.workspace = true
iced.features = ["highlighter", "tokio", "debug"]
tokio.workspace = true
tokio.features = ["fs"]
rfd = "0.12"

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more