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
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: I have a question
|
- name: I have a question
|
||||||
url: https://github.com/iced-rs/iced/discussions/new?category=q-a
|
url: https://discourse.iced.rs/c/learn/6
|
||||||
about: Open a discussion with a Q&A format.
|
about: Ask and learn from others in the Discourse forum.
|
||||||
- name: I want to start a discussion
|
- name: I want to start a discussion
|
||||||
url: https://github.com/iced-rs/iced/discussions/new
|
url: https://discourse.iced.rs/c/request-feedback/7
|
||||||
about: Open a new discussion if you have any suggestions, ideas, feature requests, or simply want to show off something you've made.
|
about: Share your idea and gather feedback in the Discourse forum.
|
||||||
- name: I want to chat with other users of the library
|
- name: I want to chat with other users of the library
|
||||||
url: https://discord.com/invite/3xZJ65GAhd
|
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
|
name: Audit
|
||||||
on: [push]
|
on:
|
||||||
|
push: {}
|
||||||
|
pull_request: {}
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
jobs:
|
jobs:
|
||||||
dependencies:
|
vulnerabilities:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: hecrj/setup-rust-action@v1
|
- uses: hecrj/setup-rust-action@v1
|
||||||
- name: Install cargo-audit
|
- name: Install cargo-audit
|
||||||
run: cargo install cargo-audit
|
run: cargo install cargo-audit
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Audit dependencies
|
- name: Resolve dependencies
|
||||||
|
run: cargo update
|
||||||
|
- name: Audit vulnerabilities
|
||||||
run: cargo audit
|
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
|
- uses: actions/checkout@master
|
||||||
- name: Enable static CRT linkage
|
- name: Enable static CRT linkage
|
||||||
run: |
|
run: |
|
||||||
mkdir .cargo
|
|
||||||
echo '[target.x86_64-pc-windows-msvc]' >> .cargo/config
|
echo '[target.x86_64-pc-windows-msvc]' >> .cargo/config
|
||||||
echo 'rustflags = ["-Ctarget-feature=+crt-static"]' >> .cargo/config
|
echo 'rustflags = ["-Ctarget-feature=+crt-static"]' >> .cargo/config
|
||||||
- name: Run the application without starting the shell
|
- 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
|
name: Document
|
||||||
on:
|
on: [push, pull_request]
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
jobs:
|
jobs:
|
||||||
all:
|
all:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
@ -18,6 +15,7 @@ jobs:
|
||||||
RUSTDOCFLAGS="--cfg docsrs" \
|
RUSTDOCFLAGS="--cfg docsrs" \
|
||||||
cargo doc --no-deps --all-features \
|
cargo doc --no-deps --all-features \
|
||||||
-p iced_core \
|
-p iced_core \
|
||||||
|
-p iced_highlighter \
|
||||||
-p iced_style \
|
-p iced_style \
|
||||||
-p iced_futures \
|
-p iced_futures \
|
||||||
-p iced_runtime \
|
-p iced_runtime \
|
||||||
|
|
@ -31,6 +29,7 @@ jobs:
|
||||||
- name: Write CNAME file
|
- name: Write CNAME file
|
||||||
run: echo 'docs.iced.rs' > ./target/doc/CNAME
|
run: echo 'docs.iced.rs' > ./target/doc/CNAME
|
||||||
- name: Publish documentation
|
- name: Publish documentation
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
with:
|
with:
|
||||||
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
|
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
|
||||||
|
|
|
||||||
4
.github/workflows/lint.yml
vendored
|
|
@ -2,11 +2,11 @@ name: Lint
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
all:
|
all:
|
||||||
runs-on: ubuntu-latest
|
runs-on: macOS-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: hecrj/setup-rust-action@v1
|
- uses: hecrj/setup-rust-action@v1
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Check lints
|
- 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
|
name: Test
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
native:
|
all:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: --deny warnings
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
|
|
@ -17,25 +19,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
export DEBIAN_FRONTED=noninteractive
|
export DEBIAN_FRONTED=noninteractive
|
||||||
sudo apt-get -qq update
|
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
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
cargo test --verbose --workspace
|
cargo test --verbose --workspace
|
||||||
cargo test --verbose --workspace --all-features
|
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/
|
pkg/
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.cargo/
|
|
||||||
dist/
|
dist/
|
||||||
traces/
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [0.9.0] - 2023-04-13
|
||||||
### Added
|
### Added
|
||||||
|
|
@ -467,7 +596,8 @@ Many thanks to...
|
||||||
### Added
|
### Added
|
||||||
- First release! :tada:
|
- 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.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.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
|
[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.
|
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:
|
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.
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
Besides directly writing code, there are many other different ways you can contribute. To name a few:
|
Besides directly writing code, there are many other different ways you can contribute. To name a few:
|
||||||
|
|
||||||
- Writing tutorials or blog posts
|
- Writing tutorials or blog posts
|
||||||
- Improving the documentation
|
- Improving the documentation
|
||||||
- Submitting bug reports and use cases
|
- 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 ecosystem overview]: ECOSYSTEM.md
|
||||||
[the roadmap]: ROADMAP.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
|
[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
|
[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]
|
[package]
|
||||||
name = "iced"
|
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"
|
description = "A cross-platform GUI library inspired by Elm"
|
||||||
license = "MIT"
|
version.workspace = true
|
||||||
repository = "https://github.com/iced-rs/iced"
|
edition.workspace = true
|
||||||
documentation = "https://docs.rs/iced"
|
authors.workspace = true
|
||||||
readme = "README.md"
|
license.workspace = true
|
||||||
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
repository.workspace = true
|
||||||
categories = ["gui"]
|
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]
|
[features]
|
||||||
default = ["wgpu"]
|
default = ["wgpu"]
|
||||||
# Enable the `wgpu` GPU-accelerated renderer backend
|
# Enable the `wgpu` GPU-accelerated renderer backend
|
||||||
wgpu = ["iced_renderer/wgpu"]
|
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
|
||||||
# Enables the `Image` widget
|
# Enables the `Image` widget
|
||||||
image = ["iced_widget/image", "image_rs"]
|
image = ["iced_widget/image", "dep:image"]
|
||||||
# Enables the `Svg` widget
|
# Enables the `Svg` widget
|
||||||
svg = ["iced_widget/svg"]
|
svg = ["iced_widget/svg"]
|
||||||
# Enables the `Canvas` widget
|
# Enables the `Canvas` widget
|
||||||
|
|
@ -39,45 +45,30 @@ palette = ["iced_core/palette"]
|
||||||
system = ["iced_winit/system"]
|
system = ["iced_winit/system"]
|
||||||
# Enables broken "sRGB linear" blending to reproduce color management of the Web
|
# Enables broken "sRGB linear" blending to reproduce color management of the Web
|
||||||
web-colors = ["iced_renderer/web-colors"]
|
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
|
# Enables the advanced module
|
||||||
advanced = []
|
advanced = []
|
||||||
# Enables experimental multi-window support.
|
# Enables experimental multi-window support.
|
||||||
multi-window = ["iced_winit/multi-window"]
|
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]
|
[dependencies]
|
||||||
iced_core = { version = "0.9", path = "core" }
|
iced_core.workspace = true
|
||||||
iced_futures = { version = "0.6", path = "futures" }
|
iced_futures.workspace = true
|
||||||
iced_renderer = { version = "0.1", path = "renderer" }
|
iced_renderer.workspace = true
|
||||||
iced_widget = { version = "0.1", path = "widget" }
|
iced_widget.workspace = true
|
||||||
iced_winit = { version = "0.9", path = "winit", features = ["application"] }
|
iced_winit.features = ["application"]
|
||||||
thiserror = "1"
|
iced_winit.workspace = true
|
||||||
|
|
||||||
[dependencies.image_rs]
|
iced_highlighter.workspace = true
|
||||||
version = "0.24"
|
iced_highlighter.optional = true
|
||||||
package = "image"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
thiserror.workspace = true
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
features = ["image", "svg", "canvas", "qr_code"]
|
image.workspace = true
|
||||||
|
image.optional = true
|
||||||
|
|
||||||
[profile.release-opt]
|
[profile.release-opt]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
|
@ -88,3 +79,85 @@ incremental = false
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
overflow-checks = false
|
overflow-checks = false
|
||||||
strip = "debuginfo"
|
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://github.com/iced-rs/iced/blob/master/LICENSE)
|
||||||
[](https://crates.io/crates/iced)
|
[](https://crates.io/crates/iced)
|
||||||
[](https://github.com/iced-rs/iced/actions)
|
[](https://github.com/iced-rs/iced/actions)
|
||||||
|
[](https://discourse.iced.rs/)
|
||||||
[](https://discord.gg/3xZJ65GAhd)
|
[](https://discord.gg/3xZJ65GAhd)
|
||||||
|
|
||||||
A cross-platform GUI library for Rust focused on simplicity and type-safety.
|
A cross-platform GUI library for Rust focused on simplicity and type-safety.
|
||||||
Inspired by [Elm].
|
Inspired by [Elm].
|
||||||
|
|
||||||
<a href="https://gfycat.com/littlesanehalicore">
|
<a href="https://iced.rs/examples/todos.mp4">
|
||||||
<img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" width="275px">
|
<img src="https://iced.rs/examples/todos.gif" width="275px">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://gfycat.com/politeadorableiberianmole">
|
<a href="https://iced.rs/examples/tour.mp4">
|
||||||
<img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif" width="273px">
|
<img src="https://iced.rs/examples/tour.gif" width="273px">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</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
|
[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
|
[the Web]: https://github.com/iced-rs/iced_web
|
||||||
[text inputs]: https://gfycat.com/alertcalmcrow-rust-gui
|
[text inputs]: https://iced.rs/examples/text_input.mp4
|
||||||
[scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui
|
[scrollables]: https://iced.rs/examples/scrollable.mp4
|
||||||
[Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee
|
[Debug overlay with performance metrics]: https://iced.rs/examples/debug.mp4
|
||||||
[Modular ecosystem]: ECOSYSTEM.md
|
[Modular ecosystem]: ECOSYSTEM.md
|
||||||
[renderer-agnostic native runtime]: native/
|
[renderer-agnostic native runtime]: runtime/
|
||||||
[`wgpu`]: https://github.com/gfx-rs/wgpu
|
[`wgpu`]: https://github.com/gfx-rs/wgpu
|
||||||
[`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
|
[`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
|
||||||
[`iced_wgpu`]: wgpu/
|
[`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`:
|
Add `iced` as a dependency in your `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
iced = "0.9"
|
iced = "0.10"
|
||||||
```
|
```
|
||||||
|
|
||||||
If your project is using a Rust edition older than 2021, then you will need to
|
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
|
Contributions are greatly appreciated! If you want to contribute, please
|
||||||
read our [contributing guidelines] for more details.
|
read our [contributing guidelines] for more details.
|
||||||
|
|
||||||
Feedback is also welcome! You can open an issue or, if you want to talk,
|
Feedback is also welcome! You can create a new topic in [our Discourse forum] or
|
||||||
come chat to our [Discord server]. Moreover, you can find me (and a bunch of
|
come chat to [our Discord server].
|
||||||
awesome folks) over the `#games-and-graphics` and `#gui-and-ui` channels in
|
|
||||||
the [Rust Community Discord]. I go by `lone_scientist#9554` there.
|
|
||||||
|
|
||||||
## Sponsors
|
## 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 Elm Architecture]: https://guide.elm-lang.org/architecture/
|
||||||
[the current issues]: https://github.com/iced-rs/iced/issues
|
[the current issues]: https://github.com/iced-rs/iced/issues
|
||||||
[contributing guidelines]: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md
|
[contributing guidelines]: https://github.com/iced-rs/iced/blob/master/CONTRIBUTING.md
|
||||||
[Discord server]: https://discord.gg/3xZJ65GAhd
|
[our Discourse forum]: https://discourse.iced.rs/
|
||||||
[Rust Community Discord]: https://bit.ly/rust-community
|
[our Discord server]: https://discord.gg/3xZJ65GAhd
|
||||||
[Cryptowatch]: https://cryptowat.ch/charts
|
[Cryptowatch]: https://cryptowat.ch/charts
|
||||||
[Kraken.com]: https://kraken.com/
|
[Kraken.com]: https://kraken.com/
|
||||||
|
|
|
||||||
116
ROADMAP.md
|
|
@ -1,120 +1,6 @@
|
||||||
# Roadmap
|
# 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.
|
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
|
[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
|
too-many-arguments-threshold = 20
|
||||||
|
enum-variant-name-threshold = 10
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,27 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_core"
|
name = "iced_core"
|
||||||
version = "0.9.0"
|
description = "The essential ideas of iced"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
version.workspace = true
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
description = "The essential concepts of Iced"
|
authors.workspace = true
|
||||||
license = "MIT"
|
license.workspace = true
|
||||||
repository = "https://github.com/iced-rs/iced"
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.2"
|
bitflags.workspace = true
|
||||||
thiserror = "1"
|
log.workspace = true
|
||||||
log = "0.4.17"
|
thiserror.workspace = true
|
||||||
twox-hash = { version = "1.5", default-features = false }
|
xxhash-rust.workspace = true
|
||||||
|
num-traits.workspace = true
|
||||||
|
|
||||||
[dependencies.palette]
|
palette.workspace = true
|
||||||
version = "0.7"
|
palette.optional = true
|
||||||
optional = true
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
instant = "0.1"
|
instant.workspace = true
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.raw-window-handle]
|
[target.'cfg(windows)'.dependencies.raw-window-handle]
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,72 @@
|
||||||
use crate::{Point, Rectangle, Vector};
|
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
|
/// Degrees
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
|
||||||
pub struct Degrees(pub f32);
|
pub struct Degrees(pub f32);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
/// Radians
|
/// Radians
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
|
||||||
pub struct Radians(pub f32);
|
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 {
|
impl From<Degrees> for Radians {
|
||||||
fn from(degrees: Degrees) -> Self {
|
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 {
|
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) {
|
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(
|
let distance_to_rect = f32::max(
|
||||||
f32::abs((bounds.y - bounds.center().y) / v1.y),
|
f32::abs(r.x * bounds.width / 2.0),
|
||||||
f32::abs(((bounds.x + bounds.width) - bounds.center().x) / v1.x),
|
f32::abs(r.y * bounds.height / 2.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let start = bounds.center() + v1 * distance_to_rect;
|
let start = bounds.center() - r * distance_to_rect;
|
||||||
let end = bounds.center() - v1 * distance_to_rect;
|
let end = bounds.center() + r * distance_to_rect;
|
||||||
|
|
||||||
(start, end)
|
(start, end)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#[cfg(feature = "palette")]
|
#[cfg(feature = "palette")]
|
||||||
use palette::rgb::{Srgb, Srgba};
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
/// Red component, 0.0 - 1.0
|
/// 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.
|
/// Converts the [`Color`] into its RGBA8 equivalent.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_rgba8(self) -> [u8; 4] {
|
pub fn into_rgba8(self) -> [u8; 4] {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ use crate::overlay;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::widget::tree::{self, Tree};
|
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::any::Any;
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
|
@ -291,7 +293,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&self, tree: &mut Tree) {
|
fn diff(&self, tree: &mut Tree) {
|
||||||
self.widget.diff(tree)
|
self.widget.diff(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
|
|
@ -304,10 +306,11 @@ where
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.widget.layout(renderer, limits)
|
self.widget.layout(tree, renderer, limits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
@ -325,11 +328,12 @@ where
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&widget::Id>,
|
id: Option<&widget::Id>,
|
||||||
|
bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(
|
operate_on_children: &mut dyn FnMut(
|
||||||
&mut dyn widget::Operation<T>,
|
&mut dyn widget::Operation<T>,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
self.operation.container(id, &mut |operation| {
|
self.operation.container(id, bounds, &mut |operation| {
|
||||||
operate_on_children(&mut MapOperation { operation });
|
operate_on_children(&mut MapOperation { operation });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -346,8 +350,10 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut dyn widget::operation::Scrollable,
|
state: &mut dyn widget::operation::Scrollable,
|
||||||
id: Option<&widget::Id>,
|
id: Option<&widget::Id>,
|
||||||
|
bounds: Rectangle,
|
||||||
|
translation: Vector,
|
||||||
) {
|
) {
|
||||||
self.operation.scrollable(state, id);
|
self.operation.scrollable(state, id, bounds, translation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_input(
|
fn text_input(
|
||||||
|
|
@ -380,6 +386,7 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, B>,
|
shell: &mut Shell<'_, B>,
|
||||||
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
let mut local_messages = Vec::new();
|
let mut local_messages = Vec::new();
|
||||||
let mut local_shell = Shell::new(&mut local_messages);
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
|
|
@ -392,6 +399,7 @@ where
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
&mut local_shell,
|
&mut local_shell,
|
||||||
|
viewport,
|
||||||
);
|
);
|
||||||
|
|
||||||
shell.merge(local_shell, &self.mapper);
|
shell.merge(local_shell, &self.mapper);
|
||||||
|
|
@ -410,7 +418,7 @@ where
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
self.widget
|
self.widget
|
||||||
.draw(tree, renderer, theme, style, layout, cursor, viewport)
|
.draw(tree, renderer, theme, style, layout, cursor, viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
@ -484,10 +492,11 @@ where
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.element.widget.layout(renderer, limits)
|
self.element.widget.layout(tree, renderer, limits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
@ -499,7 +508,7 @@ where
|
||||||
) {
|
) {
|
||||||
self.element
|
self.element
|
||||||
.widget
|
.widget
|
||||||
.operate(state, layout, renderer, operation)
|
.operate(state, layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn on_event(
|
||||||
|
|
@ -511,10 +520,11 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
self.element
|
self.element.widget.on_event(
|
||||||
.widget
|
state, event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||||
.on_event(state, event, layout, cursor, renderer, clipboard, shell)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ pub struct Font {
|
||||||
pub weight: Weight,
|
pub weight: Weight,
|
||||||
/// The [`Stretch`] of the [`Font`].
|
/// The [`Stretch`] of the [`Font`].
|
||||||
pub stretch: Stretch,
|
pub stretch: Stretch,
|
||||||
/// Whether if the [`Font`] is monospaced or not.
|
/// The [`Style`] of the [`Font`].
|
||||||
pub monospaced: bool,
|
pub style: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
|
|
@ -20,13 +20,12 @@ impl Font {
|
||||||
family: Family::SansSerif,
|
family: Family::SansSerif,
|
||||||
weight: Weight::Normal,
|
weight: Weight::Normal,
|
||||||
stretch: Stretch::Normal,
|
stretch: Stretch::Normal,
|
||||||
monospaced: false,
|
style: Style::Normal,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A monospaced font with normal [`Weight`].
|
/// A monospaced font with normal [`Weight`].
|
||||||
pub const MONOSPACE: Font = Font {
|
pub const MONOSPACE: Font = Font {
|
||||||
family: Family::Monospace,
|
family: Family::Monospace,
|
||||||
monospaced: true,
|
|
||||||
..Self::DEFAULT
|
..Self::DEFAULT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -100,3 +99,13 @@ pub enum Stretch {
|
||||||
ExtraExpanded,
|
ExtraExpanded,
|
||||||
UltraExpanded,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
|
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
|
||||||
/// or conically (TBD).
|
/// or conically (TBD).
|
||||||
///
|
|
||||||
/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`].
|
|
||||||
pub enum 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),
|
Linear(Linear),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,8 +94,8 @@ impl Linear {
|
||||||
mut self,
|
mut self,
|
||||||
stops: impl IntoIterator<Item = ColorStop>,
|
stops: impl IntoIterator<Item = ColorStop>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
for stop in stops.into_iter() {
|
for stop in stops {
|
||||||
self = self.add_stop(stop.offset, stop.color)
|
self = self.add_stop(stop.offset, stop.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
/// The hasher used to compare layouts.
|
/// The hasher used to compare layouts.
|
||||||
#[derive(Debug, Default)]
|
#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways.
|
||||||
pub struct Hasher(twox_hash::XxHash64);
|
#[derive(Default)]
|
||||||
|
pub struct Hasher(xxhash_rust::xxh3::Xxh3);
|
||||||
|
|
||||||
impl core::hash::Hasher for Hasher {
|
impl core::hash::Hasher for Hasher {
|
||||||
fn write(&mut self, bytes: &[u8]) {
|
fn write(&mut self, bytes: &[u8]) {
|
||||||
self.0.write(bytes)
|
self.0.write(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self) -> u64 {
|
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.
|
/// A [`Renderer`] that can render raster graphics.
|
||||||
///
|
///
|
||||||
/// [renderer]: crate::renderer
|
/// [renderer]: crate::renderer
|
||||||
|
|
@ -178,5 +188,10 @@ pub trait Renderer: crate::Renderer {
|
||||||
|
|
||||||
/// Draws an image with the given [`Handle`] and inside the provided
|
/// Draws an image with the given [`Handle`] and inside the provided
|
||||||
/// `bounds`.
|
/// `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 limits::Limits;
|
||||||
pub use node::Node;
|
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.
|
/// The bounds of a [`Node`] and its children, using absolute coordinates.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[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::Element;
|
||||||
|
|
||||||
use crate::layout::{Limits, Node};
|
use crate::layout::{Limits, Node};
|
||||||
|
use crate::widget;
|
||||||
use crate::{Alignment, Padding, Point, Size};
|
use crate::{Alignment, Padding, Point, Size};
|
||||||
|
|
||||||
/// The main axis of a flex layout.
|
/// The main axis of a flex layout.
|
||||||
|
|
@ -66,6 +67,7 @@ pub fn resolve<Message, Renderer>(
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
align_items: Alignment,
|
align_items: Alignment,
|
||||||
items: &[Element<'_, Message, Renderer>],
|
items: &[Element<'_, Message, Renderer>],
|
||||||
|
trees: &mut [widget::Tree],
|
||||||
) -> Node
|
) -> Node
|
||||||
where
|
where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
|
|
@ -81,7 +83,7 @@ where
|
||||||
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||||
nodes.resize(items.len(), Node::default());
|
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 {
|
let fill_factor = match axis {
|
||||||
Axis::Horizontal => child.as_widget().width(),
|
Axis::Horizontal => child.as_widget().width(),
|
||||||
Axis::Vertical => child.as_widget().height(),
|
Axis::Vertical => child.as_widget().height(),
|
||||||
|
|
@ -94,7 +96,8 @@ where
|
||||||
let child_limits =
|
let child_limits =
|
||||||
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
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();
|
let size = layout.size();
|
||||||
|
|
||||||
available -= axis.main(size);
|
available -= axis.main(size);
|
||||||
|
|
@ -108,7 +111,7 @@ where
|
||||||
|
|
||||||
let remaining = available.max(0.0);
|
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 {
|
let fill_factor = match axis {
|
||||||
Axis::Horizontal => child.as_widget().width(),
|
Axis::Horizontal => child.as_widget().width(),
|
||||||
Axis::Vertical => child.as_widget().height(),
|
Axis::Vertical => child.as_widget().height(),
|
||||||
|
|
@ -133,7 +136,8 @@ where
|
||||||
Size::new(max_width, max_height),
|
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()));
|
cross = cross.max(axis.cross(layout.size()));
|
||||||
|
|
||||||
nodes[i] = layout;
|
nodes[i] = layout;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
use crate::{Length, Padding, Size};
|
use crate::{Length, Padding, Size};
|
||||||
|
|
||||||
/// A set of size constraints for layouting.
|
/// A set of size constraints for layouting.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Limits {
|
pub struct Limits {
|
||||||
min: Size,
|
min: Size,
|
||||||
max: Size,
|
max: Size,
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,21 @@
|
||||||
//! The core library of [Iced].
|
//! The core library of [Iced].
|
||||||
//!
|
//!
|
||||||
//! This library holds basic types that can be reused and re-exported in
|
//! This library holds basic types that can be reused and re-exported in
|
||||||
//! different runtime implementations. For instance, both [`iced_native`] and
|
//! different runtime implementations.
|
||||||
//! [`iced_web`] are built on top of `iced_core`.
|
|
||||||
//!
|
//!
|
||||||
//! 
|
//! 
|
||||||
//!
|
//!
|
||||||
//! [Iced]: https://github.com/iced-rs/iced
|
//! [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(
|
#![doc(
|
||||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||||
)]
|
)]
|
||||||
|
#![forbid(unsafe_code, rust_2018_idioms)]
|
||||||
#![deny(
|
#![deny(
|
||||||
missing_debug_implementations,
|
missing_debug_implementations,
|
||||||
missing_docs,
|
missing_docs,
|
||||||
unused_results,
|
unused_results,
|
||||||
clippy::extra_unused_lifetimes,
|
rustdoc::broken_intra_doc_links
|
||||||
clippy::from_over_into,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::new_without_default,
|
|
||||||
clippy::useless_conversion
|
|
||||||
)]
|
)]
|
||||||
#![forbid(unsafe_code, rust_2018_idioms)]
|
|
||||||
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
|
|
||||||
pub mod alignment;
|
pub mod alignment;
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ pub enum Kind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Kind {
|
impl Kind {
|
||||||
fn next(&self) -> Kind {
|
fn next(self) -> Kind {
|
||||||
match self {
|
match self {
|
||||||
Kind::Single => Kind::Double,
|
Kind::Single => Kind::Double,
|
||||||
Kind::Double => Kind::Triple,
|
Kind::Double => Kind::Triple,
|
||||||
|
|
@ -61,6 +61,11 @@ impl Click {
|
||||||
self.kind
|
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 {
|
fn is_consecutive(&self, new_position: Point, time: Instant) -> bool {
|
||||||
let duration = if time > self.time {
|
let duration = if time > self.time {
|
||||||
Some(time - self.time)
|
Some(time - self.time)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::mouse;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::widget::Tree;
|
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.
|
/// An interactive component that can be displayed on top of other widgets.
|
||||||
pub trait Overlay<Message, Renderer>
|
pub trait Overlay<Message, Renderer>
|
||||||
|
|
@ -25,10 +25,11 @@ where
|
||||||
///
|
///
|
||||||
/// [`Node`]: layout::Node
|
/// [`Node`]: layout::Node
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&mut self,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
position: Point,
|
position: Point,
|
||||||
|
translation: Vector,
|
||||||
) -> layout::Node;
|
) -> layout::Node;
|
||||||
|
|
||||||
/// Draws the [`Overlay`] using the associated `Renderer`.
|
/// Draws the [`Overlay`] using the associated `Renderer`.
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use std::any::Any;
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Element<'a, Message, Renderer> {
|
pub struct Element<'a, Message, Renderer> {
|
||||||
position: Point,
|
position: Point,
|
||||||
|
translation: Vector,
|
||||||
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,7 +26,11 @@ where
|
||||||
position: Point,
|
position: Point,
|
||||||
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { position, overlay }
|
Self {
|
||||||
|
position,
|
||||||
|
overlay,
|
||||||
|
translation: Vector::ZERO,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the position of the [`Element`].
|
/// Returns the position of the [`Element`].
|
||||||
|
|
@ -36,6 +41,7 @@ where
|
||||||
/// Translates the [`Element`].
|
/// Translates the [`Element`].
|
||||||
pub fn translate(mut self, translation: Vector) -> Self {
|
pub fn translate(mut self, translation: Vector) -> Self {
|
||||||
self.position = self.position + translation;
|
self.position = self.position + translation;
|
||||||
|
self.translation = self.translation + translation;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,19 +54,24 @@ where
|
||||||
{
|
{
|
||||||
Element {
|
Element {
|
||||||
position: self.position,
|
position: self.position,
|
||||||
|
translation: self.translation,
|
||||||
overlay: Box::new(Map::new(self.overlay, f)),
|
overlay: Box::new(Map::new(self.overlay, f)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the layout of the [`Element`] in the given bounds.
|
/// Computes the layout of the [`Element`] in the given bounds.
|
||||||
pub fn layout(
|
pub fn layout(
|
||||||
&self,
|
&mut self,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.overlay
|
self.overlay.layout(
|
||||||
.layout(renderer, bounds, self.position + translation)
|
renderer,
|
||||||
|
bounds,
|
||||||
|
self.position + translation,
|
||||||
|
self.translation + translation,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes a runtime [`Event`].
|
/// Processes a runtime [`Event`].
|
||||||
|
|
@ -98,7 +109,7 @@ where
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
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`].
|
/// Applies a [`widget::Operation`] to the [`Element`].
|
||||||
|
|
@ -150,12 +161,13 @@ where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&mut self,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
position: Point,
|
position: Point,
|
||||||
|
translation: Vector,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.content.layout(renderer, bounds, position)
|
self.content.layout(renderer, bounds, position, translation)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
@ -172,11 +184,12 @@ where
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&widget::Id>,
|
id: Option<&widget::Id>,
|
||||||
|
bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(
|
operate_on_children: &mut dyn FnMut(
|
||||||
&mut dyn widget::Operation<T>,
|
&mut dyn widget::Operation<T>,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
self.operation.container(id, &mut |operation| {
|
self.operation.container(id, bounds, &mut |operation| {
|
||||||
operate_on_children(&mut MapOperation { operation });
|
operate_on_children(&mut MapOperation { operation });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -193,8 +206,10 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut dyn widget::operation::Scrollable,
|
state: &mut dyn widget::operation::Scrollable,
|
||||||
id: Option<&widget::Id>,
|
id: Option<&widget::Id>,
|
||||||
|
bounds: Rectangle,
|
||||||
|
translation: Vector,
|
||||||
) {
|
) {
|
||||||
self.operation.scrollable(state, id);
|
self.operation.scrollable(state, id, bounds, translation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_input(
|
fn text_input(
|
||||||
|
|
@ -202,7 +217,7 @@ where
|
||||||
state: &mut dyn widget::operation::TextInput,
|
state: &mut dyn widget::operation::TextInput,
|
||||||
id: Option<&widget::Id>,
|
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>) {
|
fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) {
|
||||||
|
|
@ -259,7 +274,7 @@ where
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) {
|
) {
|
||||||
self.content.draw(renderer, theme, style, layout, cursor)
|
self.content.draw(renderer, theme, style, layout, cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_over(
|
fn is_over(
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
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`]
|
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
|
||||||
/// children.
|
/// children.
|
||||||
|
|
@ -61,17 +63,16 @@ where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&mut self,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
position: Point,
|
_position: Point,
|
||||||
|
translation: Vector,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let translation = position - Point::ORIGIN;
|
|
||||||
|
|
||||||
layout::Node::with_children(
|
layout::Node::with_children(
|
||||||
bounds,
|
bounds,
|
||||||
self.children
|
self.children
|
||||||
.iter()
|
.iter_mut()
|
||||||
.map(|child| child.layout(renderer, bounds, translation))
|
.map(|child| child.layout(renderer, bounds, translation))
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
|
|
@ -138,12 +139,12 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
operation: &mut dyn widget::Operation<Message>,
|
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(
|
self.children.iter_mut().zip(layout.children()).for_each(
|
||||||
|(child, layout)| {
|
|(child, layout)| {
|
||||||
child.operate(layout, renderer, operation);
|
child.operate(layout, renderer, operation);
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,9 @@ impl Rectangle<f32> {
|
||||||
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
|
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
|
||||||
pub fn contains(&self, point: Point) -> bool {
|
pub fn contains(&self, point: Point) -> bool {
|
||||||
self.x <= point.x
|
self.x <= point.x
|
||||||
&& point.x <= self.x + self.width
|
&& point.x < self.x + self.width
|
||||||
&& self.y <= point.y
|
&& 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
|
/// 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)]
|
#[cfg(debug_assertions)]
|
||||||
pub use null::Null;
|
pub use null::Null;
|
||||||
|
|
||||||
use crate::layout;
|
use crate::{Background, BorderRadius, Color, Rectangle, Vector};
|
||||||
use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};
|
|
||||||
|
|
||||||
/// A component that can be used by widgets to draw themselves on a screen.
|
/// A component that can be used by widgets to draw themselves on a screen.
|
||||||
pub trait Renderer: Sized {
|
pub trait Renderer: Sized {
|
||||||
/// The supported theme of the [`Renderer`].
|
/// The supported theme of the [`Renderer`].
|
||||||
type Theme;
|
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.
|
/// Draws the primitives recorded in the given closure in a new layer.
|
||||||
///
|
///
|
||||||
/// The layer will clip its contents to the provided `bounds`.
|
/// The layer will clip its contents to the provided `bounds`.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
use crate::alignment;
|
||||||
use crate::renderer::{self, Renderer};
|
use crate::renderer::{self, Renderer};
|
||||||
use crate::text::{self, Text};
|
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;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -41,6 +42,8 @@ impl Renderer for Null {
|
||||||
|
|
||||||
impl text::Renderer for Null {
|
impl text::Renderer for Null {
|
||||||
type Font = Font;
|
type Font = Font;
|
||||||
|
type Paragraph = ();
|
||||||
|
type Editor = ();
|
||||||
|
|
||||||
const ICON_FONT: Font = Font::DEFAULT;
|
const ICON_FONT: Font = Font::DEFAULT;
|
||||||
const CHECKMARK_ICON: char = '0';
|
const CHECKMARK_ICON: char = '0';
|
||||||
|
|
@ -50,37 +53,117 @@ impl text::Renderer for Null {
|
||||||
Font::default()
|
Font::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_size(&self) -> f32 {
|
fn default_size(&self) -> Pixels {
|
||||||
16.0
|
Pixels(16.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||||
|
|
||||||
fn measure(
|
fn fill_paragraph(
|
||||||
&self,
|
&mut self,
|
||||||
_content: &str,
|
_paragraph: &Self::Paragraph,
|
||||||
_size: f32,
|
_position: Point,
|
||||||
_line_height: text::LineHeight,
|
_color: Color,
|
||||||
_font: Font,
|
) {
|
||||||
_bounds: Size,
|
|
||||||
_shaping: text::Shaping,
|
|
||||||
) -> Size {
|
|
||||||
Size::new(0.0, 20.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hit_test(
|
fn fill_editor(
|
||||||
&self,
|
&mut self,
|
||||||
_contents: &str,
|
_editor: &Self::Editor,
|
||||||
_size: f32,
|
_position: Point,
|
||||||
_line_height: text::LineHeight,
|
_color: Color,
|
||||||
_font: Self::Font,
|
) {
|
||||||
_bounds: Size,
|
}
|
||||||
_shaping: text::Shaping,
|
|
||||||
_point: Point,
|
fn fill_text(
|
||||||
_nearest_only: bool,
|
&mut self,
|
||||||
) -> Option<text::Hit> {
|
_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
|
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);
|
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) {
|
pub fn request_redraw(&mut self, request: window::RedrawRequest) {
|
||||||
match self.redraw_request {
|
match self.redraw_request {
|
||||||
None => {
|
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> {
|
pub fn redraw_request(&self) -> Option<window::RedrawRequest> {
|
||||||
self.redraw_request
|
self.redraw_request
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +71,7 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
if self.is_layout_invalid {
|
if self.is_layout_invalid {
|
||||||
self.is_layout_invalid = false;
|
self.is_layout_invalid = false;
|
||||||
|
|
||||||
f()
|
f();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
134
core/src/text.rs
|
|
@ -1,6 +1,15 @@
|
||||||
//! Draw and interact with text.
|
//! 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::alignment;
|
||||||
use crate::{Color, Pixels, Point, Rectangle, Size};
|
use crate::{Color, Pixels, Point, Size};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
@ -12,17 +21,14 @@ pub struct Text<'a, Font> {
|
||||||
pub content: &'a str,
|
pub content: &'a str,
|
||||||
|
|
||||||
/// The bounds of the paragraph.
|
/// The bounds of the paragraph.
|
||||||
pub bounds: Rectangle,
|
pub bounds: Size,
|
||||||
|
|
||||||
/// The size of the [`Text`] in logical pixels.
|
/// The size of the [`Text`] in logical pixels.
|
||||||
pub size: f32,
|
pub size: Pixels,
|
||||||
|
|
||||||
/// The line height of the [`Text`].
|
/// The line height of the [`Text`].
|
||||||
pub line_height: LineHeight,
|
pub line_height: LineHeight,
|
||||||
|
|
||||||
/// The color of the [`Text`].
|
|
||||||
pub color: Color,
|
|
||||||
|
|
||||||
/// The font of the [`Text`].
|
/// The font of the [`Text`].
|
||||||
pub font: Font,
|
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`].
|
/// A renderer capable of measuring and drawing [`Text`].
|
||||||
pub trait Renderer: crate::Renderer {
|
pub trait Renderer: crate::Renderer {
|
||||||
/// The font type used.
|
/// 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.
|
/// The icon font of the backend.
|
||||||
const ICON_FONT: Self::Font;
|
const ICON_FONT: Self::Font;
|
||||||
|
|
@ -151,62 +190,35 @@ pub trait Renderer: crate::Renderer {
|
||||||
fn default_font(&self) -> Self::Font;
|
fn default_font(&self) -> Self::Font;
|
||||||
|
|
||||||
/// Returns the default size of [`Text`].
|
/// Returns the default size of [`Text`].
|
||||||
fn default_size(&self) -> f32;
|
fn default_size(&self) -> Pixels;
|
||||||
|
|
||||||
/// 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>;
|
|
||||||
|
|
||||||
/// Loads a [`Self::Font`] from its bytes.
|
/// Loads a [`Self::Font`] from its bytes.
|
||||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
||||||
|
|
||||||
/// Draws the given [`Text`].
|
/// Draws the given [`Paragraph`] at the given position and with the given
|
||||||
fn fill_text(&mut self, text: Text<'_, Self::Font>);
|
/// [`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
|
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
|
||||||
/// `Mesh2D` primitive in [`iced_wgpu`].
|
/// `Mesh2D` primitive in [`iced_wgpu`].
|
||||||
///
|
///
|
||||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
|
/// [examples]: https://github.com/iced-rs/iced/tree/0.10/examples
|
||||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool
|
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.10/examples/bezier_tool
|
||||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget
|
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.10/examples/custom_widget
|
||||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry
|
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.10/examples/geometry
|
||||||
/// [`lyon`]: https://github.com/nical/lyon
|
/// [`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>
|
pub trait Widget<Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
|
|
@ -55,6 +55,7 @@ where
|
||||||
/// user interface.
|
/// user interface.
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node;
|
) -> layout::Node;
|
||||||
|
|
@ -62,7 +63,7 @@ where
|
||||||
/// Draws the [`Widget`] using the associated `Renderer`.
|
/// Draws the [`Widget`] using the associated `Renderer`.
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
state: &Tree,
|
tree: &Tree,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Renderer::Theme,
|
theme: &Renderer::Theme,
|
||||||
style: &renderer::Style,
|
style: &renderer::Style,
|
||||||
|
|
@ -115,6 +116,7 @@ where
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
_shell: &mut Shell<'_, Message>,
|
_shell: &mut Shell<'_, Message>,
|
||||||
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ pub use scrollable::Scrollable;
|
||||||
pub use text_input::TextInput;
|
pub use text_input::TextInput;
|
||||||
|
|
||||||
use crate::widget::Id;
|
use crate::widget::Id;
|
||||||
|
use crate::{Rectangle, Vector};
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -23,6 +24,7 @@ pub trait Operation<T> {
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
|
bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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>) {}
|
fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
|
||||||
|
|
||||||
/// Operates on a widget that can be scrolled.
|
/// 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.
|
/// Operates on a widget that has text input.
|
||||||
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
|
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
|
||||||
|
|
@ -92,6 +101,7 @@ where
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
|
bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
||||||
) {
|
) {
|
||||||
struct MapRef<'a, A> {
|
struct MapRef<'a, A> {
|
||||||
|
|
@ -102,11 +112,12 @@ where
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
|
bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
||||||
) {
|
) {
|
||||||
let Self { operation, .. } = self;
|
let Self { operation, .. } = self;
|
||||||
|
|
||||||
operation.container(id, &mut |operation| {
|
operation.container(id, bounds, &mut |operation| {
|
||||||
operate_on_children(&mut MapRef { operation });
|
operate_on_children(&mut MapRef { operation });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -115,8 +126,10 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut dyn Scrollable,
|
state: &mut dyn Scrollable,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
|
bounds: Rectangle,
|
||||||
|
translation: Vector,
|
||||||
) {
|
) {
|
||||||
self.operation.scrollable(state, id);
|
self.operation.scrollable(state, id, bounds, translation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focusable(
|
fn focusable(
|
||||||
|
|
@ -145,15 +158,21 @@ where
|
||||||
MapRef {
|
MapRef {
|
||||||
operation: operation.as_mut(),
|
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>) {
|
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
|
||||||
self.operation.focusable(state, id);
|
self.operation.focusable(state, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
|
fn scrollable(
|
||||||
self.operation.scrollable(state, id);
|
&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>) {
|
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||||
|
|
@ -197,6 +216,7 @@ pub fn scope<T: 'static>(
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
|
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
|
||||||
) {
|
) {
|
||||||
if id == Some(&self.target) {
|
if id == Some(&self.target) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! Operate on widgets that can be focused.
|
//! Operate on widgets that can be focused.
|
||||||
use crate::widget::operation::{Operation, Outcome};
|
use crate::widget::operation::{Operation, Outcome};
|
||||||
use crate::widget::Id;
|
use crate::widget::Id;
|
||||||
|
use crate::Rectangle;
|
||||||
|
|
||||||
/// The internal state of a widget that can be focused.
|
/// The internal state of a widget that can be focused.
|
||||||
pub trait Focusable {
|
pub trait Focusable {
|
||||||
|
|
@ -45,9 +46,10 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||||
) {
|
) {
|
||||||
operate_on_children(self)
|
operate_on_children(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self) -> Outcome<T> {
|
fn finish(&self) -> Outcome<T> {
|
||||||
|
|
@ -126,9 +129,10 @@ pub fn focus_previous<T>() -> impl Operation<T> {
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),
|
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),
|
||||||
) {
|
) {
|
||||||
operate_on_children(self)
|
operate_on_children(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self) -> Outcome<Id> {
|
fn finish(&self) -> Outcome<Id> {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! Operate on widgets that can be scrolled.
|
//! Operate on widgets that can be scrolled.
|
||||||
use crate::widget::{Id, Operation};
|
use crate::widget::{Id, Operation};
|
||||||
|
use crate::{Rectangle, Vector};
|
||||||
|
|
||||||
/// The internal state of a widget that can be scrolled.
|
/// The internal state of a widget that can be scrolled.
|
||||||
pub trait Scrollable {
|
pub trait Scrollable {
|
||||||
|
|
@ -22,12 +23,19 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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 {
|
if Some(&self.target) == id {
|
||||||
state.snap_to(self.offset);
|
state.snap_to(self.offset);
|
||||||
}
|
}
|
||||||
|
|
@ -49,12 +57,19 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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 {
|
if Some(&self.target) == id {
|
||||||
state.scroll_to(self.offset);
|
state.scroll_to(self.offset);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! Operate on widgets that have text input.
|
//! Operate on widgets that have text input.
|
||||||
use crate::widget::operation::Operation;
|
use crate::widget::operation::Operation;
|
||||||
use crate::widget::Id;
|
use crate::widget::Id;
|
||||||
|
use crate::Rectangle;
|
||||||
|
|
||||||
/// The internal state of a widget that has text input.
|
/// The internal state of a widget that has text input.
|
||||||
pub trait TextInput {
|
pub trait TextInput {
|
||||||
|
|
@ -34,9 +35,10 @@ pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&Id>,
|
_id: Option<&Id>,
|
||||||
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
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::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::text;
|
use crate::text::{self, Paragraph};
|
||||||
use crate::widget::Tree;
|
use crate::widget::tree::{self, Tree};
|
||||||
use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget};
|
use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ where
|
||||||
Renderer::Theme: StyleSheet,
|
Renderer::Theme: StyleSheet,
|
||||||
{
|
{
|
||||||
content: Cow<'a, str>,
|
content: Cow<'a, str>,
|
||||||
size: Option<f32>,
|
size: Option<Pixels>,
|
||||||
line_height: LineHeight,
|
line_height: LineHeight,
|
||||||
width: Length,
|
width: Length,
|
||||||
height: Length,
|
height: Length,
|
||||||
|
|
@ -53,7 +53,7 @@ where
|
||||||
|
|
||||||
/// Sets the size of the [`Text`].
|
/// Sets the size of the [`Text`].
|
||||||
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
|
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
|
||||||
self.size = Some(size.into().0);
|
self.size = Some(size.into());
|
||||||
self
|
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>
|
impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
Renderer::Theme: StyleSheet,
|
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 {
|
fn width(&self) -> Length {
|
||||||
self.width
|
self.width
|
||||||
}
|
}
|
||||||
|
|
@ -132,30 +144,29 @@ where
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let limits = limits.width(self.width).height(self.height);
|
layout(
|
||||||
|
tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
|
||||||
let size = self.size.unwrap_or_else(|| renderer.default_size());
|
renderer,
|
||||||
|
limits,
|
||||||
let bounds = renderer.measure(
|
self.width,
|
||||||
|
self.height,
|
||||||
&self.content,
|
&self.content,
|
||||||
size,
|
|
||||||
self.line_height,
|
self.line_height,
|
||||||
self.font.unwrap_or_else(|| renderer.default_font()),
|
self.size,
|
||||||
limits.max(),
|
self.font,
|
||||||
|
self.horizontal_alignment,
|
||||||
|
self.vertical_alignment,
|
||||||
self.shaping,
|
self.shaping,
|
||||||
);
|
)
|
||||||
|
|
||||||
let size = limits.resolve(bounds);
|
|
||||||
|
|
||||||
layout::Node::new(size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Tree,
|
tree: &Tree,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Renderer::Theme,
|
theme: &Renderer::Theme,
|
||||||
style: &renderer::Style,
|
style: &renderer::Style,
|
||||||
|
|
@ -163,22 +174,60 @@ where
|
||||||
_cursor_position: mouse::Cursor,
|
_cursor_position: mouse::Cursor,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
|
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||||
|
|
||||||
draw(
|
draw(
|
||||||
renderer,
|
renderer,
|
||||||
style,
|
style,
|
||||||
layout,
|
layout,
|
||||||
&self.content,
|
state,
|
||||||
self.size,
|
|
||||||
self.line_height,
|
|
||||||
self.font,
|
|
||||||
theme.appearance(self.style.clone()),
|
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.
|
/// Draws text using the same logic as the [`Text`] widget.
|
||||||
///
|
///
|
||||||
/// Specifically:
|
/// Specifically:
|
||||||
|
|
@ -193,44 +242,31 @@ pub fn draw<Renderer>(
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
style: &renderer::Style,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
content: &str,
|
state: &State<Renderer::Paragraph>,
|
||||||
size: Option<f32>,
|
|
||||||
line_height: LineHeight,
|
|
||||||
font: Option<Renderer::Font>,
|
|
||||||
appearance: Appearance,
|
appearance: Appearance,
|
||||||
horizontal_alignment: alignment::Horizontal,
|
|
||||||
vertical_alignment: alignment::Vertical,
|
|
||||||
shaping: Shaping,
|
|
||||||
) where
|
) where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
|
let State(ref paragraph) = state;
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let x = match horizontal_alignment {
|
let x = match paragraph.horizontal_alignment() {
|
||||||
alignment::Horizontal::Left => bounds.x,
|
alignment::Horizontal::Left => bounds.x,
|
||||||
alignment::Horizontal::Center => bounds.center_x(),
|
alignment::Horizontal::Center => bounds.center_x(),
|
||||||
alignment::Horizontal::Right => bounds.x + bounds.width,
|
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::Top => bounds.y,
|
||||||
alignment::Vertical::Center => bounds.center_y(),
|
alignment::Vertical::Center => bounds.center_y(),
|
||||||
alignment::Vertical::Bottom => bounds.y + bounds.height,
|
alignment::Vertical::Bottom => bounds.y + bounds.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
renderer.fill_paragraph(
|
||||||
|
paragraph,
|
||||||
renderer.fill_text(crate::Text {
|
Point::new(x, y),
|
||||||
content,
|
appearance.color.unwrap_or(style.text_color),
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Text<'a, Renderer>>
|
impl<'a, Message, Renderer> From<Text<'a, Renderer>>
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ impl Tree {
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
if self.tag == new.borrow().tag() {
|
if self.tag == new.borrow().tag() {
|
||||||
new.borrow().diff(self)
|
new.borrow().diff(self);
|
||||||
} else {
|
} else {
|
||||||
*self = Self::new(new);
|
*self = Self::new(new);
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +78,7 @@ impl Tree {
|
||||||
new_children,
|
new_children,
|
||||||
|tree, widget| tree.diff(widget.borrow()),
|
|tree, widget| tree.diff(widget.borrow()),
|
||||||
|widget| Self::new(widget.borrow()),
|
|widget| Self::new(widget.borrow()),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reconciliates the children of the tree with the provided list of widgets using custom
|
/// 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.
|
/// The identifier of some widget state.
|
||||||
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||||
pub struct Tag(any::TypeId);
|
pub struct Tag(any::TypeId);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! Build window-based GUI applications.
|
//! Build window-based GUI applications.
|
||||||
pub mod icon;
|
pub mod icon;
|
||||||
|
pub mod settings;
|
||||||
|
|
||||||
mod event;
|
mod event;
|
||||||
mod id;
|
mod id;
|
||||||
|
|
@ -7,7 +8,6 @@ mod level;
|
||||||
mod mode;
|
mod mode;
|
||||||
mod position;
|
mod position;
|
||||||
mod redraw_request;
|
mod redraw_request;
|
||||||
mod settings;
|
|
||||||
mod user_attention;
|
mod user_attention;
|
||||||
|
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::Size;
|
||||||
|
|
||||||
use std::mem;
|
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(
|
pub fn from_rgba(
|
||||||
rgba: Vec<u8>,
|
rgba: Vec<u8>,
|
||||||
width: u32,
|
width: u32,
|
||||||
|
|
@ -49,7 +49,7 @@ impl Icon {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[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 {
|
pub enum Error {
|
||||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
||||||
/// safely interpreted as 32bpp RGBA pixels.
|
/// safely interpreted as 32bpp RGBA pixels.
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub enum RedrawRequest {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::time::{Duration, Instant};
|
use crate::time::{Duration, Instant};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ordering() {
|
fn ordering() {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::window::{Icon, Level, Position};
|
//! Configure your windows.
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[path = "settings/windows.rs"]
|
#[path = "settings/windows.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
@ -8,6 +7,10 @@ mod platform;
|
||||||
#[path = "settings/macos.rs"]
|
#[path = "settings/macos.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[path = "settings/linux.rs"]
|
||||||
|
mod platform;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[path = "settings/wasm.rs"]
|
#[path = "settings/wasm.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
@ -15,13 +18,15 @@ mod platform;
|
||||||
#[cfg(not(any(
|
#[cfg(not(any(
|
||||||
target_os = "windows",
|
target_os = "windows",
|
||||||
target_os = "macos",
|
target_os = "macos",
|
||||||
|
target_os = "linux",
|
||||||
target_arch = "wasm32"
|
target_arch = "wasm32"
|
||||||
)))]
|
)))]
|
||||||
#[path = "settings/other.rs"]
|
#[path = "settings/other.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
||||||
pub use platform::PlatformSpecific;
|
use crate::window::{Icon, Level, Position};
|
||||||
|
|
||||||
|
pub use platform::PlatformSpecific;
|
||||||
/// The window settings of an application.
|
/// The window settings of an application.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
|
|
@ -70,8 +75,8 @@ pub struct Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
fn default() -> Settings {
|
fn default() -> Self {
|
||||||
Settings {
|
Self {
|
||||||
size: (1024, 768),
|
size: (1024, 768),
|
||||||
position: Position::default(),
|
position: Position::default(),
|
||||||
min_size: None,
|
min_size: None,
|
||||||
|
|
@ -82,8 +87,8 @@ impl Default for Settings {
|
||||||
transparent: false,
|
transparent: false,
|
||||||
level: Level::default(),
|
level: Level::default(),
|
||||||
icon: None,
|
icon: None,
|
||||||
platform_specific: Default::default(),
|
|
||||||
exit_on_close_request: true,
|
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]]:
|
def get_merged_prs_since_release(repo: str, previous_release_branch: str) -> List[Tuple[str, int, str, str]]:
|
||||||
prs = []
|
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)
|
compare_response = requests.get(compare_url, headers=HEADERS)
|
||||||
|
|
||||||
if compare_response.status_code == 200:
|
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:
|
if match:
|
||||||
pr_number = int(match.group(1))
|
pr_number = int(match.group(1))
|
||||||
pr_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
|
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)
|
pr_response = requests.get(pr_url, headers=HEADERS)
|
||||||
|
|
||||||
if pr_response.status_code == 200:
|
if pr_response.status_code == 200:
|
||||||
pr_data = pr_response.json()
|
pr_data = pr_response.json()
|
||||||
prs.append((pr_data["title"], pr_number, pr_data["html_url"], pr_data["user"]["login"]))
|
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__.
|
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">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/politeadorableiberianmole">
|
<a href="https://iced.rs/examples/tour.mp4">
|
||||||
<img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif">
|
<img src="https://iced.rs/examples/tour.gif">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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.
|
The example code is located in the __[`main`](todos/src/main.rs)__ file.
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/littlesanehalicore">
|
<a href="https://iced.rs/examples/todos.mp4">
|
||||||
<img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" height="400px">
|
<img src="https://iced.rs/examples/todos.gif" height="400px">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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.
|
The relevant code is located in the __[`main`](game_of_life/src/main.rs)__ file.
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/briefaccurateaardvark">
|
<img src="https://iced.rs/examples/game_of_life.gif">
|
||||||
<img src="https://thumbs.gfycat.com/BriefAccurateAardvark-size_restricted.gif">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can run it with `cargo run`:
|
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.
|
The example code is located in the __[`main`](styling/src/main.rs)__ file.
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif">
|
<img src="https://iced.rs/examples/styling.gif">
|
||||||
<img src="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif" height="400px">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can run it with `cargo run`:
|
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">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/gloomyweakhammerheadshark">
|
<img src="https://iced.rs/examples/coffee.gif">
|
||||||
<img src="https://thumbs.gfycat.com/GloomyWeakHammerheadshark-small.gif">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35
|
[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
iced.workspace = true
|
||||||
|
iced.features = ["canvas", "tokio", "debug"]
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ impl Application for Arc {
|
||||||
(
|
(
|
||||||
Arc {
|
Arc {
|
||||||
start: Instant::now(),
|
start: Instant::now(),
|
||||||
cache: Default::default(),
|
cache: Cache::default(),
|
||||||
},
|
},
|
||||||
Command::none(),
|
Command::none(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[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.
|
The __[`main`]__ file contains all the code of the example.
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/soulfulinfiniteantbear">
|
<img src="https://iced.rs/examples/bezier_tool.gif">
|
||||||
<img src="https://thumbs.gfycat.com/SoulfulInfiniteAntbear-small.gif">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can run it with `cargo run`:
|
You can run it with `cargo run`:
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ mod bezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_redraw(&mut self) {
|
pub fn request_redraw(&mut self) {
|
||||||
self.cache.clear()
|
self.cache.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,12 +100,9 @@ mod bezier {
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> (event::Status, Option<Curve>) {
|
) -> (event::Status, Option<Curve>) {
|
||||||
let cursor_position =
|
let Some(cursor_position) = cursor.position_in(bounds) else {
|
||||||
if let Some(position) = cursor.position_in(bounds) {
|
return (event::Status::Ignored, None);
|
||||||
position
|
};
|
||||||
} else {
|
|
||||||
return (event::Status::Ignored, None);
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse_event) => {
|
Event::Mouse(mouse_event) => {
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../.." }
|
iced.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,7 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
iced.workspace = true
|
||||||
time = { version = "0.3.5", features = ["local-offset"] }
|
iced.features = ["canvas", "tokio", "debug"]
|
||||||
|
|
||||||
|
time = { version = "0.3", features = ["local-offset"] }
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ impl Application for Clock {
|
||||||
Clock {
|
Clock {
|
||||||
now: time::OffsetDateTime::now_local()
|
now: time::OffsetDateTime::now_local()
|
||||||
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
|
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
|
||||||
clock: Default::default(),
|
clock: Cache::default(),
|
||||||
},
|
},
|
||||||
Command::none(),
|
Command::none(),
|
||||||
)
|
)
|
||||||
|
|
@ -141,7 +141,7 @@ impl<Message> canvas::Program<Message, Renderer> for Clock {
|
||||||
frame.with_save(|frame| {
|
frame.with_save(|frame| {
|
||||||
frame.rotate(hand_rotation(self.now.second(), 60));
|
frame.rotate(hand_rotation(self.now.second(), 60));
|
||||||
frame.stroke(&long_hand, thin_stroke());
|
frame.stroke(&long_hand, thin_stroke());
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
vec![clock]
|
vec![clock]
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,7 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["canvas", "palette"] }
|
iced.workspace = true
|
||||||
palette = "0.7.0"
|
iced.features = ["canvas", "palette"]
|
||||||
|
|
||||||
|
palette.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,11 @@
|
||||||
A color palette generator, based on a user-defined root color.
|
A color palette generator, based on a user-defined root color.
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/dirtylonebighornsheep">
|
<img src="screenshot.png">
|
||||||
<img src="screenshot.png">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can run it with `cargo run`:
|
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::canvas::{self, Canvas, Frame, Geometry, Path};
|
||||||
use iced::widget::{column, row, text, Slider};
|
use iced::widget::{column, row, text, Slider};
|
||||||
use iced::{
|
use iced::{
|
||||||
Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings,
|
Color, Element, Length, Pixels, Point, Rectangle, Renderer, Sandbox,
|
||||||
Size, Vector,
|
Settings, Size, Vector,
|
||||||
};
|
};
|
||||||
use palette::{
|
use palette::{
|
||||||
self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
|
self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
|
||||||
|
|
@ -168,7 +168,7 @@ impl Theme {
|
||||||
let mut text = canvas::Text {
|
let mut text = canvas::Text {
|
||||||
horizontal_alignment: alignment::Horizontal::Center,
|
horizontal_alignment: alignment::Horizontal::Center,
|
||||||
vertical_alignment: alignment::Vertical::Top,
|
vertical_alignment: alignment::Vertical::Top,
|
||||||
size: 15.0,
|
size: Pixels(15.0),
|
||||||
..canvas::Text::default()
|
..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
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["debug", "lazy"] }
|
iced.workspace = true
|
||||||
|
iced.features = ["debug", "lazy"]
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,8 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[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.
|
The __[`main`]__ file contains all the code of the example.
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/fairdeadcatbird">
|
<img src="https://iced.rs/examples/counter.gif">
|
||||||
<img src="https://thumbs.gfycat.com/FairDeadCatbird-small.gif">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can run it with `cargo run`:
|
You can run it with `cargo run`:
|
||||||
|
|
@ -15,4 +13,12 @@ You can run it with `cargo run`:
|
||||||
cargo run --package counter
|
cargo run --package counter
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The web version can be run with [`trunk`]:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd examples/counter
|
||||||
|
trunk serve
|
||||||
|
```
|
||||||
|
|
||||||
[`main`]: src/main.rs
|
[`main`]: src/main.rs
|
||||||
|
[`trunk`]: https://trunkrs.dev/
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["advanced"] }
|
iced.workspace = true
|
||||||
|
iced.features = ["advanced"]
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ mod quad {
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
_tree: &mut widget::Tree,
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
_limits: &layout::Limits,
|
_limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> 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
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[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.
|
The __[`main`]__ file contains all the code of the example.
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/jealouscornyhomalocephale">
|
<img src="https://iced.rs/examples/custom_widget.gif">
|
||||||
<img src="https://thumbs.gfycat.com/JealousCornyHomalocephale-small.gif">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can run it with `cargo run`:
|
You can run it with `cargo run`:
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ mod circle {
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
|
_tree: &mut widget::Tree,
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
_limits: &layout::Limits,
|
_limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["tokio"] }
|
iced.workspace = true
|
||||||
|
iced.features = ["tokio"]
|
||||||
|
|
||||||
[dependencies.reqwest]
|
[dependencies.reqwest]
|
||||||
version = "0.11"
|
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.
|
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">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/wildearlyafricanwilddog">
|
<img src="https://iced.rs/examples/download_progress.gif">
|
||||||
<img src="https://thumbs.gfycat.com/WildEarlyAfricanwilddog-small.gif">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can run it with `cargo run`:
|
You can run it with `cargo run`:
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ impl Download {
|
||||||
| State::Errored { .. } => {
|
| State::Errored { .. } => {
|
||||||
self.state = State::Downloading { progress: 0.0 };
|
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"
|
||||||