Merge branch 'master' into beacon
This commit is contained in:
commit
aaf396256e
284 changed files with 18747 additions and 15450 deletions
|
|
@ -1,53 +1,2 @@
|
|||
[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
|
||||
"""
|
||||
lint = "clippy --workspace --benches --all-features --no-deps -- -D warnings"
|
||||
|
|
|
|||
2
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
2
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
|
|
@ -28,7 +28,7 @@ body:
|
|||
|
||||
If you have any issues running any of the examples, make sure your graphics drivers are up-to-date. If the issues persist, please report them to the authors of the libraries directly!
|
||||
|
||||
[the `wgpu` examples]: https://github.com/gfx-rs/wgpu/tree/master/wgpu/examples
|
||||
[the `wgpu` examples]: https://github.com/gfx-rs/wgpu/tree/trunk/examples
|
||||
[the `glow` examples]: https://github.com/grovesNL/glow/tree/main/examples
|
||||
options:
|
||||
- label: My hardware is compatible and my graphics drivers are up-to-date.
|
||||
|
|
|
|||
4
.github/workflows/audit.yml
vendored
4
.github/workflows/audit.yml
vendored
|
|
@ -8,7 +8,7 @@ jobs:
|
|||
vulnerabilities:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
- uses: actions/checkout@master
|
||||
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
# artifacts:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: hecrj/setup-rust-action@v1
|
||||
# - uses: hecrj/setup-rust-action@v2
|
||||
# - name: Install cargo-outdated
|
||||
# run: cargo install cargo-outdated
|
||||
# - uses: actions/checkout@master
|
||||
|
|
|
|||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
|
|
@ -7,7 +7,7 @@ jobs:
|
|||
todos_linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
- uses: actions/checkout@master
|
||||
|
|
@ -36,7 +36,7 @@ jobs:
|
|||
todos_windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
- uses: actions/checkout@master
|
||||
- name: Enable static CRT linkage
|
||||
run: |
|
||||
|
|
@ -56,7 +56,7 @@ jobs:
|
|||
todos_macos:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
- uses: actions/checkout@master
|
||||
- name: Build todos binary
|
||||
env:
|
||||
|
|
@ -73,7 +73,7 @@ jobs:
|
|||
todos_raspberry:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
- uses: actions/checkout@master
|
||||
- name: Install cross
|
||||
run: cargo install cross
|
||||
|
|
|
|||
20
.github/workflows/check.yml
vendored
20
.github/workflows/check.yml
vendored
|
|
@ -1,20 +1,12 @@
|
|||
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
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
rust-version: stable
|
||||
targets: wasm32-unknown-unknown
|
||||
|
|
@ -25,5 +17,11 @@ jobs:
|
|||
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
|
||||
|
||||
widget:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
- uses: actions/checkout@master
|
||||
- name: Check standalone `iced_widget` crate
|
||||
run: cargo check --package iced_widget --features image,svg,canvas
|
||||
|
|
|
|||
5
.github/workflows/document.yml
vendored
5
.github/workflows/document.yml
vendored
|
|
@ -6,7 +6,7 @@ jobs:
|
|||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
rust-version: nightly-2023-12-11
|
||||
- uses: actions/checkout@v2
|
||||
|
|
@ -16,7 +16,6 @@ jobs:
|
|||
cargo doc --no-deps --all-features \
|
||||
-p iced_core \
|
||||
-p iced_highlighter \
|
||||
-p iced_style \
|
||||
-p iced_futures \
|
||||
-p iced_runtime \
|
||||
-p iced_graphics \
|
||||
|
|
@ -28,6 +27,8 @@ jobs:
|
|||
-p iced
|
||||
- name: Write CNAME file
|
||||
run: echo 'docs.iced.rs' > ./target/doc/CNAME
|
||||
- name: Copy redirect file as index.html
|
||||
run: cp docs/redirect.html target/doc/index.html
|
||||
- name: Publish documentation
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
|
|
|
|||
2
.github/workflows/format.yml
vendored
2
.github/workflows/format.yml
vendored
|
|
@ -4,7 +4,7 @@ jobs:
|
|||
all:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
components: rustfmt
|
||||
- uses: actions/checkout@master
|
||||
|
|
|
|||
9
.github/workflows/lint.yml
vendored
9
.github/workflows/lint.yml
vendored
|
|
@ -2,11 +2,16 @@ name: Lint
|
|||
on: [push, pull_request]
|
||||
jobs:
|
||||
all:
|
||||
runs-on: macOS-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
components: clippy
|
||||
- uses: actions/checkout@master
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
export DEBIAN_FRONTED=noninteractive
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -y libxkbcommon-dev libgtk-3-dev
|
||||
- name: Check lints
|
||||
run: cargo lint
|
||||
|
|
|
|||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
rust: [stable, beta]
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- uses: actions/checkout@master
|
||||
|
|
|
|||
58
Cargo.toml
58
Cargo.toml
|
|
@ -10,6 +10,9 @@ homepage.workspace = true
|
|||
categories.workspace = true
|
||||
keywords.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
all-features = true
|
||||
|
|
@ -18,9 +21,11 @@ all-features = true
|
|||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[features]
|
||||
default = ["wgpu", "fira-sans"]
|
||||
default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"]
|
||||
# Enable the `wgpu` GPU-accelerated renderer backend
|
||||
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
|
||||
# Enable the `tiny-skia` software renderer backend
|
||||
tiny-skia = ["iced_renderer/tiny-skia"]
|
||||
# Enables the `Image` widget
|
||||
image = ["iced_widget/image", "dep:image"]
|
||||
# Enables the `Svg` widget
|
||||
|
|
@ -39,8 +44,6 @@ tokio = ["iced_futures/tokio"]
|
|||
async-std = ["iced_futures/async-std"]
|
||||
# Enables `smol` as the `executor::Default` on native platforms
|
||||
smol = ["iced_futures/smol"]
|
||||
# Enables advanced color conversion via `palette`
|
||||
palette = ["iced_core/palette"]
|
||||
# Enables querying system information
|
||||
system = ["iced_winit/system"]
|
||||
# Enables broken "sRGB linear" blending to reproduce color management of the Web
|
||||
|
|
@ -52,9 +55,11 @@ highlighter = ["iced_highlighter"]
|
|||
# Enables experimental multi-window support.
|
||||
multi-window = ["iced_winit/multi-window"]
|
||||
# Enables the advanced module
|
||||
advanced = []
|
||||
advanced = ["iced_core/advanced", "iced_widget/advanced"]
|
||||
# Enables embedding Fira Sans as the default font on Wasm builds
|
||||
fira-sans = ["iced_renderer/fira-sans"]
|
||||
# Enables auto-detecting light/dark mode for the built-in theme
|
||||
auto-detect-theme = ["iced_core/auto-detect-theme"]
|
||||
|
||||
[dependencies]
|
||||
iced_core.workspace = true
|
||||
|
|
@ -73,6 +78,15 @@ thiserror.workspace = true
|
|||
image.workspace = true
|
||||
image.optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5"
|
||||
iced_wgpu.workspace = true
|
||||
|
||||
[[bench]]
|
||||
name = "wgpu"
|
||||
harness = false
|
||||
required-features = ["canvas"]
|
||||
|
||||
[profile.release-opt]
|
||||
inherits = "release"
|
||||
codegen-units = 1
|
||||
|
|
@ -93,7 +107,6 @@ members = [
|
|||
"renderer",
|
||||
"runtime",
|
||||
"sentinel",
|
||||
"style",
|
||||
"tiny_skia",
|
||||
"wgpu",
|
||||
"widget",
|
||||
|
|
@ -121,7 +134,6 @@ iced_highlighter = { version = "0.13.0-dev", path = "highlighter" }
|
|||
iced_renderer = { version = "0.13.0-dev", path = "renderer" }
|
||||
iced_runtime = { version = "0.13.0-dev", path = "runtime" }
|
||||
iced_sentinel = { version = "0.13.0-dev", path = "sentinel" }
|
||||
iced_style = { version = "0.13.0-dev", path = "style" }
|
||||
iced_tiny_skia = { version = "0.13.0-dev", path = "tiny_skia" }
|
||||
iced_wgpu = { version = "0.13.0-dev", path = "wgpu" }
|
||||
iced_widget = { version = "0.13.0-dev", path = "widget" }
|
||||
|
|
@ -131,10 +143,12 @@ async-std = "1.0"
|
|||
bincode = "1.3"
|
||||
bitflags = "2.0"
|
||||
bytemuck = { version = "1.0", features = ["derive"] }
|
||||
bytes = "1.6"
|
||||
cosmic-text = "0.10"
|
||||
dark-light = "1.0"
|
||||
futures = "0.3"
|
||||
glam = "0.25"
|
||||
glyphon = "0.5"
|
||||
glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "f07e7bab705e69d39a5e6e52c73039a93c4552f8" }
|
||||
guillotiere = "0.6"
|
||||
half = "2.2"
|
||||
image = "0.24"
|
||||
|
|
@ -162,13 +176,37 @@ 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.67"
|
||||
web-time = "0.2"
|
||||
web-time = "1.1"
|
||||
wgpu = "0.19"
|
||||
winapi = "0.3"
|
||||
window_clipboard = "0.4.1"
|
||||
winit = { git = "https://github.com/iced-rs/winit.git", rev = "b91e39ece2c0d378c3b80da7f3ab50e17bb798a5" }
|
||||
winit = { git = "https://github.com/iced-rs/winit.git", rev = "8affa522bc6dcc497d332a28c03491d22a22f5a7" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
rust_2018_idioms = "deny"
|
||||
missing_debug_implementations = "deny"
|
||||
missing_docs = "deny"
|
||||
unsafe_code = "deny"
|
||||
unused_results = "deny"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
type-complexity = "allow"
|
||||
semicolon_if_nothing_returned = "deny"
|
||||
trivially-copy-pass-by-ref = "deny"
|
||||
default_trait_access = "deny"
|
||||
match-wildcard-for-single-variants = "deny"
|
||||
redundant-closure-for-method-calls = "deny"
|
||||
filter_map_next = "deny"
|
||||
manual_let_else = "deny"
|
||||
unused_async = "deny"
|
||||
from_over_into = "deny"
|
||||
needless_borrow = "deny"
|
||||
new_without_default = "deny"
|
||||
useless_conversion = "deny"
|
||||
|
||||
[workspace.lints.rustdoc]
|
||||
broken_intra_doc_links = "forbid"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ pkgs.mkShell rec {
|
|||
freetype
|
||||
freetype.dev
|
||||
libGL
|
||||
pkgconfig
|
||||
pkg-config
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
|
|
|
|||
32
README.md
32
README.md
|
|
@ -98,8 +98,8 @@ that can be incremented and decremented using two buttons.
|
|||
We start by modelling the __state__ of our application:
|
||||
|
||||
```rust
|
||||
#[derive(Default)]
|
||||
struct Counter {
|
||||
// The counter value
|
||||
value: i32,
|
||||
}
|
||||
```
|
||||
|
|
@ -110,8 +110,8 @@ the button presses. These interactions are our __messages__:
|
|||
```rust
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Message {
|
||||
IncrementPressed,
|
||||
DecrementPressed,
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -126,15 +126,15 @@ impl Counter {
|
|||
// We use a column: a simple vertical layout
|
||||
column![
|
||||
// The increment button. We tell it to produce an
|
||||
// `IncrementPressed` message when pressed
|
||||
button("+").on_press(Message::IncrementPressed),
|
||||
// `Increment` message when pressed
|
||||
button("+").on_press(Message::Increment),
|
||||
|
||||
// We show the value of the counter here
|
||||
text(self.value).size(50),
|
||||
|
||||
// The decrement button. We tell it to produce a
|
||||
// `DecrementPressed` message when pressed
|
||||
button("-").on_press(Message::DecrementPressed),
|
||||
// `Decrement` message when pressed
|
||||
button("-").on_press(Message::Decrement),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -149,10 +149,10 @@ impl Counter {
|
|||
|
||||
pub fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::IncrementPressed => {
|
||||
Message::Increment => {
|
||||
self.value += 1;
|
||||
}
|
||||
Message::DecrementPressed => {
|
||||
Message::Decrement => {
|
||||
self.value -= 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -160,15 +160,22 @@ impl Counter {
|
|||
}
|
||||
```
|
||||
|
||||
And that's everything! We just wrote a whole user interface. Iced is now able
|
||||
to:
|
||||
And that's everything! We just wrote a whole user interface. Let's run it:
|
||||
|
||||
```rust
|
||||
fn main() -> iced::Result {
|
||||
iced::run("A cool counter", Counter::update, Counter::view)
|
||||
}
|
||||
```
|
||||
|
||||
Iced will automatically:
|
||||
|
||||
1. Take the result of our __view logic__ and layout its widgets.
|
||||
1. Process events from our system and produce __messages__ for our
|
||||
__update logic__.
|
||||
1. Draw the resulting user interface.
|
||||
|
||||
Browse the [documentation] and the [examples] to learn more!
|
||||
Read the [book], the [documentation], and the [examples] to learn more!
|
||||
|
||||
## Implementation details
|
||||
|
||||
|
|
@ -208,6 +215,7 @@ come chat to [our Discord server].
|
|||
|
||||
The development of Iced is sponsored by the [Cryptowatch] team at [Kraken.com]
|
||||
|
||||
[book]: https://book.iced.rs/
|
||||
[documentation]: https://docs.rs/iced/
|
||||
[examples]: https://github.com/iced-rs/iced/tree/master/examples
|
||||
[Coffee]: https://github.com/hecrj/coffee
|
||||
|
|
|
|||
9
benches/ipsum.txt
Normal file
9
benches/ipsum.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at elit mollis, dictum nunc non, tempus metus. Sed iaculis ac mauris eu lobortis. Integer elementum venenatis eros, id placerat odio feugiat vel. Maecenas consequat convallis tincidunt. Nunc eu lorem justo. Praesent quis ornare sapien. Aliquam interdum tortor ut rhoncus faucibus. Suspendisse molestie scelerisque nulla, eget sodales lacus sodales vel. Nunc placerat id arcu sodales venenatis. Praesent ullamcorper viverra nibh eget efficitur. Aliquam molestie felis vehicula, finibus sapien eget, accumsan purus. Praesent vestibulum eleifend consectetur. Sed tincidunt lectus a libero efficitur, non scelerisque lectus tincidunt.
|
||||
|
||||
Cras ullamcorper tincidunt tellus non tempor. Integer pulvinar turpis quam, nec pharetra purus egestas non. Vivamus sed ipsum consequat, dignissim ante et, suscipit nibh. Quisque et mauris eu erat rutrum cursus. Pellentesque ut neque eu neque eleifend auctor ac hendrerit dolor. Morbi eget egestas ex. Integer hendrerit ipsum in enim bibendum, at vehicula ipsum dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce tempus consectetur tortor, vel fermentum sem pulvinar eget. Maecenas rutrum fringilla eros a pellentesque. Cras quis magna consectetur, tristique massa vel, aliquet nunc. Aliquam erat volutpat. Suspendisse porttitor risus id auctor fermentum. Vivamus efficitur tellus sed tortor cursus tincidunt. Sed auctor varius arcu, non congue tellus vehicula finibus.
|
||||
|
||||
Fusce a tincidunt urna. Nunc at quam ac enim tempor vehicula imperdiet in sapien. Donec lobortis tristique felis vel semper. Quisque vulputate felis eu enim vestibulum malesuada. Fusce a lobortis mauris, iaculis eleifend ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus sodales vel elit dignissim mattis.
|
||||
|
||||
Aliquam placerat vulputate dignissim. Proin pellentesque vitae arcu ut feugiat. Nunc mi felis, ornare at gravida sed, vestibulum sed urna. Duis fermentum maximus viverra. Donec imperdiet pellentesque sollicitudin. Cras non sem quis metus bibendum molestie. Duis imperdiet nec lectus eu rutrum. Mauris congue enim purus, in iaculis arcu dapibus ut. Nullam id erat tincidunt, iaculis dolor non, lobortis magna. Proin convallis scelerisque maximus. Morbi at lorem fringilla libero blandit fringilla. Ut aliquet tellus non sem dictum viverra. Aenean venenatis purus eget lacus placerat, non mollis mauris pellentesque.
|
||||
|
||||
Etiam elit diam, aliquet quis suscipit non, condimentum viverra odio. Praesent mi enim, suscipit id mi in, rhoncus ultricies lorem. Nulla facilisi. Integer convallis sagittis euismod. Vestibulum porttitor sodales turpis ac accumsan. Nullam molestie turpis vel lacus tincidunt, sed finibus erat pharetra. Nullam vestibulum turpis id sollicitudin accumsan. Praesent eget posuere lacus. Donec vehicula, nisl nec suscipit porta, felis lorem gravida orci, a hendrerit tellus nibh sit amet elit.
|
||||
227
benches/wgpu.rs
Normal file
227
benches/wgpu.rs
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
#![allow(missing_docs)]
|
||||
use criterion::{criterion_group, criterion_main, Bencher, Criterion};
|
||||
|
||||
use iced::alignment;
|
||||
use iced::mouse;
|
||||
use iced::widget::{canvas, scrollable, stack, text};
|
||||
use iced::{
|
||||
Color, Element, Font, Length, Pixels, Point, Rectangle, Size, Theme,
|
||||
};
|
||||
use iced_wgpu::Renderer;
|
||||
|
||||
criterion_main!(benches);
|
||||
criterion_group!(benches, wgpu_benchmark);
|
||||
|
||||
#[allow(unused_results)]
|
||||
pub fn wgpu_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("wgpu — canvas (light)", |b| {
|
||||
benchmark(b, |_| scene(10));
|
||||
});
|
||||
c.bench_function("wgpu — canvas (heavy)", |b| {
|
||||
benchmark(b, |_| scene(1_000));
|
||||
});
|
||||
|
||||
c.bench_function("wgpu - layered text (light)", |b| {
|
||||
benchmark(b, |_| layered_text(10));
|
||||
});
|
||||
c.bench_function("wgpu - layered text (heavy)", |b| {
|
||||
benchmark(b, |_| layered_text(1_000));
|
||||
});
|
||||
|
||||
c.bench_function("wgpu - dynamic text (light)", |b| {
|
||||
benchmark(b, |i| dynamic_text(1_000, i));
|
||||
});
|
||||
c.bench_function("wgpu - dynamic text (heavy)", |b| {
|
||||
benchmark(b, |i| dynamic_text(100_000, i));
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark<'a>(
|
||||
bencher: &mut Bencher<'_>,
|
||||
view: impl Fn(usize) -> Element<'a, (), Theme, Renderer>,
|
||||
) {
|
||||
use iced_futures::futures::executor;
|
||||
use iced_wgpu::graphics;
|
||||
use iced_wgpu::graphics::Antialiasing;
|
||||
use iced_wgpu::wgpu;
|
||||
use iced_winit::core;
|
||||
use iced_winit::runtime;
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::all(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let adapter = executor::block_on(instance.request_adapter(
|
||||
&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
compatible_surface: None,
|
||||
force_fallback_adapter: false,
|
||||
},
|
||||
))
|
||||
.expect("request adapter");
|
||||
|
||||
let (device, queue) = executor::block_on(adapter.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits::default(),
|
||||
},
|
||||
None,
|
||||
))
|
||||
.expect("request device");
|
||||
|
||||
let format = wgpu::TextureFormat::Bgra8UnormSrgb;
|
||||
|
||||
let mut engine = iced_wgpu::Engine::new(
|
||||
&adapter,
|
||||
&device,
|
||||
&queue,
|
||||
format,
|
||||
Some(Antialiasing::MSAAx4),
|
||||
);
|
||||
|
||||
let mut renderer =
|
||||
Renderer::new(&device, &engine, Font::DEFAULT, Pixels::from(16));
|
||||
|
||||
let viewport =
|
||||
graphics::Viewport::with_physical_size(Size::new(3840, 2160), 2.0);
|
||||
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: None,
|
||||
size: wgpu::Extent3d {
|
||||
width: 3840,
|
||||
height: 2160,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let texture_view =
|
||||
texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut i = 0;
|
||||
let mut cache = Some(runtime::user_interface::Cache::default());
|
||||
|
||||
bencher.iter(|| {
|
||||
let mut user_interface = runtime::UserInterface::build(
|
||||
view(i),
|
||||
viewport.logical_size(),
|
||||
cache.take().unwrap(),
|
||||
&mut renderer,
|
||||
);
|
||||
|
||||
let _ = user_interface.draw(
|
||||
&mut renderer,
|
||||
&Theme::Dark,
|
||||
&core::renderer::Style {
|
||||
text_color: Color::WHITE,
|
||||
},
|
||||
mouse::Cursor::Unavailable,
|
||||
);
|
||||
|
||||
cache = Some(user_interface.into_cache());
|
||||
|
||||
let mut encoder =
|
||||
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: None,
|
||||
});
|
||||
|
||||
renderer.present(
|
||||
&mut engine,
|
||||
&device,
|
||||
&queue,
|
||||
&mut encoder,
|
||||
Some(Color::BLACK),
|
||||
format,
|
||||
&texture_view,
|
||||
&viewport,
|
||||
);
|
||||
|
||||
let submission = engine.submit(&queue, encoder);
|
||||
let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission));
|
||||
|
||||
i += 1;
|
||||
});
|
||||
}
|
||||
|
||||
fn scene<'a, Message: 'a>(n: usize) -> Element<'a, Message, Theme, Renderer> {
|
||||
struct Scene {
|
||||
n: usize,
|
||||
}
|
||||
|
||||
impl<Message, Theme> canvas::Program<Message, Theme, Renderer> for Scene {
|
||||
type State = canvas::Cache<Renderer>;
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
cache: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry<Renderer>> {
|
||||
vec![cache.draw(renderer, bounds.size(), |frame| {
|
||||
for i in 0..self.n {
|
||||
frame.fill_rectangle(
|
||||
Point::new(0.0, i as f32),
|
||||
Size::new(10.0, 10.0),
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
|
||||
for i in 0..self.n {
|
||||
frame.fill_text(canvas::Text {
|
||||
content: i.to_string(),
|
||||
position: Point::new(0.0, i as f32),
|
||||
color: Color::BLACK,
|
||||
size: Pixels::from(16),
|
||||
line_height: text::LineHeight::default(),
|
||||
font: Font::DEFAULT,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
shaping: text::Shaping::Basic,
|
||||
});
|
||||
}
|
||||
})]
|
||||
}
|
||||
}
|
||||
|
||||
canvas(Scene { n })
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn layered_text<'a, Message: 'a>(
|
||||
n: usize,
|
||||
) -> Element<'a, Message, Theme, Renderer> {
|
||||
stack((0..n).map(|i| text(format!("I am paragraph {i}!")).into()))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn dynamic_text<'a, Message: 'a>(
|
||||
n: usize,
|
||||
i: usize,
|
||||
) -> Element<'a, Message, Theme, Renderer> {
|
||||
const LOREM_IPSUM: &str = include_str!("ipsum.txt");
|
||||
|
||||
scrollable(
|
||||
text(format!(
|
||||
"{}... Iteration {i}",
|
||||
std::iter::repeat(LOREM_IPSUM.chars())
|
||||
.flatten()
|
||||
.take(n)
|
||||
.collect::<String>(),
|
||||
))
|
||||
.size(10),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
|
@ -10,18 +10,28 @@ homepage.workspace = true
|
|||
categories.workspace = true
|
||||
keywords.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
auto-detect-theme = ["dep:dark-light"]
|
||||
advanced = []
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
bytes.workspace = true
|
||||
glam.workspace = true
|
||||
log.workspace = true
|
||||
num-traits.workspace = true
|
||||
once_cell.workspace = true
|
||||
palette.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
smol_str.workspace = true
|
||||
thiserror.workspace = true
|
||||
web-time.workspace = true
|
||||
xxhash-rust.workspace = true
|
||||
|
||||
palette.workspace = true
|
||||
palette.optional = true
|
||||
dark-light.workspace = true
|
||||
dark-light.optional = true
|
||||
|
||||
serde.workspace = true
|
||||
serde.optional = true
|
||||
|
|
|
|||
|
|
@ -1,12 +1,75 @@
|
|||
use crate::{Point, Rectangle, Vector};
|
||||
|
||||
use std::f32::consts::{FRAC_PI_2, PI};
|
||||
use std::ops::{Add, AddAssign, Div, Mul, RangeInclusive, Sub, SubAssign};
|
||||
use std::ops::{Add, AddAssign, Div, Mul, RangeInclusive, Rem, Sub, SubAssign};
|
||||
|
||||
/// Degrees
|
||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Degrees(pub f32);
|
||||
|
||||
impl Degrees {
|
||||
/// The range of degrees of a circle.
|
||||
pub const RANGE: RangeInclusive<Self> = Self(0.0)..=Self(360.0);
|
||||
}
|
||||
|
||||
impl PartialEq<f32> for Degrees {
|
||||
fn eq(&self, other: &f32) -> bool {
|
||||
self.0.eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<f32> for Degrees {
|
||||
fn partial_cmp(&self, other: &f32) -> Option<std::cmp::Ordering> {
|
||||
self.0.partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Degrees {
|
||||
fn from(degrees: f32) -> Self {
|
||||
Self(degrees)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Degrees {
|
||||
fn from(degrees: u8) -> Self {
|
||||
Self(f32::from(degrees))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Degrees> for f32 {
|
||||
fn from(degrees: Degrees) -> Self {
|
||||
degrees.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Degrees> for f64 {
|
||||
fn from(degrees: Degrees) -> Self {
|
||||
Self::from(degrees.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Degrees {
|
||||
type Output = Degrees;
|
||||
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Self(self.0 * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl num_traits::FromPrimitive for Degrees {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
/// Radians
|
||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Radians(pub f32);
|
||||
|
|
@ -53,6 +116,12 @@ impl From<u8> for Radians {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Radians> for f32 {
|
||||
fn from(radians: Radians) -> Self {
|
||||
radians.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Radians> for f64 {
|
||||
fn from(radians: Radians) -> Self {
|
||||
Self::from(radians.0)
|
||||
|
|
@ -95,6 +164,14 @@ impl Add for Radians {
|
|||
}
|
||||
}
|
||||
|
||||
impl Add<Degrees> for Radians {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Degrees) -> Self::Output {
|
||||
Self(self.0 + rhs.0.to_radians())
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Radians {
|
||||
fn add_assign(&mut self, rhs: Radians) {
|
||||
self.0 = self.0 + rhs.0;
|
||||
|
|
@ -140,3 +217,23 @@ impl Div for Radians {
|
|||
Self(self.0 / rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem for Radians {
|
||||
type Output = Self;
|
||||
|
||||
fn rem(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 % rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<f32> for Radians {
|
||||
fn eq(&self, other: &f32) -> bool {
|
||||
self.0.eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<f32> for Radians {
|
||||
fn partial_cmp(&self, other: &f32) -> Option<std::cmp::Ordering> {
|
||||
self.0.partial_cmp(other)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,19 @@ pub enum Background {
|
|||
// TODO: Add image variant
|
||||
}
|
||||
|
||||
impl Background {
|
||||
/// Scales the alpha channel of the [`Background`] by the given
|
||||
/// factor.
|
||||
pub fn scale_alpha(self, factor: f32) -> Self {
|
||||
match self {
|
||||
Self::Color(color) => Self::Color(color.scale_alpha(factor)),
|
||||
Self::Gradient(gradient) => {
|
||||
Self::Gradient(gradient.scale_alpha(factor))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for Background {
|
||||
fn from(color: Color) -> Self {
|
||||
Background::Color(color)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! Draw lines around containers.
|
||||
use crate::Color;
|
||||
use crate::{Color, Pixels};
|
||||
|
||||
/// A border.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
|
|
@ -15,11 +15,38 @@ pub struct Border {
|
|||
}
|
||||
|
||||
impl Border {
|
||||
/// Creates a new default [`Border`] with the given [`Radius`].
|
||||
pub fn with_radius(radius: impl Into<Radius>) -> Self {
|
||||
/// Creates a new default rounded [`Border`] with the given [`Radius`].
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_core::Border;
|
||||
/// #
|
||||
/// assert_eq!(Border::rounded(10), Border::default().with_radius(10));
|
||||
/// ```
|
||||
pub fn rounded(radius: impl Into<Radius>) -> Self {
|
||||
Self::default().with_radius(radius)
|
||||
}
|
||||
|
||||
/// Updates the [`Color`] of the [`Border`].
|
||||
pub fn with_color(self, color: impl Into<Color>) -> Self {
|
||||
Self {
|
||||
color: color.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the [`Radius`] of the [`Border`].
|
||||
pub fn with_radius(self, radius: impl Into<Radius>) -> Self {
|
||||
Self {
|
||||
radius: radius.into(),
|
||||
..Self::default()
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the width of the [`Border`].
|
||||
pub fn with_width(self, width: impl Into<Pixels>) -> Self {
|
||||
Self {
|
||||
width: width.into().0,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
#[cfg(feature = "palette")]
|
||||
use palette::rgb::{Srgb, Srgba};
|
||||
|
||||
/// A color in the `sRGB` color space.
|
||||
|
|
@ -152,6 +151,14 @@ impl Color {
|
|||
pub fn inverse(self) -> Color {
|
||||
Color::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a)
|
||||
}
|
||||
|
||||
/// Scales the alpha channel of the [`Color`] by the given factor.
|
||||
pub fn scale_alpha(self, factor: f32) -> Color {
|
||||
Self {
|
||||
a: self.a * factor,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f32; 3]> for Color {
|
||||
|
|
@ -203,7 +210,6 @@ macro_rules! color {
|
|||
}};
|
||||
}
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
/// Converts from palette's `Rgba` type to a [`Color`].
|
||||
impl From<Srgba> for Color {
|
||||
fn from(rgba: Srgba) -> Self {
|
||||
|
|
@ -211,7 +217,6 @@ impl From<Srgba> for Color {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
/// Converts from [`Color`] to palette's `Rgba` type.
|
||||
impl From<Color> for Srgba {
|
||||
fn from(c: Color) -> Self {
|
||||
|
|
@ -219,7 +224,6 @@ impl From<Color> for Srgba {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
/// Converts from palette's `Rgb` type to a [`Color`].
|
||||
impl From<Srgb> for Color {
|
||||
fn from(rgb: Srgb) -> Self {
|
||||
|
|
@ -227,7 +231,6 @@ impl From<Srgb> for Color {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
/// Converts from [`Color`] to palette's `Rgb` type.
|
||||
impl From<Color> for Srgb {
|
||||
fn from(c: Color) -> Self {
|
||||
|
|
@ -235,7 +238,6 @@ impl From<Color> for Srgb {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
//! Control the fit of some content (like an image) within a space.
|
||||
use crate::Size;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// The strategy used to fit the contents of a widget to its bounding box.
|
||||
///
|
||||
/// Each variant of this enum is a strategy that can be applied for resolving
|
||||
|
|
@ -11,7 +13,7 @@ use crate::Size;
|
|||
/// in CSS, see [Mozilla's docs][1], or run the `tour` example
|
||||
///
|
||||
/// [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
|
||||
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum ContentFit {
|
||||
/// Scale as big as it can be without needing to crop or hide parts.
|
||||
///
|
||||
|
|
@ -23,6 +25,7 @@ pub enum ContentFit {
|
|||
/// This is a great fit for when you need to display an image without losing
|
||||
/// any part of it, particularly when the image itself is the focus of the
|
||||
/// screen.
|
||||
#[default]
|
||||
Contain,
|
||||
|
||||
/// Scale the image to cover all of the bounding box, cropping if needed.
|
||||
|
|
@ -117,3 +120,15 @@ impl ContentFit {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ContentFit {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
ContentFit::Contain => "Contain",
|
||||
ContentFit::Cover => "Cover",
|
||||
ContentFit::Fill => "Fill",
|
||||
ContentFit::None => "None",
|
||||
ContentFit::ScaleDown => "Scale Down",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,52 +94,34 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
|
|||
/// producing them. Let's implement our __view logic__ now:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # mod counter {
|
||||
/// # #[derive(Debug, Clone, Copy)]
|
||||
/// # pub enum Message {}
|
||||
/// # pub struct Counter;
|
||||
/// # mod iced {
|
||||
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
|
||||
/// #
|
||||
/// # impl Counter {
|
||||
/// # pub fn view(
|
||||
/// # &self,
|
||||
/// # ) -> iced_core::Element<Message, (), iced_core::renderer::Null> {
|
||||
/// # pub mod widget {
|
||||
/// # pub fn row<'a, Message>(iter: impl IntoIterator<Item = super::Element<'a, Message>>) -> super::Element<'a, Message> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # mod iced {
|
||||
/// # pub use iced_core::renderer::Null as Renderer;
|
||||
/// # pub use iced_core::Element;
|
||||
/// # mod counter {
|
||||
/// # #[derive(Debug, Clone, Copy)]
|
||||
/// # pub enum Message {}
|
||||
/// # pub struct Counter;
|
||||
/// #
|
||||
/// # pub mod widget {
|
||||
/// # pub struct Row<Message> {
|
||||
/// # _t: std::marker::PhantomData<Message>,
|
||||
/// # }
|
||||
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
|
||||
/// #
|
||||
/// # impl<Message> Row<Message> {
|
||||
/// # pub fn new() -> Self {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// #
|
||||
/// # pub fn spacing(mut self, _: u32) -> Self {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// #
|
||||
/// # pub fn push(
|
||||
/// # mut self,
|
||||
/// # _: iced_core::Element<Message, (), iced_core::renderer::Null>,
|
||||
/// # ) -> Self {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # impl Counter {
|
||||
/// # pub fn view(&self) -> Element<Message> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// use counter::Counter;
|
||||
///
|
||||
/// use iced::widget::Row;
|
||||
/// use iced::{Element, Renderer};
|
||||
/// use iced::widget::row;
|
||||
/// use iced::Element;
|
||||
///
|
||||
/// struct ManyCounters {
|
||||
/// counters: Vec<Counter>,
|
||||
|
|
@ -151,24 +133,21 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
|
|||
/// }
|
||||
///
|
||||
/// impl ManyCounters {
|
||||
/// pub fn view(&mut self) -> Row<Message> {
|
||||
/// // We can quickly populate a `Row` by folding over our counters
|
||||
/// self.counters.iter_mut().enumerate().fold(
|
||||
/// Row::new().spacing(20),
|
||||
/// |row, (index, counter)| {
|
||||
/// // We display the counter
|
||||
/// let element: Element<counter::Message, _, _> =
|
||||
/// counter.view().into();
|
||||
///
|
||||
/// row.push(
|
||||
/// pub fn view(&self) -> Element<Message> {
|
||||
/// // We can quickly populate a `row` by mapping our counters
|
||||
/// row(
|
||||
/// self.counters
|
||||
/// .iter()
|
||||
/// .map(Counter::view)
|
||||
/// .enumerate()
|
||||
/// .map(|(index, counter)| {
|
||||
/// // Here we turn our `Element<counter::Message>` into
|
||||
/// // an `Element<Message>` by combining the `index` and the
|
||||
/// // message of the `element`.
|
||||
/// element
|
||||
/// .map(move |message| Message::Counter(index, message)),
|
||||
/// )
|
||||
/// },
|
||||
/// counter.map(move |message| Message::Counter(index, message))
|
||||
/// }),
|
||||
/// )
|
||||
/// .into()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -12,17 +12,13 @@ pub enum Gradient {
|
|||
}
|
||||
|
||||
impl Gradient {
|
||||
/// Adjust the opacity of the gradient by a multiplier applied to each color stop.
|
||||
pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self {
|
||||
match &mut self {
|
||||
/// Scales the alpha channel of the [`Gradient`] by the given factor.
|
||||
pub fn scale_alpha(self, factor: f32) -> Self {
|
||||
match self {
|
||||
Gradient::Linear(linear) => {
|
||||
for stop in linear.stops.iter_mut().flatten() {
|
||||
stop.color.a *= alpha_multiplier;
|
||||
}
|
||||
Gradient::Linear(linear.scale_alpha(factor))
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,4 +96,14 @@ impl Linear {
|
|||
|
||||
self
|
||||
}
|
||||
|
||||
/// Scales the alpha channel of the [`Linear`] gradient by the given
|
||||
/// factor.
|
||||
pub fn scale_alpha(mut self, factor: f32) -> Self {
|
||||
for stop in self.stops.iter_mut().flatten() {
|
||||
stop.color.a *= factor;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/// The hasher used to compare layouts.
|
||||
#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways.
|
||||
#[derive(Default)]
|
||||
pub struct Hasher(xxhash_rust::xxh3::Xxh3);
|
||||
pub struct Hasher(rustc_hash::FxHasher);
|
||||
|
||||
impl core::hash::Hasher for Hasher {
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,45 @@
|
|||
//! Load and draw raster graphics.
|
||||
use crate::{Hasher, Rectangle, Size};
|
||||
pub use bytes::Bytes;
|
||||
|
||||
use std::hash::{Hash, Hasher as _};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use crate::{Radians, Rectangle, Size};
|
||||
|
||||
use rustc_hash::FxHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// A handle of some image data.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Handle {
|
||||
id: u64,
|
||||
data: Data,
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum Handle {
|
||||
/// A file handle. The image data will be read
|
||||
/// from the file path.
|
||||
///
|
||||
/// Use [`from_path`] to create this variant.
|
||||
///
|
||||
/// [`from_path`]: Self::from_path
|
||||
Path(Id, PathBuf),
|
||||
|
||||
/// A handle pointing to some encoded image bytes in-memory.
|
||||
///
|
||||
/// Use [`from_bytes`] to create this variant.
|
||||
///
|
||||
/// [`from_bytes`]: Self::from_bytes
|
||||
Bytes(Id, Bytes),
|
||||
|
||||
/// A handle pointing to decoded image pixels in RGBA format.
|
||||
///
|
||||
/// Use [`from_rgba`] to create this variant.
|
||||
///
|
||||
/// [`from_rgba`]: Self::from_rgba
|
||||
Rgba {
|
||||
/// The id of this handle.
|
||||
id: Id,
|
||||
/// The width of the image.
|
||||
width: u32,
|
||||
/// The height of the image.
|
||||
height: u32,
|
||||
/// The pixels.
|
||||
pixels: Bytes,
|
||||
},
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
|
|
@ -17,56 +47,48 @@ impl Handle {
|
|||
///
|
||||
/// Makes an educated guess about the image format by examining the data in the file.
|
||||
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
|
||||
Self::from_data(Data::Path(path.into()))
|
||||
let path = path.into();
|
||||
|
||||
Self::Path(Id::path(&path), path)
|
||||
}
|
||||
|
||||
/// Creates an image [`Handle`] containing the image pixels directly. This
|
||||
/// function expects the input data to be provided as a `Vec<u8>` of RGBA
|
||||
/// pixels.
|
||||
///
|
||||
/// This is useful if you have already decoded your image.
|
||||
pub fn from_pixels(
|
||||
width: u32,
|
||||
height: u32,
|
||||
pixels: impl AsRef<[u8]> + Send + Sync + 'static,
|
||||
) -> Handle {
|
||||
Self::from_data(Data::Rgba {
|
||||
width,
|
||||
height,
|
||||
pixels: Bytes::new(pixels),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an image [`Handle`] containing the image data directly.
|
||||
/// Creates an image [`Handle`] containing the encoded image data directly.
|
||||
///
|
||||
/// Makes an educated guess about the image format by examining the given data.
|
||||
///
|
||||
/// This is useful if you already have your image loaded in-memory, maybe
|
||||
/// because you downloaded or generated it procedurally.
|
||||
pub fn from_memory(
|
||||
bytes: impl AsRef<[u8]> + Send + Sync + 'static,
|
||||
) -> Handle {
|
||||
Self::from_data(Data::Bytes(Bytes::new(bytes)))
|
||||
pub fn from_bytes(bytes: impl Into<Bytes>) -> Handle {
|
||||
Self::Bytes(Id::unique(), bytes.into())
|
||||
}
|
||||
|
||||
fn from_data(data: Data) -> Handle {
|
||||
let mut hasher = Hasher::default();
|
||||
data.hash(&mut hasher);
|
||||
|
||||
Handle {
|
||||
id: hasher.finish(),
|
||||
data,
|
||||
/// Creates an image [`Handle`] containing the decoded image pixels directly.
|
||||
///
|
||||
/// This function expects the pixel data to be provided as a collection of [`Bytes`]
|
||||
/// of RGBA pixels. Therefore, the length of the pixel data should always be
|
||||
/// `width * height * 4`.
|
||||
///
|
||||
/// This is useful if you have already decoded your image.
|
||||
pub fn from_rgba(
|
||||
width: u32,
|
||||
height: u32,
|
||||
pixels: impl Into<Bytes>,
|
||||
) -> Handle {
|
||||
Self::Rgba {
|
||||
id: Id::unique(),
|
||||
width,
|
||||
height,
|
||||
pixels: pixels.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the unique identifier of the [`Handle`].
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Returns a reference to the image [`Data`].
|
||||
pub fn data(&self) -> &Data {
|
||||
&self.data
|
||||
pub fn id(&self) -> Id {
|
||||
match self {
|
||||
Handle::Path(id, _)
|
||||
| Handle::Bytes(id, _)
|
||||
| Handle::Rgba { id, .. } => *id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,93 +101,49 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl Hash for Handle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around raw image data.
|
||||
///
|
||||
/// It behaves like a `&[u8]`.
|
||||
#[derive(Clone)]
|
||||
pub struct Bytes(Arc<dyn AsRef<[u8]> + Send + Sync + 'static>);
|
||||
|
||||
impl Bytes {
|
||||
/// Creates new [`Bytes`] around `data`.
|
||||
pub fn new(data: impl AsRef<[u8]> + Send + Sync + 'static) -> Self {
|
||||
Self(Arc::new(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Bytes {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.as_ref().as_ref().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Bytes {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.as_ref().as_ref().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Bytes {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let a = self.as_ref();
|
||||
let b = other.as_ref();
|
||||
core::ptr::eq(a, b) || a == b
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Bytes {}
|
||||
|
||||
impl AsRef<[u8]> for Bytes {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Bytes {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &[u8] {
|
||||
self.0.as_ref().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// The data of a raster image.
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Data {
|
||||
/// File data
|
||||
Path(PathBuf),
|
||||
|
||||
/// In-memory data
|
||||
Bytes(Bytes),
|
||||
|
||||
/// Decoded image pixels in RGBA format.
|
||||
Rgba {
|
||||
/// The width of the image.
|
||||
width: u32,
|
||||
/// The height of the image.
|
||||
height: u32,
|
||||
/// The pixels.
|
||||
pixels: Bytes,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Data {
|
||||
impl std::fmt::Debug for Handle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Data::Path(path) => write!(f, "Path({path:?})"),
|
||||
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
||||
Data::Rgba { width, height, .. } => {
|
||||
Self::Path(_, path) => write!(f, "Path({path:?})"),
|
||||
Self::Bytes(_, _) => write!(f, "Bytes(...)"),
|
||||
Self::Rgba { width, height, .. } => {
|
||||
write!(f, "Pixels({width} * {height})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The unique identifier of some [`Handle`] data.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Id(_Id);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
enum _Id {
|
||||
Unique(u64),
|
||||
Hash(u64),
|
||||
}
|
||||
|
||||
impl Id {
|
||||
fn unique() -> Self {
|
||||
use std::sync::atomic::{self, AtomicU64};
|
||||
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
Self(_Id::Unique(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)))
|
||||
}
|
||||
|
||||
fn path(path: impl AsRef<Path>) -> Self {
|
||||
let hash = {
|
||||
let mut hasher = FxHasher::default();
|
||||
path.as_ref().hash(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
};
|
||||
|
||||
Self(_Id::Hash(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/// Image filtering strategy.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub enum FilterMethod {
|
||||
|
|
@ -183,17 +161,19 @@ pub trait Renderer: crate::Renderer {
|
|||
/// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`]
|
||||
///
|
||||
/// [`Handle`]: Self::Handle
|
||||
type Handle: Clone + Hash;
|
||||
type Handle: Clone;
|
||||
|
||||
/// Returns the dimensions of an image for the given [`Handle`].
|
||||
fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;
|
||||
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
|
||||
|
||||
/// Draws an image with the given [`Handle`] and inside the provided
|
||||
/// `bounds`.
|
||||
fn draw(
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
handle: Self::Handle,
|
||||
filter_method: FilterMethod,
|
||||
bounds: Rectangle,
|
||||
rotation: Radians,
|
||||
opacity: f32,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
|
||||
/// Returns an iterator over the [`Layout`] of the children of a [`Node`].
|
||||
pub fn children(self) -> impl Iterator<Item = Layout<'a>> {
|
||||
pub fn children(self) -> impl DoubleEndedIterator<Item = Layout<'a>> {
|
||||
self.node.children().iter().map(move |node| {
|
||||
Layout::with_offset(
|
||||
Vector::new(self.position.x, self.position.y),
|
||||
|
|
|
|||
|
|
@ -80,14 +80,9 @@ where
|
|||
|
||||
let mut fill_main_sum = 0;
|
||||
let mut cross = match axis {
|
||||
Axis::Horizontal => match height {
|
||||
Length::Shrink => 0.0,
|
||||
_ => max_cross,
|
||||
},
|
||||
Axis::Vertical => match width {
|
||||
Length::Shrink => 0.0,
|
||||
_ => max_cross,
|
||||
},
|
||||
Axis::Vertical if width == Length::Shrink => 0.0,
|
||||
Axis::Horizontal if height == Length::Shrink => 0.0,
|
||||
_ => max_cross,
|
||||
};
|
||||
|
||||
let mut available = axis.main(limits.max()) - total_spacing;
|
||||
|
|
@ -103,35 +98,14 @@ where
|
|||
};
|
||||
|
||||
if fill_main_factor == 0 {
|
||||
if fill_cross_factor == 0 {
|
||||
let (max_width, max_height) = axis.pack(available, max_cross);
|
||||
|
||||
let child_limits =
|
||||
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
||||
|
||||
let layout =
|
||||
child.as_widget().layout(tree, renderer, &child_limits);
|
||||
let size = layout.size();
|
||||
|
||||
available -= axis.main(size);
|
||||
cross = cross.max(axis.cross(size));
|
||||
|
||||
nodes[i] = layout;
|
||||
}
|
||||
} else {
|
||||
fill_main_sum += fill_main_factor;
|
||||
}
|
||||
}
|
||||
|
||||
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
|
||||
let (fill_main_factor, fill_cross_factor) = {
|
||||
let size = child.as_widget().size();
|
||||
|
||||
axis.pack(size.width.fill_factor(), size.height.fill_factor())
|
||||
};
|
||||
|
||||
if fill_main_factor == 0 && fill_cross_factor != 0 {
|
||||
let (max_width, max_height) = axis.pack(available, cross);
|
||||
let (max_width, max_height) = axis.pack(
|
||||
available,
|
||||
if fill_cross_factor == 0 {
|
||||
max_cross
|
||||
} else {
|
||||
cross
|
||||
},
|
||||
);
|
||||
|
||||
let child_limits =
|
||||
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
||||
|
|
@ -141,9 +115,11 @@ where
|
|||
let size = layout.size();
|
||||
|
||||
available -= axis.main(size);
|
||||
cross = cross.max(axis.cross(layout.size()));
|
||||
cross = cross.max(axis.cross(size));
|
||||
|
||||
nodes[i] = layout;
|
||||
} else {
|
||||
fill_main_sum += fill_main_factor;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -175,14 +151,15 @@ where
|
|||
max_main
|
||||
};
|
||||
|
||||
let max_cross = if fill_cross_factor == 0 {
|
||||
max_cross
|
||||
} else {
|
||||
cross
|
||||
};
|
||||
|
||||
let (min_width, min_height) = axis.pack(min_main, 0.0);
|
||||
let (max_width, max_height) = axis.pack(max_main, max_cross);
|
||||
let (max_width, max_height) = axis.pack(
|
||||
max_main,
|
||||
if fill_cross_factor == 0 {
|
||||
max_cross
|
||||
} else {
|
||||
cross
|
||||
},
|
||||
);
|
||||
|
||||
let child_limits = Limits::new(
|
||||
Size::new(min_width, min_height),
|
||||
|
|
|
|||
|
|
@ -9,14 +9,6 @@
|
|||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||
)]
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
unused_results,
|
||||
rust_2018_idioms,
|
||||
rustdoc::broken_intra_doc_links
|
||||
)]
|
||||
pub mod alignment;
|
||||
pub mod border;
|
||||
pub mod clipboard;
|
||||
|
|
@ -31,6 +23,7 @@ pub mod overlay;
|
|||
pub mod renderer;
|
||||
pub mod svg;
|
||||
pub mod text;
|
||||
pub mod theme;
|
||||
pub mod time;
|
||||
pub mod touch;
|
||||
pub mod widget;
|
||||
|
|
@ -41,12 +34,12 @@ mod background;
|
|||
mod color;
|
||||
mod content_fit;
|
||||
mod element;
|
||||
mod hasher;
|
||||
mod length;
|
||||
mod padding;
|
||||
mod pixels;
|
||||
mod point;
|
||||
mod rectangle;
|
||||
mod rotation;
|
||||
mod shadow;
|
||||
mod shell;
|
||||
mod size;
|
||||
|
|
@ -64,7 +57,6 @@ pub use element::Element;
|
|||
pub use event::Event;
|
||||
pub use font::Font;
|
||||
pub use gradient::Gradient;
|
||||
pub use hasher::Hasher;
|
||||
pub use layout::Layout;
|
||||
pub use length::Length;
|
||||
pub use overlay::Overlay;
|
||||
|
|
@ -73,10 +65,12 @@ pub use pixels::Pixels;
|
|||
pub use point::Point;
|
||||
pub use rectangle::Rectangle;
|
||||
pub use renderer::Renderer;
|
||||
pub use rotation::Rotation;
|
||||
pub use shadow::Shadow;
|
||||
pub use shell::Shell;
|
||||
pub use size::Size;
|
||||
pub use text::Text;
|
||||
pub use theme::Theme;
|
||||
pub use transformation::Transformation;
|
||||
pub use vector::Vector;
|
||||
pub use widget::Widget;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#[allow(missing_docs)]
|
||||
pub enum Interaction {
|
||||
#[default]
|
||||
None,
|
||||
Idle,
|
||||
Pointer,
|
||||
Grab,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ where
|
|||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse::Interaction::Idle
|
||||
mouse::Interaction::None
|
||||
}
|
||||
|
||||
/// Returns true if the cursor is over the [`Overlay`].
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{Point, Size, Vector};
|
||||
use crate::{Point, Radians, Size, Vector};
|
||||
|
||||
/// A rectangle.
|
||||
/// An axis-aligned rectangle.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Rectangle<T = f32> {
|
||||
/// X coordinate of the top-left corner.
|
||||
|
|
@ -16,24 +16,32 @@ pub struct Rectangle<T = f32> {
|
|||
pub height: T,
|
||||
}
|
||||
|
||||
impl Rectangle<f32> {
|
||||
/// Creates a new [`Rectangle`] with its top-left corner in the given
|
||||
/// [`Point`] and with the provided [`Size`].
|
||||
pub fn new(top_left: Point, size: Size) -> Self {
|
||||
impl<T> Rectangle<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
/// Creates a new [`Rectangle`] with its top-left corner at the origin
|
||||
/// and with the provided [`Size`].
|
||||
pub fn with_size(size: Size<T>) -> Self {
|
||||
Self {
|
||||
x: top_left.x,
|
||||
y: top_left.y,
|
||||
x: T::default(),
|
||||
y: T::default(),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Rectangle`] with its top-left corner at the origin
|
||||
/// and with the provided [`Size`].
|
||||
pub fn with_size(size: Size) -> Self {
|
||||
impl Rectangle<f32> {
|
||||
/// A rectangle starting at [`Point::ORIGIN`] with infinite width and height.
|
||||
pub const INFINITE: Self = Self::new(Point::ORIGIN, Size::INFINITY);
|
||||
|
||||
/// Creates a new [`Rectangle`] with its top-left corner in the given
|
||||
/// [`Point`] and with the provided [`Size`].
|
||||
pub const fn new(top_left: Point, size: Size) -> Self {
|
||||
Self {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
x: top_left.x,
|
||||
y: top_left.y,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
}
|
||||
|
|
@ -139,13 +147,20 @@ impl Rectangle<f32> {
|
|||
}
|
||||
|
||||
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
|
||||
pub fn snap(self) -> Rectangle<u32> {
|
||||
Rectangle {
|
||||
pub fn snap(self) -> Option<Rectangle<u32>> {
|
||||
let width = self.width as u32;
|
||||
let height = self.height as u32;
|
||||
|
||||
if width < 1 || height < 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Rectangle {
|
||||
x: self.x as u32,
|
||||
y: self.y as u32,
|
||||
width: self.width as u32,
|
||||
height: self.height as u32,
|
||||
}
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
/// Expands the [`Rectangle`] a given amount.
|
||||
|
|
@ -157,6 +172,18 @@ impl Rectangle<f32> {
|
|||
height: self.height + amount * 2.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotates the [`Rectangle`] and returns the smallest [`Rectangle`]
|
||||
/// containing it.
|
||||
pub fn rotate(self, rotation: Radians) -> Self {
|
||||
let size = self.size().rotate(rotation);
|
||||
let position = Point::new(
|
||||
self.center_x() - size.width / 2.0,
|
||||
self.center_y() - size.height / 2.0,
|
||||
);
|
||||
|
||||
Self::new(position, size)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Rectangle<f32> {
|
||||
|
|
@ -212,3 +239,19 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Mul<Vector<T>> for Rectangle<T>
|
||||
where
|
||||
T: std::ops::Mul<Output = T> + Copy,
|
||||
{
|
||||
type Output = Rectangle<T>;
|
||||
|
||||
fn mul(self, scale: Vector<T>) -> Self {
|
||||
Rectangle {
|
||||
x: self.x * scale.x,
|
||||
y: self.y * scale.y,
|
||||
width: self.width * scale.x,
|
||||
height: self.height * scale.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,26 +2,47 @@
|
|||
#[cfg(debug_assertions)]
|
||||
mod null;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub use null::Null;
|
||||
|
||||
use crate::{
|
||||
Background, Border, Color, Rectangle, Shadow, Size, Transformation, Vector,
|
||||
};
|
||||
|
||||
/// A component that can be used by widgets to draw themselves on a screen.
|
||||
pub trait Renderer: Sized {
|
||||
pub trait Renderer {
|
||||
/// Starts recording a new layer.
|
||||
fn start_layer(&mut self, bounds: Rectangle);
|
||||
|
||||
/// Ends recording a new layer.
|
||||
///
|
||||
/// The new layer will clip its contents to the provided `bounds`.
|
||||
fn end_layer(&mut self);
|
||||
|
||||
/// Draws the primitives recorded in the given closure in a new layer.
|
||||
///
|
||||
/// The layer will clip its contents to the provided `bounds`.
|
||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self));
|
||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
||||
self.start_layer(bounds);
|
||||
f(self);
|
||||
self.end_layer();
|
||||
}
|
||||
|
||||
/// Starts recording with a new [`Transformation`].
|
||||
fn start_transformation(&mut self, transformation: Transformation);
|
||||
|
||||
/// Ends recording a new layer.
|
||||
///
|
||||
/// The new layer will clip its contents to the provided `bounds`.
|
||||
fn end_transformation(&mut self);
|
||||
|
||||
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
|
||||
fn with_transformation(
|
||||
&mut self,
|
||||
transformation: Transformation,
|
||||
f: impl FnOnce(&mut Self),
|
||||
);
|
||||
) {
|
||||
self.start_transformation(transformation);
|
||||
f(self);
|
||||
self.end_transformation();
|
||||
}
|
||||
|
||||
/// Applies a translation to the primitives recorded in the given closure.
|
||||
fn with_translation(
|
||||
|
|
|
|||
|
|
@ -1,34 +1,21 @@
|
|||
use crate::alignment;
|
||||
use crate::image;
|
||||
use crate::renderer::{self, Renderer};
|
||||
use crate::svg;
|
||||
use crate::text::{self, Text};
|
||||
use crate::{
|
||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||
Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
|
||||
Transformation,
|
||||
};
|
||||
|
||||
use std::borrow::Cow;
|
||||
impl Renderer for () {
|
||||
fn start_layer(&mut self, _bounds: Rectangle) {}
|
||||
|
||||
/// A renderer that does nothing.
|
||||
///
|
||||
/// It can be useful if you are writing tests!
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Null;
|
||||
fn end_layer(&mut self) {}
|
||||
|
||||
impl Null {
|
||||
/// Creates a new [`Null`] renderer.
|
||||
pub fn new() -> Null {
|
||||
Null
|
||||
}
|
||||
}
|
||||
fn start_transformation(&mut self, _transformation: Transformation) {}
|
||||
|
||||
impl Renderer for Null {
|
||||
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
|
||||
|
||||
fn with_transformation(
|
||||
&mut self,
|
||||
_transformation: Transformation,
|
||||
_f: impl FnOnce(&mut Self),
|
||||
) {
|
||||
}
|
||||
fn end_transformation(&mut self) {}
|
||||
|
||||
fn clear(&mut self) {}
|
||||
|
||||
|
|
@ -40,7 +27,7 @@ impl Renderer for Null {
|
|||
}
|
||||
}
|
||||
|
||||
impl text::Renderer for Null {
|
||||
impl text::Renderer for () {
|
||||
type Font = Font;
|
||||
type Paragraph = ();
|
||||
type Editor = ();
|
||||
|
|
@ -57,8 +44,6 @@ impl text::Renderer for Null {
|
|||
Pixels(16.0)
|
||||
}
|
||||
|
||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||
|
||||
fn fill_paragraph(
|
||||
&mut self,
|
||||
_paragraph: &Self::Paragraph,
|
||||
|
|
@ -79,7 +64,7 @@ impl text::Renderer for Null {
|
|||
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
_paragraph: Text<'_, Self::Font>,
|
||||
_paragraph: Text,
|
||||
_position: Point,
|
||||
_color: Color,
|
||||
_clip_bounds: Rectangle,
|
||||
|
|
@ -90,11 +75,11 @@ impl text::Renderer for Null {
|
|||
impl text::Paragraph for () {
|
||||
type Font = Font;
|
||||
|
||||
fn with_text(_text: Text<'_, Self::Font>) -> Self {}
|
||||
fn with_text(_text: Text<&str>) -> Self {}
|
||||
|
||||
fn resize(&mut self, _new_bounds: Size) {}
|
||||
|
||||
fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference {
|
||||
fn compare(&self, _text: Text<&str>) -> text::Difference {
|
||||
text::Difference::None
|
||||
}
|
||||
|
||||
|
|
@ -174,3 +159,37 @@ impl text::Editor for () {
|
|||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl image::Renderer for () {
|
||||
type Handle = ();
|
||||
|
||||
fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
|
||||
Size::default()
|
||||
}
|
||||
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
_handle: Self::Handle,
|
||||
_filter_method: image::FilterMethod,
|
||||
_bounds: Rectangle,
|
||||
_rotation: Radians,
|
||||
_opacity: f32,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl svg::Renderer for () {
|
||||
fn measure_svg(&self, _handle: &svg::Handle) -> Size<u32> {
|
||||
Size::default()
|
||||
}
|
||||
|
||||
fn draw_svg(
|
||||
&mut self,
|
||||
_handle: svg::Handle,
|
||||
_color: Option<Color>,
|
||||
_bounds: Rectangle,
|
||||
_rotation: Radians,
|
||||
_opacity: f32,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
72
core/src/rotation.rs
Normal file
72
core/src/rotation.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
//! Control the rotation of some content (like an image) within a space.
|
||||
use crate::{Degrees, Radians, Size};
|
||||
|
||||
/// The strategy used to rotate the content.
|
||||
///
|
||||
/// This is used to control the behavior of the layout when the content is rotated
|
||||
/// by a certain angle.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Rotation {
|
||||
/// The element will float while rotating. The layout will be kept exactly as it was
|
||||
/// before the rotation.
|
||||
///
|
||||
/// This is especially useful when used for animations, as it will avoid the
|
||||
/// layout being shifted or resized when smoothly i.e. an icon.
|
||||
///
|
||||
/// This is the default.
|
||||
Floating(Radians),
|
||||
/// The element will be solid while rotating. The layout will be adjusted to fit
|
||||
/// the rotated content.
|
||||
///
|
||||
/// This allows you to rotate an image and have the layout adjust to fit the new
|
||||
/// size of the image.
|
||||
Solid(Radians),
|
||||
}
|
||||
|
||||
impl Rotation {
|
||||
/// Returns the angle of the [`Rotation`] in [`Radians`].
|
||||
pub fn radians(self) -> Radians {
|
||||
match self {
|
||||
Rotation::Floating(radians) | Rotation::Solid(radians) => radians,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the angle of the [`Rotation`] in [`Radians`].
|
||||
pub fn radians_mut(&mut self) -> &mut Radians {
|
||||
match self {
|
||||
Rotation::Floating(radians) | Rotation::Solid(radians) => radians,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the angle of the [`Rotation`] in [`Degrees`].
|
||||
pub fn degrees(self) -> Degrees {
|
||||
Degrees(self.radians().0.to_degrees())
|
||||
}
|
||||
|
||||
/// Applies the [`Rotation`] to the given [`Size`], returning
|
||||
/// the minimum [`Size`] containing the rotated one.
|
||||
pub fn apply(self, size: Size) -> Size {
|
||||
match self {
|
||||
Self::Floating(_) => size,
|
||||
Self::Solid(rotation) => size.rotate(rotation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Rotation {
|
||||
fn default() -> Self {
|
||||
Self::Floating(Radians(0.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Radians> for Rotation {
|
||||
fn from(radians: Radians) -> Self {
|
||||
Self::Floating(radians)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Rotation {
|
||||
fn from(radians: f32) -> Self {
|
||||
Self::Floating(Radians(radians))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::Vector;
|
||||
use crate::{Radians, Vector};
|
||||
|
||||
/// An amount of space in 2 dimensions.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub struct Size<T = f32> {
|
||||
/// The width.
|
||||
pub width: T,
|
||||
|
|
@ -51,22 +51,35 @@ impl Size {
|
|||
height: self.height + other.height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotates the given [`Size`] and returns the minimum [`Size`]
|
||||
/// containing it.
|
||||
pub fn rotate(self, rotation: Radians) -> Size {
|
||||
let radians = f32::from(rotation);
|
||||
|
||||
Size {
|
||||
width: (self.width * radians.cos()).abs()
|
||||
+ (self.height * radians.sin()).abs(),
|
||||
height: (self.width * radians.sin()).abs()
|
||||
+ (self.height * radians.cos()).abs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f32; 2]> for Size {
|
||||
fn from([width, height]: [f32; 2]) -> Self {
|
||||
impl<T> From<[T; 2]> for Size<T> {
|
||||
fn from([width, height]: [T; 2]) -> Self {
|
||||
Size { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u16; 2]> for Size {
|
||||
fn from([width, height]: [u16; 2]) -> Self {
|
||||
Size::new(width.into(), height.into())
|
||||
impl<T> From<(T, T)> for Size<T> {
|
||||
fn from((width, height): (T, T)) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector<f32>> for Size {
|
||||
fn from(vector: Vector<f32>) -> Self {
|
||||
impl<T> From<Vector<T>> for Size<T> {
|
||||
fn from(vector: Vector<T>) -> Self {
|
||||
Size {
|
||||
width: vector.x,
|
||||
height: vector.y,
|
||||
|
|
@ -74,20 +87,23 @@ impl From<Vector<f32>> for Size {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Size> for [f32; 2] {
|
||||
fn from(size: Size) -> [f32; 2] {
|
||||
impl<T> From<Size<T>> for [T; 2] {
|
||||
fn from(size: Size<T>) -> Self {
|
||||
[size.width, size.height]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size> for Vector<f32> {
|
||||
fn from(size: Size) -> Self {
|
||||
impl<T> From<Size<T>> for Vector<T> {
|
||||
fn from(size: Size<T>) -> Self {
|
||||
Vector::new(size.width, size.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for Size {
|
||||
type Output = Size;
|
||||
impl<T> std::ops::Sub for Size<T>
|
||||
where
|
||||
T: std::ops::Sub<Output = T>,
|
||||
{
|
||||
type Output = Size<T>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Size {
|
||||
|
|
@ -96,3 +112,31 @@ impl std::ops::Sub for Size {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Mul<T> for Size<T>
|
||||
where
|
||||
T: std::ops::Mul<Output = T> + Copy,
|
||||
{
|
||||
type Output = Size<T>;
|
||||
|
||||
fn mul(self, rhs: T) -> Self::Output {
|
||||
Size {
|
||||
width: self.width * rhs,
|
||||
height: self.height * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Mul<Vector<T>> for Size<T>
|
||||
where
|
||||
T: std::ops::Mul<Output = T> + Copy,
|
||||
{
|
||||
type Output = Size<T>;
|
||||
|
||||
fn mul(self, scale: Vector<T>) -> Self::Output {
|
||||
Size {
|
||||
width: self.width * scale.x,
|
||||
height: self.height * scale.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Load and draw vector graphics.
|
||||
use crate::{Color, Hasher, Rectangle, Size};
|
||||
use crate::{Color, Radians, Rectangle, Size};
|
||||
|
||||
use rustc_hash::FxHasher;
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher as _};
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -30,7 +31,7 @@ impl Handle {
|
|||
}
|
||||
|
||||
fn from_data(data: Data) -> Handle {
|
||||
let mut hasher = Hasher::default();
|
||||
let mut hasher = FxHasher::default();
|
||||
data.hash(&mut hasher);
|
||||
|
||||
Handle {
|
||||
|
|
@ -91,8 +92,15 @@ impl std::fmt::Debug for Data {
|
|||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Returns the default dimensions of an SVG for the given [`Handle`].
|
||||
fn dimensions(&self, handle: &Handle) -> Size<u32>;
|
||||
fn measure_svg(&self, handle: &Handle) -> Size<u32>;
|
||||
|
||||
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
|
||||
fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle);
|
||||
fn draw_svg(
|
||||
&mut self,
|
||||
handle: Handle,
|
||||
color: Option<Color>,
|
||||
bounds: Rectangle,
|
||||
rotation: Radians,
|
||||
opacity: f32,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,13 @@ pub use paragraph::Paragraph;
|
|||
use crate::alignment;
|
||||
use crate::{Color, Pixels, Point, Rectangle, Size};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// A paragraph.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Text<'a, Font> {
|
||||
pub struct Text<Content = String, Font = crate::Font> {
|
||||
/// The content of the paragraph.
|
||||
pub content: &'a str,
|
||||
pub content: Content,
|
||||
|
||||
/// The bounds of the paragraph.
|
||||
pub bounds: Size,
|
||||
|
|
@ -192,9 +191,6 @@ pub trait Renderer: crate::Renderer {
|
|||
/// Returns the default size of [`Text`].
|
||||
fn default_size(&self) -> Pixels;
|
||||
|
||||
/// Loads a [`Self::Font`] from its bytes.
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
||||
|
||||
/// Draws the given [`Paragraph`] at the given position and with the given
|
||||
/// [`Color`].
|
||||
fn fill_paragraph(
|
||||
|
|
@ -219,7 +215,7 @@ pub trait Renderer: crate::Renderer {
|
|||
/// [`Color`].
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
text: Text<'_, Self::Font>,
|
||||
text: Text<String, Self::Font>,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ pub trait Paragraph: Sized + Default {
|
|||
type Font: Copy + PartialEq;
|
||||
|
||||
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
|
||||
fn with_text(text: Text<'_, Self::Font>) -> Self;
|
||||
fn with_text(text: Text<&str, 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;
|
||||
fn compare(&self, text: Text<&str, Self::Font>) -> Difference;
|
||||
|
||||
/// Returns the horizontal alignment of the [`Paragraph`].
|
||||
fn horizontal_alignment(&self) -> alignment::Horizontal;
|
||||
|
|
@ -35,7 +35,7 @@ pub trait Paragraph: Sized + Default {
|
|||
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>) {
|
||||
fn update(&mut self, text: Text<&str, Self::Font>) {
|
||||
match self.compare(text) {
|
||||
Difference::None => {}
|
||||
Difference::Bounds => {
|
||||
|
|
|
|||
248
core/src/theme.rs
Normal file
248
core/src/theme.rs
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
//! Use the built-in theme and styles.
|
||||
pub mod palette;
|
||||
|
||||
pub use palette::Palette;
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A built-in theme.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Theme {
|
||||
/// The built-in light variant.
|
||||
Light,
|
||||
/// The built-in dark variant.
|
||||
Dark,
|
||||
/// The built-in Dracula variant.
|
||||
Dracula,
|
||||
/// The built-in Nord variant.
|
||||
Nord,
|
||||
/// The built-in Solarized Light variant.
|
||||
SolarizedLight,
|
||||
/// The built-in Solarized Dark variant.
|
||||
SolarizedDark,
|
||||
/// The built-in Gruvbox Light variant.
|
||||
GruvboxLight,
|
||||
/// The built-in Gruvbox Dark variant.
|
||||
GruvboxDark,
|
||||
/// The built-in Catppuccin Latte variant.
|
||||
CatppuccinLatte,
|
||||
/// The built-in Catppuccin Frappé variant.
|
||||
CatppuccinFrappe,
|
||||
/// The built-in Catppuccin Macchiato variant.
|
||||
CatppuccinMacchiato,
|
||||
/// The built-in Catppuccin Mocha variant.
|
||||
CatppuccinMocha,
|
||||
/// The built-in Tokyo Night variant.
|
||||
TokyoNight,
|
||||
/// The built-in Tokyo Night Storm variant.
|
||||
TokyoNightStorm,
|
||||
/// The built-in Tokyo Night Light variant.
|
||||
TokyoNightLight,
|
||||
/// The built-in Kanagawa Wave variant.
|
||||
KanagawaWave,
|
||||
/// The built-in Kanagawa Dragon variant.
|
||||
KanagawaDragon,
|
||||
/// The built-in Kanagawa Lotus variant.
|
||||
KanagawaLotus,
|
||||
/// The built-in Moonfly variant.
|
||||
Moonfly,
|
||||
/// The built-in Nightfly variant.
|
||||
Nightfly,
|
||||
/// The built-in Oxocarbon variant.
|
||||
Oxocarbon,
|
||||
/// The built-in Ferra variant:
|
||||
Ferra,
|
||||
/// A [`Theme`] that uses a [`Custom`] palette.
|
||||
Custom(Arc<Custom>),
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
/// A list with all the defined themes.
|
||||
pub const ALL: &'static [Self] = &[
|
||||
Self::Light,
|
||||
Self::Dark,
|
||||
Self::Dracula,
|
||||
Self::Nord,
|
||||
Self::SolarizedLight,
|
||||
Self::SolarizedDark,
|
||||
Self::GruvboxLight,
|
||||
Self::GruvboxDark,
|
||||
Self::CatppuccinLatte,
|
||||
Self::CatppuccinFrappe,
|
||||
Self::CatppuccinMacchiato,
|
||||
Self::CatppuccinMocha,
|
||||
Self::TokyoNight,
|
||||
Self::TokyoNightStorm,
|
||||
Self::TokyoNightLight,
|
||||
Self::KanagawaWave,
|
||||
Self::KanagawaDragon,
|
||||
Self::KanagawaLotus,
|
||||
Self::Moonfly,
|
||||
Self::Nightfly,
|
||||
Self::Oxocarbon,
|
||||
Self::Ferra,
|
||||
];
|
||||
|
||||
/// Creates a new custom [`Theme`] from the given [`Palette`].
|
||||
pub fn custom(name: String, palette: Palette) -> Self {
|
||||
Self::custom_with_fn(name, palette, palette::Extended::generate)
|
||||
}
|
||||
|
||||
/// Creates a new custom [`Theme`] from the given [`Palette`], with
|
||||
/// a custom generator of a [`palette::Extended`].
|
||||
pub fn custom_with_fn(
|
||||
name: String,
|
||||
palette: Palette,
|
||||
generate: impl FnOnce(Palette) -> palette::Extended,
|
||||
) -> Self {
|
||||
Self::Custom(Arc::new(Custom::with_fn(name, palette, generate)))
|
||||
}
|
||||
|
||||
/// Returns the [`Palette`] of the [`Theme`].
|
||||
pub fn palette(&self) -> Palette {
|
||||
match self {
|
||||
Self::Light => Palette::LIGHT,
|
||||
Self::Dark => Palette::DARK,
|
||||
Self::Dracula => Palette::DRACULA,
|
||||
Self::Nord => Palette::NORD,
|
||||
Self::SolarizedLight => Palette::SOLARIZED_LIGHT,
|
||||
Self::SolarizedDark => Palette::SOLARIZED_DARK,
|
||||
Self::GruvboxLight => Palette::GRUVBOX_LIGHT,
|
||||
Self::GruvboxDark => Palette::GRUVBOX_DARK,
|
||||
Self::CatppuccinLatte => Palette::CATPPUCCIN_LATTE,
|
||||
Self::CatppuccinFrappe => Palette::CATPPUCCIN_FRAPPE,
|
||||
Self::CatppuccinMacchiato => Palette::CATPPUCCIN_MACCHIATO,
|
||||
Self::CatppuccinMocha => Palette::CATPPUCCIN_MOCHA,
|
||||
Self::TokyoNight => Palette::TOKYO_NIGHT,
|
||||
Self::TokyoNightStorm => Palette::TOKYO_NIGHT_STORM,
|
||||
Self::TokyoNightLight => Palette::TOKYO_NIGHT_LIGHT,
|
||||
Self::KanagawaWave => Palette::KANAGAWA_WAVE,
|
||||
Self::KanagawaDragon => Palette::KANAGAWA_DRAGON,
|
||||
Self::KanagawaLotus => Palette::KANAGAWA_LOTUS,
|
||||
Self::Moonfly => Palette::MOONFLY,
|
||||
Self::Nightfly => Palette::NIGHTFLY,
|
||||
Self::Oxocarbon => Palette::OXOCARBON,
|
||||
Self::Ferra => Palette::FERRA,
|
||||
Self::Custom(custom) => custom.palette,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`palette::Extended`] of the [`Theme`].
|
||||
pub fn extended_palette(&self) -> &palette::Extended {
|
||||
match self {
|
||||
Self::Light => &palette::EXTENDED_LIGHT,
|
||||
Self::Dark => &palette::EXTENDED_DARK,
|
||||
Self::Dracula => &palette::EXTENDED_DRACULA,
|
||||
Self::Nord => &palette::EXTENDED_NORD,
|
||||
Self::SolarizedLight => &palette::EXTENDED_SOLARIZED_LIGHT,
|
||||
Self::SolarizedDark => &palette::EXTENDED_SOLARIZED_DARK,
|
||||
Self::GruvboxLight => &palette::EXTENDED_GRUVBOX_LIGHT,
|
||||
Self::GruvboxDark => &palette::EXTENDED_GRUVBOX_DARK,
|
||||
Self::CatppuccinLatte => &palette::EXTENDED_CATPPUCCIN_LATTE,
|
||||
Self::CatppuccinFrappe => &palette::EXTENDED_CATPPUCCIN_FRAPPE,
|
||||
Self::CatppuccinMacchiato => {
|
||||
&palette::EXTENDED_CATPPUCCIN_MACCHIATO
|
||||
}
|
||||
Self::CatppuccinMocha => &palette::EXTENDED_CATPPUCCIN_MOCHA,
|
||||
Self::TokyoNight => &palette::EXTENDED_TOKYO_NIGHT,
|
||||
Self::TokyoNightStorm => &palette::EXTENDED_TOKYO_NIGHT_STORM,
|
||||
Self::TokyoNightLight => &palette::EXTENDED_TOKYO_NIGHT_LIGHT,
|
||||
Self::KanagawaWave => &palette::EXTENDED_KANAGAWA_WAVE,
|
||||
Self::KanagawaDragon => &palette::EXTENDED_KANAGAWA_DRAGON,
|
||||
Self::KanagawaLotus => &palette::EXTENDED_KANAGAWA_LOTUS,
|
||||
Self::Moonfly => &palette::EXTENDED_MOONFLY,
|
||||
Self::Nightfly => &palette::EXTENDED_NIGHTFLY,
|
||||
Self::Oxocarbon => &palette::EXTENDED_OXOCARBON,
|
||||
Self::Ferra => &palette::EXTENDED_FERRA,
|
||||
Self::Custom(custom) => &custom.extended,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
#[cfg(feature = "auto-detect-theme")]
|
||||
{
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
static DEFAULT: Lazy<Theme> =
|
||||
Lazy::new(|| match dark_light::detect() {
|
||||
dark_light::Mode::Dark => Theme::Dark,
|
||||
dark_light::Mode::Light | dark_light::Mode::Default => {
|
||||
Theme::Light
|
||||
}
|
||||
});
|
||||
|
||||
DEFAULT.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "auto-detect-theme"))]
|
||||
Theme::Light
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Theme {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Light => write!(f, "Light"),
|
||||
Self::Dark => write!(f, "Dark"),
|
||||
Self::Dracula => write!(f, "Dracula"),
|
||||
Self::Nord => write!(f, "Nord"),
|
||||
Self::SolarizedLight => write!(f, "Solarized Light"),
|
||||
Self::SolarizedDark => write!(f, "Solarized Dark"),
|
||||
Self::GruvboxLight => write!(f, "Gruvbox Light"),
|
||||
Self::GruvboxDark => write!(f, "Gruvbox Dark"),
|
||||
Self::CatppuccinLatte => write!(f, "Catppuccin Latte"),
|
||||
Self::CatppuccinFrappe => write!(f, "Catppuccin Frappé"),
|
||||
Self::CatppuccinMacchiato => write!(f, "Catppuccin Macchiato"),
|
||||
Self::CatppuccinMocha => write!(f, "Catppuccin Mocha"),
|
||||
Self::TokyoNight => write!(f, "Tokyo Night"),
|
||||
Self::TokyoNightStorm => write!(f, "Tokyo Night Storm"),
|
||||
Self::TokyoNightLight => write!(f, "Tokyo Night Light"),
|
||||
Self::KanagawaWave => write!(f, "Kanagawa Wave"),
|
||||
Self::KanagawaDragon => write!(f, "Kanagawa Dragon"),
|
||||
Self::KanagawaLotus => write!(f, "Kanagawa Lotus"),
|
||||
Self::Moonfly => write!(f, "Moonfly"),
|
||||
Self::Nightfly => write!(f, "Nightfly"),
|
||||
Self::Oxocarbon => write!(f, "Oxocarbon"),
|
||||
Self::Ferra => write!(f, "Ferra"),
|
||||
Self::Custom(custom) => custom.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Theme`] with a customized [`Palette`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Custom {
|
||||
name: String,
|
||||
palette: Palette,
|
||||
extended: palette::Extended,
|
||||
}
|
||||
|
||||
impl Custom {
|
||||
/// Creates a [`Custom`] theme from the given [`Palette`].
|
||||
pub fn new(name: String, palette: Palette) -> Self {
|
||||
Self::with_fn(name, palette, palette::Extended::generate)
|
||||
}
|
||||
|
||||
/// Creates a [`Custom`] theme from the given [`Palette`] with
|
||||
/// a custom generator of a [`palette::Extended`].
|
||||
pub fn with_fn(
|
||||
name: String,
|
||||
palette: Palette,
|
||||
generate: impl FnOnce(Palette) -> palette::Extended,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
palette,
|
||||
extended: generate(palette),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Custom {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.name)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
//! Define the colors of a theme.
|
||||
use crate::core::{color, Color};
|
||||
use crate::{color, Color};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use palette::color_difference::Wcag21RelativeContrast;
|
||||
|
|
@ -277,6 +277,17 @@ impl Palette {
|
|||
success: color!(0x00c15a),
|
||||
danger: color!(0xf62d0f),
|
||||
};
|
||||
|
||||
/// The built-in [Ferra] variant of a [`Palette`].
|
||||
///
|
||||
/// [Ferra]: https://github.com/casperstorm/ferra
|
||||
pub const FERRA: Self = Self {
|
||||
background: color!(0x2b292d),
|
||||
text: color!(0xfecdb2),
|
||||
primary: color!(0xd1d1e0),
|
||||
success: color!(0xb1b695),
|
||||
danger: color!(0xe06b75),
|
||||
};
|
||||
}
|
||||
|
||||
/// An extended set of colors generated from a [`Palette`].
|
||||
|
|
@ -380,6 +391,10 @@ pub static EXTENDED_NIGHTFLY: Lazy<Extended> =
|
|||
pub static EXTENDED_OXOCARBON: Lazy<Extended> =
|
||||
Lazy::new(|| Extended::generate(Palette::OXOCARBON));
|
||||
|
||||
/// The built-in Ferra variant of an [`Extended`] palette.
|
||||
pub static EXTENDED_FERRA: Lazy<Extended> =
|
||||
Lazy::new(|| Extended::generate(Palette::FERRA));
|
||||
|
||||
impl Extended {
|
||||
/// Generates an [`Extended`] palette from a simple [`Palette`].
|
||||
pub fn generate(palette: Palette) -> Self {
|
||||
|
|
@ -599,10 +614,15 @@ fn mix(a: Color, b: Color, factor: f32) -> Color {
|
|||
fn readable(background: Color, text: Color) -> Color {
|
||||
if is_readable(background, text) {
|
||||
text
|
||||
} else if is_dark(background) {
|
||||
Color::WHITE
|
||||
} else {
|
||||
Color::BLACK
|
||||
let white_contrast = relative_contrast(background, Color::WHITE);
|
||||
let black_contrast = relative_contrast(background, Color::BLACK);
|
||||
|
||||
if white_contrast >= black_contrast {
|
||||
Color::WHITE
|
||||
} else {
|
||||
Color::BLACK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -617,6 +637,13 @@ fn is_readable(a: Color, b: Color) -> bool {
|
|||
a_srgb.has_enhanced_contrast_text(b_srgb)
|
||||
}
|
||||
|
||||
fn relative_contrast(a: Color, b: Color) -> f32 {
|
||||
let a_srgb = Rgb::from(a);
|
||||
let b_srgb = Rgb::from(b);
|
||||
|
||||
a_srgb.relative_contrast(b_srgb)
|
||||
}
|
||||
|
||||
fn to_hsl(color: Color) -> Hsl {
|
||||
Hsl::from_color(Rgb::from(color))
|
||||
}
|
||||
|
|
@ -42,6 +42,12 @@ impl Transformation {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Transformation {
|
||||
fn default() -> Self {
|
||||
Transformation::IDENTITY
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Transformation {
|
||||
type Output = Self;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ impl<T> Vector<T> {
|
|||
impl Vector {
|
||||
/// The zero [`Vector`].
|
||||
pub const ZERO: Self = Self::new(0.0, 0.0);
|
||||
|
||||
/// The unit [`Vector`].
|
||||
pub const UNIT: Self = Self::new(0.0, 0.0);
|
||||
}
|
||||
|
||||
impl<T> std::ops::Add for Vector<T>
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ where
|
|||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse::Interaction::Idle
|
||||
mouse::Interaction::None
|
||||
}
|
||||
|
||||
/// Returns the overlay of the [`Widget`], if there is any.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use crate::renderer;
|
|||
use crate::text::{self, Paragraph};
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
|
||||
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme,
|
||||
Widget,
|
||||
};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
|
@ -17,10 +18,10 @@ pub use text::{LineHeight, Shaping};
|
|||
#[allow(missing_debug_implementations)]
|
||||
pub struct Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: StyleSheet,
|
||||
Theme: Catalog,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
content: Cow<'a, str>,
|
||||
fragment: Fragment<'a>,
|
||||
size: Option<Pixels>,
|
||||
line_height: LineHeight,
|
||||
width: Length,
|
||||
|
|
@ -29,18 +30,18 @@ where
|
|||
vertical_alignment: alignment::Vertical,
|
||||
font: Option<Renderer::Font>,
|
||||
shaping: Shaping,
|
||||
style: Theme::Style,
|
||||
class: Theme::Class<'a>,
|
||||
}
|
||||
|
||||
impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: StyleSheet,
|
||||
Theme: Catalog,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
/// Create a new fragment of [`Text`] with the given contents.
|
||||
pub fn new(content: impl Into<Cow<'a, str>>) -> Self {
|
||||
pub fn new(fragment: impl IntoFragment<'a>) -> Self {
|
||||
Text {
|
||||
content: content.into(),
|
||||
fragment: fragment.into_fragment(),
|
||||
size: None,
|
||||
line_height: LineHeight::default(),
|
||||
font: None,
|
||||
|
|
@ -49,7 +50,7 @@ where
|
|||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
shaping: Shaping::Basic,
|
||||
style: Default::default(),
|
||||
class: Theme::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,12 +74,6 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`Text`].
|
||||
pub fn style(mut self, style: impl Into<Theme::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Text`] boundaries.
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
|
|
@ -114,6 +109,42 @@ where
|
|||
self.shaping = shaping;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`Text`].
|
||||
#[must_use]
|
||||
pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
|
||||
where
|
||||
Theme::Class<'a>: From<StyleFn<'a, Theme>>,
|
||||
{
|
||||
self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Color`] of the [`Text`].
|
||||
pub fn color(self, color: impl Into<Color>) -> Self
|
||||
where
|
||||
Theme::Class<'a>: From<StyleFn<'a, Theme>>,
|
||||
{
|
||||
self.color_maybe(Some(color))
|
||||
}
|
||||
|
||||
/// Sets the [`Color`] of the [`Text`], if `Some`.
|
||||
pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
|
||||
where
|
||||
Theme::Class<'a>: From<StyleFn<'a, Theme>>,
|
||||
{
|
||||
let color = color.map(Into::into);
|
||||
|
||||
self.style(move |_theme| Style { color })
|
||||
}
|
||||
|
||||
/// Sets the style class of the [`Text`].
|
||||
#[cfg(feature = "advanced")]
|
||||
#[must_use]
|
||||
pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
|
||||
self.class = class.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The internal state of a [`Text`] widget.
|
||||
|
|
@ -123,7 +154,7 @@ pub struct State<P: Paragraph>(P);
|
|||
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: StyleSheet,
|
||||
Theme: Catalog,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
|
|
@ -153,7 +184,7 @@ where
|
|||
limits,
|
||||
self.width,
|
||||
self.height,
|
||||
&self.content,
|
||||
&self.fragment,
|
||||
self.line_height,
|
||||
self.size,
|
||||
self.font,
|
||||
|
|
@ -168,21 +199,15 @@ where
|
|||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Theme,
|
||||
style: &renderer::Style,
|
||||
defaults: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||
let style = theme.style(&self.class);
|
||||
|
||||
draw(
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
state,
|
||||
theme.appearance(self.style.clone()),
|
||||
viewport,
|
||||
);
|
||||
draw(renderer, defaults, layout, state, style, viewport);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -242,7 +267,7 @@ pub fn draw<Renderer>(
|
|||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
state: &State<Renderer::Paragraph>,
|
||||
appearance: Appearance,
|
||||
appearance: Style,
|
||||
viewport: &Rectangle,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
|
|
@ -273,7 +298,7 @@ pub fn draw<Renderer>(
|
|||
impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: StyleSheet + 'a,
|
||||
Theme: Catalog + 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
{
|
||||
fn from(
|
||||
|
|
@ -283,30 +308,9 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Theme, Renderer> Clone for Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: StyleSheet,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
content: self.content.clone(),
|
||||
size: self.size,
|
||||
line_height: self.line_height,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
horizontal_alignment: self.horizontal_alignment,
|
||||
vertical_alignment: self.vertical_alignment,
|
||||
font: self.font,
|
||||
style: self.style.clone(),
|
||||
shaping: self.shaping,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
|
||||
where
|
||||
Theme: StyleSheet,
|
||||
Theme: Catalog + 'a,
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
fn from(content: &'a str) -> Self {
|
||||
|
|
@ -317,7 +321,7 @@ where
|
|||
impl<'a, Message, Theme, Renderer> From<&'a str>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: StyleSheet + 'a,
|
||||
Theme: Catalog + 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
{
|
||||
fn from(content: &'a str) -> Self {
|
||||
|
|
@ -325,20 +329,118 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The style sheet of some text.
|
||||
pub trait StyleSheet {
|
||||
/// The supported style of the [`StyleSheet`].
|
||||
type Style: Default + Clone;
|
||||
|
||||
/// Produces the [`Appearance`] of some text.
|
||||
fn appearance(&self, style: Self::Style) -> Appearance;
|
||||
}
|
||||
|
||||
/// The apperance of some text.
|
||||
/// The appearance of some text.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Appearance {
|
||||
pub struct Style {
|
||||
/// The [`Color`] of the text.
|
||||
///
|
||||
/// The default, `None`, means using the inherited color.
|
||||
pub color: Option<Color>,
|
||||
}
|
||||
|
||||
/// The theme catalog of a [`Text`].
|
||||
pub trait Catalog: Sized {
|
||||
/// The item class of this [`Catalog`].
|
||||
type Class<'a>;
|
||||
|
||||
/// The default class produced by this [`Catalog`].
|
||||
fn default<'a>() -> Self::Class<'a>;
|
||||
|
||||
/// The [`Style`] of a class with the given status.
|
||||
fn style(&self, item: &Self::Class<'_>) -> Style;
|
||||
}
|
||||
|
||||
/// A styling function for a [`Text`].
|
||||
///
|
||||
/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
|
||||
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> Self::Class<'a> {
|
||||
Box::new(|_theme| Style::default())
|
||||
}
|
||||
|
||||
fn style(&self, class: &Self::Class<'_>) -> Style {
|
||||
class(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A fragment of [`Text`].
|
||||
///
|
||||
/// This is just an alias to a string that may be either
|
||||
/// borrowed or owned.
|
||||
pub type Fragment<'a> = Cow<'a, str>;
|
||||
|
||||
/// A trait for converting a value to some text [`Fragment`].
|
||||
pub trait IntoFragment<'a> {
|
||||
/// Converts the value to some text [`Fragment`].
|
||||
fn into_fragment(self) -> Fragment<'a>;
|
||||
}
|
||||
|
||||
impl<'a> IntoFragment<'a> for Fragment<'a> {
|
||||
fn into_fragment(self) -> Fragment<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> {
|
||||
fn into_fragment(self) -> Fragment<'a> {
|
||||
Fragment::Borrowed(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoFragment<'a> for &'a str {
|
||||
fn into_fragment(self) -> Fragment<'a> {
|
||||
Fragment::Borrowed(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoFragment<'a> for &'a String {
|
||||
fn into_fragment(self) -> Fragment<'a> {
|
||||
Fragment::Borrowed(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoFragment<'a> for String {
|
||||
fn into_fragment(self) -> Fragment<'a> {
|
||||
Fragment::Owned(self)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! into_fragment {
|
||||
($type:ty) => {
|
||||
impl<'a> IntoFragment<'a> for $type {
|
||||
fn into_fragment(self) -> Fragment<'a> {
|
||||
Fragment::Owned(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoFragment<'a> for &$type {
|
||||
fn into_fragment(self) -> Fragment<'a> {
|
||||
Fragment::Owned(self.to_string())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
into_fragment!(char);
|
||||
into_fragment!(bool);
|
||||
|
||||
into_fragment!(u8);
|
||||
into_fragment!(u16);
|
||||
into_fragment!(u32);
|
||||
into_fragment!(u64);
|
||||
into_fragment!(u128);
|
||||
into_fragment!(usize);
|
||||
|
||||
into_fragment!(i8);
|
||||
into_fragment!(i16);
|
||||
into_fragment!(i32);
|
||||
into_fragment!(i64);
|
||||
into_fragment!(i128);
|
||||
into_fragment!(isize);
|
||||
|
||||
into_fragment!(f32);
|
||||
into_fragment!(f64);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub enum RedrawRequest {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::time::{Duration, Instant};
|
||||
use crate::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn ordering() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
//! Platform specific settings for WebAssembly.
|
||||
|
||||
/// The platform specific window settings of an application.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PlatformSpecific {
|
||||
/// The identifier of a DOM element that will be replaced with the
|
||||
/// application.
|
||||
///
|
||||
/// If set to `None`, the application will be appended to the HTML body.
|
||||
///
|
||||
/// By default, it is set to `"iced"`.
|
||||
pub target: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecific {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
target: Some(String::from("iced")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ enable = ["dep:iced_sentinel", "dep:once_cell"]
|
|||
|
||||
[dependencies]
|
||||
iced_core.workspace = true
|
||||
iced_style.workspace = true
|
||||
|
||||
iced_sentinel.workspace = true
|
||||
iced_sentinel.optional = true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
pub use iced_core as core;
|
||||
pub use iced_style as style;
|
||||
|
||||
use crate::core::theme;
|
||||
use crate::core::window;
|
||||
use crate::style::theme;
|
||||
|
||||
pub use internal::Timer;
|
||||
|
||||
|
|
@ -52,9 +51,9 @@ pub fn skip_next_timing() {
|
|||
|
||||
#[cfg(feature = "enable")]
|
||||
mod internal {
|
||||
use crate::core::theme;
|
||||
use crate::core::time::{Instant, SystemTime};
|
||||
use crate::core::window;
|
||||
use crate::style::theme;
|
||||
|
||||
use iced_sentinel::client::{self, Client};
|
||||
use iced_sentinel::timing::{self, Timing};
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="68" height="68" fill="none" viewBox="0 0 68 68"><rect width="68" height="68" fill="url(#paint0_linear)" rx="18"/><path fill="url(#paint1_linear)" fill-rule="evenodd" d="M52.2952 16.986L43.0273 26.4088L41.0081 24.4228L50.2761 15L52.2952 16.986ZM43.6175 17.0578L29.7156 31.192L27.6964 29.206L41.5983 15.0718L43.6175 17.0578ZM23.3109 24.9347C25.065 23.1615 26.8188 21.3887 30.4157 17.7317L32.4348 19.7177C28.8353 23.3774 27.0795 25.1523 25.3241 26.9267C23.8744 28.3921 22.425 29.8572 19.9355 32.3857L30.4685 35.1388L39.0223 26.442L41.0414 28.428L32.4876 37.1247L35.4218 47.6353L49.3019 33.7718L51.3033 35.7756L35.1269 51.9327L20.0283 47.7728L15.6165 32.7371L16.2 32.1438C19.7995 28.4842 21.5555 26.7091 23.3109 24.9347ZM32.6721 48.3186L29.7581 37.8804L19.2731 35.1398L22.3017 45.4614L32.6721 48.3186ZM48.3953 29.7021L50.7414 27.3561L48.7387 25.3535L46.3844 27.7078L36.4462 37.812L38.4654 39.7979L48.3953 29.7021Z" clip-rule="evenodd"/><defs><linearGradient id="paint0_linear" x1="34" x2="34" y1="0" y2="68" gradientUnits="userSpaceOnUse"><stop stop-color="#00A3FF"/><stop offset="1" stop-color="#30F"/></linearGradient><linearGradient id="paint1_linear" x1="20.5" x2="60" y1="47.466" y2="6.966" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity=".65"/></linearGradient></defs></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="160" height="180" fill="none" version="1.1" viewBox="35 20 185 195" xmlns="http://www.w3.org/2000/svg"><g filter="url(#filter0_f)"><rect x="59" y="51.001" width="136" height="160" rx="32" fill="url(#paint0_linear)"/></g><rect x="42" y="31.001" width="169.9" height="169.9" rx="49.815" fill="url(#paint1_linear)"/><path d="m182.62 65.747-28.136 28.606-6.13-6.0291 28.136-28.606 6.13 6.0291zm-26.344 0.218-42.204 42.909-6.13-6.029 42.204-42.909 6.13 6.0291zm-61.648 23.913c5.3254-5.3831 10.65-10.765 21.569-21.867l6.13 6.0291c-10.927 11.11-16.258 16.498-21.587 21.885-4.4007 4.4488-8.8009 8.8968-16.359 16.573l31.977 8.358 25.968-26.402 6.13 6.0292-25.968 26.402 8.907 31.908 42.138-42.087 6.076 6.083-49.109 49.05-45.837-12.628-13.394-45.646 1.7714-1.801c10.928-11.111 16.258-16.499 21.588-21.886zm28.419 70.99-8.846-31.689-31.831-8.32 9.1945 31.335 31.482 8.674zm47.734-56.517 7.122-7.1221-6.08-6.0797-7.147 7.1474-30.171 30.674 6.13 6.029 30.146-30.649z" clip-rule="evenodd" fill="url(#paint2_linear)" fill-rule="evenodd"/><defs><filter id="filter0_f" x="55" y="47.001" width="144" height="168" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur" stdDeviation="2"/></filter><linearGradient id="paint0_linear" x1="127" x2="127" y1="51.001" y2="211" gradientUnits="userSpaceOnUse"><stop offset=".052083"/><stop stop-opacity=".08" offset="1"/></linearGradient><linearGradient id="paint1_linear" x1="212" x2="57.5" y1="31.001" y2="189" gradientUnits="userSpaceOnUse"><stop stop-color="#00A3FF" offset="0"/><stop stop-color="#30f" offset="1"/></linearGradient><linearGradient id="paint2_linear" x1="86.098" x2="206.01" y1="158.28" y2="35.327" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" offset="0"/><stop stop-color="#fff" offset="1"/></linearGradient></defs></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2 KiB |
13
docs/redirect.html
Normal file
13
docs/redirect.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Redirecting...</title>
|
||||
<meta http-equiv="refresh" content="0; URL='/iced/'" />
|
||||
</head>
|
||||
<body>
|
||||
<p>If you are not redirected automatically, follow this <a href="/iced/">link</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,20 +1,17 @@
|
|||
use std::{f32::consts::PI, time::Instant};
|
||||
|
||||
use iced::executor;
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas::{
|
||||
self, stroke, Cache, Canvas, Geometry, Path, Stroke,
|
||||
};
|
||||
use iced::{
|
||||
Application, Command, Element, Length, Point, Rectangle, Renderer,
|
||||
Settings, Subscription, Theme,
|
||||
};
|
||||
use iced::{Element, Length, Point, Rectangle, Renderer, Subscription, Theme};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Arc::run(Settings {
|
||||
antialiasing: true,
|
||||
..Settings::default()
|
||||
})
|
||||
iced::program("Arc - Iced", Arc::update, Arc::view)
|
||||
.subscription(Arc::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct Arc {
|
||||
|
|
@ -27,30 +24,9 @@ enum Message {
|
|||
Tick,
|
||||
}
|
||||
|
||||
impl Application for Arc {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
(
|
||||
Arc {
|
||||
start: Instant::now(),
|
||||
cache: Cache::default(),
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Arc - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, _: Message) -> Command<Message> {
|
||||
impl Arc {
|
||||
fn update(&mut self, _: Message) {
|
||||
self.cache.clear();
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
|
|
@ -60,16 +36,21 @@ impl Application for Arc {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
Theme::Dark
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
iced::time::every(std::time::Duration::from_millis(10))
|
||||
.map(|_| Message::Tick)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Arc {
|
||||
fn default() -> Self {
|
||||
Arc {
|
||||
start: Instant::now(),
|
||||
cache: Cache::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message> canvas::Program<Message> for Arc {
|
||||
type State = ();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
//! This example showcases an interactive `Canvas` for drawing Bézier curves.
|
||||
use iced::widget::{button, column, text};
|
||||
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
||||
use iced::alignment;
|
||||
use iced::widget::{button, container, horizontal_space, hover};
|
||||
use iced::{Element, Length, Theme};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings {
|
||||
antialiasing: true,
|
||||
..Settings::default()
|
||||
})
|
||||
iced::program("Bezier Tool - Iced", Example::update, Example::view)
|
||||
.theme(|_| Theme::CatppuccinMocha)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -21,17 +22,7 @@ enum Message {
|
|||
Clear,
|
||||
}
|
||||
|
||||
impl Sandbox for Example {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Example::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Bezier tool - Iced")
|
||||
}
|
||||
|
||||
impl Example {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::AddCurve(curve) => {
|
||||
|
|
@ -46,14 +37,22 @@ impl Sandbox for Example {
|
|||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
column![
|
||||
text("Bezier tool example").width(Length::Shrink).size(50),
|
||||
container(hover(
|
||||
self.bezier.view(&self.curves).map(Message::AddCurve),
|
||||
button("Clear").padding(8).on_press(Message::Clear),
|
||||
]
|
||||
if self.curves.is_empty() {
|
||||
container(horizontal_space())
|
||||
} else {
|
||||
container(
|
||||
button("Clear")
|
||||
.style(button::danger)
|
||||
.on_press(Message::Clear),
|
||||
)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.align_x(alignment::Horizontal::Right)
|
||||
},
|
||||
))
|
||||
.padding(20)
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
@ -148,27 +147,24 @@ mod bezier {
|
|||
&self,
|
||||
state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let content = self.state.cache.draw(
|
||||
renderer,
|
||||
bounds.size(),
|
||||
|frame: &mut Frame| {
|
||||
Curve::draw_all(self.curves, frame);
|
||||
let content =
|
||||
self.state.cache.draw(renderer, bounds.size(), |frame| {
|
||||
Curve::draw_all(self.curves, frame, theme);
|
||||
|
||||
frame.stroke(
|
||||
&Path::rectangle(Point::ORIGIN, frame.size()),
|
||||
Stroke::default().with_width(2.0),
|
||||
Stroke::default()
|
||||
.with_width(2.0)
|
||||
.with_color(theme.palette().text),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if let Some(pending) = state {
|
||||
let pending_curve = pending.draw(renderer, bounds, cursor);
|
||||
|
||||
vec![content, pending_curve]
|
||||
vec![content, pending.draw(renderer, theme, bounds, cursor)]
|
||||
} else {
|
||||
vec![content]
|
||||
}
|
||||
|
|
@ -196,7 +192,7 @@ mod bezier {
|
|||
}
|
||||
|
||||
impl Curve {
|
||||
fn draw_all(curves: &[Curve], frame: &mut Frame) {
|
||||
fn draw_all(curves: &[Curve], frame: &mut Frame, theme: &Theme) {
|
||||
let curves = Path::new(|p| {
|
||||
for curve in curves {
|
||||
p.move_to(curve.from);
|
||||
|
|
@ -204,7 +200,12 @@ mod bezier {
|
|||
}
|
||||
});
|
||||
|
||||
frame.stroke(&curves, Stroke::default().with_width(2.0));
|
||||
frame.stroke(
|
||||
&curves,
|
||||
Stroke::default()
|
||||
.with_width(2.0)
|
||||
.with_color(theme.palette().text),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,6 +219,7 @@ mod bezier {
|
|||
fn draw(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> Geometry {
|
||||
|
|
@ -227,7 +229,12 @@ mod bezier {
|
|||
match *self {
|
||||
Pending::One { from } => {
|
||||
let line = Path::line(from, cursor_position);
|
||||
frame.stroke(&line, Stroke::default().with_width(2.0));
|
||||
frame.stroke(
|
||||
&line,
|
||||
Stroke::default()
|
||||
.with_width(2.0)
|
||||
.with_color(theme.palette().text),
|
||||
);
|
||||
}
|
||||
Pending::Two { from, to } => {
|
||||
let curve = Curve {
|
||||
|
|
@ -236,7 +243,7 @@ mod bezier {
|
|||
control: cursor_position,
|
||||
};
|
||||
|
||||
Curve::draw_all(&[curve], &mut frame);
|
||||
Curve::draw_all(&[curve], &mut frame, theme);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use iced::executor;
|
||||
use iced::font::{self, Font};
|
||||
use iced::theme;
|
||||
use iced::widget::{checkbox, column, container, row, text};
|
||||
use iced::{Application, Command, Element, Length, Settings, Theme};
|
||||
use iced::widget::{center, checkbox, column, row, text};
|
||||
use iced::{Element, Font};
|
||||
|
||||
const ICON_FONT: Font = Font::with_name("icons");
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
iced::program("Checkbox - Iced", Example::update, Example::view)
|
||||
.font(include_bytes!("../fonts/icons.ttf").as_slice())
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -22,28 +21,10 @@ enum Message {
|
|||
DefaultToggled(bool),
|
||||
CustomToggled(bool),
|
||||
StyledToggled(bool),
|
||||
FontLoaded(Result<(), font::Error>),
|
||||
}
|
||||
|
||||
impl Application for Example {
|
||||
type Message = Message;
|
||||
type Flags = ();
|
||||
type Executor = executor::Default;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
||||
(
|
||||
Self::default(),
|
||||
font::load(include_bytes!("../fonts/icons.ttf").as_slice())
|
||||
.map(Message::FontLoaded),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Checkbox - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
impl Example {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::DefaultToggled(default) => {
|
||||
self.default = default;
|
||||
|
|
@ -54,27 +35,23 @@ impl Application for Example {
|
|||
Message::CustomToggled(custom) => {
|
||||
self.custom = custom;
|
||||
}
|
||||
Message::FontLoaded(_) => (),
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let default_checkbox = checkbox("Default", self.default)
|
||||
.on_toggle(Message::DefaultToggled);
|
||||
|
||||
let styled_checkbox = |label, style| {
|
||||
let styled_checkbox = |label| {
|
||||
checkbox(label, self.styled)
|
||||
.on_toggle_maybe(self.default.then_some(Message::StyledToggled))
|
||||
.style(style)
|
||||
};
|
||||
|
||||
let checkboxes = row![
|
||||
styled_checkbox("Primary", theme::Checkbox::Primary),
|
||||
styled_checkbox("Secondary", theme::Checkbox::Secondary),
|
||||
styled_checkbox("Success", theme::Checkbox::Success),
|
||||
styled_checkbox("Danger", theme::Checkbox::Danger),
|
||||
styled_checkbox("Primary").style(checkbox::primary),
|
||||
styled_checkbox("Secondary").style(checkbox::secondary),
|
||||
styled_checkbox("Success").style(checkbox::success),
|
||||
styled_checkbox("Danger").style(checkbox::danger),
|
||||
]
|
||||
.spacing(20);
|
||||
|
||||
|
|
@ -91,11 +68,6 @@ impl Application for Example {
|
|||
let content =
|
||||
column![default_checkbox, checkboxes, custom_checkbox].spacing(20);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ iced.workspace = true
|
|||
iced.features = ["canvas", "tokio", "debug"]
|
||||
|
||||
time = { version = "0.3", features = ["local-offset"] }
|
||||
tracing-subscriber = "0.3"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
use iced::executor;
|
||||
use iced::alignment;
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
|
||||
use iced::widget::{canvas, container};
|
||||
use iced::{
|
||||
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
|
||||
Settings, Subscription, Theme, Vector,
|
||||
Degrees, Element, Font, Length, Point, Rectangle, Renderer, Subscription,
|
||||
Theme, Vector,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Clock::run(Settings {
|
||||
antialiasing: true,
|
||||
..Settings::default()
|
||||
})
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
iced::program("Clock - Iced", Clock::update, Clock::view)
|
||||
.subscription(Clock::subscription)
|
||||
.theme(Clock::theme)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct Clock {
|
||||
|
|
@ -24,28 +27,8 @@ enum Message {
|
|||
Tick(time::OffsetDateTime),
|
||||
}
|
||||
|
||||
impl Application for Clock {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
(
|
||||
Clock {
|
||||
now: time::OffsetDateTime::now_local()
|
||||
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
|
||||
clock: Cache::default(),
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Clock - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
impl Clock {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Tick(local_time) => {
|
||||
let now = local_time;
|
||||
|
|
@ -56,8 +39,6 @@ impl Application for Clock {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
|
|
@ -80,6 +61,21 @@ impl Application for Clock {
|
|||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
Theme::ALL[(self.now.unix_timestamp() as usize / 10) % Theme::ALL.len()]
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Clock {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
now: time::OffsetDateTime::now_local()
|
||||
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
|
||||
clock: Cache::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message> canvas::Program<Message> for Clock {
|
||||
|
|
@ -89,16 +85,18 @@ impl<Message> canvas::Program<Message> for Clock {
|
|||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let clock = self.clock.draw(renderer, bounds.size(), |frame| {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
let center = frame.center();
|
||||
let radius = frame.width().min(frame.height()) / 2.0;
|
||||
|
||||
let background = Path::circle(center, radius);
|
||||
frame.fill(&background, Color::from_rgb8(0x12, 0x93, 0xD8));
|
||||
frame.fill(&background, palette.secondary.strong.color);
|
||||
|
||||
let short_hand =
|
||||
Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius));
|
||||
|
|
@ -111,7 +109,7 @@ impl<Message> canvas::Program<Message> for Clock {
|
|||
let thin_stroke = || -> Stroke {
|
||||
Stroke {
|
||||
width,
|
||||
style: stroke::Style::Solid(Color::WHITE),
|
||||
style: stroke::Style::Solid(palette.secondary.strong.text),
|
||||
line_cap: LineCap::Round,
|
||||
..Stroke::default()
|
||||
}
|
||||
|
|
@ -120,7 +118,7 @@ impl<Message> canvas::Program<Message> for Clock {
|
|||
let wide_stroke = || -> Stroke {
|
||||
Stroke {
|
||||
width: width * 3.0,
|
||||
style: stroke::Style::Solid(Color::WHITE),
|
||||
style: stroke::Style::Solid(palette.secondary.strong.text),
|
||||
line_cap: LineCap::Round,
|
||||
..Stroke::default()
|
||||
}
|
||||
|
|
@ -139,8 +137,31 @@ impl<Message> canvas::Program<Message> for Clock {
|
|||
});
|
||||
|
||||
frame.with_save(|frame| {
|
||||
frame.rotate(hand_rotation(self.now.second(), 60));
|
||||
let rotation = hand_rotation(self.now.second(), 60);
|
||||
|
||||
frame.rotate(rotation);
|
||||
frame.stroke(&long_hand, thin_stroke());
|
||||
|
||||
let rotate_factor = if rotation < 180.0 { 1.0 } else { -1.0 };
|
||||
|
||||
frame.rotate(Degrees(-90.0 * rotate_factor));
|
||||
frame.fill_text(canvas::Text {
|
||||
content: theme.to_string(),
|
||||
size: (radius / 15.0).into(),
|
||||
position: Point::new(
|
||||
(0.78 * radius) * rotate_factor,
|
||||
-width * 2.0,
|
||||
),
|
||||
color: palette.secondary.strong.text,
|
||||
horizontal_alignment: if rotate_factor > 0.0 {
|
||||
alignment::Horizontal::Right
|
||||
} else {
|
||||
alignment::Horizontal::Left
|
||||
},
|
||||
vertical_alignment: alignment::Vertical::Bottom,
|
||||
font: Font::MONOSPACE,
|
||||
..canvas::Text::default()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -148,8 +169,8 @@ impl<Message> canvas::Program<Message> for Clock {
|
|||
}
|
||||
}
|
||||
|
||||
fn hand_rotation(n: u8, total: u8) -> f32 {
|
||||
fn hand_rotation(n: u8, total: u8) -> Degrees {
|
||||
let turns = n as f32 / total as f32;
|
||||
|
||||
2.0 * std::f32::consts::PI * turns
|
||||
Degrees(360.0 * turns)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced.features = ["canvas", "palette"]
|
||||
iced.features = ["canvas"]
|
||||
|
||||
palette.workspace = true
|
||||
|
|
|
|||
|
|
@ -3,20 +3,23 @@ use iced::mouse;
|
|||
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
|
||||
use iced::widget::{column, row, text, Slider};
|
||||
use iced::{
|
||||
Color, Element, Length, Pixels, Point, Rectangle, Renderer, Sandbox,
|
||||
Settings, Size, Vector,
|
||||
};
|
||||
use palette::{
|
||||
self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
|
||||
Color, Element, Font, Length, Pixels, Point, Rectangle, Renderer, Size,
|
||||
Vector,
|
||||
};
|
||||
use palette::{convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
ColorPalette::run(Settings {
|
||||
antialiasing: true,
|
||||
..Settings::default()
|
||||
})
|
||||
iced::program(
|
||||
"Color Palette - Iced",
|
||||
ColorPalette::update,
|
||||
ColorPalette::view,
|
||||
)
|
||||
.theme(ColorPalette::theme)
|
||||
.default_font(Font::MONOSPACE)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -40,17 +43,7 @@ pub enum Message {
|
|||
LchColorChanged(palette::Lch),
|
||||
}
|
||||
|
||||
impl Sandbox for ColorPalette {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Color palette - Iced")
|
||||
}
|
||||
|
||||
impl ColorPalette {
|
||||
fn update(&mut self, message: Message) {
|
||||
let srgb = match message {
|
||||
Message::RgbColorChanged(rgb) => Rgb::from(rgb),
|
||||
|
|
@ -87,6 +80,19 @@ impl Sandbox for ColorPalette {
|
|||
.spacing(10)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> iced::Theme {
|
||||
iced::Theme::custom(
|
||||
String::from("Custom"),
|
||||
iced::theme::Palette {
|
||||
background: self.theme.base,
|
||||
primary: *self.theme.lower.first().unwrap(),
|
||||
text: *self.theme.higher.last().unwrap(),
|
||||
success: *self.theme.lower.last().unwrap(),
|
||||
danger: *self.theme.higher.last().unwrap(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -150,7 +156,7 @@ impl Theme {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
fn draw(&self, frame: &mut Frame, text_color: Color) {
|
||||
let pad = 20.0;
|
||||
|
||||
let box_size = Size {
|
||||
|
|
@ -169,6 +175,7 @@ impl Theme {
|
|||
horizontal_alignment: alignment::Horizontal::Center,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
size: Pixels(15.0),
|
||||
color: text_color,
|
||||
..canvas::Text::default()
|
||||
};
|
||||
|
||||
|
|
@ -246,12 +253,14 @@ impl<Message> canvas::Program<Message> for Theme {
|
|||
&self,
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &iced::Theme,
|
||||
theme: &iced::Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry> {
|
||||
let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
|
||||
self.draw(frame);
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
self.draw(frame, palette.background.base.text);
|
||||
});
|
||||
|
||||
vec![theme]
|
||||
|
|
@ -308,7 +317,7 @@ impl<C: ColorSpace + Copy> ColorPicker<C> {
|
|||
slider(cr1, c1, move |v| C::new(v, c2, c3)),
|
||||
slider(cr2, c2, move |v| C::new(c1, v, c3)),
|
||||
slider(cr3, c3, move |v| C::new(c1, c2, v)),
|
||||
text(color.to_string()).width(185).size(14),
|
||||
text(color.to_string()).width(185).size(12),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use iced::widget::{
|
||||
column, combo_box, container, scrollable, text, vertical_space,
|
||||
center, column, combo_box, scrollable, text, vertical_space,
|
||||
};
|
||||
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
||||
use iced::{Alignment, Element, Length};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
iced::run("Combo Box - Iced", Example::update, Example::view)
|
||||
}
|
||||
|
||||
struct Example {
|
||||
|
|
@ -20,9 +20,7 @@ enum Message {
|
|||
Closed,
|
||||
}
|
||||
|
||||
impl Sandbox for Example {
|
||||
type Message = Message;
|
||||
|
||||
impl Example {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
languages: combo_box::State::new(Language::ALL.to_vec()),
|
||||
|
|
@ -31,10 +29,6 @@ impl Sandbox for Example {
|
|||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Combo box - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Selected(language) => {
|
||||
|
|
@ -74,12 +68,13 @@ impl Sandbox for Example {
|
|||
.align_items(Alignment::Center)
|
||||
.spacing(10);
|
||||
|
||||
container(scrollable(content))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(scrollable(content)).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Example {
|
||||
fn default() -> Self {
|
||||
Example::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use iced::widget::container;
|
||||
use iced::{Element, Length, Sandbox, Settings};
|
||||
use iced::widget::center;
|
||||
use iced::Element;
|
||||
|
||||
use numeric_input::numeric_input;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Component::run(Settings::default())
|
||||
iced::run("Component - Iced", Component::update, Component::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -17,17 +17,7 @@ enum Message {
|
|||
NumericInputChanged(Option<u32>),
|
||||
}
|
||||
|
||||
impl Sandbox for Component {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Component - Iced")
|
||||
}
|
||||
|
||||
impl Component {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::NumericInputChanged(value) => {
|
||||
|
|
@ -37,10 +27,8 @@ impl Sandbox for Component {
|
|||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
container(numeric_input(self.value, Message::NumericInputChanged))
|
||||
center(numeric_input(self.value, Message::NumericInputChanged))
|
||||
.padding(20)
|
||||
.height(Length::Fill)
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +69,10 @@ mod numeric_input {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Message> Component<Message> for NumericInput<Message> {
|
||||
impl<Message, Theme> Component<Message, Theme> for NumericInput<Message>
|
||||
where
|
||||
Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static,
|
||||
{
|
||||
type State = ();
|
||||
type Event = Event;
|
||||
|
||||
|
|
@ -111,7 +102,7 @@ mod numeric_input {
|
|||
}
|
||||
}
|
||||
|
||||
fn view(&self, _state: &Self::State) -> Element<Event> {
|
||||
fn view(&self, _state: &Self::State) -> Element<'_, Event, Theme> {
|
||||
let button = |label, on_press| {
|
||||
button(
|
||||
text(label)
|
||||
|
|
@ -152,8 +143,10 @@ mod numeric_input {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> From<NumericInput<Message>> for Element<'a, Message>
|
||||
impl<'a, Message, Theme> From<NumericInput<Message>>
|
||||
for Element<'a, Message, Theme>
|
||||
where
|
||||
Theme: text::Catalog + button::Catalog + text_input::Catalog + 'static,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(numeric_input: NumericInput<Message>) -> Self {
|
||||
|
|
|
|||
|
|
@ -1,50 +1,40 @@
|
|||
use iced::widget::{button, column, text};
|
||||
use iced::{Alignment, Element, Sandbox, Settings};
|
||||
use iced::widget::{button, column, text, Column};
|
||||
use iced::Alignment;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Counter::run(Settings::default())
|
||||
iced::run("A cool counter", Counter::update, Counter::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Counter {
|
||||
value: i32,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Message {
|
||||
IncrementPressed,
|
||||
DecrementPressed,
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
|
||||
impl Sandbox for Counter {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Self { value: 0 }
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Counter - Iced")
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::IncrementPressed => {
|
||||
Message::Increment => {
|
||||
self.value += 1;
|
||||
}
|
||||
Message::DecrementPressed => {
|
||||
Message::Decrement => {
|
||||
self.value -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
fn view(&self) -> Column<Message> {
|
||||
column![
|
||||
button("Increment").on_press(Message::IncrementPressed),
|
||||
button("Increment").on_press(Message::Increment),
|
||||
text(self.value).size(50),
|
||||
button("Decrement").on_press(Message::DecrementPressed)
|
||||
button("Decrement").on_press(Message::Decrement)
|
||||
]
|
||||
.padding(20)
|
||||
.align_items(Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,13 +81,11 @@ mod quad {
|
|||
}
|
||||
}
|
||||
|
||||
use iced::widget::{column, container, slider, text};
|
||||
use iced::{
|
||||
Alignment, Color, Element, Length, Sandbox, Settings, Shadow, Vector,
|
||||
};
|
||||
use iced::widget::{center, column, slider, text};
|
||||
use iced::{Alignment, Color, Element, Shadow, Vector};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
iced::run("Custom Quad - Iced", Example::update, Example::view)
|
||||
}
|
||||
|
||||
struct Example {
|
||||
|
|
@ -109,9 +107,7 @@ enum Message {
|
|||
ShadowBlurRadiusChanged(f32),
|
||||
}
|
||||
|
||||
impl Sandbox for Example {
|
||||
type Message = Message;
|
||||
|
||||
impl Example {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
radius: [50.0; 4],
|
||||
|
|
@ -124,10 +120,6 @@ impl Sandbox for Example {
|
|||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Custom widget - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
let [tl, tr, br, bl] = self.radius;
|
||||
match message {
|
||||
|
|
@ -195,11 +187,12 @@ impl Sandbox for Example {
|
|||
.max_width(500)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Example {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,16 @@ 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::widget::{center, checkbox, column, row, shader, slider, text};
|
||||
use iced::window;
|
||||
use iced::{
|
||||
Alignment, Application, Color, Command, Element, Length, Subscription,
|
||||
Theme,
|
||||
};
|
||||
use iced::{Alignment, Color, Element, Length, Subscription};
|
||||
|
||||
fn main() -> iced::Result {
|
||||
IcedCubes::run(iced::Settings::default())
|
||||
iced::program("Custom Shader - Iced", IcedCubes::update, IcedCubes::view)
|
||||
.subscription(IcedCubes::subscription)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct IcedCubes {
|
||||
|
|
@ -30,27 +28,15 @@ enum Message {
|
|||
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(),
|
||||
)
|
||||
impl IcedCubes {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
start: Instant::now(),
|
||||
scene: Scene::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
"Iced Cubes".to_string()
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::CubeAmountChanged(amount) => {
|
||||
self.scene.change_amount(amount);
|
||||
|
|
@ -68,11 +54,9 @@ impl Application for IcedCubes {
|
|||
self.scene.light_color = color;
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<'_, Self::Message> {
|
||||
fn view(&self) -> Element<'_, Message> {
|
||||
let top_controls = row![
|
||||
control(
|
||||
"Amount",
|
||||
|
|
@ -139,19 +123,20 @@ impl Application for IcedCubes {
|
|||
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()
|
||||
center(column![shader, controls].align_items(Alignment::Center)).into()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Self::Message> {
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
window::frames().map(Message::Tick)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for IcedCubes {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn control<'a>(
|
||||
label: &'static str,
|
||||
control: impl Into<Element<'a, Message>>,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use pipeline::cube::{self, Cube};
|
|||
|
||||
use iced::mouse;
|
||||
use iced::time::Duration;
|
||||
use iced::widget::shader;
|
||||
use iced::{Color, Rectangle, Size};
|
||||
use iced::widget::shader::{self, Viewport};
|
||||
use iced::{Color, Rectangle};
|
||||
|
||||
use glam::Vec3;
|
||||
use rand::Rng;
|
||||
|
|
@ -130,25 +130,29 @@ impl Primitive {
|
|||
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,
|
||||
format: wgpu::TextureFormat,
|
||||
storage: &mut shader::Storage,
|
||||
_bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
if !storage.has::<Pipeline>() {
|
||||
storage.store(Pipeline::new(device, queue, format, target_size));
|
||||
storage.store(Pipeline::new(
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
viewport.physical_size(),
|
||||
));
|
||||
}
|
||||
|
||||
let pipeline = storage.get_mut::<Pipeline>().unwrap();
|
||||
|
||||
//upload data to GPU
|
||||
// Upload data to GPU
|
||||
pipeline.update(
|
||||
device,
|
||||
queue,
|
||||
target_size,
|
||||
viewport.physical_size(),
|
||||
&self.uniforms,
|
||||
self.cubes.len(),
|
||||
&self.cubes,
|
||||
|
|
@ -157,20 +161,19 @@ impl shader::Primitive for Primitive {
|
|||
|
||||
fn render(
|
||||
&self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
storage: &shader::Storage,
|
||||
target: &wgpu::TextureView,
|
||||
_target_size: Size<u32>,
|
||||
viewport: Rectangle<u32>,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
clip_bounds: &Rectangle<u32>,
|
||||
) {
|
||||
//at this point our pipeline should always be initialized
|
||||
// At this point our pipeline should always be initialized
|
||||
let pipeline = storage.get::<Pipeline>().unwrap();
|
||||
|
||||
//render primitive
|
||||
// Render primitive
|
||||
pipeline.render(
|
||||
target,
|
||||
encoder,
|
||||
viewport,
|
||||
*clip_bounds,
|
||||
self.cubes.len() as u32,
|
||||
self.show_depth_buffer,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ mod circle {
|
|||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: layout.bounds(),
|
||||
border: Border::with_radius(self.radius),
|
||||
border: Border::rounded(self.radius),
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
Color::BLACK,
|
||||
|
|
@ -82,11 +82,11 @@ mod circle {
|
|||
}
|
||||
|
||||
use circle::circle;
|
||||
use iced::widget::{column, container, slider, text};
|
||||
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
||||
use iced::widget::{center, column, slider, text};
|
||||
use iced::{Alignment, Element};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
iced::run("Custom Widget - Iced", Example::update, Example::view)
|
||||
}
|
||||
|
||||
struct Example {
|
||||
|
|
@ -98,17 +98,11 @@ enum Message {
|
|||
RadiusChanged(f32),
|
||||
}
|
||||
|
||||
impl Sandbox for Example {
|
||||
type Message = Message;
|
||||
|
||||
impl Example {
|
||||
fn new() -> Self {
|
||||
Example { radius: 50.0 }
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Custom widget - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::RadiusChanged(radius) => {
|
||||
|
|
@ -128,11 +122,12 @@ impl Sandbox for Example {
|
|||
.max_width(500)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Example {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,12 +12,6 @@ pub fn file<I: 'static + Hash + Copy + Send + Sync, T: ToString>(
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Clone)]
|
||||
pub struct Download<I> {
|
||||
id: I,
|
||||
url: String,
|
||||
}
|
||||
|
||||
async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) {
|
||||
match state {
|
||||
State::Ready(url) => {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
use iced::executor;
|
||||
use iced::widget::{button, column, container, progress_bar, text, Column};
|
||||
use iced::{
|
||||
Alignment, Application, Command, Element, Length, Settings, Subscription,
|
||||
Theme,
|
||||
};
|
||||
|
||||
mod download;
|
||||
|
||||
use iced::widget::{button, center, column, progress_bar, text, Column};
|
||||
use iced::{Alignment, Element, Subscription};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
iced::program("Download Progress - Iced", Example::update, Example::view)
|
||||
.subscription(Example::subscription)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -24,27 +22,15 @@ pub enum Message {
|
|||
DownloadProgressed((usize, download::Progress)),
|
||||
}
|
||||
|
||||
impl Application for Example {
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Executor = executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Example, Command<Message>) {
|
||||
(
|
||||
Example {
|
||||
downloads: vec![Download::new(0)],
|
||||
last_id: 0,
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
impl Example {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
downloads: vec![Download::new(0)],
|
||||
last_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Download progress - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Add => {
|
||||
self.last_id += 1;
|
||||
|
|
@ -63,9 +49,7 @@ impl Application for Example {
|
|||
download.progress(progress);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
|
|
@ -83,13 +67,13 @@ impl Application for Example {
|
|||
.spacing(20)
|
||||
.align_items(Alignment::End);
|
||||
|
||||
container(downloads)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.padding(20)
|
||||
.into()
|
||||
center(downloads).padding(20).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Example {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
use iced::executor;
|
||||
use iced::highlighter::{self, Highlighter};
|
||||
use iced::keyboard;
|
||||
use iced::theme::{self, Theme};
|
||||
use iced::widget::{
|
||||
button, column, container, horizontal_space, pick_list, row, text,
|
||||
text_editor, tooltip,
|
||||
};
|
||||
use iced::{
|
||||
Alignment, Application, Command, Element, Font, Length, Settings,
|
||||
Subscription,
|
||||
};
|
||||
use iced::{Alignment, Command, Element, Font, Length, Subscription, Theme};
|
||||
|
||||
use std::ffi;
|
||||
use std::io;
|
||||
|
|
@ -17,11 +12,13 @@ use std::path::{Path, PathBuf};
|
|||
use std::sync::Arc;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Editor::run(Settings {
|
||||
fonts: vec![include_bytes!("../fonts/icons.ttf").as_slice().into()],
|
||||
default_font: Font::MONOSPACE,
|
||||
..Settings::default()
|
||||
})
|
||||
iced::program("Editor - Iced", Editor::update, Editor::view)
|
||||
.load(Editor::load)
|
||||
.subscription(Editor::subscription)
|
||||
.theme(Editor::theme)
|
||||
.font(include_bytes!("../fonts/icons.ttf").as_slice())
|
||||
.default_font(Font::MONOSPACE)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct Editor {
|
||||
|
|
@ -43,27 +40,22 @@ enum Message {
|
|||
FileSaved(Result<PathBuf, Error>),
|
||||
}
|
||||
|
||||
impl Application for Editor {
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Executor = executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
||||
(
|
||||
Self {
|
||||
file: None,
|
||||
content: text_editor::Content::new(),
|
||||
theme: highlighter::Theme::SolarizedDark,
|
||||
is_loading: true,
|
||||
is_dirty: false,
|
||||
},
|
||||
Command::perform(load_file(default_file()), Message::FileOpened),
|
||||
)
|
||||
impl Editor {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
file: None,
|
||||
content: text_editor::Content::new(),
|
||||
theme: highlighter::Theme::SolarizedDark,
|
||||
is_loading: true,
|
||||
is_dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Editor - Iced")
|
||||
fn load() -> Command<Message> {
|
||||
Command::perform(
|
||||
load_file(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR"))),
|
||||
Message::FileOpened,
|
||||
)
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
|
|
@ -222,16 +214,18 @@ impl Application for Editor {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Editor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Error {
|
||||
DialogClosed,
|
||||
IoError(io::ErrorKind),
|
||||
}
|
||||
|
||||
fn default_file() -> PathBuf {
|
||||
PathBuf::from(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR")))
|
||||
}
|
||||
|
||||
async fn open_file() -> Result<(PathBuf, Arc<String>), Error> {
|
||||
let picked_file = rfd::AsyncFileDialog::new()
|
||||
.set_title("Open a text file...")
|
||||
|
|
@ -239,10 +233,14 @@ async fn open_file() -> Result<(PathBuf, Arc<String>), Error> {
|
|||
.await
|
||||
.ok_or(Error::DialogClosed)?;
|
||||
|
||||
load_file(picked_file.path().to_owned()).await
|
||||
load_file(picked_file).await
|
||||
}
|
||||
|
||||
async fn load_file(path: PathBuf) -> Result<(PathBuf, Arc<String>), Error> {
|
||||
async fn load_file(
|
||||
path: impl Into<PathBuf>,
|
||||
) -> Result<(PathBuf, Arc<String>), Error> {
|
||||
let path = path.into();
|
||||
|
||||
let contents = tokio::fs::read_to_string(&path)
|
||||
.await
|
||||
.map(Arc::new)
|
||||
|
|
@ -279,7 +277,7 @@ fn action<'a, Message: Clone + 'a>(
|
|||
label: &'a str,
|
||||
on_press: Option<Message>,
|
||||
) -> Element<'a, Message> {
|
||||
let action = button(container(content).width(30).center_x());
|
||||
let action = button(container(content).center_x().width(30));
|
||||
|
||||
if let Some(on_press) = on_press {
|
||||
tooltip(
|
||||
|
|
@ -287,10 +285,10 @@ fn action<'a, Message: Clone + 'a>(
|
|||
label,
|
||||
tooltip::Position::FollowCursor,
|
||||
)
|
||||
.style(theme::Container::Box)
|
||||
.style(container::rounded_box)
|
||||
.into()
|
||||
} else {
|
||||
action.style(theme::Button::Secondary).into()
|
||||
action.style(button::secondary).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
use iced::alignment;
|
||||
use iced::event::{self, Event};
|
||||
use iced::executor;
|
||||
use iced::widget::{button, checkbox, container, text, Column};
|
||||
use iced::widget::{button, center, checkbox, text, Column};
|
||||
use iced::window;
|
||||
use iced::{
|
||||
Alignment, Application, Command, Element, Length, Settings, Subscription,
|
||||
Theme,
|
||||
};
|
||||
use iced::{Alignment, Command, Element, Length, Subscription};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Events::run(Settings {
|
||||
window: window::Settings {
|
||||
exit_on_close_request: false,
|
||||
..window::Settings::default()
|
||||
},
|
||||
..Settings::default()
|
||||
})
|
||||
iced::program("Events - Iced", Events::update, Events::view)
|
||||
.subscription(Events::subscription)
|
||||
.exit_on_close_request(false)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
|
@ -31,20 +24,7 @@ enum Message {
|
|||
Exit,
|
||||
}
|
||||
|
||||
impl Application for Events {
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Executor = executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Events, Command<Message>) {
|
||||
(Events::default(), Command::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Events - Iced")
|
||||
}
|
||||
|
||||
impl Events {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::EventOccurred(event) if self.enabled => {
|
||||
|
|
@ -104,11 +84,6 @@ impl Application for Events {
|
|||
.push(toggle)
|
||||
.push(exit);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
use iced::executor;
|
||||
use iced::widget::{button, column, container};
|
||||
use iced::widget::{button, center, column};
|
||||
use iced::window;
|
||||
use iced::{Alignment, Application, Command, Element, Length, Settings, Theme};
|
||||
use iced::{Alignment, Command, Element};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Exit::run(Settings::default())
|
||||
iced::program("Exit - Iced", Exit::update, Exit::view).run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -18,20 +17,7 @@ enum Message {
|
|||
Exit,
|
||||
}
|
||||
|
||||
impl Application for Exit {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
(Self::default(), Command::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Exit - Iced")
|
||||
}
|
||||
|
||||
impl Exit {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::Confirm => window::close(window::Id::MAIN),
|
||||
|
|
@ -60,12 +46,6 @@ impl Application for Exit {
|
|||
.spacing(10)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(20)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).padding(20).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
examples/ferris/Cargo.toml
Normal file
10
examples/ferris/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "ferris"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced.features = ["image", "tokio", "debug"]
|
||||
211
examples/ferris/src/main.rs
Normal file
211
examples/ferris/src/main.rs
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
use iced::time::Instant;
|
||||
use iced::widget::{
|
||||
center, checkbox, column, container, image, pick_list, row, slider, text,
|
||||
};
|
||||
use iced::window;
|
||||
use iced::{
|
||||
Alignment, Color, ContentFit, Degrees, Element, Length, Radians, Rotation,
|
||||
Subscription, Theme,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
iced::program("Ferris - Iced", Image::update, Image::view)
|
||||
.subscription(Image::subscription)
|
||||
.theme(|_| Theme::TokyoNight)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct Image {
|
||||
width: f32,
|
||||
opacity: f32,
|
||||
rotation: Rotation,
|
||||
content_fit: ContentFit,
|
||||
spin: bool,
|
||||
last_tick: Instant,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Message {
|
||||
WidthChanged(f32),
|
||||
OpacityChanged(f32),
|
||||
RotationStrategyChanged(RotationStrategy),
|
||||
RotationChanged(Degrees),
|
||||
ContentFitChanged(ContentFit),
|
||||
SpinToggled(bool),
|
||||
RedrawRequested(Instant),
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::WidthChanged(width) => {
|
||||
self.width = width;
|
||||
}
|
||||
Message::OpacityChanged(opacity) => {
|
||||
self.opacity = opacity;
|
||||
}
|
||||
Message::RotationStrategyChanged(strategy) => {
|
||||
self.rotation = match strategy {
|
||||
RotationStrategy::Floating => {
|
||||
Rotation::Floating(self.rotation.radians())
|
||||
}
|
||||
RotationStrategy::Solid => {
|
||||
Rotation::Solid(self.rotation.radians())
|
||||
}
|
||||
};
|
||||
}
|
||||
Message::RotationChanged(rotation) => {
|
||||
self.rotation = match self.rotation {
|
||||
Rotation::Floating(_) => {
|
||||
Rotation::Floating(rotation.into())
|
||||
}
|
||||
Rotation::Solid(_) => Rotation::Solid(rotation.into()),
|
||||
};
|
||||
}
|
||||
Message::ContentFitChanged(content_fit) => {
|
||||
self.content_fit = content_fit;
|
||||
}
|
||||
Message::SpinToggled(spin) => {
|
||||
self.spin = spin;
|
||||
self.last_tick = Instant::now();
|
||||
}
|
||||
Message::RedrawRequested(now) => {
|
||||
const ROTATION_SPEED: Degrees = Degrees(360.0);
|
||||
|
||||
let delta = (now - self.last_tick).as_millis() as f32 / 1_000.0;
|
||||
|
||||
*self.rotation.radians_mut() = (self.rotation.radians()
|
||||
+ ROTATION_SPEED * delta)
|
||||
% (2.0 * Radians::PI);
|
||||
|
||||
self.last_tick = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
if self.spin {
|
||||
window::frames().map(Message::RedrawRequested)
|
||||
} else {
|
||||
Subscription::none()
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let i_am_ferris = column![
|
||||
"Hello!",
|
||||
Element::from(
|
||||
image(format!(
|
||||
"{}/../tour/images/ferris.png",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
))
|
||||
.width(self.width)
|
||||
.content_fit(self.content_fit)
|
||||
.rotation(self.rotation)
|
||||
.opacity(self.opacity)
|
||||
)
|
||||
.explain(Color::WHITE),
|
||||
"I am Ferris!"
|
||||
]
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
let fit = row![
|
||||
pick_list(
|
||||
[
|
||||
ContentFit::Contain,
|
||||
ContentFit::Cover,
|
||||
ContentFit::Fill,
|
||||
ContentFit::None,
|
||||
ContentFit::ScaleDown
|
||||
],
|
||||
Some(self.content_fit),
|
||||
Message::ContentFitChanged
|
||||
)
|
||||
.width(Length::Fill),
|
||||
pick_list(
|
||||
[RotationStrategy::Floating, RotationStrategy::Solid],
|
||||
Some(match self.rotation {
|
||||
Rotation::Floating(_) => RotationStrategy::Floating,
|
||||
Rotation::Solid(_) => RotationStrategy::Solid,
|
||||
}),
|
||||
Message::RotationStrategyChanged,
|
||||
)
|
||||
.width(Length::Fill),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::End);
|
||||
|
||||
let properties = row![
|
||||
with_value(
|
||||
slider(100.0..=500.0, self.width, Message::WidthChanged),
|
||||
format!("Width: {}px", self.width)
|
||||
),
|
||||
with_value(
|
||||
slider(0.0..=1.0, self.opacity, Message::OpacityChanged)
|
||||
.step(0.01),
|
||||
format!("Opacity: {:.2}", self.opacity)
|
||||
),
|
||||
with_value(
|
||||
row![
|
||||
slider(
|
||||
Degrees::RANGE,
|
||||
self.rotation.degrees(),
|
||||
Message::RotationChanged
|
||||
),
|
||||
checkbox("Spin!", self.spin)
|
||||
.text_size(12)
|
||||
.on_toggle(Message::SpinToggled)
|
||||
.size(12)
|
||||
]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center),
|
||||
format!("Rotation: {:.0}°", f32::from(self.rotation.degrees()))
|
||||
)
|
||||
]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::End);
|
||||
|
||||
container(column![fit, center(i_am_ferris), properties].spacing(10))
|
||||
.padding(10)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Image {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: 300.0,
|
||||
opacity: 1.0,
|
||||
rotation: Rotation::default(),
|
||||
content_fit: ContentFit::default(),
|
||||
spin: false,
|
||||
last_tick: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum RotationStrategy {
|
||||
Floating,
|
||||
Solid,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RotationStrategy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Floating => "Floating",
|
||||
Self::Solid => "Solid",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn with_value<'a>(
|
||||
control: impl Into<Element<'a, Message>>,
|
||||
value: String,
|
||||
) -> Element<'a, Message> {
|
||||
column![control.into(), text(value).size(12).line_height(1.0)]
|
||||
.spacing(2)
|
||||
.align_items(Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
|
|
@ -5,32 +5,24 @@ mod preset;
|
|||
use grid::Grid;
|
||||
use preset::Preset;
|
||||
|
||||
use iced::executor;
|
||||
use iced::theme::{self, Theme};
|
||||
use iced::time;
|
||||
use iced::widget::{
|
||||
button, checkbox, column, container, pick_list, row, slider, text,
|
||||
};
|
||||
use iced::window;
|
||||
use iced::{
|
||||
Alignment, Application, Command, Element, Length, Settings, Subscription,
|
||||
};
|
||||
use iced::{Alignment, Command, Element, Length, Subscription, Theme};
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
GameOfLife::run(Settings {
|
||||
antialiasing: true,
|
||||
window: window::Settings {
|
||||
position: window::Position::Centered,
|
||||
..window::Settings::default()
|
||||
},
|
||||
..Settings::default()
|
||||
})
|
||||
iced::program("Game of Life - Iced", GameOfLife::update, GameOfLife::view)
|
||||
.subscription(GameOfLife::subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.antialiasing(true)
|
||||
.centered()
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct GameOfLife {
|
||||
grid: Grid,
|
||||
is_playing: bool,
|
||||
|
|
@ -52,24 +44,16 @@ enum Message {
|
|||
PresetPicked(Preset),
|
||||
}
|
||||
|
||||
impl Application for GameOfLife {
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Executor = executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
(
|
||||
Self {
|
||||
speed: 5,
|
||||
..Self::default()
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Game of Life - Iced")
|
||||
impl GameOfLife {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
grid: Grid::default(),
|
||||
is_playing: false,
|
||||
queued_ticks: 0,
|
||||
speed: 5,
|
||||
next_speed: None,
|
||||
version: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
|
|
@ -154,9 +138,11 @@ impl Application for GameOfLife {
|
|||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
Theme::Dark
|
||||
impl Default for GameOfLife {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +157,7 @@ fn view_controls<'a>(
|
|||
.on_press(Message::TogglePlayback),
|
||||
button("Next")
|
||||
.on_press(Message::Next)
|
||||
.style(theme::Button::Secondary),
|
||||
.style(button::secondary),
|
||||
]
|
||||
.spacing(10);
|
||||
|
||||
|
|
@ -185,17 +171,14 @@ fn view_controls<'a>(
|
|||
row![
|
||||
playback_controls,
|
||||
speed_controls,
|
||||
checkbox("Grid", is_grid_enabled)
|
||||
.on_toggle(Message::ToggleGrid)
|
||||
.size(16)
|
||||
.spacing(5)
|
||||
.text_size(16),
|
||||
pick_list(preset::ALL, Some(preset), Message::PresetPicked)
|
||||
.padding(8)
|
||||
.text_size(16),
|
||||
button("Clear")
|
||||
.on_press(Message::Clear)
|
||||
.style(theme::Button::Destructive),
|
||||
checkbox("Grid", is_grid_enabled).on_toggle(Message::ToggleGrid),
|
||||
row![
|
||||
pick_list(preset::ALL, Some(preset), Message::PresetPicked),
|
||||
button("Clear")
|
||||
.on_press(Message::Clear)
|
||||
.style(button::danger)
|
||||
]
|
||||
.spacing(10)
|
||||
]
|
||||
.padding(10)
|
||||
.spacing(20)
|
||||
|
|
@ -619,9 +602,7 @@ mod grid {
|
|||
frame.into_geometry()
|
||||
};
|
||||
|
||||
if self.scaling < 0.2 || !self.show_lines {
|
||||
vec![life, overlay]
|
||||
} else {
|
||||
if self.scaling >= 0.2 && self.show_lines {
|
||||
let grid =
|
||||
self.grid_cache.draw(renderer, bounds.size(), |frame| {
|
||||
frame.translate(center);
|
||||
|
|
@ -658,6 +639,8 @@ mod grid {
|
|||
});
|
||||
|
||||
vec![life, grid, overlay]
|
||||
} else {
|
||||
vec![life, overlay]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ mod rainbow {
|
|||
use iced::advanced::renderer;
|
||||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::mouse;
|
||||
use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
|
||||
use iced::{
|
||||
Element, Length, Rectangle, Renderer, Size, Theme, Transformation,
|
||||
Vector,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Rainbow;
|
||||
|
|
@ -44,7 +47,9 @@ mod rainbow {
|
|||
cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
|
||||
use iced::advanced::graphics::mesh::{
|
||||
self, Mesh, Renderer as _, SolidVertex2D,
|
||||
};
|
||||
use iced::advanced::Renderer as _;
|
||||
|
||||
let bounds = layout.bounds();
|
||||
|
|
@ -77,7 +82,6 @@ mod rainbow {
|
|||
let posn_l = [0.0, bounds.height / 2.0];
|
||||
|
||||
let mesh = Mesh::Solid {
|
||||
size: bounds.size(),
|
||||
buffers: mesh::Indexed {
|
||||
vertices: vec![
|
||||
SolidVertex2D {
|
||||
|
|
@ -128,6 +132,8 @@ mod rainbow {
|
|||
0, 8, 1, // L
|
||||
],
|
||||
},
|
||||
transformation: Transformation::IDENTITY,
|
||||
clip_bounds: Rectangle::INFINITE,
|
||||
};
|
||||
|
||||
renderer.with_translation(
|
||||
|
|
@ -147,51 +153,30 @@ mod rainbow {
|
|||
}
|
||||
|
||||
use iced::widget::{column, container, scrollable};
|
||||
use iced::{Element, Length, Sandbox, Settings};
|
||||
use iced::Element;
|
||||
use rainbow::rainbow;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
iced::run("Custom 2D Geometry - Iced", |_: &mut _, _| {}, view)
|
||||
}
|
||||
|
||||
struct Example;
|
||||
|
||||
impl Sandbox for Example {
|
||||
type Message = ();
|
||||
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Custom 2D geometry - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, _: ()) {}
|
||||
|
||||
fn view(&self) -> Element<()> {
|
||||
let content = column![
|
||||
rainbow(),
|
||||
"In this example we draw a custom widget Rainbow, using \
|
||||
fn view(_state: &()) -> Element<'_, ()> {
|
||||
let content = column![
|
||||
rainbow(),
|
||||
"In this example we draw a custom widget Rainbow, using \
|
||||
the Mesh2D primitive. This primitive supplies a list of \
|
||||
triangles, expressed as vertices and indices.",
|
||||
"Move your cursor over it, and see the center vertex \
|
||||
"Move your cursor over it, and see the center vertex \
|
||||
follow you!",
|
||||
"Every Vertex2D defines its own color. You could use the \
|
||||
"Every Vertex2D defines its own color. You could use the \
|
||||
Mesh2D primitive to render virtually any two-dimensional \
|
||||
geometry for your widget.",
|
||||
]
|
||||
.padding(20)
|
||||
.spacing(20)
|
||||
.max_width(500);
|
||||
]
|
||||
.padding(20)
|
||||
.spacing(20)
|
||||
.max_width(500);
|
||||
|
||||
let scrollable =
|
||||
scrollable(container(content).width(Length::Fill).center_x());
|
||||
let scrollable = scrollable(container(content).center_x());
|
||||
|
||||
container(scrollable)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
container(scrollable).center_y().into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,17 @@
|
|||
use iced::application;
|
||||
use iced::theme::{self, Theme};
|
||||
use iced::gradient;
|
||||
use iced::program;
|
||||
use iced::widget::{
|
||||
checkbox, column, container, horizontal_space, row, slider, text,
|
||||
};
|
||||
use iced::{gradient, window};
|
||||
use iced::{
|
||||
Alignment, Background, Color, Element, Length, Radians, Sandbox, Settings,
|
||||
};
|
||||
use iced::{Alignment, Color, Element, Length, Radians, Theme};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
Gradient::run(Settings {
|
||||
window: window::Settings {
|
||||
transparent: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
iced::program("Gradient - Iced", Gradient::update, Gradient::view)
|
||||
.style(Gradient::style)
|
||||
.transparent(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -36,9 +30,7 @@ enum Message {
|
|||
TransparentToggled(bool),
|
||||
}
|
||||
|
||||
impl Sandbox for Gradient {
|
||||
type Message = Message;
|
||||
|
||||
impl Gradient {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
start: Color::WHITE,
|
||||
|
|
@ -48,10 +40,6 @@ impl Sandbox for Gradient {
|
|||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Gradient")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::StartChanged(color) => self.start = color,
|
||||
|
|
@ -72,19 +60,15 @@ impl Sandbox for Gradient {
|
|||
} = *self;
|
||||
|
||||
let gradient_box = container(horizontal_space())
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(move |_: &_| {
|
||||
.style(move |_theme| {
|
||||
let gradient = gradient::Linear::new(angle)
|
||||
.add_stop(0.0, start)
|
||||
.add_stop(1.0, end)
|
||||
.into();
|
||||
.add_stop(1.0, end);
|
||||
|
||||
container::Appearance {
|
||||
background: Some(Background::Gradient(gradient)),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
gradient.into()
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
let angle_picker = row![
|
||||
text("Angle").width(64),
|
||||
|
|
@ -111,20 +95,26 @@ impl Sandbox for Gradient {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn style(&self) -> theme::Application {
|
||||
fn style(&self, theme: &Theme) -> program::Appearance {
|
||||
use program::DefaultStyle;
|
||||
|
||||
if self.transparent {
|
||||
theme::Application::custom(|theme: &Theme| {
|
||||
application::Appearance {
|
||||
background_color: Color::TRANSPARENT,
|
||||
text_color: theme.palette().text,
|
||||
}
|
||||
})
|
||||
program::Appearance {
|
||||
background_color: Color::TRANSPARENT,
|
||||
text_color: theme.palette().text,
|
||||
}
|
||||
} else {
|
||||
theme::Application::Default
|
||||
Theme::default_style(theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Gradient {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn color_picker(label: &str, color: Color) -> Element<'_, Color> {
|
||||
row![
|
||||
text(label).width(64),
|
||||
|
|
|
|||
|
|
@ -10,25 +10,8 @@ The __[`main`]__ file contains all the code of the example.
|
|||
|
||||
You can run it with `cargo run`:
|
||||
```
|
||||
cargo run --package integration_wgpu
|
||||
cargo run --package integration
|
||||
```
|
||||
|
||||
### How to run this example with WebGL backend
|
||||
NOTE: Currently, WebGL backend is is still experimental, so expect bugs.
|
||||
|
||||
```sh
|
||||
# 0. Install prerequisites
|
||||
cargo install wasm-bindgen-cli https
|
||||
# 1. cd to the current folder
|
||||
# 2. Compile wasm module
|
||||
cargo build -p integration_wgpu --target wasm32-unknown-unknown
|
||||
# 3. Invoke wasm-bindgen
|
||||
wasm-bindgen ../../target/wasm32-unknown-unknown/debug/integration_wgpu.wasm --out-dir . --target web --no-typescript
|
||||
# 4. run http server
|
||||
http
|
||||
# 5. Open 127.0.0.1:8000 in browser
|
||||
```
|
||||
|
||||
|
||||
[`main`]: src/main.rs
|
||||
[`wgpu`]: https://github.com/gfx-rs/wgpu
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
||||
<title>Iced - wgpu + WebGL integration</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>integration_wgpu</h1>
|
||||
<canvas id="iced_canvas"></canvas>
|
||||
<script type="module">
|
||||
import init from "./integration.js";
|
||||
init('./integration_bg.wasm');
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
use iced_wgpu::Renderer;
|
||||
use iced_widget::{slider, text_input, Column, Row, Text};
|
||||
use iced_winit::core::{Alignment, Color, Element, Length};
|
||||
use iced_widget::{column, container, row, slider, text, text_input};
|
||||
use iced_winit::core::alignment;
|
||||
use iced_winit::core::{Color, Element, Length, Theme};
|
||||
use iced_winit::runtime::{Command, Program};
|
||||
use iced_winit::style::Theme;
|
||||
|
||||
pub struct Controls {
|
||||
background_color: Color,
|
||||
text: String,
|
||||
input: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
BackgroundColorChanged(Color),
|
||||
TextChanged(String),
|
||||
InputChanged(String),
|
||||
}
|
||||
|
||||
impl Controls {
|
||||
pub fn new() -> Controls {
|
||||
Controls {
|
||||
background_color: Color::BLACK,
|
||||
text: String::default(),
|
||||
input: String::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,8 +38,8 @@ impl Program for Controls {
|
|||
Message::BackgroundColorChanged(color) => {
|
||||
self.background_color = color;
|
||||
}
|
||||
Message::TextChanged(text) => {
|
||||
self.text = text;
|
||||
Message::InputChanged(input) => {
|
||||
self.input = input;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,60 +48,48 @@ impl Program for Controls {
|
|||
|
||||
fn view(&self) -> Element<Message, Theme, Renderer> {
|
||||
let background_color = self.background_color;
|
||||
let text = &self.text;
|
||||
|
||||
let sliders = Row::new()
|
||||
.width(500)
|
||||
.spacing(20)
|
||||
.push(
|
||||
slider(0.0..=1.0, background_color.r, move |r| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
r,
|
||||
..background_color
|
||||
})
|
||||
let sliders = row![
|
||||
slider(0.0..=1.0, background_color.r, move |r| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
r,
|
||||
..background_color
|
||||
})
|
||||
.step(0.01),
|
||||
)
|
||||
.push(
|
||||
slider(0.0..=1.0, background_color.g, move |g| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
g,
|
||||
..background_color
|
||||
})
|
||||
})
|
||||
.step(0.01),
|
||||
slider(0.0..=1.0, background_color.g, move |g| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
g,
|
||||
..background_color
|
||||
})
|
||||
.step(0.01),
|
||||
)
|
||||
.push(
|
||||
slider(0.0..=1.0, background_color.b, move |b| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
b,
|
||||
..background_color
|
||||
})
|
||||
})
|
||||
.step(0.01),
|
||||
slider(0.0..=1.0, background_color.b, move |b| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
b,
|
||||
..background_color
|
||||
})
|
||||
.step(0.01),
|
||||
);
|
||||
})
|
||||
.step(0.01),
|
||||
]
|
||||
.width(500)
|
||||
.spacing(20);
|
||||
|
||||
Row::new()
|
||||
.height(Length::Fill)
|
||||
.align_items(Alignment::End)
|
||||
.push(
|
||||
Column::new().align_items(Alignment::End).push(
|
||||
Column::new()
|
||||
.padding(10)
|
||||
.spacing(10)
|
||||
.push(Text::new("Background color").style(Color::WHITE))
|
||||
.push(sliders)
|
||||
.push(
|
||||
Text::new(format!("{background_color:?}"))
|
||||
.size(14)
|
||||
.style(Color::WHITE),
|
||||
)
|
||||
.push(
|
||||
text_input("Placeholder", text)
|
||||
.on_input(Message::TextChanged),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
container(
|
||||
column![
|
||||
text("Background color").color(Color::WHITE),
|
||||
text(format!("{background_color:?}"))
|
||||
.size(14)
|
||||
.color(Color::WHITE),
|
||||
text_input("Placeholder", &self.input)
|
||||
.on_input(Message::InputChanged),
|
||||
sliders,
|
||||
]
|
||||
.spacing(10),
|
||||
)
|
||||
.padding(10)
|
||||
.height(Length::Fill)
|
||||
.align_y(alignment::Vertical::Bottom)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,315 +5,347 @@ use controls::Controls;
|
|||
use scene::Scene;
|
||||
|
||||
use iced_wgpu::graphics::Viewport;
|
||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
||||
use iced_wgpu::{wgpu, Engine, Renderer};
|
||||
use iced_winit::conversion;
|
||||
use iced_winit::core::mouse;
|
||||
use iced_winit::core::renderer;
|
||||
use iced_winit::core::window;
|
||||
use iced_winit::core::{Color, Font, Pixels, Size};
|
||||
use iced_winit::core::{Color, Font, Pixels, Size, Theme};
|
||||
use iced_winit::futures;
|
||||
use iced_winit::runtime::program;
|
||||
use iced_winit::style::Theme;
|
||||
use iced_winit::winit;
|
||||
use iced_winit::Clipboard;
|
||||
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event::WindowEvent,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::ModifiersState,
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsCast;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::HtmlCanvasElement;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let canvas_element = {
|
||||
console_log::init().expect("Initialize logger");
|
||||
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
|
||||
web_sys::window()
|
||||
.and_then(|win| win.document())
|
||||
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
|
||||
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
|
||||
.expect("Get canvas element")
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn main() -> Result<(), winit::error::EventLoopError> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Initialize winit
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_canvas(Some(canvas_element))
|
||||
.build(&event_loop)?;
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum Runner {
|
||||
Loading,
|
||||
Ready {
|
||||
window: Arc<winit::window::Window>,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
surface: wgpu::Surface<'static>,
|
||||
format: wgpu::TextureFormat,
|
||||
engine: Engine,
|
||||
renderer: Renderer,
|
||||
scene: Scene,
|
||||
state: program::State<Controls>,
|
||||
cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
|
||||
clipboard: Clipboard,
|
||||
viewport: Viewport,
|
||||
modifiers: ModifiersState,
|
||||
resized: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let window = winit::window::Window::new(&event_loop)?;
|
||||
impl winit::application::ApplicationHandler for Runner {
|
||||
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
if let Self::Loading = self {
|
||||
let window = Arc::new(
|
||||
event_loop
|
||||
.create_window(
|
||||
winit::window::WindowAttributes::default(),
|
||||
)
|
||||
.expect("Create window"),
|
||||
);
|
||||
|
||||
let window = Arc::new(window);
|
||||
let physical_size = window.inner_size();
|
||||
let viewport = Viewport::with_physical_size(
|
||||
Size::new(physical_size.width, physical_size.height),
|
||||
window.scale_factor(),
|
||||
);
|
||||
let clipboard = Clipboard::connect(&window);
|
||||
|
||||
let physical_size = window.inner_size();
|
||||
let mut viewport = Viewport::with_physical_size(
|
||||
Size::new(physical_size.width, physical_size.height),
|
||||
window.scale_factor(),
|
||||
);
|
||||
let mut cursor_position = None;
|
||||
let mut modifiers = ModifiersState::default();
|
||||
let mut clipboard = Clipboard::connect(&window);
|
||||
let backend =
|
||||
wgpu::util::backend_bits_from_env().unwrap_or_default();
|
||||
|
||||
// Initialize wgpu
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let default_backend = wgpu::Backends::GL;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let default_backend = wgpu::Backends::PRIMARY;
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: backend,
|
||||
..Default::default()
|
||||
});
|
||||
let surface = instance
|
||||
.create_surface(window.clone())
|
||||
.expect("Create window surface");
|
||||
|
||||
let backend =
|
||||
wgpu::util::backend_bits_from_env().unwrap_or(default_backend);
|
||||
let (format, adapter, device, queue) =
|
||||
futures::futures::executor::block_on(async {
|
||||
let adapter =
|
||||
wgpu::util::initialize_adapter_from_env_or_default(
|
||||
&instance,
|
||||
Some(&surface),
|
||||
)
|
||||
.await
|
||||
.expect("Create adapter");
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: backend,
|
||||
..Default::default()
|
||||
});
|
||||
let surface = instance.create_surface(window.clone())?;
|
||||
let adapter_features = adapter.features();
|
||||
|
||||
let (format, adapter, device, queue) =
|
||||
futures::futures::executor::block_on(async {
|
||||
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
|
||||
&instance,
|
||||
Some(&surface),
|
||||
)
|
||||
.await
|
||||
.expect("Create adapter");
|
||||
let capabilities = surface.get_capabilities(&adapter);
|
||||
|
||||
let adapter_features = adapter.features();
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
required_features: adapter_features
|
||||
& wgpu::Features::default(),
|
||||
required_limits: wgpu::Limits::default(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Request device");
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
|
||||
.using_resolution(adapter.limits());
|
||||
(
|
||||
capabilities
|
||||
.formats
|
||||
.iter()
|
||||
.copied()
|
||||
.find(wgpu::TextureFormat::is_srgb)
|
||||
.or_else(|| {
|
||||
capabilities.formats.first().copied()
|
||||
})
|
||||
.expect("Get preferred format"),
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let needed_limits = wgpu::Limits::default();
|
||||
|
||||
let capabilities = surface.get_capabilities(&adapter);
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
required_features: adapter_features
|
||||
& wgpu::Features::default(),
|
||||
required_limits: needed_limits,
|
||||
surface.configure(
|
||||
&device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format,
|
||||
width: physical_size.width,
|
||||
height: physical_size.height,
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Request device");
|
||||
);
|
||||
|
||||
(
|
||||
capabilities
|
||||
.formats
|
||||
.iter()
|
||||
.copied()
|
||||
.find(wgpu::TextureFormat::is_srgb)
|
||||
.or_else(|| capabilities.formats.first().copied())
|
||||
.expect("Get preferred format"),
|
||||
adapter,
|
||||
// Initialize scene and GUI controls
|
||||
let scene = Scene::new(&device, format);
|
||||
let controls = Controls::new();
|
||||
|
||||
// Initialize iced
|
||||
let engine =
|
||||
Engine::new(&adapter, &device, &queue, format, None);
|
||||
let mut renderer = Renderer::new(
|
||||
&device,
|
||||
&engine,
|
||||
Font::default(),
|
||||
Pixels::from(16),
|
||||
);
|
||||
|
||||
let state = program::State::new(
|
||||
controls,
|
||||
viewport.logical_size(),
|
||||
&mut renderer,
|
||||
);
|
||||
|
||||
// You should change this if you want to render continuously
|
||||
event_loop.set_control_flow(ControlFlow::Wait);
|
||||
|
||||
*self = Self::Ready {
|
||||
window,
|
||||
device,
|
||||
queue,
|
||||
surface,
|
||||
format,
|
||||
engine,
|
||||
renderer,
|
||||
scene,
|
||||
state,
|
||||
cursor_position: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
clipboard,
|
||||
viewport,
|
||||
resized: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||
_window_id: winit::window::WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
let Self::Ready {
|
||||
window,
|
||||
device,
|
||||
queue,
|
||||
)
|
||||
});
|
||||
surface,
|
||||
format,
|
||||
engine,
|
||||
renderer,
|
||||
scene,
|
||||
state,
|
||||
viewport,
|
||||
cursor_position,
|
||||
modifiers,
|
||||
clipboard,
|
||||
resized,
|
||||
} = self
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
surface.configure(
|
||||
&device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format,
|
||||
width: physical_size.width,
|
||||
height: physical_size.height,
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
},
|
||||
);
|
||||
match event {
|
||||
WindowEvent::RedrawRequested => {
|
||||
if *resized {
|
||||
let size = window.inner_size();
|
||||
|
||||
let mut resized = false;
|
||||
|
||||
// Initialize scene and GUI controls
|
||||
let scene = Scene::new(&device, format);
|
||||
let controls = Controls::new();
|
||||
|
||||
// Initialize iced
|
||||
let mut renderer = Renderer::new(
|
||||
Backend::new(&adapter, &device, &queue, Settings::default(), format),
|
||||
Font::default(),
|
||||
Pixels(16.0),
|
||||
);
|
||||
|
||||
let mut state =
|
||||
program::State::new(controls, viewport.logical_size(), &mut renderer);
|
||||
|
||||
// Run event loop
|
||||
event_loop.run(move |event, window_target| {
|
||||
// You should change this if you want to render continuosly
|
||||
window_target.set_control_flow(ControlFlow::Wait);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
if resized {
|
||||
let size = window.inner_size();
|
||||
|
||||
viewport = Viewport::with_physical_size(
|
||||
Size::new(size.width, size.height),
|
||||
window.scale_factor(),
|
||||
);
|
||||
|
||||
surface.configure(
|
||||
&device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
},
|
||||
);
|
||||
|
||||
resized = false;
|
||||
}
|
||||
|
||||
match surface.get_current_texture() {
|
||||
Ok(frame) => {
|
||||
let mut encoder = device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor { label: None },
|
||||
*viewport = Viewport::with_physical_size(
|
||||
Size::new(size.width, size.height),
|
||||
window.scale_factor(),
|
||||
);
|
||||
|
||||
let program = state.program();
|
||||
|
||||
let view = frame.texture.create_view(
|
||||
&wgpu::TextureViewDescriptor::default(),
|
||||
surface.configure(
|
||||
device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
format: *format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
},
|
||||
);
|
||||
|
||||
{
|
||||
// We clear the frame
|
||||
let mut render_pass = Scene::clear(
|
||||
&view,
|
||||
&mut encoder,
|
||||
program.background_color(),
|
||||
*resized = false;
|
||||
}
|
||||
|
||||
match surface.get_current_texture() {
|
||||
Ok(frame) => {
|
||||
let mut encoder = device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor { label: None },
|
||||
);
|
||||
|
||||
// Draw the scene
|
||||
scene.draw(&mut render_pass);
|
||||
}
|
||||
let program = state.program();
|
||||
|
||||
// And then iced on top
|
||||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(
|
||||
&device,
|
||||
&queue,
|
||||
let view = frame.texture.create_view(
|
||||
&wgpu::TextureViewDescriptor::default(),
|
||||
);
|
||||
|
||||
{
|
||||
// We clear the frame
|
||||
let mut render_pass = Scene::clear(
|
||||
&view,
|
||||
&mut encoder,
|
||||
program.background_color(),
|
||||
);
|
||||
|
||||
// Draw the scene
|
||||
scene.draw(&mut render_pass);
|
||||
}
|
||||
|
||||
// And then iced on top
|
||||
renderer.present(
|
||||
engine,
|
||||
device,
|
||||
queue,
|
||||
&mut encoder,
|
||||
None,
|
||||
frame.texture.format(),
|
||||
&view,
|
||||
primitive,
|
||||
&viewport,
|
||||
viewport,
|
||||
);
|
||||
});
|
||||
|
||||
// Then we submit the work
|
||||
queue.submit(Some(encoder.finish()));
|
||||
frame.present();
|
||||
// Then we submit the work
|
||||
engine.submit(queue, encoder);
|
||||
frame.present();
|
||||
|
||||
// Update the mouse cursor
|
||||
window.set_cursor_icon(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(error) => match error {
|
||||
wgpu::SurfaceError::OutOfMemory => {
|
||||
panic!(
|
||||
"Swapchain error: {error}. \
|
||||
// Update the mouse cursor
|
||||
window.set_cursor(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(error) => match error {
|
||||
wgpu::SurfaceError::OutOfMemory => {
|
||||
panic!(
|
||||
"Swapchain error: {error}. \
|
||||
Rendering cannot continue."
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
// Try rendering again next frame.
|
||||
window.request_redraw();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
*cursor_position = Some(position);
|
||||
}
|
||||
WindowEvent::ModifiersChanged(new_modifiers) => {
|
||||
*modifiers = new_modifiers.state();
|
||||
}
|
||||
WindowEvent::Resized(_) => {
|
||||
*resized = true;
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
event_loop.exit();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Map window event to iced event
|
||||
if let Some(event) = iced_winit::conversion::window_event(
|
||||
window::Id::MAIN,
|
||||
event,
|
||||
window.scale_factor(),
|
||||
*modifiers,
|
||||
) {
|
||||
state.queue_event(event);
|
||||
}
|
||||
|
||||
// If there are events pending
|
||||
if !state.is_queue_empty() {
|
||||
// We update iced
|
||||
let _ = state.update(
|
||||
viewport.logical_size(),
|
||||
cursor_position
|
||||
.map(|p| {
|
||||
conversion::cursor_position(
|
||||
p,
|
||||
viewport.scale_factor(),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
// Try rendering again next frame.
|
||||
window.request_redraw();
|
||||
}
|
||||
})
|
||||
.map(mouse::Cursor::Available)
|
||||
.unwrap_or(mouse::Cursor::Unavailable),
|
||||
renderer,
|
||||
&Theme::Dark,
|
||||
&renderer::Style {
|
||||
text_color: Color::WHITE,
|
||||
},
|
||||
}
|
||||
clipboard,
|
||||
);
|
||||
|
||||
// and request a redraw
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
cursor_position = Some(position);
|
||||
}
|
||||
WindowEvent::ModifiersChanged(new_modifiers) => {
|
||||
modifiers = new_modifiers.state();
|
||||
}
|
||||
WindowEvent::Resized(_) => {
|
||||
resized = true;
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
window_target.exit();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Map window event to iced event
|
||||
if let Some(event) = iced_winit::conversion::window_event(
|
||||
window::Id::MAIN,
|
||||
event,
|
||||
window.scale_factor(),
|
||||
modifiers,
|
||||
) {
|
||||
state.queue_event(event);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are events pending
|
||||
if !state.is_queue_empty() {
|
||||
// We update iced
|
||||
let _ = state.update(
|
||||
viewport.logical_size(),
|
||||
cursor_position
|
||||
.map(|p| {
|
||||
conversion::cursor_position(p, viewport.scale_factor())
|
||||
})
|
||||
.map(mouse::Cursor::Available)
|
||||
.unwrap_or(mouse::Cursor::Unavailable),
|
||||
&mut renderer,
|
||||
&Theme::Dark,
|
||||
&renderer::Style {
|
||||
text_color: Color::WHITE,
|
||||
},
|
||||
&mut clipboard,
|
||||
);
|
||||
|
||||
// and request a redraw
|
||||
window.request_redraw();
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
let mut runner = Runner::Loading;
|
||||
event_loop.run_app(&mut runner)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::mouse;
|
||||
use iced::theme;
|
||||
use iced::widget::{
|
||||
button, canvas, checkbox, column, container, horizontal_space, pick_list,
|
||||
row, scrollable, text, vertical_rule,
|
||||
button, canvas, center, checkbox, column, container, horizontal_space,
|
||||
pick_list, row, scrollable, text,
|
||||
};
|
||||
use iced::{
|
||||
color, Alignment, Application, Color, Command, Element, Font, Length,
|
||||
Point, Rectangle, Renderer, Settings, Subscription, Theme,
|
||||
color, Alignment, Element, Font, Length, Point, Rectangle, Renderer,
|
||||
Subscription, Theme,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Layout::run(Settings::default())
|
||||
iced::program(Layout::title, Layout::update, Layout::view)
|
||||
.subscription(Layout::subscription)
|
||||
.theme(Layout::theme)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Default, Debug)]
|
||||
struct Layout {
|
||||
example: Example,
|
||||
explain: bool,
|
||||
|
|
@ -30,28 +31,12 @@ enum Message {
|
|||
ThemeSelected(Theme),
|
||||
}
|
||||
|
||||
impl Application for Layout {
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Executor = executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
||||
(
|
||||
Self {
|
||||
example: Example::default(),
|
||||
explain: false,
|
||||
theme: Theme::Light,
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
fn title(&self) -> String {
|
||||
format!("{} - Layout - Iced", self.example.title)
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Message) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Next => {
|
||||
self.example = self.example.next();
|
||||
|
|
@ -66,8 +51,6 @@ impl Application for Layout {
|
|||
self.theme = theme;
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
|
|
@ -93,22 +76,18 @@ impl Application for Layout {
|
|||
.spacing(20)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
let example = container(if self.explain {
|
||||
let example = center(if self.explain {
|
||||
self.example.view().explain(color!(0x0000ff))
|
||||
} else {
|
||||
self.example.view()
|
||||
})
|
||||
.style(|theme: &Theme| {
|
||||
.style(|theme| {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
container::Appearance::default()
|
||||
container::Style::default()
|
||||
.with_border(palette.background.strong.color, 4.0)
|
||||
})
|
||||
.padding(4)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y();
|
||||
.padding(4);
|
||||
|
||||
let controls = row([
|
||||
(!self.example.is_first()).then_some(
|
||||
|
|
@ -167,10 +146,6 @@ impl Example {
|
|||
title: "Application",
|
||||
view: application,
|
||||
},
|
||||
Self {
|
||||
title: "Nested Quotes",
|
||||
view: nested_quotes,
|
||||
},
|
||||
];
|
||||
|
||||
fn is_first(self) -> bool {
|
||||
|
|
@ -216,12 +191,7 @@ impl Default for Example {
|
|||
}
|
||||
|
||||
fn centered<'a>() -> Element<'a, Message> {
|
||||
container(text("I am centered!").size(50))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(text("I am centered!").size(50)).into()
|
||||
}
|
||||
|
||||
fn column_<'a>() -> Element<'a, Message> {
|
||||
|
|
@ -266,10 +236,10 @@ fn application<'a>() -> Element<'a, Message> {
|
|||
.padding(10)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.style(|theme: &Theme| {
|
||||
.style(|theme| {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
container::Appearance::default()
|
||||
container::Style::default()
|
||||
.with_border(palette.background.strong.color, 1)
|
||||
});
|
||||
|
||||
|
|
@ -280,8 +250,7 @@ fn application<'a>() -> Element<'a, Message> {
|
|||
.width(200)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.style(theme::Container::Box)
|
||||
.height(Length::Fill)
|
||||
.style(container::rounded_box)
|
||||
.center_y();
|
||||
|
||||
let content = container(
|
||||
|
|
@ -304,38 +273,6 @@ fn application<'a>() -> Element<'a, Message> {
|
|||
column![header, row![sidebar, content]].into()
|
||||
}
|
||||
|
||||
fn nested_quotes<'a>() -> Element<'a, Message> {
|
||||
(1..5)
|
||||
.fold(column![text("Original text")].padding(10), |quotes, i| {
|
||||
column![
|
||||
container(
|
||||
row![vertical_rule(2), quotes].height(Length::Shrink)
|
||||
)
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
container::Appearance::default().with_background(
|
||||
if palette.is_dark {
|
||||
Color {
|
||||
a: 0.01,
|
||||
..Color::WHITE
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
a: 0.08,
|
||||
..Color::BLACK
|
||||
}
|
||||
},
|
||||
)
|
||||
}),
|
||||
text(format!("Reply {i}"))
|
||||
]
|
||||
.spacing(10)
|
||||
.padding(10)
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> {
|
||||
struct Square;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
use iced::theme;
|
||||
use iced::widget::{
|
||||
button, column, horizontal_space, lazy, pick_list, row, scrollable, text,
|
||||
text_input,
|
||||
};
|
||||
use iced::{Element, Length, Sandbox, Settings};
|
||||
use iced::{Element, Length};
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
App::run(Settings::default())
|
||||
iced::run("Lazy - Iced", App::update, App::view)
|
||||
}
|
||||
|
||||
struct App {
|
||||
|
|
@ -121,17 +120,7 @@ enum Message {
|
|||
ItemColorChanged(Item, Color),
|
||||
}
|
||||
|
||||
impl Sandbox for App {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Lazy - Iced")
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::InputChanged(input) => {
|
||||
|
|
@ -181,11 +170,10 @@ impl Sandbox for App {
|
|||
column(items.into_iter().map(|item| {
|
||||
let button = button("Delete")
|
||||
.on_press(Message::DeleteItem(item.clone()))
|
||||
.style(theme::Button::Destructive);
|
||||
.style(button::danger);
|
||||
|
||||
row![
|
||||
text(&item.name)
|
||||
.style(theme::Text::Color(item.color.into())),
|
||||
text(item.name.clone()).color(item.color),
|
||||
horizontal_space(),
|
||||
pick_list(Color::ALL, Some(item.color), move |color| {
|
||||
Message::ItemColorChanged(item.clone(), color)
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ where
|
|||
|renderer| {
|
||||
use iced::advanced::graphics::geometry::Renderer as _;
|
||||
|
||||
renderer.draw(vec![geometry]);
|
||||
renderer.draw_geometry(geometry);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use iced::executor;
|
||||
use iced::widget::{column, container, row, slider, text};
|
||||
use iced::{Application, Command, Element, Length, Settings, Theme};
|
||||
use iced::widget::{center, column, row, slider, text};
|
||||
use iced::Element;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -12,51 +11,31 @@ use circular::Circular;
|
|||
use linear::Linear;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
LoadingSpinners::run(Settings {
|
||||
antialiasing: true,
|
||||
..Default::default()
|
||||
})
|
||||
iced::program(
|
||||
"Loading Spinners - Iced",
|
||||
LoadingSpinners::update,
|
||||
LoadingSpinners::view,
|
||||
)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct LoadingSpinners {
|
||||
cycle_duration: f32,
|
||||
}
|
||||
|
||||
impl Default for LoadingSpinners {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cycle_duration: 2.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Message {
|
||||
CycleDurationChanged(f32),
|
||||
}
|
||||
|
||||
impl Application for LoadingSpinners {
|
||||
type Message = Message;
|
||||
type Flags = ();
|
||||
type Executor = executor::Default;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
||||
(Self::default(), Command::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Loading Spinners - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
impl LoadingSpinners {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::CycleDurationChanged(duration) => {
|
||||
self.cycle_duration = duration;
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
|
|
@ -94,7 +73,7 @@ impl Application for LoadingSpinners {
|
|||
})
|
||||
.spacing(20);
|
||||
|
||||
container(
|
||||
center(
|
||||
column.push(
|
||||
row![
|
||||
text("Cycle duration:"),
|
||||
|
|
@ -108,10 +87,14 @@ impl Application for LoadingSpinners {
|
|||
.spacing(20.0),
|
||||
),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoadingSpinners {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cycle_duration: 2.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,46 @@
|
|||
use iced::widget::{button, column, container, text};
|
||||
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
||||
use iced::widget::{button, center, column, text};
|
||||
use iced::{Alignment, Element};
|
||||
|
||||
use loupe::loupe;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Counter::run(Settings::default())
|
||||
iced::run("Loupe - Iced", Loupe::update, Loupe::view)
|
||||
}
|
||||
|
||||
struct Counter {
|
||||
value: i32,
|
||||
#[derive(Default)]
|
||||
struct Loupe {
|
||||
value: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Message {
|
||||
IncrementPressed,
|
||||
DecrementPressed,
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
|
||||
impl Sandbox for Counter {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Self { value: 0 }
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Counter - Iced")
|
||||
}
|
||||
|
||||
impl Loupe {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::IncrementPressed => {
|
||||
Message::Increment => {
|
||||
self.value += 1;
|
||||
}
|
||||
Message::DecrementPressed => {
|
||||
Message::Decrement => {
|
||||
self.value -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
container(loupe(
|
||||
center(loupe(
|
||||
3.0,
|
||||
column![
|
||||
button("Increment").on_press(Message::IncrementPressed),
|
||||
button("Increment").on_press(Message::Increment),
|
||||
text(self.value).size(50),
|
||||
button("Decrement").on_press(Message::DecrementPressed)
|
||||
button("Decrement").on_press(Message::Decrement)
|
||||
]
|
||||
.padding(20)
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
@ -168,7 +155,7 @@ mod loupe {
|
|||
if cursor.is_over(layout.bounds()) {
|
||||
mouse::Interaction::ZoomIn
|
||||
} else {
|
||||
mouse::Interaction::Idle
|
||||
mouse::Interaction::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,18 @@
|
|||
use iced::event::{self, Event};
|
||||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::keyboard::key;
|
||||
use iced::theme;
|
||||
use iced::widget::{
|
||||
self, button, column, container, horizontal_space, pick_list, row, text,
|
||||
text_input,
|
||||
};
|
||||
use iced::{
|
||||
Alignment, Application, Command, Element, Length, Settings, Subscription,
|
||||
self, button, center, column, container, horizontal_space, mouse_area,
|
||||
opaque, pick_list, row, stack, text, text_input,
|
||||
};
|
||||
use iced::{Alignment, Color, Command, Element, Length, Subscription};
|
||||
|
||||
use modal::Modal;
|
||||
use std::fmt;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
App::run(Settings::default())
|
||||
iced::program("Modal - Iced", App::update, App::view)
|
||||
.subscription(App::subscription)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -37,21 +34,8 @@ enum Message {
|
|||
Event(Event),
|
||||
}
|
||||
|
||||
impl Application for App {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = iced::Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
(App::default(), Command::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Modal - Iced")
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Self::Message> {
|
||||
impl App {
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
event::listen().map(Message::Event)
|
||||
}
|
||||
|
||||
|
|
@ -114,13 +98,7 @@ impl Application for App {
|
|||
row![text("Top Left"), horizontal_space(), text("Top Right")]
|
||||
.align_items(Alignment::Start)
|
||||
.height(Length::Fill),
|
||||
container(
|
||||
button(text("Show Modal")).on_press(Message::ShowModal)
|
||||
)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
center(button(text("Show Modal")).on_press(Message::ShowModal)),
|
||||
row![
|
||||
text("Bottom Left"),
|
||||
horizontal_space(),
|
||||
|
|
@ -131,12 +109,10 @@ impl Application for App {
|
|||
]
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
.padding(10);
|
||||
|
||||
if self.show_modal {
|
||||
let modal = container(
|
||||
let signup = container(
|
||||
column![
|
||||
text("Sign Up").size(24),
|
||||
column![
|
||||
|
|
@ -175,11 +151,9 @@ impl Application for App {
|
|||
)
|
||||
.width(300)
|
||||
.padding(10)
|
||||
.style(theme::Container::Box);
|
||||
.style(container::rounded_box);
|
||||
|
||||
Modal::new(content, modal)
|
||||
.on_blur(Message::HideModal)
|
||||
.into()
|
||||
modal(content, signup, Message::HideModal)
|
||||
} else {
|
||||
content.into()
|
||||
}
|
||||
|
|
@ -218,326 +192,29 @@ impl fmt::Display for Plan {
|
|||
}
|
||||
}
|
||||
|
||||
mod modal {
|
||||
use iced::advanced::layout::{self, Layout};
|
||||
use iced::advanced::overlay;
|
||||
use iced::advanced::renderer;
|
||||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::advanced::{self, Clipboard, Shell};
|
||||
use iced::alignment::Alignment;
|
||||
use iced::event;
|
||||
use iced::mouse;
|
||||
use iced::{Color, Element, Event, Length, Point, Rectangle, Size, Vector};
|
||||
|
||||
/// A widget that centers a modal element over some base element
|
||||
pub struct Modal<'a, Message, Theme, Renderer> {
|
||||
base: Element<'a, Message, Theme, Renderer>,
|
||||
modal: Element<'a, Message, Theme, Renderer>,
|
||||
on_blur: Option<Message>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Modal<'a, Message, Theme, Renderer> {
|
||||
/// Returns a new [`Modal`]
|
||||
pub fn new(
|
||||
base: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
modal: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
base: base.into(),
|
||||
modal: modal.into(),
|
||||
on_blur: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the message that will be produces when the background
|
||||
/// of the [`Modal`] is pressed
|
||||
pub fn on_blur(self, on_blur: Message) -> Self {
|
||||
Self {
|
||||
on_blur: Some(on_blur),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for Modal<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: advanced::Renderer,
|
||||
Message: Clone,
|
||||
{
|
||||
fn children(&self) -> Vec<widget::Tree> {
|
||||
vec![
|
||||
widget::Tree::new(&self.base),
|
||||
widget::Tree::new(&self.modal),
|
||||
]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut widget::Tree) {
|
||||
tree.diff_children(&[&self.base, &self.modal]);
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<Length> {
|
||||
self.base.as_widget().size()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut widget::Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.base.as_widget().layout(
|
||||
&mut tree.children[0],
|
||||
renderer,
|
||||
limits,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
state: &mut widget::Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.base.as_widget_mut().on_event(
|
||||
&mut state.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
state: &widget::Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.base.as_widget().draw(
|
||||
&state.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
state: &'b mut widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
Some(overlay::Element::new(Box::new(Overlay {
|
||||
position: layout.position() + translation,
|
||||
content: &mut self.modal,
|
||||
tree: &mut state.children[1],
|
||||
size: layout.bounds().size(),
|
||||
on_blur: self.on_blur.clone(),
|
||||
})))
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
state: &widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.base.as_widget().mouse_interaction(
|
||||
&state.children[0],
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
state: &mut widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
self.base.as_widget().operate(
|
||||
&mut state.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
operation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct Overlay<'a, 'b, Message, Theme, Renderer> {
|
||||
position: Point,
|
||||
content: &'b mut Element<'a, Message, Theme, Renderer>,
|
||||
tree: &'b mut widget::Tree,
|
||||
size: Size,
|
||||
on_blur: Option<Message>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, Message, Theme, Renderer>
|
||||
overlay::Overlay<Message, Theme, Renderer>
|
||||
for Overlay<'a, 'b, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: advanced::Renderer,
|
||||
Message: Clone,
|
||||
{
|
||||
fn layout(
|
||||
&mut self,
|
||||
renderer: &Renderer,
|
||||
_bounds: Size,
|
||||
) -> layout::Node {
|
||||
let limits = layout::Limits::new(Size::ZERO, self.size)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
let child = self
|
||||
.content
|
||||
.as_widget()
|
||||
.layout(self.tree, renderer, &limits)
|
||||
.align(Alignment::Center, Alignment::Center, limits.max());
|
||||
|
||||
layout::Node::with_children(self.size, vec![child])
|
||||
.move_to(self.position)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
let content_bounds = layout.children().next().unwrap().bounds();
|
||||
|
||||
if let Some(message) = self.on_blur.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(
|
||||
mouse::Button::Left,
|
||||
)) = &event
|
||||
{
|
||||
if !cursor.is_over(content_bounds) {
|
||||
shell.publish(message.clone());
|
||||
return event::Status::Captured;
|
||||
fn modal<'a, Message>(
|
||||
base: impl Into<Element<'a, Message>>,
|
||||
content: impl Into<Element<'a, Message>>,
|
||||
on_blur: Message,
|
||||
) -> Element<'a, Message>
|
||||
where
|
||||
Message: Clone + 'a,
|
||||
{
|
||||
stack![
|
||||
base.into(),
|
||||
mouse_area(center(opaque(content)).style(|_theme| {
|
||||
container::Style {
|
||||
background: Some(
|
||||
Color {
|
||||
a: 0.8,
|
||||
..Color::BLACK
|
||||
}
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
..container::Style::default()
|
||||
}
|
||||
|
||||
self.content.as_widget_mut().on_event(
|
||||
self.tree,
|
||||
event,
|
||||
layout.children().next().unwrap(),
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
&layout.bounds(),
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
) {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: layout.bounds(),
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
Color {
|
||||
a: 0.80,
|
||||
..Color::BLACK
|
||||
},
|
||||
);
|
||||
|
||||
self.content.as_widget().draw(
|
||||
self.tree,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout.children().next().unwrap(),
|
||||
cursor,
|
||||
&layout.bounds(),
|
||||
);
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
self.content.as_widget().operate(
|
||||
self.tree,
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
operation,
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
self.tree,
|
||||
layout.children().next().unwrap(),
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'c>(
|
||||
&'c mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
self.tree,
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
Vector::ZERO,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> From<Modal<'a, Message, Theme, Renderer>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: 'a,
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + advanced::Renderer,
|
||||
{
|
||||
fn from(modal: Modal<'a, Message, Theme, Renderer>) -> Self {
|
||||
Element::new(modal)
|
||||
}
|
||||
}
|
||||
}))
|
||||
.on_press(on_blur)
|
||||
]
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use iced::event;
|
||||
use iced::executor;
|
||||
use iced::multi_window::{self, Application};
|
||||
use iced::widget::{button, column, container, scrollable, text, text_input};
|
||||
use iced::widget::{
|
||||
button, center, column, container, scrollable, text, text_input,
|
||||
};
|
||||
use iced::window;
|
||||
use iced::{
|
||||
Alignment, Command, Element, Length, Point, Settings, Subscription, Theme,
|
||||
|
|
@ -128,12 +130,7 @@ impl multi_window::Application for Example {
|
|||
fn view(&self, window: window::Id) -> Element<Message> {
|
||||
let content = self.windows.get(&window).unwrap().view(window);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).into()
|
||||
}
|
||||
|
||||
fn theme(&self, window: window::Id) -> Self::Theme {
|
||||
|
|
@ -210,6 +207,6 @@ impl Window {
|
|||
.align_items(Alignment::Center),
|
||||
);
|
||||
|
||||
container(content).width(200).center_x().into()
|
||||
container(content).center_x().width(200).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,101 +2,58 @@
|
|||
//! a circle around each fingertip. This only works on touch-enabled
|
||||
//! computers like Microsoft Surface.
|
||||
use iced::mouse;
|
||||
use iced::touch;
|
||||
use iced::widget::canvas::event;
|
||||
use iced::widget::canvas::stroke::{self, Stroke};
|
||||
use iced::widget::canvas::{self, Canvas, Geometry};
|
||||
use iced::{
|
||||
executor, touch, window, Application, Color, Command, Element, Length,
|
||||
Point, Rectangle, Renderer, Settings, Subscription, Theme,
|
||||
};
|
||||
use iced::{Color, Element, Length, Point, Rectangle, Renderer, Theme};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
Multitouch::run(Settings {
|
||||
antialiasing: true,
|
||||
window: window::Settings {
|
||||
position: window::Position::Centered,
|
||||
..window::Settings::default()
|
||||
},
|
||||
..Settings::default()
|
||||
})
|
||||
iced::program("Multitouch - Iced", Multitouch::update, Multitouch::view)
|
||||
.antialiasing(true)
|
||||
.centered()
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Multitouch {
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
cache: canvas::Cache,
|
||||
fingers: HashMap<touch::Finger, Point>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
cache: canvas::Cache::new(),
|
||||
fingers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Message {
|
||||
FingerPressed { id: touch::Finger, position: Point },
|
||||
FingerLifted { id: touch::Finger },
|
||||
}
|
||||
|
||||
impl Application for Multitouch {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
(
|
||||
Multitouch {
|
||||
state: State::new(),
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Multitouch - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
impl Multitouch {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::FingerPressed { id, position } => {
|
||||
self.state.fingers.insert(id, position);
|
||||
self.state.cache.clear();
|
||||
self.fingers.insert(id, position);
|
||||
self.cache.clear();
|
||||
}
|
||||
Message::FingerLifted { id } => {
|
||||
self.state.fingers.remove(&id);
|
||||
self.state.cache.clear();
|
||||
self.fingers.remove(&id);
|
||||
self.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
Subscription::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
Canvas::new(&self.state)
|
||||
Canvas::new(self)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl canvas::Program<Message> for State {
|
||||
impl canvas::Program<Message> for Multitouch {
|
||||
type State = ();
|
||||
|
||||
fn update(
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
use iced::alignment::{self, Alignment};
|
||||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::theme::{self, Theme};
|
||||
use iced::widget::pane_grid::{self, PaneGrid};
|
||||
use iced::widget::{
|
||||
button, column, container, responsive, row, scrollable, text,
|
||||
};
|
||||
use iced::{
|
||||
Application, Color, Command, Element, Length, Settings, Size, Subscription,
|
||||
};
|
||||
use iced::{Color, Element, Length, Size, Subscription};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
iced::program("Pane Grid - Iced", Example::update, Example::view)
|
||||
.subscription(Example::subscription)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct Example {
|
||||
|
|
@ -35,30 +33,18 @@ enum Message {
|
|||
CloseFocused,
|
||||
}
|
||||
|
||||
impl Application for Example {
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Executor = executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
impl Example {
|
||||
fn new() -> Self {
|
||||
let (panes, _) = pane_grid::State::new(Pane::new(0));
|
||||
|
||||
(
|
||||
Example {
|
||||
panes,
|
||||
panes_created: 1,
|
||||
focus: None,
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
Example {
|
||||
panes,
|
||||
panes_created: 1,
|
||||
focus: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Pane grid - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Split(axis, pane) => {
|
||||
let result =
|
||||
|
|
@ -132,8 +118,6 @@ impl Application for Example {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
|
|
@ -162,7 +146,7 @@ impl Application for Example {
|
|||
let title = row![
|
||||
pin_button,
|
||||
"Pane",
|
||||
text(pane.id.to_string()).style(if is_focused {
|
||||
text(pane.id.to_string()).color(if is_focused {
|
||||
PANE_ID_COLOR_FOCUSED
|
||||
} else {
|
||||
PANE_ID_COLOR_UNFOCUSED
|
||||
|
|
@ -209,6 +193,12 @@ impl Application for Example {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Example {
|
||||
fn default() -> Self {
|
||||
Example::new()
|
||||
}
|
||||
}
|
||||
|
||||
const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb(
|
||||
0xFF as f32 / 255.0,
|
||||
0xC7 as f32 / 255.0,
|
||||
|
|
@ -287,10 +277,7 @@ fn view_content<'a>(
|
|||
)
|
||||
]
|
||||
.push_maybe(if total_panes > 1 && !is_pinned {
|
||||
Some(
|
||||
button("Close", Message::Close(pane))
|
||||
.style(theme::Button::Destructive),
|
||||
)
|
||||
Some(button("Close", Message::Close(pane)).style(button::danger))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
|
|
@ -304,12 +291,7 @@ fn view_content<'a>(
|
|||
.spacing(10)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
container(scrollable(content))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(5)
|
||||
.center_y()
|
||||
.into()
|
||||
container(scrollable(content)).center_y().padding(5).into()
|
||||
}
|
||||
|
||||
fn view_controls<'a>(
|
||||
|
|
@ -327,7 +309,7 @@ fn view_controls<'a>(
|
|||
|
||||
Some(
|
||||
button(text(content).size(14))
|
||||
.style(theme::Button::Secondary)
|
||||
.style(button::secondary)
|
||||
.padding(3)
|
||||
.on_press(message),
|
||||
)
|
||||
|
|
@ -336,7 +318,7 @@ fn view_controls<'a>(
|
|||
});
|
||||
|
||||
let close = button(text("Close").size(14))
|
||||
.style(theme::Button::Destructive)
|
||||
.style(button::danger)
|
||||
.padding(3)
|
||||
.on_press_maybe(if total_panes > 1 && !is_pinned {
|
||||
Some(Message::Close(pane))
|
||||
|
|
@ -351,30 +333,30 @@ mod style {
|
|||
use iced::widget::container;
|
||||
use iced::{Border, Theme};
|
||||
|
||||
pub fn title_bar_active(theme: &Theme) -> container::Appearance {
|
||||
pub fn title_bar_active(theme: &Theme) -> container::Style {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
container::Appearance {
|
||||
container::Style {
|
||||
text_color: Some(palette.background.strong.text),
|
||||
background: Some(palette.background.strong.color.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title_bar_focused(theme: &Theme) -> container::Appearance {
|
||||
pub fn title_bar_focused(theme: &Theme) -> container::Style {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
container::Appearance {
|
||||
container::Style {
|
||||
text_color: Some(palette.primary.strong.text),
|
||||
background: Some(palette.primary.strong.color.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pane_active(theme: &Theme) -> container::Appearance {
|
||||
pub fn pane_active(theme: &Theme) -> container::Style {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
container::Appearance {
|
||||
container::Style {
|
||||
background: Some(palette.background.weak.color.into()),
|
||||
border: Border {
|
||||
width: 2.0,
|
||||
|
|
@ -385,10 +367,10 @@ mod style {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn pane_focused(theme: &Theme) -> container::Appearance {
|
||||
pub fn pane_focused(theme: &Theme) -> container::Style {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
container::Appearance {
|
||||
container::Style {
|
||||
background: Some(palette.background.weak.color.into()),
|
||||
border: Border {
|
||||
width: 2.0,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use iced::widget::{column, pick_list, scrollable, vertical_space};
|
||||
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
||||
use iced::{Alignment, Element, Length};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
iced::run("Pick List - Iced", Example::update, Example::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -15,17 +15,7 @@ enum Message {
|
|||
LanguageSelected(Language),
|
||||
}
|
||||
|
||||
impl Sandbox for Example {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Pick list - Iced")
|
||||
}
|
||||
|
||||
impl Example {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::LanguageSelected(language) => {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
use iced::futures;
|
||||
use iced::widget::{self, column, container, image, row, text};
|
||||
use iced::{
|
||||
Alignment, Application, Color, Command, Element, Length, Settings, Theme,
|
||||
};
|
||||
use iced::widget::{self, center, column, image, row, text};
|
||||
use iced::{Alignment, Command, Element, Length};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Pokedex::run(Settings::default())
|
||||
iced::program(Pokedex::title, Pokedex::update, Pokedex::view)
|
||||
.load(Pokedex::search)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
enum Pokedex {
|
||||
#[default]
|
||||
Loading,
|
||||
Loaded { pokemon: Pokemon },
|
||||
Loaded {
|
||||
pokemon: Pokemon,
|
||||
},
|
||||
Errored,
|
||||
}
|
||||
|
||||
|
|
@ -21,17 +24,9 @@ enum Message {
|
|||
Search,
|
||||
}
|
||||
|
||||
impl Application for Pokedex {
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Executor = iced::executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Pokedex, Command<Message>) {
|
||||
(
|
||||
Pokedex::Loading,
|
||||
Command::perform(Pokemon::search(), Message::PokemonFound),
|
||||
)
|
||||
impl Pokedex {
|
||||
fn search() -> Command<Message> {
|
||||
Command::perform(Pokemon::search(), Message::PokemonFound)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
|
|
@ -61,7 +56,7 @@ impl Application for Pokedex {
|
|||
_ => {
|
||||
*self = Pokedex::Loading;
|
||||
|
||||
Command::perform(Pokemon::search(), Message::PokemonFound)
|
||||
Self::search()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -88,12 +83,7 @@ impl Application for Pokedex {
|
|||
.align_items(Alignment::End),
|
||||
};
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +106,7 @@ impl Pokemon {
|
|||
text(&self.name).size(30).width(Length::Fill),
|
||||
text(format!("#{}", self.number))
|
||||
.size(20)
|
||||
.style(Color::from([0.5, 0.5, 0.5])),
|
||||
.color([0.5, 0.5, 0.5]),
|
||||
]
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20),
|
||||
|
|
@ -193,7 +183,7 @@ impl Pokemon {
|
|||
{
|
||||
let bytes = reqwest::get(&url).await?.bytes().await?;
|
||||
|
||||
Ok(image::Handle::from_memory(bytes))
|
||||
Ok(image::Handle::from_bytes(bytes))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use iced::widget::{column, progress_bar, slider};
|
||||
use iced::{Element, Sandbox, Settings};
|
||||
use iced::Element;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Progress::run(Settings::default())
|
||||
iced::run("Progress Bar - Iced", Progress::update, Progress::view)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -15,17 +15,7 @@ enum Message {
|
|||
SliderChanged(f32),
|
||||
}
|
||||
|
||||
impl Sandbox for Progress {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("A simple Progressbar")
|
||||
}
|
||||
|
||||
impl Progress {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::SliderChanged(x) => self.value = x,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
use iced::widget::{
|
||||
column, container, pick_list, qr_code, row, text, text_input,
|
||||
};
|
||||
use iced::{Alignment, Element, Length, Sandbox, Settings, Theme};
|
||||
use iced::widget::{center, column, pick_list, qr_code, row, text, text_input};
|
||||
use iced::{Alignment, Element, Theme};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
QRGenerator::run(Settings::default())
|
||||
iced::program(
|
||||
"QR Code Generator - Iced",
|
||||
QRGenerator::update,
|
||||
QRGenerator::view,
|
||||
)
|
||||
.theme(QRGenerator::theme)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -20,17 +24,7 @@ enum Message {
|
|||
ThemeChanged(Theme),
|
||||
}
|
||||
|
||||
impl Sandbox for QRGenerator {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
QRGenerator::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("QR Code Generator - Iced")
|
||||
}
|
||||
|
||||
impl QRGenerator {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::DataChanged(mut data) => {
|
||||
|
|
@ -76,13 +70,7 @@ impl Sandbox for QRGenerator {
|
|||
.spacing(20)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(20)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).padding(20).into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
use iced::alignment;
|
||||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::theme;
|
||||
use iced::widget::{button, column, container, image, row, text, text_input};
|
||||
use iced::window;
|
||||
use iced::window::screenshot::{self, Screenshot};
|
||||
use iced::{
|
||||
Alignment, Application, Command, ContentFit, Element, Length, Rectangle,
|
||||
Subscription, Theme,
|
||||
Alignment, Command, ContentFit, Element, Length, Rectangle, Subscription,
|
||||
};
|
||||
|
||||
use ::image as img;
|
||||
|
|
@ -16,9 +13,12 @@ use ::image::ColorType;
|
|||
fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
Example::run(iced::Settings::default())
|
||||
iced::program("Screenshot - Iced", Example::update, Example::view)
|
||||
.subscription(Example::subscription)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Example {
|
||||
screenshot: Option<Screenshot>,
|
||||
saved_png_path: Option<Result<String, PngError>>,
|
||||
|
|
@ -43,33 +43,8 @@ enum Message {
|
|||
HeightInputChanged(Option<u32>),
|
||||
}
|
||||
|
||||
impl Application for Example {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||
(
|
||||
Example {
|
||||
screenshot: None,
|
||||
saved_png_path: None,
|
||||
png_saving: false,
|
||||
crop_error: None,
|
||||
x_input_value: None,
|
||||
y_input_value: None,
|
||||
width_input_value: None,
|
||||
height_input_value: None,
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
"Screenshot".to_string()
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||
impl Example {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::Screenshot => {
|
||||
return iced::window::screenshot(
|
||||
|
|
@ -131,10 +106,10 @@ impl Application for Example {
|
|||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<'_, Self::Message> {
|
||||
fn view(&self) -> Element<'_, Message> {
|
||||
let image: Element<Message> = if let Some(screenshot) = &self.screenshot
|
||||
{
|
||||
image(image::Handle::from_pixels(
|
||||
image(image::Handle::from_rgba(
|
||||
screenshot.size.width,
|
||||
screenshot.size.height,
|
||||
screenshot.clone(),
|
||||
|
|
@ -148,12 +123,10 @@ impl Application for Example {
|
|||
};
|
||||
|
||||
let image = container(image)
|
||||
.center_y()
|
||||
.padding(10)
|
||||
.style(theme::Container::Box)
|
||||
.width(Length::FillPortion(2))
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y();
|
||||
.style(container::rounded_box)
|
||||
.width(Length::FillPortion(2));
|
||||
|
||||
let crop_origin_controls = row![
|
||||
text("X:")
|
||||
|
|
@ -216,9 +189,9 @@ impl Application for Example {
|
|||
)
|
||||
} else {
|
||||
button(centered_text("Saving..."))
|
||||
.style(theme::Button::Secondary)
|
||||
.style(button::secondary)
|
||||
}
|
||||
.style(theme::Button::Secondary)
|
||||
.style(button::secondary)
|
||||
.padding([10, 20, 10, 20])
|
||||
.width(Length::Fill)
|
||||
]
|
||||
|
|
@ -227,7 +200,7 @@ impl Application for Example {
|
|||
crop_controls,
|
||||
button(centered_text("Crop"))
|
||||
.on_press(Message::Crop)
|
||||
.style(theme::Button::Destructive)
|
||||
.style(button::danger)
|
||||
.padding([10, 20, 10, 20])
|
||||
.width(Length::Fill),
|
||||
]
|
||||
|
|
@ -238,12 +211,7 @@ impl Application for Example {
|
|||
.spacing(40)
|
||||
};
|
||||
|
||||
let side_content = container(controls)
|
||||
.align_x(alignment::Horizontal::Center)
|
||||
.width(Length::FillPortion(1))
|
||||
.height(Length::Fill)
|
||||
.center_y()
|
||||
.center_x();
|
||||
let side_content = container(controls).center_y();
|
||||
|
||||
let content = row![side_content, image]
|
||||
.spacing(10)
|
||||
|
|
@ -251,16 +219,10 @@ impl Application for Example {
|
|||
.height(Length::Fill)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(10)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
container(content).padding(10).into()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Self::Message> {
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
use keyboard::key;
|
||||
|
||||
keyboard::on_key_press(|key, _modifiers| {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
use iced::executor;
|
||||
use iced::widget::scrollable::Properties;
|
||||
use iced::widget::{
|
||||
button, column, container, horizontal_space, progress_bar, radio, row,
|
||||
scrollable, slider, text, vertical_space, Scrollable,
|
||||
};
|
||||
use iced::{
|
||||
Alignment, Application, Color, Command, Element, Length, Settings, Theme,
|
||||
};
|
||||
use iced::{Alignment, Border, Color, Command, Element, Length, Theme};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
static SCROLLABLE_ID: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique);
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
ScrollableDemo::run(Settings::default())
|
||||
iced::program(
|
||||
"Scrollable - Iced",
|
||||
ScrollableDemo::update,
|
||||
ScrollableDemo::view,
|
||||
)
|
||||
.theme(ScrollableDemo::theme)
|
||||
.run()
|
||||
}
|
||||
|
||||
struct ScrollableDemo {
|
||||
|
|
@ -44,28 +47,16 @@ enum Message {
|
|||
Scrolled(scrollable::Viewport),
|
||||
}
|
||||
|
||||
impl Application for ScrollableDemo {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
||||
(
|
||||
ScrollableDemo {
|
||||
scrollable_direction: Direction::Vertical,
|
||||
scrollbar_width: 10,
|
||||
scrollbar_margin: 0,
|
||||
scroller_width: 10,
|
||||
current_scroll_offset: scrollable::RelativeOffset::START,
|
||||
alignment: scrollable::Alignment::Start,
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Scrollable - Iced")
|
||||
impl ScrollableDemo {
|
||||
fn new() -> Self {
|
||||
ScrollableDemo {
|
||||
scrollable_direction: Direction::Vertical,
|
||||
scrollbar_width: 10,
|
||||
scrollbar_margin: 0,
|
||||
scroller_width: 10,
|
||||
current_scroll_offset: scrollable::RelativeOffset::START,
|
||||
alignment: scrollable::Alignment::Start,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
|
|
@ -336,18 +327,24 @@ impl Application for ScrollableDemo {
|
|||
.spacing(10)
|
||||
.into();
|
||||
|
||||
container(content).padding(20).center_x().center_y().into()
|
||||
container(content).padding(20).into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Self::Theme {
|
||||
fn theme(&self) -> Theme {
|
||||
Theme::Dark
|
||||
}
|
||||
}
|
||||
|
||||
fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance {
|
||||
progress_bar::Appearance {
|
||||
background: theme.extended_palette().background.strong.color.into(),
|
||||
bar: Color::from_rgb8(250, 85, 134).into(),
|
||||
border_radius: 0.0.into(),
|
||||
impl Default for ScrollableDemo {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Style {
|
||||
progress_bar::Style {
|
||||
background: theme.extended_palette().background.strong.color.into(),
|
||||
bar: Color::from_rgb8(250, 85, 134).into(),
|
||||
border: Border::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use iced::executor;
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas::event::{self, Event};
|
||||
use iced::widget::canvas::{self, Canvas};
|
||||
use iced::widget::canvas::{self, Canvas, Geometry};
|
||||
use iced::widget::{column, row, slider, text};
|
||||
use iced::{
|
||||
Application, Color, Command, Length, Point, Rectangle, Renderer, Settings,
|
||||
Size, Theme,
|
||||
};
|
||||
use iced::{Color, Length, Point, Rectangle, Renderer, Size, Theme};
|
||||
|
||||
use rand::Rng;
|
||||
use std::fmt::Debug;
|
||||
|
||||
fn main() -> iced::Result {
|
||||
SierpinskiEmulator::run(Settings {
|
||||
antialiasing: true,
|
||||
..Settings::default()
|
||||
})
|
||||
iced::program(
|
||||
"Sierpinski Triangle - Iced",
|
||||
SierpinskiEmulator::update,
|
||||
SierpinskiEmulator::view,
|
||||
)
|
||||
.antialiasing(true)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
struct SierpinskiEmulator {
|
||||
graph: SierpinskiGraph,
|
||||
}
|
||||
|
|
@ -31,27 +29,8 @@ pub enum Message {
|
|||
PointRemoved,
|
||||
}
|
||||
|
||||
impl Application for SierpinskiEmulator {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
||||
let emulator = SierpinskiEmulator {
|
||||
graph: SierpinskiGraph::new(),
|
||||
};
|
||||
(emulator, Command::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
"Sierpinski Triangle Emulator".to_string()
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Self::Message,
|
||||
) -> iced::Command<Self::Message> {
|
||||
impl SierpinskiEmulator {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::IterationSet(cur_iter) => {
|
||||
self.graph.iteration = cur_iter;
|
||||
|
|
@ -67,11 +46,9 @@ impl Application for SierpinskiEmulator {
|
|||
}
|
||||
|
||||
self.graph.redraw();
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> iced::Element<'_, Self::Message> {
|
||||
fn view(&self) -> iced::Element<'_, Message> {
|
||||
column![
|
||||
Canvas::new(&self.graph)
|
||||
.width(Length::Fill)
|
||||
|
|
@ -134,7 +111,7 @@ impl canvas::Program<Message> for SierpinskiGraph {
|
|||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
) -> Vec<Geometry> {
|
||||
let geom = self.cache.draw(renderer, bounds.size(), |frame| {
|
||||
frame.stroke(
|
||||
&canvas::Path::rectangle(Point::ORIGIN, frame.size()),
|
||||
|
|
@ -167,10 +144,6 @@ impl canvas::Program<Message> for SierpinskiGraph {
|
|||
}
|
||||
|
||||
impl SierpinskiGraph {
|
||||
fn new() -> SierpinskiGraph {
|
||||
SierpinskiGraph::default()
|
||||
}
|
||||
|
||||
fn redraw(&mut self) {
|
||||
self.cache.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use iced::widget::{column, container, slider, text, vertical_slider};
|
||||
use iced::{Element, Length, Sandbox, Settings};
|
||||
use iced::widget::{center, column, container, slider, text, vertical_slider};
|
||||
use iced::Element;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Slider::run(Settings::default())
|
||||
iced::run("Slider - Iced", Slider::update, Slider::view)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -17,10 +17,8 @@ pub struct Slider {
|
|||
shift_step: u8,
|
||||
}
|
||||
|
||||
impl Sandbox for Slider {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Slider {
|
||||
impl Slider {
|
||||
fn new() -> Self {
|
||||
Slider {
|
||||
value: 50,
|
||||
default: 50,
|
||||
|
|
@ -29,10 +27,6 @@ impl Sandbox for Slider {
|
|||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Slider - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::SliderChanged(value) => {
|
||||
|
|
@ -60,18 +54,20 @@ impl Sandbox for Slider {
|
|||
|
||||
let text = text(self.value);
|
||||
|
||||
container(
|
||||
center(
|
||||
column![
|
||||
container(v_slider).width(Length::Fill).center_x(),
|
||||
container(h_slider).width(Length::Fill).center_x(),
|
||||
container(text).width(Length::Fill).center_x(),
|
||||
container(v_slider).center_x(),
|
||||
container(h_slider).center_x(),
|
||||
container(text).center_x()
|
||||
]
|
||||
.spacing(25),
|
||||
)
|
||||
.height(Length::Fill)
|
||||
.width(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Slider {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,15 @@
|
|||
//! Inspired by the example found in the MDN docs[1].
|
||||
//!
|
||||
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
|
||||
use iced::application;
|
||||
use iced::executor;
|
||||
use iced::mouse;
|
||||
use iced::theme::{self, Theme};
|
||||
use iced::widget::canvas;
|
||||
use iced::widget::canvas::gradient;
|
||||
use iced::widget::canvas::stroke::{self, Stroke};
|
||||
use iced::widget::canvas::Path;
|
||||
use iced::widget::canvas::{Geometry, Path};
|
||||
use iced::window;
|
||||
use iced::{
|
||||
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
|
||||
Settings, Size, Subscription, Vector,
|
||||
Color, Element, Length, Point, Rectangle, Renderer, Size, Subscription,
|
||||
Theme, Vector,
|
||||
};
|
||||
|
||||
use std::time::Instant;
|
||||
|
|
@ -25,12 +22,17 @@ use std::time::Instant;
|
|||
pub fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
SolarSystem::run(Settings {
|
||||
antialiasing: true,
|
||||
..Settings::default()
|
||||
})
|
||||
iced::program(
|
||||
"Solar System - Iced",
|
||||
SolarSystem::update,
|
||||
SolarSystem::view,
|
||||
)
|
||||
.subscription(SolarSystem::subscription)
|
||||
.theme(SolarSystem::theme)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SolarSystem {
|
||||
state: State,
|
||||
}
|
||||
|
|
@ -40,33 +42,13 @@ enum Message {
|
|||
Tick(Instant),
|
||||
}
|
||||
|
||||
impl Application for SolarSystem {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
(
|
||||
SolarSystem {
|
||||
state: State::new(),
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Solar system - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
impl SolarSystem {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Tick(instant) => {
|
||||
self.state.update(instant);
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
|
|
@ -77,18 +59,7 @@ impl Application for SolarSystem {
|
|||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
Theme::Dark
|
||||
}
|
||||
|
||||
fn style(&self) -> theme::Application {
|
||||
fn dark_background(_theme: &Theme) -> application::Appearance {
|
||||
application::Appearance {
|
||||
background_color: Color::BLACK,
|
||||
text_color: Color::WHITE,
|
||||
}
|
||||
}
|
||||
|
||||
theme::Application::custom(dark_background)
|
||||
Theme::Moonfly
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
|
|
@ -159,7 +130,7 @@ impl<Message> canvas::Program<Message> for State {
|
|||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
) -> Vec<Geometry> {
|
||||
use std::f32::consts::PI;
|
||||
|
||||
let background =
|
||||
|
|
@ -229,3 +200,9 @@ impl<Message> canvas::Program<Message> for State {
|
|||
vec![background, system]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,31 @@
|
|||
use iced::alignment;
|
||||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::theme::{self, Theme};
|
||||
use iced::time;
|
||||
use iced::widget::{button, column, container, row, text};
|
||||
use iced::{
|
||||
Alignment, Application, Command, Element, Length, Settings, Subscription,
|
||||
};
|
||||
use iced::widget::{button, center, column, row, text};
|
||||
use iced::{Alignment, Element, Subscription, Theme};
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Stopwatch::run(Settings::default())
|
||||
iced::program("Stopwatch - Iced", Stopwatch::update, Stopwatch::view)
|
||||
.subscription(Stopwatch::subscription)
|
||||
.theme(Stopwatch::theme)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Stopwatch {
|
||||
duration: Duration,
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum State {
|
||||
#[default]
|
||||
Idle,
|
||||
Ticking { last_tick: Instant },
|
||||
Ticking {
|
||||
last_tick: Instant,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -31,27 +35,8 @@ enum Message {
|
|||
Tick(Instant),
|
||||
}
|
||||
|
||||
impl Application for Stopwatch {
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Executor = executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Stopwatch, Command<Message>) {
|
||||
(
|
||||
Stopwatch {
|
||||
duration: Duration::default(),
|
||||
state: State::Idle,
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Stopwatch - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
impl Stopwatch {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Toggle => match self.state {
|
||||
State::Idle => {
|
||||
|
|
@ -73,8 +58,6 @@ impl Application for Stopwatch {
|
|||
self.duration = Duration::default();
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
|
|
@ -136,7 +119,7 @@ impl Application for Stopwatch {
|
|||
};
|
||||
|
||||
let reset_button = button("Reset")
|
||||
.style(theme::Button::Destructive)
|
||||
.style(button::danger)
|
||||
.on_press(Message::Reset);
|
||||
|
||||
let controls = row![toggle_button, reset_button].spacing(20);
|
||||
|
|
@ -145,12 +128,7 @@ impl Application for Stopwatch {
|
|||
.align_items(Alignment::Center)
|
||||
.spacing(20);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use iced::widget::{
|
||||
button, checkbox, column, container, horizontal_rule, pick_list,
|
||||
progress_bar, row, scrollable, slider, text, text_input, toggler,
|
||||
vertical_rule, vertical_space,
|
||||
button, center, checkbox, column, horizontal_rule, pick_list, progress_bar,
|
||||
row, scrollable, slider, text, text_input, toggler, vertical_rule,
|
||||
vertical_space,
|
||||
};
|
||||
use iced::{Alignment, Element, Length, Sandbox, Settings, Theme};
|
||||
use iced::{Alignment, Element, Length, Theme};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Styling::run(Settings::default())
|
||||
iced::program("Styling - Iced", Styling::update, Styling::view)
|
||||
.theme(Styling::theme)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -28,17 +30,7 @@ enum Message {
|
|||
TogglerToggled(bool),
|
||||
}
|
||||
|
||||
impl Sandbox for Styling {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Styling::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Styling - Iced")
|
||||
}
|
||||
|
||||
impl Styling {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::ThemeChanged(theme) => {
|
||||
|
|
@ -114,12 +106,7 @@ impl Sandbox for Styling {
|
|||
.padding(20)
|
||||
.max_width(600);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
center(content).into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use iced::theme;
|
||||
use iced::widget::{checkbox, column, container, svg};
|
||||
use iced::{color, Element, Length, Sandbox, Settings};
|
||||
use iced::widget::{center, checkbox, column, container, svg};
|
||||
use iced::{color, Element, Length};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Tiger::run(Settings::default())
|
||||
iced::run("SVG - Iced", Tiger::update, Tiger::view)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
|
@ -16,18 +15,8 @@ pub enum Message {
|
|||
ToggleColorFilter(bool),
|
||||
}
|
||||
|
||||
impl Sandbox for Tiger {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Tiger::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("SVG - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Message) {
|
||||
impl Tiger {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::ToggleColorFilter(apply_color_filter) => {
|
||||
self.apply_color_filter = apply_color_filter;
|
||||
|
|
@ -35,19 +24,19 @@ impl Sandbox for Tiger {
|
|||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Self::Message> {
|
||||
fn view(&self) -> Element<Message> {
|
||||
let handle = svg::Handle::from_path(format!(
|
||||
"{}/resources/tiger.svg",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
));
|
||||
|
||||
let svg = svg(handle).width(Length::Fill).height(Length::Fill).style(
|
||||
if self.apply_color_filter {
|
||||
theme::Svg::custom_fn(|_theme| svg::Appearance {
|
||||
color: Some(color!(0x0000ff)),
|
||||
})
|
||||
} else {
|
||||
theme::Svg::Default
|
||||
|_theme, _status| svg::Style {
|
||||
color: if self.apply_color_filter {
|
||||
Some(color!(0x0000ff))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -55,19 +44,12 @@ impl Sandbox for Tiger {
|
|||
checkbox("Apply a color filter", self.apply_color_filter)
|
||||
.on_toggle(Message::ToggleColorFilter);
|
||||
|
||||
container(
|
||||
column![
|
||||
svg,
|
||||
container(apply_color_filter).width(Length::Fill).center_x()
|
||||
]
|
||||
.spacing(20)
|
||||
.height(Length::Fill),
|
||||
center(
|
||||
column![svg, container(apply_color_filter).center_x()]
|
||||
.spacing(20)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(20)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
use iced::widget::{button, column, container, text};
|
||||
use iced::{
|
||||
executor, system, Application, Command, Element, Length, Settings, Theme,
|
||||
};
|
||||
|
||||
use bytesize::ByteSize;
|
||||
use iced::{system, Command, Element};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
iced::program("System Information - Iced", Example::update, Example::view)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum Example {
|
||||
#[default]
|
||||
Loading,
|
||||
Loaded { information: system::Information },
|
||||
Loaded {
|
||||
information: system::Information,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -22,23 +23,7 @@ enum Message {
|
|||
Refresh,
|
||||
}
|
||||
|
||||
impl Application for Example {
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
type Executor = executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
(
|
||||
Self::Loading,
|
||||
system::fetch_information(Message::InformationReceived),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("System Information - Iced")
|
||||
}
|
||||
|
||||
impl Example {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::Refresh => {
|
||||
|
|
@ -55,6 +40,8 @@ impl Application for Example {
|
|||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
use bytesize::ByteSize;
|
||||
|
||||
let content: Element<_> = match self {
|
||||
Example::Loading => text("Loading...").size(40).into(),
|
||||
Example::Loaded { information } => {
|
||||
|
|
@ -149,11 +136,6 @@ impl Application for Example {
|
|||
}
|
||||
};
|
||||
|
||||
container(content)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
container(content).center().into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
examples/the_matrix/Cargo.toml
Normal file
13
examples/the_matrix/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "the_matrix"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
iced.features = ["canvas", "tokio", "debug"]
|
||||
|
||||
rand = "0.8"
|
||||
tracing-subscriber = "0.3"
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue