Merge branch 'master' into feat/multi-window-support
53
.cargo/config.toml
Normal 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
|
||||
"""
|
||||
10
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -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
|
|
@ -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
|
||||
24
.github/workflows/audit.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
1
.github/workflows/build.yml
vendored
|
|
@ -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
|
|
@ -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
|
||||
7
.github/workflows/document.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
4
.github/workflows/lint.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
23
.github/workflows/test.yml
vendored
|
|
@ -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
|
|
@ -2,6 +2,5 @@ target/
|
|||
pkg/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
.cargo/
|
||||
dist/
|
||||
traces/
|
||||
|
|
|
|||
132
CHANGELOG.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
157
Cargo.toml
|
|
@ -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 }
|
||||
|
|
|
|||
29
README.md
|
|
@ -9,16 +9,17 @@
|
|||
[](https://github.com/iced-rs/iced/blob/master/LICENSE)
|
||||
[](https://crates.io/crates/iced)
|
||||
[](https://github.com/iced-rs/iced/actions)
|
||||
[](https://discourse.iced.rs/)
|
||||
[](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/
|
||||
|
|
|
|||
116
ROADMAP.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
too-many-arguments-threshold = 20
|
||||
enum-variant-name-threshold = 10
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! [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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
134
core/src/text.rs
|
|
@ -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
|
|
@ -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>),
|
||||
}
|
||||
88
core/src/text/highlighter.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
59
core/src/text/paragraph.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
core/src/window/settings/linux.rs
Normal 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 application’s .desktop file.
|
||||
pub application_id: String,
|
||||
}
|
||||
|
|
@ -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"]))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["canvas", "tokio", "debug"]
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ impl Application for Arc {
|
|||
(
|
||||
Arc {
|
||||
start: Instant::now(),
|
||||
cache: Default::default(),
|
||||
cache: Cache::default(),
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["canvas"]
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ mod bezier {
|
|||
}
|
||||
|
||||
pub fn request_redraw(&mut self) {
|
||||
self.cache.clear()
|
||||
self.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,12 +100,9 @@ mod bezier {
|
|||
bounds: Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> (event::Status, Option<Curve>) {
|
||||
let cursor_position =
|
||||
if let Some(position) = cursor.position_in(bounds) {
|
||||
position
|
||||
} else {
|
||||
return (event::Status::Ignored, None);
|
||||
};
|
||||
let Some(cursor_position) = cursor.position_in(bounds) else {
|
||||
return (event::Status::Ignored, None);
|
||||
};
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse_event) => {
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced.workspace = true
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
<img src="screenshot.png">
|
||||
</div>
|
||||
|
||||
You can run it with `cargo run`:
|
||||
|
||||
```
|
||||
cargo run --package pure_color_palette
|
||||
cargo run --package color_palette
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
};
|
||||
|
||||
|
|
|
|||
10
examples/combo_box/Cargo.toml
Normal 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"]
|
||||
18
examples/combo_box/README.md
Normal 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
|
||||
BIN
examples/combo_box/combobox.gif
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
142
examples/combo_box/src/main.rs
Normal 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",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug", "lazy"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["debug", "lazy"]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["advanced"]
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ mod quad {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &mut widget::Tree,
|
||||
_renderer: &Renderer,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
17
examples/custom_shader/Cargo.toml
Normal 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"
|
||||
163
examples/custom_shader/src/main.rs
Normal 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()
|
||||
}
|
||||
186
examples/custom_shader/src/scene.rs
Normal 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),
|
||||
)
|
||||
}
|
||||
53
examples/custom_shader/src/scene/camera.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
619
examples/custom_shader/src/scene/pipeline.rs
Normal 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()
|
||||
}
|
||||
41
examples/custom_shader/src/scene/pipeline/buffer.rs
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
326
examples/custom_shader/src/scene/pipeline/cube.rs
Normal 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),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
23
examples/custom_shader/src/scene/pipeline/uniforms.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
31
examples/custom_shader/src/scene/pipeline/vertex.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
123
examples/custom_shader/src/shaders/cubes.wgsl
Normal 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;
|
||||
}
|
||||
48
examples/custom_shader/src/shaders/depth.wgsl
Normal 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);
|
||||
}
|
||||
BIN
examples/custom_shader/textures/ice_cube_normal_map.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
examples/custom_shader/textures/skybox/neg_x.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
examples/custom_shader/textures/skybox/neg_y.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
examples/custom_shader/textures/skybox/neg_z.jpg
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
examples/custom_shader/textures/skybox/pos_x.jpg
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
examples/custom_shader/textures/skybox/pos_y.jpg
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
examples/custom_shader/textures/skybox/pos_z.jpg
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["advanced"]
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ mod circle {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &mut widget::Tree,
|
||||
_renderer: &Renderer,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["tokio"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["tokio"]
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11"
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ impl Download {
|
|||
| State::Errored { .. } => {
|
||||
self.state = State::Downloading { progress: 0.0 };
|
||||
}
|
||||
_ => {}
|
||||
State::Downloading { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
15
examples/editor/Cargo.toml
Normal 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"
|
||||