Merge branch 'master' into remove-vertex-indexing
|
|
@ -17,8 +17,6 @@ clippy --workspace --no-deps -- \
|
||||||
-D clippy::useless_conversion
|
-D clippy::useless_conversion
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
|
|
||||||
|
|
||||||
nitpick = """
|
nitpick = """
|
||||||
clippy --workspace --no-deps -- \
|
clippy --workspace --no-deps -- \
|
||||||
-D warnings \
|
-D warnings \
|
||||||
|
|
|
||||||
1
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
|
|
@ -25,7 +25,6 @@ body:
|
||||||
Before filing an issue...
|
Before filing an issue...
|
||||||
|
|
||||||
- If you are using `wgpu`, you need an environment that supports Vulkan, Metal, or DirectX 12. Please, make sure you can run [the `wgpu` examples].
|
- If you are using `wgpu`, you need an environment that supports Vulkan, Metal, or DirectX 12. Please, make sure you can run [the `wgpu` examples].
|
||||||
- If you are using `glow`, you need support for OpenGL 2.1+. Please, make sure you can run [the `glow` examples].
|
|
||||||
|
|
||||||
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!
|
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!
|
||||||
|
|
||||||
|
|
|
||||||
6
.github/workflows/audit.yml
vendored
|
|
@ -12,6 +12,8 @@ jobs:
|
||||||
- name: Install cargo-audit
|
- name: Install cargo-audit
|
||||||
run: cargo install cargo-audit
|
run: cargo install cargo-audit
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
|
- name: Resolve dependencies
|
||||||
|
run: cargo update
|
||||||
- name: Audit vulnerabilities
|
- name: Audit vulnerabilities
|
||||||
run: cargo audit
|
run: cargo audit
|
||||||
|
|
||||||
|
|
@ -22,5 +24,7 @@ jobs:
|
||||||
- name: Install cargo-outdated
|
- name: Install cargo-outdated
|
||||||
run: cargo install cargo-outdated
|
run: cargo install cargo-outdated
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
|
- name: Delete `web-sys` dependency from `integration` example
|
||||||
|
run: sed -i '$d' examples/integration/Cargo.toml
|
||||||
- name: Find outdated dependencies
|
- name: Find outdated dependencies
|
||||||
run: cargo outdated --workspace --exit-code 1
|
run: cargo outdated --workspace --exit-code 1 --ignore raw-window-handle
|
||||||
|
|
|
||||||
29
.github/workflows/check.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
name: Check
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
widget:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: hecrj/setup-rust-action@v1
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Check standalone `iced_widget` crate
|
||||||
|
run: cargo check --package iced_widget --features image,svg,canvas
|
||||||
|
|
||||||
|
wasm:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: --cfg=web_sys_unstable_apis
|
||||||
|
steps:
|
||||||
|
- uses: hecrj/setup-rust-action@v1
|
||||||
|
with:
|
||||||
|
rust-version: stable
|
||||||
|
targets: wasm32-unknown-unknown
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Run checks
|
||||||
|
run: cargo check --package iced --target wasm32-unknown-unknown
|
||||||
|
- name: Check compilation of `tour` example
|
||||||
|
run: cargo build --package tour --target wasm32-unknown-unknown
|
||||||
|
- name: Check compilation of `todos` example
|
||||||
|
run: cargo build --package todos --target wasm32-unknown-unknown
|
||||||
|
- name: Check compilation of `integration` example
|
||||||
|
run: cargo build --package integration --target wasm32-unknown-unknown
|
||||||
3
.github/workflows/document.yml
vendored
|
|
@ -8,13 +8,14 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: hecrj/setup-rust-action@v1
|
- uses: hecrj/setup-rust-action@v1
|
||||||
with:
|
with:
|
||||||
rust-version: nightly
|
rust-version: nightly-2023-12-11
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Generate documentation
|
- name: Generate documentation
|
||||||
run: |
|
run: |
|
||||||
RUSTDOCFLAGS="--cfg docsrs" \
|
RUSTDOCFLAGS="--cfg docsrs" \
|
||||||
cargo doc --no-deps --all-features \
|
cargo doc --no-deps --all-features \
|
||||||
-p iced_core \
|
-p iced_core \
|
||||||
|
-p iced_highlighter \
|
||||||
-p iced_style \
|
-p iced_style \
|
||||||
-p iced_futures \
|
-p iced_futures \
|
||||||
-p iced_runtime \
|
-p iced_runtime \
|
||||||
|
|
|
||||||
2
.github/workflows/lint.yml
vendored
|
|
@ -2,7 +2,7 @@ name: Lint
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
all:
|
all:
|
||||||
runs-on: ubuntu-latest
|
runs-on: macOS-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: hecrj/setup-rust-action@v1
|
- uses: hecrj/setup-rust-action@v1
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
25
.github/workflows/test.yml
vendored
|
|
@ -1,8 +1,10 @@
|
||||||
name: Test
|
name: Test
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
native:
|
all:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: --deny warnings
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
|
|
@ -17,27 +19,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
export DEBIAN_FRONTED=noninteractive
|
export DEBIAN_FRONTED=noninteractive
|
||||||
sudo apt-get -qq update
|
sudo apt-get -qq update
|
||||||
sudo apt-get install -y libxkbcommon-dev
|
sudo apt-get install -y libxkbcommon-dev libgtk-3-dev
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
cargo test --verbose --workspace
|
cargo test --verbose --workspace
|
||||||
cargo test --verbose --workspace --all-features
|
cargo test --verbose --workspace --all-features
|
||||||
|
|
||||||
web:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
RUSTFLAGS: --cfg=web_sys_unstable_apis
|
|
||||||
steps:
|
|
||||||
- uses: hecrj/setup-rust-action@v1
|
|
||||||
with:
|
|
||||||
rust-version: stable
|
|
||||||
targets: wasm32-unknown-unknown
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- name: Run checks
|
|
||||||
run: cargo check --package iced --target wasm32-unknown-unknown
|
|
||||||
- name: Check compilation of `tour` example
|
|
||||||
run: cargo build --package tour --target wasm32-unknown-unknown
|
|
||||||
- name: Check compilation of `todos` example
|
|
||||||
run: cargo build --package todos --target wasm32-unknown-unknown
|
|
||||||
- name: Check compilation of `integration` example
|
|
||||||
run: cargo build --package integration --target wasm32-unknown-unknown
|
|
||||||
|
|
|
||||||
37
Cargo.toml
|
|
@ -20,7 +20,7 @@ maintenance = { status = "actively-developed" }
|
||||||
[features]
|
[features]
|
||||||
default = ["wgpu"]
|
default = ["wgpu"]
|
||||||
# Enable the `wgpu` GPU-accelerated renderer backend
|
# Enable the `wgpu` GPU-accelerated renderer backend
|
||||||
wgpu = ["iced_renderer/wgpu"]
|
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
|
||||||
# Enables the `Image` widget
|
# Enables the `Image` widget
|
||||||
image = ["iced_widget/image", "dep:image"]
|
image = ["iced_widget/image", "dep:image"]
|
||||||
# Enables the `Svg` widget
|
# Enables the `Svg` widget
|
||||||
|
|
@ -47,6 +47,10 @@ system = ["iced_winit/system"]
|
||||||
web-colors = ["iced_renderer/web-colors"]
|
web-colors = ["iced_renderer/web-colors"]
|
||||||
# Enables the WebGL backend, replacing WebGPU
|
# Enables the WebGL backend, replacing WebGPU
|
||||||
webgl = ["iced_renderer/webgl"]
|
webgl = ["iced_renderer/webgl"]
|
||||||
|
# Enables the syntax `highlighter` module
|
||||||
|
highlighter = ["iced_highlighter"]
|
||||||
|
# Enables experimental multi-window support.
|
||||||
|
multi-window = ["iced_winit/multi-window"]
|
||||||
# Enables the advanced module
|
# Enables the advanced module
|
||||||
advanced = []
|
advanced = []
|
||||||
|
|
||||||
|
|
@ -58,6 +62,9 @@ iced_widget.workspace = true
|
||||||
iced_winit.features = ["application"]
|
iced_winit.features = ["application"]
|
||||||
iced_winit.workspace = true
|
iced_winit.workspace = true
|
||||||
|
|
||||||
|
iced_highlighter.workspace = true
|
||||||
|
iced_highlighter.optional = true
|
||||||
|
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|
||||||
image.workspace = true
|
image.workspace = true
|
||||||
|
|
@ -78,8 +85,9 @@ members = [
|
||||||
"core",
|
"core",
|
||||||
"futures",
|
"futures",
|
||||||
"graphics",
|
"graphics",
|
||||||
"runtime",
|
"highlighter",
|
||||||
"renderer",
|
"renderer",
|
||||||
|
"runtime",
|
||||||
"style",
|
"style",
|
||||||
"tiny_skia",
|
"tiny_skia",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
|
|
@ -103,6 +111,7 @@ iced = { version = "0.12", path = "." }
|
||||||
iced_core = { version = "0.12", path = "core" }
|
iced_core = { version = "0.12", path = "core" }
|
||||||
iced_futures = { version = "0.12", path = "futures" }
|
iced_futures = { version = "0.12", path = "futures" }
|
||||||
iced_graphics = { version = "0.12", path = "graphics" }
|
iced_graphics = { version = "0.12", path = "graphics" }
|
||||||
|
iced_highlighter = { version = "0.12", path = "highlighter" }
|
||||||
iced_renderer = { version = "0.12", path = "renderer" }
|
iced_renderer = { version = "0.12", path = "renderer" }
|
||||||
iced_runtime = { version = "0.12", path = "runtime" }
|
iced_runtime = { version = "0.12", path = "runtime" }
|
||||||
iced_style = { version = "0.12", path = "style" }
|
iced_style = { version = "0.12", path = "style" }
|
||||||
|
|
@ -114,14 +123,13 @@ iced_winit = { version = "0.12", path = "winit" }
|
||||||
async-std = "1.0"
|
async-std = "1.0"
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
bytemuck = { version = "1.0", features = ["derive"] }
|
bytemuck = { version = "1.0", features = ["derive"] }
|
||||||
cosmic-text = "0.9"
|
cosmic-text = "0.10"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
glam = "0.24"
|
glam = "0.24"
|
||||||
glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "20f0f8fa80e0d0df4c63634ce9176fa489546ca9" }
|
glyphon = "0.5"
|
||||||
guillotiere = "0.6"
|
guillotiere = "0.6"
|
||||||
half = "2.2"
|
half = "2.2"
|
||||||
image = "0.24"
|
image = "0.24"
|
||||||
instant = "0.1"
|
|
||||||
kamadak-exif = "0.5"
|
kamadak-exif = "0.5"
|
||||||
kurbo = "0.9"
|
kurbo = "0.9"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
@ -132,22 +140,25 @@ once_cell = "1.0"
|
||||||
ouroboros = "0.17"
|
ouroboros = "0.17"
|
||||||
palette = "0.7"
|
palette = "0.7"
|
||||||
qrcode = { version = "0.12", default-features = false }
|
qrcode = { version = "0.12", default-features = false }
|
||||||
raw-window-handle = "0.5"
|
raw-window-handle = "0.6"
|
||||||
resvg = "0.35"
|
resvg = "0.36"
|
||||||
rustc-hash = "1.0"
|
rustc-hash = "1.0"
|
||||||
smol = "1.0"
|
smol = "1.0"
|
||||||
softbuffer = "0.2"
|
smol_str = "0.2"
|
||||||
|
softbuffer = "0.4"
|
||||||
|
syntect = "5.1"
|
||||||
sysinfo = "0.28"
|
sysinfo = "0.28"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tiny-skia = "0.10"
|
tiny-skia = "0.11"
|
||||||
tokio = "1.0"
|
tokio = "1.0"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
twox-hash = { version = "1.0", default-features = false }
|
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
||||||
unicode-segmentation = "1.0"
|
unicode-segmentation = "1.0"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
wasm-timer = "0.2"
|
wasm-timer = "0.2"
|
||||||
web-sys = "0.3"
|
web-sys = "0.3"
|
||||||
wgpu = "0.17"
|
web-time = "0.2"
|
||||||
|
wgpu = "0.19"
|
||||||
winapi = "0.3"
|
winapi = "0.3"
|
||||||
window_clipboard = "0.3"
|
window_clipboard = "0.4"
|
||||||
winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e", default-features = false }
|
winit = { git = "https://github.com/iced-rs/winit.git", rev = "b91e39ece2c0d378c3b80da7f3ab50e17bb798a5" }
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ The widgets of a _graphical_ user interface produce some primitives that eventua
|
||||||
Currently, there are two different official renderers:
|
Currently, there are two different official renderers:
|
||||||
|
|
||||||
- [`iced_wgpu`] is powered by [`wgpu`] and supports Vulkan, DirectX 12, and Metal.
|
- [`iced_wgpu`] is powered by [`wgpu`] and supports Vulkan, DirectX 12, and Metal.
|
||||||
- [`iced_glow`] is powered by [`glow`] and supports OpenGL 2.1+ and OpenGL ES 2.0+.
|
- [`tiny-skia`] is used as a fallback software renderer when `wgpu` is not supported.
|
||||||
|
|
||||||
Additionally, the [`iced_graphics`] subcrate contains a bunch of backend-agnostic types that can be leveraged to build renderers. Both of the renderers rely on the graphical foundations provided by this crate.
|
Additionally, the [`iced_graphics`] subcrate contains a bunch of backend-agnostic types that can be leveraged to build renderers. Both of the renderers rely on the graphical foundations provided by this crate.
|
||||||
|
|
||||||
|
|
@ -54,10 +54,7 @@ The widgets of a graphical user _interface_ are interactive. __Shells__ gather a
|
||||||
|
|
||||||
Normally, a shell will be responsible of creating a window and managing the lifecycle of a user interface, implementing a runtime of [The Elm Architecture].
|
Normally, a shell will be responsible of creating a window and managing the lifecycle of a user interface, implementing a runtime of [The Elm Architecture].
|
||||||
|
|
||||||
As of now, there are two official shells:
|
As of now, there is one official shell: [`iced_winit`] implements a shell runtime on top of [`winit`].
|
||||||
|
|
||||||
- [`iced_winit`] implements a shell runtime on top of [`winit`].
|
|
||||||
- [`iced_glutin`] is similar to [`iced_winit`], but it also deals with [OpenGL context creation].
|
|
||||||
|
|
||||||
## The web target
|
## The web target
|
||||||
The Web platform provides all the abstractions necessary to draw widgets and gather users interactions.
|
The Web platform provides all the abstractions necessary to draw widgets and gather users interactions.
|
||||||
|
|
@ -91,5 +88,4 @@ Finally, [`iced`] unifies everything into a simple abstraction to create cross-p
|
||||||
[`winit`]: https://github.com/rust-windowing/winit
|
[`winit`]: https://github.com/rust-windowing/winit
|
||||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||||
[`dodrio`]: https://github.com/fitzgen/dodrio
|
[`dodrio`]: https://github.com/fitzgen/dodrio
|
||||||
[OpenGL context creation]: https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context
|
|
||||||
[The Elm Architecture]: https://guide.elm-lang.org/architecture/
|
[The Elm Architecture]: https://guide.elm-lang.org/architecture/
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
[](https://github.com/iced-rs/iced/blob/master/LICENSE)
|
[](https://github.com/iced-rs/iced/blob/master/LICENSE)
|
||||||
[](https://crates.io/crates/iced)
|
[](https://crates.io/crates/iced)
|
||||||
[](https://github.com/iced-rs/iced/actions)
|
[](https://github.com/iced-rs/iced/actions)
|
||||||
[](https://discourse.iced.rs/)
|
[](https://discourse.iced.rs/)
|
||||||
[](https://discord.gg/3xZJ65GAhd)
|
[](https://discord.gg/3xZJ65GAhd)
|
||||||
|
|
||||||
A cross-platform GUI library for Rust focused on simplicity and type-safety.
|
A cross-platform GUI library for Rust focused on simplicity and type-safety.
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,17 @@ keywords.workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags.workspace = true
|
bitflags.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
thiserror.workspace = true
|
|
||||||
twox-hash.workspace = true
|
|
||||||
num-traits.workspace = true
|
num-traits.workspace = true
|
||||||
|
smol_str.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
web-time.workspace = true
|
||||||
|
xxhash-rust.workspace = true
|
||||||
|
|
||||||
palette.workspace = true
|
palette.workspace = true
|
||||||
palette.optional = true
|
palette.optional = true
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
instant.workspace = true
|
raw-window-handle.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
approx = "0.5"
|
approx = "0.5"
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,26 @@ impl Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Color`] from its linear RGBA components.
|
||||||
|
pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||||
|
// As described in:
|
||||||
|
// https://en.wikipedia.org/wiki/SRGB
|
||||||
|
fn gamma_component(u: f32) -> f32 {
|
||||||
|
if u < 0.0031308 {
|
||||||
|
12.92 * u
|
||||||
|
} else {
|
||||||
|
1.055 * u.powf(1.0 / 2.4) - 0.055
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
r: gamma_component(r),
|
||||||
|
g: gamma_component(g),
|
||||||
|
b: gamma_component(b),
|
||||||
|
a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts the [`Color`] into its RGBA8 equivalent.
|
/// Converts the [`Color`] into its RGBA8 equivalent.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_rgba8(self) -> [u8; 4] {
|
pub fn into_rgba8(self) -> [u8; 4] {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::widget::tree::{self, Tree};
|
use crate::widget::tree::{self, Tree};
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget,
|
Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
@ -296,12 +296,8 @@ where
|
||||||
self.widget.diff(tree);
|
self.widget.diff(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(&self) -> Length {
|
fn size(&self) -> Size<Length> {
|
||||||
self.widget.width()
|
self.widget.size()
|
||||||
}
|
|
||||||
|
|
||||||
fn height(&self) -> Length {
|
|
||||||
self.widget.height()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -466,12 +462,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn size(&self) -> Size<Length> {
|
||||||
self.element.widget.width()
|
self.element.widget.size()
|
||||||
}
|
|
||||||
|
|
||||||
fn height(&self) -> Length {
|
|
||||||
self.element.widget.height()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub enum Event {
|
||||||
Mouse(mouse::Event),
|
Mouse(mouse::Event),
|
||||||
|
|
||||||
/// A window event
|
/// A window event
|
||||||
Window(window::Event),
|
Window(window::Id, window::Event),
|
||||||
|
|
||||||
/// A touch event
|
/// A touch event
|
||||||
Touch(touch::Event),
|
Touch(touch::Event),
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ pub struct Font {
|
||||||
pub stretch: Stretch,
|
pub stretch: Stretch,
|
||||||
/// The [`Style`] of the [`Font`].
|
/// The [`Style`] of the [`Font`].
|
||||||
pub style: Style,
|
pub style: Style,
|
||||||
/// Whether if the [`Font`] is monospaced or not.
|
|
||||||
pub monospaced: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
|
|
@ -23,13 +21,11 @@ impl Font {
|
||||||
weight: Weight::Normal,
|
weight: Weight::Normal,
|
||||||
stretch: Stretch::Normal,
|
stretch: Stretch::Normal,
|
||||||
style: Style::Normal,
|
style: Style::Normal,
|
||||||
monospaced: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A monospaced font with normal [`Weight`].
|
/// A monospaced font with normal [`Weight`].
|
||||||
pub const MONOSPACE: Font = Font {
|
pub const MONOSPACE: Font = Font {
|
||||||
family: Family::Monospace,
|
family: Family::Monospace,
|
||||||
monospaced: true,
|
|
||||||
..Self::DEFAULT
|
..Self::DEFAULT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/// The hasher used to compare layouts.
|
/// The hasher used to compare layouts.
|
||||||
#[derive(Debug, Default)]
|
#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways.
|
||||||
pub struct Hasher(twox_hash::XxHash64);
|
#[derive(Default)]
|
||||||
|
pub struct Hasher(xxhash_rust::xxh3::Xxh3);
|
||||||
|
|
||||||
impl core::hash::Hasher for Hasher {
|
impl core::hash::Hasher for Hasher {
|
||||||
fn write(&mut self, bytes: &[u8]) {
|
fn write(&mut self, bytes: &[u8]) {
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,16 @@ impl std::fmt::Debug for Data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Image filtering strategy.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||||
|
pub enum FilterMethod {
|
||||||
|
/// Bilinear interpolation.
|
||||||
|
#[default]
|
||||||
|
Linear,
|
||||||
|
/// Nearest neighbor.
|
||||||
|
Nearest,
|
||||||
|
}
|
||||||
|
|
||||||
/// A [`Renderer`] that can render raster graphics.
|
/// A [`Renderer`] that can render raster graphics.
|
||||||
///
|
///
|
||||||
/// [renderer]: crate::renderer
|
/// [renderer]: crate::renderer
|
||||||
|
|
@ -178,5 +188,10 @@ pub trait Renderer: crate::Renderer {
|
||||||
|
|
||||||
/// Draws an image with the given [`Handle`] and inside the provided
|
/// Draws an image with the given [`Handle`] and inside the provided
|
||||||
/// `bounds`.
|
/// `bounds`.
|
||||||
fn draw(&mut self, handle: Self::Handle, bounds: Rectangle);
|
fn draw(
|
||||||
|
&mut self,
|
||||||
|
handle: Self::Handle,
|
||||||
|
filter_method: FilterMethod,
|
||||||
|
bounds: Rectangle,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
//! Listen to keyboard events.
|
//! Listen to keyboard events.
|
||||||
|
pub mod key;
|
||||||
|
|
||||||
mod event;
|
mod event;
|
||||||
mod key_code;
|
mod location;
|
||||||
mod modifiers;
|
mod modifiers;
|
||||||
|
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
pub use key_code::KeyCode;
|
pub use key::Key;
|
||||||
|
pub use location::Location;
|
||||||
pub use modifiers::Modifiers;
|
pub use modifiers::Modifiers;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{KeyCode, Modifiers};
|
use crate::keyboard::{Key, Location, Modifiers};
|
||||||
|
use crate::SmolStr;
|
||||||
|
|
||||||
/// A keyboard event.
|
/// A keyboard event.
|
||||||
///
|
///
|
||||||
|
|
@ -6,29 +7,35 @@ use super::{KeyCode, Modifiers};
|
||||||
/// additional events, feel free to [open an issue] and share your use case!_
|
/// additional events, feel free to [open an issue] and share your use case!_
|
||||||
///
|
///
|
||||||
/// [open an issue]: https://github.com/iced-rs/iced/issues
|
/// [open an issue]: https://github.com/iced-rs/iced/issues
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
/// A keyboard key was pressed.
|
/// A keyboard key was pressed.
|
||||||
KeyPressed {
|
KeyPressed {
|
||||||
/// The key identifier
|
/// The key pressed.
|
||||||
key_code: KeyCode,
|
key: Key,
|
||||||
|
|
||||||
/// The state of the modifier keys
|
/// The location of the key.
|
||||||
|
location: Location,
|
||||||
|
|
||||||
|
/// The state of the modifier keys.
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
|
|
||||||
|
/// The text produced by the key press, if any.
|
||||||
|
text: Option<SmolStr>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A keyboard key was released.
|
/// A keyboard key was released.
|
||||||
KeyReleased {
|
KeyReleased {
|
||||||
/// The key identifier
|
/// The key released.
|
||||||
key_code: KeyCode,
|
key: Key,
|
||||||
|
|
||||||
/// The state of the modifier keys
|
/// The location of the key.
|
||||||
|
location: Location,
|
||||||
|
|
||||||
|
/// The state of the modifier keys.
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A unicode character was received.
|
|
||||||
CharacterReceived(char),
|
|
||||||
|
|
||||||
/// The keyboard modifiers have changed.
|
/// The keyboard modifiers have changed.
|
||||||
ModifiersChanged(Modifiers),
|
ModifiersChanged(Modifiers),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
744
core/src/keyboard/key.rs
Normal file
|
|
@ -0,0 +1,744 @@
|
||||||
|
//! Identify keyboard keys.
|
||||||
|
use crate::SmolStr;
|
||||||
|
|
||||||
|
/// A key on the keyboard.
|
||||||
|
///
|
||||||
|
/// This is mostly the `Key` type found in [`winit`].
|
||||||
|
///
|
||||||
|
/// [`winit`]: https://docs.rs/winit/0.29.10/winit/keyboard/enum.Key.html
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum Key<C = SmolStr> {
|
||||||
|
/// A key with an established name.
|
||||||
|
Named(Named),
|
||||||
|
|
||||||
|
/// A key string that corresponds to the character typed by the user, taking into account the
|
||||||
|
/// user’s current locale setting, and any system-level keyboard mapping overrides that are in
|
||||||
|
/// effect.
|
||||||
|
Character(C),
|
||||||
|
|
||||||
|
/// An unidentified key.
|
||||||
|
Unidentified,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key {
|
||||||
|
/// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on
|
||||||
|
/// `Key`. All other variants remain unchanged.
|
||||||
|
pub fn as_ref(&self) -> Key<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Named(named) => Key::Named(*named),
|
||||||
|
Self::Character(c) => Key::Character(c.as_ref()),
|
||||||
|
Self::Unidentified => Key::Unidentified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A named key.
|
||||||
|
///
|
||||||
|
/// This is mostly the `NamedKey` type found in [`winit`].
|
||||||
|
///
|
||||||
|
/// [`winit`]: https://docs.rs/winit/0.29.10/winit/keyboard/enum.Key.html
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum Named {
|
||||||
|
/// The `Alt` (Alternative) key.
|
||||||
|
///
|
||||||
|
/// This key enables the alternate modifier function for interpreting concurrent or subsequent
|
||||||
|
/// keyboard input. This key value is also used for the Apple <kbd>Option</kbd> key.
|
||||||
|
Alt,
|
||||||
|
/// The Alternate Graphics (<kbd>AltGr</kbd> or <kbd>AltGraph</kbd>) key.
|
||||||
|
///
|
||||||
|
/// This key is used enable the ISO Level 3 shift modifier (the standard `Shift` key is the
|
||||||
|
/// level 2 modifier).
|
||||||
|
AltGraph,
|
||||||
|
/// The `Caps Lock` (Capital) key.
|
||||||
|
///
|
||||||
|
/// Toggle capital character lock function for interpreting subsequent keyboard input event.
|
||||||
|
CapsLock,
|
||||||
|
/// The `Control` or `Ctrl` key.
|
||||||
|
///
|
||||||
|
/// Used to enable control modifier function for interpreting concurrent or subsequent keyboard
|
||||||
|
/// input.
|
||||||
|
Control,
|
||||||
|
/// The Function switch `Fn` key. Activating this key simultaneously with another key changes
|
||||||
|
/// that key’s value to an alternate character or function. This key is often handled directly
|
||||||
|
/// in the keyboard hardware and does not usually generate key events.
|
||||||
|
Fn,
|
||||||
|
/// The Function-Lock (`FnLock` or `F-Lock`) key. Activating this key switches the mode of the
|
||||||
|
/// keyboard to changes some keys' values to an alternate character or function. This key is
|
||||||
|
/// often handled directly in the keyboard hardware and does not usually generate key events.
|
||||||
|
FnLock,
|
||||||
|
/// The `NumLock` or Number Lock key. Used to toggle numpad mode function for interpreting
|
||||||
|
/// subsequent keyboard input.
|
||||||
|
NumLock,
|
||||||
|
/// Toggle between scrolling and cursor movement modes.
|
||||||
|
ScrollLock,
|
||||||
|
/// Used to enable shift modifier function for interpreting concurrent or subsequent keyboard
|
||||||
|
/// input.
|
||||||
|
Shift,
|
||||||
|
/// The Symbol modifier key (used on some virtual keyboards).
|
||||||
|
Symbol,
|
||||||
|
SymbolLock,
|
||||||
|
// Legacy modifier key. Also called "Super" in certain places.
|
||||||
|
Meta,
|
||||||
|
// Legacy modifier key.
|
||||||
|
Hyper,
|
||||||
|
/// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard
|
||||||
|
/// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` key.
|
||||||
|
///
|
||||||
|
/// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key.
|
||||||
|
Super,
|
||||||
|
/// The `Enter` or `↵` key. Used to activate current selection or accept current input. This key
|
||||||
|
/// value is also used for the `Return` (Macintosh numpad) key. This key value is also used for
|
||||||
|
/// the Android `KEYCODE_DPAD_CENTER`.
|
||||||
|
Enter,
|
||||||
|
/// The Horizontal Tabulation `Tab` key.
|
||||||
|
Tab,
|
||||||
|
/// Used in text to insert a space between words. Usually located below the character keys.
|
||||||
|
Space,
|
||||||
|
/// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`)
|
||||||
|
ArrowDown,
|
||||||
|
/// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`)
|
||||||
|
ArrowLeft,
|
||||||
|
/// Navigate or traverse rightward. (`KEYCODE_DPAD_RIGHT`)
|
||||||
|
ArrowRight,
|
||||||
|
/// Navigate or traverse upward. (`KEYCODE_DPAD_UP`)
|
||||||
|
ArrowUp,
|
||||||
|
/// The End key, used with keyboard entry to go to the end of content (`KEYCODE_MOVE_END`).
|
||||||
|
End,
|
||||||
|
/// The Home key, used with keyboard entry, to go to start of content (`KEYCODE_MOVE_HOME`).
|
||||||
|
/// For the mobile phone `Home` key (which goes to the phone’s main screen), use [`GoHome`].
|
||||||
|
///
|
||||||
|
/// [`GoHome`]: Self::GoHome
|
||||||
|
Home,
|
||||||
|
/// Scroll down or display next page of content.
|
||||||
|
PageDown,
|
||||||
|
/// Scroll up or display previous page of content.
|
||||||
|
PageUp,
|
||||||
|
/// Used to remove the character to the left of the cursor. This key value is also used for
|
||||||
|
/// the key labeled `Delete` on MacOS keyboards.
|
||||||
|
Backspace,
|
||||||
|
/// Remove the currently selected input.
|
||||||
|
Clear,
|
||||||
|
/// Copy the current selection. (`APPCOMMAND_COPY`)
|
||||||
|
Copy,
|
||||||
|
/// The Cursor Select key.
|
||||||
|
CrSel,
|
||||||
|
/// Cut the current selection. (`APPCOMMAND_CUT`)
|
||||||
|
Cut,
|
||||||
|
/// Used to delete the character to the right of the cursor. This key value is also used for the
|
||||||
|
/// key labeled `Delete` on MacOS keyboards when `Fn` is active.
|
||||||
|
Delete,
|
||||||
|
/// The Erase to End of Field key. This key deletes all characters from the current cursor
|
||||||
|
/// position to the end of the current field.
|
||||||
|
EraseEof,
|
||||||
|
/// The Extend Selection (Exsel) key.
|
||||||
|
ExSel,
|
||||||
|
/// Toggle between text modes for insertion or overtyping.
|
||||||
|
/// (`KEYCODE_INSERT`)
|
||||||
|
Insert,
|
||||||
|
/// The Paste key. (`APPCOMMAND_PASTE`)
|
||||||
|
Paste,
|
||||||
|
/// Redo the last action. (`APPCOMMAND_REDO`)
|
||||||
|
Redo,
|
||||||
|
/// Undo the last action. (`APPCOMMAND_UNDO`)
|
||||||
|
Undo,
|
||||||
|
/// The Accept (Commit, OK) key. Accept current option or input method sequence conversion.
|
||||||
|
Accept,
|
||||||
|
/// Redo or repeat an action.
|
||||||
|
Again,
|
||||||
|
/// The Attention (Attn) key.
|
||||||
|
Attn,
|
||||||
|
Cancel,
|
||||||
|
/// Show the application’s context menu.
|
||||||
|
/// This key is commonly found between the right `Super` key and the right `Control` key.
|
||||||
|
ContextMenu,
|
||||||
|
/// The `Esc` key. This key was originally used to initiate an escape sequence, but is
|
||||||
|
/// now more generally used to exit or "escape" the current context, such as closing a dialog
|
||||||
|
/// or exiting full screen mode.
|
||||||
|
Escape,
|
||||||
|
Execute,
|
||||||
|
/// Open the Find dialog. (`APPCOMMAND_FIND`)
|
||||||
|
Find,
|
||||||
|
/// Open a help dialog or toggle display of help information. (`APPCOMMAND_HELP`,
|
||||||
|
/// `KEYCODE_HELP`)
|
||||||
|
Help,
|
||||||
|
/// Pause the current state or application (as appropriate).
|
||||||
|
///
|
||||||
|
/// Note: Do not use this value for the `Pause` button on media controllers. Use `"MediaPause"`
|
||||||
|
/// instead.
|
||||||
|
Pause,
|
||||||
|
/// Play or resume the current state or application (as appropriate).
|
||||||
|
///
|
||||||
|
/// Note: Do not use this value for the `Play` button on media controllers. Use `"MediaPlay"`
|
||||||
|
/// instead.
|
||||||
|
Play,
|
||||||
|
/// The properties (Props) key.
|
||||||
|
Props,
|
||||||
|
Select,
|
||||||
|
/// The ZoomIn key. (`KEYCODE_ZOOM_IN`)
|
||||||
|
ZoomIn,
|
||||||
|
/// The ZoomOut key. (`KEYCODE_ZOOM_OUT`)
|
||||||
|
ZoomOut,
|
||||||
|
/// The Brightness Down key. Typically controls the display brightness.
|
||||||
|
/// (`KEYCODE_BRIGHTNESS_DOWN`)
|
||||||
|
BrightnessDown,
|
||||||
|
/// The Brightness Up key. Typically controls the display brightness. (`KEYCODE_BRIGHTNESS_UP`)
|
||||||
|
BrightnessUp,
|
||||||
|
/// Toggle removable media to eject (open) and insert (close) state. (`KEYCODE_MEDIA_EJECT`)
|
||||||
|
Eject,
|
||||||
|
LogOff,
|
||||||
|
/// Toggle power state. (`KEYCODE_POWER`)
|
||||||
|
/// Note: Note: Some devices might not expose this key to the operating environment.
|
||||||
|
Power,
|
||||||
|
/// The `PowerOff` key. Sometime called `PowerDown`.
|
||||||
|
PowerOff,
|
||||||
|
/// Initiate print-screen function.
|
||||||
|
PrintScreen,
|
||||||
|
/// The Hibernate key. This key saves the current state of the computer to disk so that it can
|
||||||
|
/// be restored. The computer will then shutdown.
|
||||||
|
Hibernate,
|
||||||
|
/// The Standby key. This key turns off the display and places the computer into a low-power
|
||||||
|
/// mode without completely shutting down. It is sometimes labelled `Suspend` or `Sleep` key.
|
||||||
|
/// (`KEYCODE_SLEEP`)
|
||||||
|
Standby,
|
||||||
|
/// The WakeUp key. (`KEYCODE_WAKEUP`)
|
||||||
|
WakeUp,
|
||||||
|
/// Initate the multi-candidate mode.
|
||||||
|
AllCandidates,
|
||||||
|
Alphanumeric,
|
||||||
|
/// Initiate the Code Input mode to allow characters to be entered by
|
||||||
|
/// their code points.
|
||||||
|
CodeInput,
|
||||||
|
/// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a
|
||||||
|
/// manner similar to a dead key, triggering a mode where subsequent key presses are combined to
|
||||||
|
/// produce a different character.
|
||||||
|
Compose,
|
||||||
|
/// Convert the current input method sequence.
|
||||||
|
Convert,
|
||||||
|
/// The Final Mode `Final` key used on some Asian keyboards, to enable the final mode for IMEs.
|
||||||
|
FinalMode,
|
||||||
|
/// Switch to the first character group. (ISO/IEC 9995)
|
||||||
|
GroupFirst,
|
||||||
|
/// Switch to the last character group. (ISO/IEC 9995)
|
||||||
|
GroupLast,
|
||||||
|
/// Switch to the next character group. (ISO/IEC 9995)
|
||||||
|
GroupNext,
|
||||||
|
/// Switch to the previous character group. (ISO/IEC 9995)
|
||||||
|
GroupPrevious,
|
||||||
|
/// Toggle between or cycle through input modes of IMEs.
|
||||||
|
ModeChange,
|
||||||
|
NextCandidate,
|
||||||
|
/// Accept current input method sequence without
|
||||||
|
/// conversion in IMEs.
|
||||||
|
NonConvert,
|
||||||
|
PreviousCandidate,
|
||||||
|
Process,
|
||||||
|
SingleCandidate,
|
||||||
|
/// Toggle between Hangul and English modes.
|
||||||
|
HangulMode,
|
||||||
|
HanjaMode,
|
||||||
|
JunjaMode,
|
||||||
|
/// The Eisu key. This key may close the IME, but its purpose is defined by the current IME.
|
||||||
|
/// (`KEYCODE_EISU`)
|
||||||
|
Eisu,
|
||||||
|
/// The (Half-Width) Characters key.
|
||||||
|
Hankaku,
|
||||||
|
/// The Hiragana (Japanese Kana characters) key.
|
||||||
|
Hiragana,
|
||||||
|
/// The Hiragana/Katakana toggle key. (`KEYCODE_KATAKANA_HIRAGANA`)
|
||||||
|
HiraganaKatakana,
|
||||||
|
/// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from
|
||||||
|
/// romaji mode).
|
||||||
|
KanaMode,
|
||||||
|
/// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is
|
||||||
|
/// typically used to switch to a hiragana keyboard for the purpose of converting input into
|
||||||
|
/// kanji. (`KEYCODE_KANA`)
|
||||||
|
KanjiMode,
|
||||||
|
/// The Katakana (Japanese Kana characters) key.
|
||||||
|
Katakana,
|
||||||
|
/// The Roman characters function key.
|
||||||
|
Romaji,
|
||||||
|
/// The Zenkaku (Full-Width) Characters key.
|
||||||
|
Zenkaku,
|
||||||
|
/// The Zenkaku/Hankaku (full-width/half-width) toggle key. (`KEYCODE_ZENKAKU_HANKAKU`)
|
||||||
|
ZenkakuHankaku,
|
||||||
|
/// General purpose virtual function key, as index 1.
|
||||||
|
Soft1,
|
||||||
|
/// General purpose virtual function key, as index 2.
|
||||||
|
Soft2,
|
||||||
|
/// General purpose virtual function key, as index 3.
|
||||||
|
Soft3,
|
||||||
|
/// General purpose virtual function key, as index 4.
|
||||||
|
Soft4,
|
||||||
|
/// Select next (numerically or logically) lower channel. (`APPCOMMAND_MEDIA_CHANNEL_DOWN`,
|
||||||
|
/// `KEYCODE_CHANNEL_DOWN`)
|
||||||
|
ChannelDown,
|
||||||
|
/// Select next (numerically or logically) higher channel. (`APPCOMMAND_MEDIA_CHANNEL_UP`,
|
||||||
|
/// `KEYCODE_CHANNEL_UP`)
|
||||||
|
ChannelUp,
|
||||||
|
/// Close the current document or message (Note: This doesn’t close the application).
|
||||||
|
/// (`APPCOMMAND_CLOSE`)
|
||||||
|
Close,
|
||||||
|
/// Open an editor to forward the current message. (`APPCOMMAND_FORWARD_MAIL`)
|
||||||
|
MailForward,
|
||||||
|
/// Open an editor to reply to the current message. (`APPCOMMAND_REPLY_TO_MAIL`)
|
||||||
|
MailReply,
|
||||||
|
/// Send the current message. (`APPCOMMAND_SEND_MAIL`)
|
||||||
|
MailSend,
|
||||||
|
/// Close the current media, for example to close a CD or DVD tray. (`KEYCODE_MEDIA_CLOSE`)
|
||||||
|
MediaClose,
|
||||||
|
/// Initiate or continue forward playback at faster than normal speed, or increase speed if
|
||||||
|
/// already fast forwarding. (`APPCOMMAND_MEDIA_FAST_FORWARD`, `KEYCODE_MEDIA_FAST_FORWARD`)
|
||||||
|
MediaFastForward,
|
||||||
|
/// Pause the currently playing media. (`APPCOMMAND_MEDIA_PAUSE`, `KEYCODE_MEDIA_PAUSE`)
|
||||||
|
///
|
||||||
|
/// Note: Media controller devices should use this value rather than `"Pause"` for their pause
|
||||||
|
/// keys.
|
||||||
|
MediaPause,
|
||||||
|
/// Initiate or continue media playback at normal speed, if not currently playing at normal
|
||||||
|
/// speed. (`APPCOMMAND_MEDIA_PLAY`, `KEYCODE_MEDIA_PLAY`)
|
||||||
|
MediaPlay,
|
||||||
|
/// Toggle media between play and pause states. (`APPCOMMAND_MEDIA_PLAY_PAUSE`,
|
||||||
|
/// `KEYCODE_MEDIA_PLAY_PAUSE`)
|
||||||
|
MediaPlayPause,
|
||||||
|
/// Initiate or resume recording of currently selected media. (`APPCOMMAND_MEDIA_RECORD`,
|
||||||
|
/// `KEYCODE_MEDIA_RECORD`)
|
||||||
|
MediaRecord,
|
||||||
|
/// Initiate or continue reverse playback at faster than normal speed, or increase speed if
|
||||||
|
/// already rewinding. (`APPCOMMAND_MEDIA_REWIND`, `KEYCODE_MEDIA_REWIND`)
|
||||||
|
MediaRewind,
|
||||||
|
/// Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped.
|
||||||
|
/// (`APPCOMMAND_MEDIA_STOP`, `KEYCODE_MEDIA_STOP`)
|
||||||
|
MediaStop,
|
||||||
|
/// Seek to next media or program track. (`APPCOMMAND_MEDIA_NEXTTRACK`, `KEYCODE_MEDIA_NEXT`)
|
||||||
|
MediaTrackNext,
|
||||||
|
/// Seek to previous media or program track. (`APPCOMMAND_MEDIA_PREVIOUSTRACK`,
|
||||||
|
/// `KEYCODE_MEDIA_PREVIOUS`)
|
||||||
|
MediaTrackPrevious,
|
||||||
|
/// Open a new document or message. (`APPCOMMAND_NEW`)
|
||||||
|
New,
|
||||||
|
/// Open an existing document or message. (`APPCOMMAND_OPEN`)
|
||||||
|
Open,
|
||||||
|
/// Print the current document or message. (`APPCOMMAND_PRINT`)
|
||||||
|
Print,
|
||||||
|
/// Save the current document or message. (`APPCOMMAND_SAVE`)
|
||||||
|
Save,
|
||||||
|
/// Spellcheck the current document or selection. (`APPCOMMAND_SPELL_CHECK`)
|
||||||
|
SpellCheck,
|
||||||
|
/// The `11` key found on media numpads that
|
||||||
|
/// have buttons from `1` ... `12`.
|
||||||
|
Key11,
|
||||||
|
/// The `12` key found on media numpads that
|
||||||
|
/// have buttons from `1` ... `12`.
|
||||||
|
Key12,
|
||||||
|
/// Adjust audio balance leftward. (`VK_AUDIO_BALANCE_LEFT`)
|
||||||
|
AudioBalanceLeft,
|
||||||
|
/// Adjust audio balance rightward. (`VK_AUDIO_BALANCE_RIGHT`)
|
||||||
|
AudioBalanceRight,
|
||||||
|
/// Decrease audio bass boost or cycle down through bass boost states. (`APPCOMMAND_BASS_DOWN`,
|
||||||
|
/// `VK_BASS_BOOST_DOWN`)
|
||||||
|
AudioBassBoostDown,
|
||||||
|
/// Toggle bass boost on/off. (`APPCOMMAND_BASS_BOOST`)
|
||||||
|
AudioBassBoostToggle,
|
||||||
|
/// Increase audio bass boost or cycle up through bass boost states. (`APPCOMMAND_BASS_UP`,
|
||||||
|
/// `VK_BASS_BOOST_UP`)
|
||||||
|
AudioBassBoostUp,
|
||||||
|
/// Adjust audio fader towards front. (`VK_FADER_FRONT`)
|
||||||
|
AudioFaderFront,
|
||||||
|
/// Adjust audio fader towards rear. (`VK_FADER_REAR`)
|
||||||
|
AudioFaderRear,
|
||||||
|
/// Advance surround audio mode to next available mode. (`VK_SURROUND_MODE_NEXT`)
|
||||||
|
AudioSurroundModeNext,
|
||||||
|
/// Decrease treble. (`APPCOMMAND_TREBLE_DOWN`)
|
||||||
|
AudioTrebleDown,
|
||||||
|
/// Increase treble. (`APPCOMMAND_TREBLE_UP`)
|
||||||
|
AudioTrebleUp,
|
||||||
|
/// Decrease audio volume. (`APPCOMMAND_VOLUME_DOWN`, `KEYCODE_VOLUME_DOWN`)
|
||||||
|
AudioVolumeDown,
|
||||||
|
/// Increase audio volume. (`APPCOMMAND_VOLUME_UP`, `KEYCODE_VOLUME_UP`)
|
||||||
|
AudioVolumeUp,
|
||||||
|
/// Toggle between muted state and prior volume level. (`APPCOMMAND_VOLUME_MUTE`,
|
||||||
|
/// `KEYCODE_VOLUME_MUTE`)
|
||||||
|
AudioVolumeMute,
|
||||||
|
/// Toggle the microphone on/off. (`APPCOMMAND_MIC_ON_OFF_TOGGLE`)
|
||||||
|
MicrophoneToggle,
|
||||||
|
/// Decrease microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_DOWN`)
|
||||||
|
MicrophoneVolumeDown,
|
||||||
|
/// Increase microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_UP`)
|
||||||
|
MicrophoneVolumeUp,
|
||||||
|
/// Mute the microphone. (`APPCOMMAND_MICROPHONE_VOLUME_MUTE`, `KEYCODE_MUTE`)
|
||||||
|
MicrophoneVolumeMute,
|
||||||
|
/// Show correction list when a word is incorrectly identified. (`APPCOMMAND_CORRECTION_LIST`)
|
||||||
|
SpeechCorrectionList,
|
||||||
|
/// Toggle between dictation mode and command/control mode.
|
||||||
|
/// (`APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE`)
|
||||||
|
SpeechInputToggle,
|
||||||
|
/// The first generic "LaunchApplication" key. This is commonly associated with launching "My
|
||||||
|
/// Computer", and may have a computer symbol on the key. (`APPCOMMAND_LAUNCH_APP1`)
|
||||||
|
LaunchApplication1,
|
||||||
|
/// The second generic "LaunchApplication" key. This is commonly associated with launching
|
||||||
|
/// "Calculator", and may have a calculator symbol on the key. (`APPCOMMAND_LAUNCH_APP2`,
|
||||||
|
/// `KEYCODE_CALCULATOR`)
|
||||||
|
LaunchApplication2,
|
||||||
|
/// The "Calendar" key. (`KEYCODE_CALENDAR`)
|
||||||
|
LaunchCalendar,
|
||||||
|
/// The "Contacts" key. (`KEYCODE_CONTACTS`)
|
||||||
|
LaunchContacts,
|
||||||
|
/// The "Mail" key. (`APPCOMMAND_LAUNCH_MAIL`)
|
||||||
|
LaunchMail,
|
||||||
|
/// The "Media Player" key. (`APPCOMMAND_LAUNCH_MEDIA_SELECT`)
|
||||||
|
LaunchMediaPlayer,
|
||||||
|
LaunchMusicPlayer,
|
||||||
|
LaunchPhone,
|
||||||
|
LaunchScreenSaver,
|
||||||
|
LaunchSpreadsheet,
|
||||||
|
LaunchWebBrowser,
|
||||||
|
LaunchWebCam,
|
||||||
|
LaunchWordProcessor,
|
||||||
|
/// Navigate to previous content or page in current history. (`APPCOMMAND_BROWSER_BACKWARD`)
|
||||||
|
BrowserBack,
|
||||||
|
/// Open the list of browser favorites. (`APPCOMMAND_BROWSER_FAVORITES`)
|
||||||
|
BrowserFavorites,
|
||||||
|
/// Navigate to next content or page in current history. (`APPCOMMAND_BROWSER_FORWARD`)
|
||||||
|
BrowserForward,
|
||||||
|
/// Go to the user’s preferred home page. (`APPCOMMAND_BROWSER_HOME`)
|
||||||
|
BrowserHome,
|
||||||
|
/// Refresh the current page or content. (`APPCOMMAND_BROWSER_REFRESH`)
|
||||||
|
BrowserRefresh,
|
||||||
|
/// Call up the user’s preferred search page. (`APPCOMMAND_BROWSER_SEARCH`)
|
||||||
|
BrowserSearch,
|
||||||
|
/// Stop loading the current page or content. (`APPCOMMAND_BROWSER_STOP`)
|
||||||
|
BrowserStop,
|
||||||
|
/// The Application switch key, which provides a list of recent apps to switch between.
|
||||||
|
/// (`KEYCODE_APP_SWITCH`)
|
||||||
|
AppSwitch,
|
||||||
|
/// The Call key. (`KEYCODE_CALL`)
|
||||||
|
Call,
|
||||||
|
/// The Camera key. (`KEYCODE_CAMERA`)
|
||||||
|
Camera,
|
||||||
|
/// The Camera focus key. (`KEYCODE_FOCUS`)
|
||||||
|
CameraFocus,
|
||||||
|
/// The End Call key. (`KEYCODE_ENDCALL`)
|
||||||
|
EndCall,
|
||||||
|
/// The Back key. (`KEYCODE_BACK`)
|
||||||
|
GoBack,
|
||||||
|
/// The Home key, which goes to the phone’s main screen. (`KEYCODE_HOME`)
|
||||||
|
GoHome,
|
||||||
|
/// The Headset Hook key. (`KEYCODE_HEADSETHOOK`)
|
||||||
|
HeadsetHook,
|
||||||
|
LastNumberRedial,
|
||||||
|
/// The Notification key. (`KEYCODE_NOTIFICATION`)
|
||||||
|
Notification,
|
||||||
|
/// Toggle between manner mode state: silent, vibrate, ring, ... (`KEYCODE_MANNER_MODE`)
|
||||||
|
MannerMode,
|
||||||
|
VoiceDial,
|
||||||
|
/// Switch to viewing TV. (`KEYCODE_TV`)
|
||||||
|
TV,
|
||||||
|
/// TV 3D Mode. (`KEYCODE_3D_MODE`)
|
||||||
|
TV3DMode,
|
||||||
|
/// Toggle between antenna and cable input. (`KEYCODE_TV_ANTENNA_CABLE`)
|
||||||
|
TVAntennaCable,
|
||||||
|
/// Audio description. (`KEYCODE_TV_AUDIO_DESCRIPTION`)
|
||||||
|
TVAudioDescription,
|
||||||
|
/// Audio description mixing volume down. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN`)
|
||||||
|
TVAudioDescriptionMixDown,
|
||||||
|
/// Audio description mixing volume up. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP`)
|
||||||
|
TVAudioDescriptionMixUp,
|
||||||
|
/// Contents menu. (`KEYCODE_TV_CONTENTS_MENU`)
|
||||||
|
TVContentsMenu,
|
||||||
|
/// Contents menu. (`KEYCODE_TV_DATA_SERVICE`)
|
||||||
|
TVDataService,
|
||||||
|
/// Switch the input mode on an external TV. (`KEYCODE_TV_INPUT`)
|
||||||
|
TVInput,
|
||||||
|
/// Switch to component input #1. (`KEYCODE_TV_INPUT_COMPONENT_1`)
|
||||||
|
TVInputComponent1,
|
||||||
|
/// Switch to component input #2. (`KEYCODE_TV_INPUT_COMPONENT_2`)
|
||||||
|
TVInputComponent2,
|
||||||
|
/// Switch to composite input #1. (`KEYCODE_TV_INPUT_COMPOSITE_1`)
|
||||||
|
TVInputComposite1,
|
||||||
|
/// Switch to composite input #2. (`KEYCODE_TV_INPUT_COMPOSITE_2`)
|
||||||
|
TVInputComposite2,
|
||||||
|
/// Switch to HDMI input #1. (`KEYCODE_TV_INPUT_HDMI_1`)
|
||||||
|
TVInputHDMI1,
|
||||||
|
/// Switch to HDMI input #2. (`KEYCODE_TV_INPUT_HDMI_2`)
|
||||||
|
TVInputHDMI2,
|
||||||
|
/// Switch to HDMI input #3. (`KEYCODE_TV_INPUT_HDMI_3`)
|
||||||
|
TVInputHDMI3,
|
||||||
|
/// Switch to HDMI input #4. (`KEYCODE_TV_INPUT_HDMI_4`)
|
||||||
|
TVInputHDMI4,
|
||||||
|
/// Switch to VGA input #1. (`KEYCODE_TV_INPUT_VGA_1`)
|
||||||
|
TVInputVGA1,
|
||||||
|
/// Media context menu. (`KEYCODE_TV_MEDIA_CONTEXT_MENU`)
|
||||||
|
TVMediaContext,
|
||||||
|
/// Toggle network. (`KEYCODE_TV_NETWORK`)
|
||||||
|
TVNetwork,
|
||||||
|
/// Number entry. (`KEYCODE_TV_NUMBER_ENTRY`)
|
||||||
|
TVNumberEntry,
|
||||||
|
/// Toggle the power on an external TV. (`KEYCODE_TV_POWER`)
|
||||||
|
TVPower,
|
||||||
|
/// Radio. (`KEYCODE_TV_RADIO_SERVICE`)
|
||||||
|
TVRadioService,
|
||||||
|
/// Satellite. (`KEYCODE_TV_SATELLITE`)
|
||||||
|
TVSatellite,
|
||||||
|
/// Broadcast Satellite. (`KEYCODE_TV_SATELLITE_BS`)
|
||||||
|
TVSatelliteBS,
|
||||||
|
/// Communication Satellite. (`KEYCODE_TV_SATELLITE_CS`)
|
||||||
|
TVSatelliteCS,
|
||||||
|
/// Toggle between available satellites. (`KEYCODE_TV_SATELLITE_SERVICE`)
|
||||||
|
TVSatelliteToggle,
|
||||||
|
/// Analog Terrestrial. (`KEYCODE_TV_TERRESTRIAL_ANALOG`)
|
||||||
|
TVTerrestrialAnalog,
|
||||||
|
/// Digital Terrestrial. (`KEYCODE_TV_TERRESTRIAL_DIGITAL`)
|
||||||
|
TVTerrestrialDigital,
|
||||||
|
/// Timer programming. (`KEYCODE_TV_TIMER_PROGRAMMING`)
|
||||||
|
TVTimer,
|
||||||
|
/// Switch the input mode on an external AVR (audio/video receiver). (`KEYCODE_AVR_INPUT`)
|
||||||
|
AVRInput,
|
||||||
|
/// Toggle the power on an external AVR (audio/video receiver). (`KEYCODE_AVR_POWER`)
|
||||||
|
AVRPower,
|
||||||
|
/// General purpose color-coded media function key, as index 0 (red). (`VK_COLORED_KEY_0`,
|
||||||
|
/// `KEYCODE_PROG_RED`)
|
||||||
|
ColorF0Red,
|
||||||
|
/// General purpose color-coded media function key, as index 1 (green). (`VK_COLORED_KEY_1`,
|
||||||
|
/// `KEYCODE_PROG_GREEN`)
|
||||||
|
ColorF1Green,
|
||||||
|
/// General purpose color-coded media function key, as index 2 (yellow). (`VK_COLORED_KEY_2`,
|
||||||
|
/// `KEYCODE_PROG_YELLOW`)
|
||||||
|
ColorF2Yellow,
|
||||||
|
/// General purpose color-coded media function key, as index 3 (blue). (`VK_COLORED_KEY_3`,
|
||||||
|
/// `KEYCODE_PROG_BLUE`)
|
||||||
|
ColorF3Blue,
|
||||||
|
/// General purpose color-coded media function key, as index 4 (grey). (`VK_COLORED_KEY_4`)
|
||||||
|
ColorF4Grey,
|
||||||
|
/// General purpose color-coded media function key, as index 5 (brown). (`VK_COLORED_KEY_5`)
|
||||||
|
ColorF5Brown,
|
||||||
|
/// Toggle the display of Closed Captions. (`VK_CC`, `KEYCODE_CAPTIONS`)
|
||||||
|
ClosedCaptionToggle,
|
||||||
|
/// Adjust brightness of device, by toggling between or cycling through states. (`VK_DIMMER`)
|
||||||
|
Dimmer,
|
||||||
|
/// Swap video sources. (`VK_DISPLAY_SWAP`)
|
||||||
|
DisplaySwap,
|
||||||
|
/// Select Digital Video Rrecorder. (`KEYCODE_DVR`)
|
||||||
|
DVR,
|
||||||
|
/// Exit the current application. (`VK_EXIT`)
|
||||||
|
Exit,
|
||||||
|
/// Clear program or content stored as favorite 0. (`VK_CLEAR_FAVORITE_0`)
|
||||||
|
FavoriteClear0,
|
||||||
|
/// Clear program or content stored as favorite 1. (`VK_CLEAR_FAVORITE_1`)
|
||||||
|
FavoriteClear1,
|
||||||
|
/// Clear program or content stored as favorite 2. (`VK_CLEAR_FAVORITE_2`)
|
||||||
|
FavoriteClear2,
|
||||||
|
/// Clear program or content stored as favorite 3. (`VK_CLEAR_FAVORITE_3`)
|
||||||
|
FavoriteClear3,
|
||||||
|
/// Select (recall) program or content stored as favorite 0. (`VK_RECALL_FAVORITE_0`)
|
||||||
|
FavoriteRecall0,
|
||||||
|
/// Select (recall) program or content stored as favorite 1. (`VK_RECALL_FAVORITE_1`)
|
||||||
|
FavoriteRecall1,
|
||||||
|
/// Select (recall) program or content stored as favorite 2. (`VK_RECALL_FAVORITE_2`)
|
||||||
|
FavoriteRecall2,
|
||||||
|
/// Select (recall) program or content stored as favorite 3. (`VK_RECALL_FAVORITE_3`)
|
||||||
|
FavoriteRecall3,
|
||||||
|
/// Store current program or content as favorite 0. (`VK_STORE_FAVORITE_0`)
|
||||||
|
FavoriteStore0,
|
||||||
|
/// Store current program or content as favorite 1. (`VK_STORE_FAVORITE_1`)
|
||||||
|
FavoriteStore1,
|
||||||
|
/// Store current program or content as favorite 2. (`VK_STORE_FAVORITE_2`)
|
||||||
|
FavoriteStore2,
|
||||||
|
/// Store current program or content as favorite 3. (`VK_STORE_FAVORITE_3`)
|
||||||
|
FavoriteStore3,
|
||||||
|
/// Toggle display of program or content guide. (`VK_GUIDE`, `KEYCODE_GUIDE`)
|
||||||
|
Guide,
|
||||||
|
/// If guide is active and displayed, then display next day’s content. (`VK_NEXT_DAY`)
|
||||||
|
GuideNextDay,
|
||||||
|
/// If guide is active and displayed, then display previous day’s content. (`VK_PREV_DAY`)
|
||||||
|
GuidePreviousDay,
|
||||||
|
/// Toggle display of information about currently selected context or media. (`VK_INFO`,
|
||||||
|
/// `KEYCODE_INFO`)
|
||||||
|
Info,
|
||||||
|
/// Toggle instant replay. (`VK_INSTANT_REPLAY`)
|
||||||
|
InstantReplay,
|
||||||
|
/// Launch linked content, if available and appropriate. (`VK_LINK`)
|
||||||
|
Link,
|
||||||
|
/// List the current program. (`VK_LIST`)
|
||||||
|
ListProgram,
|
||||||
|
/// Toggle display listing of currently available live content or programs. (`VK_LIVE`)
|
||||||
|
LiveContent,
|
||||||
|
/// Lock or unlock current content or program. (`VK_LOCK`)
|
||||||
|
Lock,
|
||||||
|
/// Show a list of media applications: audio/video players and image viewers. (`VK_APPS`)
|
||||||
|
///
|
||||||
|
/// Note: Do not confuse this key value with the Windows' `VK_APPS` / `VK_CONTEXT_MENU` key,
|
||||||
|
/// which is encoded as `"ContextMenu"`.
|
||||||
|
MediaApps,
|
||||||
|
/// Audio track key. (`KEYCODE_MEDIA_AUDIO_TRACK`)
|
||||||
|
MediaAudioTrack,
|
||||||
|
/// Select previously selected channel or media. (`VK_LAST`, `KEYCODE_LAST_CHANNEL`)
|
||||||
|
MediaLast,
|
||||||
|
/// Skip backward to next content or program. (`KEYCODE_MEDIA_SKIP_BACKWARD`)
|
||||||
|
MediaSkipBackward,
|
||||||
|
/// Skip forward to next content or program. (`VK_SKIP`, `KEYCODE_MEDIA_SKIP_FORWARD`)
|
||||||
|
MediaSkipForward,
|
||||||
|
/// Step backward to next content or program. (`KEYCODE_MEDIA_STEP_BACKWARD`)
|
||||||
|
MediaStepBackward,
|
||||||
|
/// Step forward to next content or program. (`KEYCODE_MEDIA_STEP_FORWARD`)
|
||||||
|
MediaStepForward,
|
||||||
|
/// Media top menu. (`KEYCODE_MEDIA_TOP_MENU`)
|
||||||
|
MediaTopMenu,
|
||||||
|
/// Navigate in. (`KEYCODE_NAVIGATE_IN`)
|
||||||
|
NavigateIn,
|
||||||
|
/// Navigate to next key. (`KEYCODE_NAVIGATE_NEXT`)
|
||||||
|
NavigateNext,
|
||||||
|
/// Navigate out. (`KEYCODE_NAVIGATE_OUT`)
|
||||||
|
NavigateOut,
|
||||||
|
/// Navigate to previous key. (`KEYCODE_NAVIGATE_PREVIOUS`)
|
||||||
|
NavigatePrevious,
|
||||||
|
/// Cycle to next favorite channel (in favorites list). (`VK_NEXT_FAVORITE_CHANNEL`)
|
||||||
|
NextFavoriteChannel,
|
||||||
|
/// Cycle to next user profile (if there are multiple user profiles). (`VK_USER`)
|
||||||
|
NextUserProfile,
|
||||||
|
/// Access on-demand content or programs. (`VK_ON_DEMAND`)
|
||||||
|
OnDemand,
|
||||||
|
/// Pairing key to pair devices. (`KEYCODE_PAIRING`)
|
||||||
|
Pairing,
|
||||||
|
/// Move picture-in-picture window down. (`VK_PINP_DOWN`)
|
||||||
|
PinPDown,
|
||||||
|
/// Move picture-in-picture window. (`VK_PINP_MOVE`)
|
||||||
|
PinPMove,
|
||||||
|
/// Toggle display of picture-in-picture window. (`VK_PINP_TOGGLE`)
|
||||||
|
PinPToggle,
|
||||||
|
/// Move picture-in-picture window up. (`VK_PINP_UP`)
|
||||||
|
PinPUp,
|
||||||
|
/// Decrease media playback speed. (`VK_PLAY_SPEED_DOWN`)
|
||||||
|
PlaySpeedDown,
|
||||||
|
/// Reset playback to normal speed. (`VK_PLAY_SPEED_RESET`)
|
||||||
|
PlaySpeedReset,
|
||||||
|
/// Increase media playback speed. (`VK_PLAY_SPEED_UP`)
|
||||||
|
PlaySpeedUp,
|
||||||
|
/// Toggle random media or content shuffle mode. (`VK_RANDOM_TOGGLE`)
|
||||||
|
RandomToggle,
|
||||||
|
/// Not a physical key, but this key code is sent when the remote control battery is low.
|
||||||
|
/// (`VK_RC_LOW_BATTERY`)
|
||||||
|
RcLowBattery,
|
||||||
|
/// Toggle or cycle between media recording speeds. (`VK_RECORD_SPEED_NEXT`)
|
||||||
|
RecordSpeedNext,
|
||||||
|
/// Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output).
|
||||||
|
/// (`VK_RF_BYPASS`)
|
||||||
|
RfBypass,
|
||||||
|
/// Toggle scan channels mode. (`VK_SCAN_CHANNELS_TOGGLE`)
|
||||||
|
ScanChannelsToggle,
|
||||||
|
/// Advance display screen mode to next available mode. (`VK_SCREEN_MODE_NEXT`)
|
||||||
|
ScreenModeNext,
|
||||||
|
/// Toggle display of device settings screen. (`VK_SETTINGS`, `KEYCODE_SETTINGS`)
|
||||||
|
Settings,
|
||||||
|
/// Toggle split screen mode. (`VK_SPLIT_SCREEN_TOGGLE`)
|
||||||
|
SplitScreenToggle,
|
||||||
|
/// Switch the input mode on an external STB (set top box). (`KEYCODE_STB_INPUT`)
|
||||||
|
STBInput,
|
||||||
|
/// Toggle the power on an external STB (set top box). (`KEYCODE_STB_POWER`)
|
||||||
|
STBPower,
|
||||||
|
/// Toggle display of subtitles, if available. (`VK_SUBTITLE`)
|
||||||
|
Subtitle,
|
||||||
|
/// Toggle display of teletext, if available (`VK_TELETEXT`, `KEYCODE_TV_TELETEXT`).
|
||||||
|
Teletext,
|
||||||
|
/// Advance video mode to next available mode. (`VK_VIDEO_MODE_NEXT`)
|
||||||
|
VideoModeNext,
|
||||||
|
/// Cause device to identify itself in some manner, e.g., audibly or visibly. (`VK_WINK`)
|
||||||
|
Wink,
|
||||||
|
/// Toggle between full-screen and scaled content, or alter magnification level. (`VK_ZOOM`,
|
||||||
|
/// `KEYCODE_TV_ZOOM_MODE`)
|
||||||
|
ZoomToggle,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F1,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F2,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F3,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F4,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F5,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F6,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F7,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F8,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F9,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F10,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F11,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F12,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F13,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F14,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F15,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F16,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F17,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F18,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F19,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F20,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F21,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F22,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F23,
|
||||||
|
/// General-purpose function key.
|
||||||
|
/// Usually found at the top of the keyboard.
|
||||||
|
F24,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F25,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F26,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F27,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F28,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F29,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F30,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F31,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F32,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F33,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F34,
|
||||||
|
/// General-purpose function key.
|
||||||
|
F35,
|
||||||
|
}
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
/// The symbolic name of a keyboard key.
|
|
||||||
///
|
|
||||||
/// This is mostly the `KeyCode` type found in [`winit`].
|
|
||||||
///
|
|
||||||
/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/
|
|
||||||
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
|
|
||||||
#[repr(u32)]
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub enum KeyCode {
|
|
||||||
/// The '1' key over the letters.
|
|
||||||
Key1,
|
|
||||||
/// The '2' key over the letters.
|
|
||||||
Key2,
|
|
||||||
/// The '3' key over the letters.
|
|
||||||
Key3,
|
|
||||||
/// The '4' key over the letters.
|
|
||||||
Key4,
|
|
||||||
/// The '5' key over the letters.
|
|
||||||
Key5,
|
|
||||||
/// The '6' key over the letters.
|
|
||||||
Key6,
|
|
||||||
/// The '7' key over the letters.
|
|
||||||
Key7,
|
|
||||||
/// The '8' key over the letters.
|
|
||||||
Key8,
|
|
||||||
/// The '9' key over the letters.
|
|
||||||
Key9,
|
|
||||||
/// The '0' key over the 'O' and 'P' keys.
|
|
||||||
Key0,
|
|
||||||
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
D,
|
|
||||||
E,
|
|
||||||
F,
|
|
||||||
G,
|
|
||||||
H,
|
|
||||||
I,
|
|
||||||
J,
|
|
||||||
K,
|
|
||||||
L,
|
|
||||||
M,
|
|
||||||
N,
|
|
||||||
O,
|
|
||||||
P,
|
|
||||||
Q,
|
|
||||||
R,
|
|
||||||
S,
|
|
||||||
T,
|
|
||||||
U,
|
|
||||||
V,
|
|
||||||
W,
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
Z,
|
|
||||||
|
|
||||||
/// The Escape key, next to F1.
|
|
||||||
Escape,
|
|
||||||
|
|
||||||
F1,
|
|
||||||
F2,
|
|
||||||
F3,
|
|
||||||
F4,
|
|
||||||
F5,
|
|
||||||
F6,
|
|
||||||
F7,
|
|
||||||
F8,
|
|
||||||
F9,
|
|
||||||
F10,
|
|
||||||
F11,
|
|
||||||
F12,
|
|
||||||
F13,
|
|
||||||
F14,
|
|
||||||
F15,
|
|
||||||
F16,
|
|
||||||
F17,
|
|
||||||
F18,
|
|
||||||
F19,
|
|
||||||
F20,
|
|
||||||
F21,
|
|
||||||
F22,
|
|
||||||
F23,
|
|
||||||
F24,
|
|
||||||
|
|
||||||
/// Print Screen/SysRq.
|
|
||||||
Snapshot,
|
|
||||||
/// Scroll Lock.
|
|
||||||
Scroll,
|
|
||||||
/// Pause/Break key, next to Scroll lock.
|
|
||||||
Pause,
|
|
||||||
|
|
||||||
/// `Insert`, next to Backspace.
|
|
||||||
Insert,
|
|
||||||
Home,
|
|
||||||
Delete,
|
|
||||||
End,
|
|
||||||
PageDown,
|
|
||||||
PageUp,
|
|
||||||
|
|
||||||
Left,
|
|
||||||
Up,
|
|
||||||
Right,
|
|
||||||
Down,
|
|
||||||
|
|
||||||
/// The Backspace key, right over Enter.
|
|
||||||
Backspace,
|
|
||||||
/// The Enter key.
|
|
||||||
Enter,
|
|
||||||
/// The space bar.
|
|
||||||
Space,
|
|
||||||
|
|
||||||
/// The "Compose" key on Linux.
|
|
||||||
Compose,
|
|
||||||
|
|
||||||
Caret,
|
|
||||||
|
|
||||||
Numlock,
|
|
||||||
Numpad0,
|
|
||||||
Numpad1,
|
|
||||||
Numpad2,
|
|
||||||
Numpad3,
|
|
||||||
Numpad4,
|
|
||||||
Numpad5,
|
|
||||||
Numpad6,
|
|
||||||
Numpad7,
|
|
||||||
Numpad8,
|
|
||||||
Numpad9,
|
|
||||||
NumpadAdd,
|
|
||||||
NumpadDivide,
|
|
||||||
NumpadDecimal,
|
|
||||||
NumpadComma,
|
|
||||||
NumpadEnter,
|
|
||||||
NumpadEquals,
|
|
||||||
NumpadMultiply,
|
|
||||||
NumpadSubtract,
|
|
||||||
|
|
||||||
AbntC1,
|
|
||||||
AbntC2,
|
|
||||||
Apostrophe,
|
|
||||||
Apps,
|
|
||||||
Asterisk,
|
|
||||||
At,
|
|
||||||
Ax,
|
|
||||||
Backslash,
|
|
||||||
Calculator,
|
|
||||||
Capital,
|
|
||||||
Colon,
|
|
||||||
Comma,
|
|
||||||
Convert,
|
|
||||||
Equals,
|
|
||||||
Grave,
|
|
||||||
Kana,
|
|
||||||
Kanji,
|
|
||||||
LAlt,
|
|
||||||
LBracket,
|
|
||||||
LControl,
|
|
||||||
LShift,
|
|
||||||
LWin,
|
|
||||||
Mail,
|
|
||||||
MediaSelect,
|
|
||||||
MediaStop,
|
|
||||||
Minus,
|
|
||||||
Mute,
|
|
||||||
MyComputer,
|
|
||||||
NavigateForward, // also called "Next"
|
|
||||||
NavigateBackward, // also called "Prior"
|
|
||||||
NextTrack,
|
|
||||||
NoConvert,
|
|
||||||
OEM102,
|
|
||||||
Period,
|
|
||||||
PlayPause,
|
|
||||||
Plus,
|
|
||||||
Power,
|
|
||||||
PrevTrack,
|
|
||||||
RAlt,
|
|
||||||
RBracket,
|
|
||||||
RControl,
|
|
||||||
RShift,
|
|
||||||
RWin,
|
|
||||||
Semicolon,
|
|
||||||
Slash,
|
|
||||||
Sleep,
|
|
||||||
Stop,
|
|
||||||
Sysrq,
|
|
||||||
Tab,
|
|
||||||
Underline,
|
|
||||||
Unlabeled,
|
|
||||||
VolumeDown,
|
|
||||||
VolumeUp,
|
|
||||||
Wake,
|
|
||||||
WebBack,
|
|
||||||
WebFavorites,
|
|
||||||
WebForward,
|
|
||||||
WebHome,
|
|
||||||
WebRefresh,
|
|
||||||
WebSearch,
|
|
||||||
WebStop,
|
|
||||||
Yen,
|
|
||||||
Copy,
|
|
||||||
Paste,
|
|
||||||
Cut,
|
|
||||||
}
|
|
||||||
12
core/src/keyboard/location.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
/// The location of a key on the keyboard.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Location {
|
||||||
|
/// The standard group of keys on the keyboard.
|
||||||
|
Standard,
|
||||||
|
/// The left side of the keyboard.
|
||||||
|
Left,
|
||||||
|
/// The right side of the keyboard.
|
||||||
|
Right,
|
||||||
|
/// The numpad of the keyboard.
|
||||||
|
Numpad,
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ pub mod flex;
|
||||||
pub use limits::Limits;
|
pub use limits::Limits;
|
||||||
pub use node::Node;
|
pub use node::Node;
|
||||||
|
|
||||||
use crate::{Point, Rectangle, Size, Vector};
|
use crate::{Length, Padding, Point, Rectangle, Size, Vector};
|
||||||
|
|
||||||
/// The bounds of a [`Node`] and its children, using absolute coordinates.
|
/// The bounds of a [`Node`] and its children, using absolute coordinates.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -71,12 +71,12 @@ pub fn next_to_each_other(
|
||||||
left: impl FnOnce(&Limits) -> Node,
|
left: impl FnOnce(&Limits) -> Node,
|
||||||
right: impl FnOnce(&Limits) -> Node,
|
right: impl FnOnce(&Limits) -> Node,
|
||||||
) -> Node {
|
) -> Node {
|
||||||
let mut left_node = left(limits);
|
let left_node = left(limits);
|
||||||
let left_size = left_node.size();
|
let left_size = left_node.size();
|
||||||
|
|
||||||
let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0));
|
let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0));
|
||||||
|
|
||||||
let mut right_node = right(&right_limits);
|
let right_node = right(&right_limits);
|
||||||
let right_size = right_node.size();
|
let right_size = right_node.size();
|
||||||
|
|
||||||
let (left_y, right_y) = if left_size.height > right_size.height {
|
let (left_y, right_y) = if left_size.height > right_size.height {
|
||||||
|
|
@ -85,14 +85,106 @@ pub fn next_to_each_other(
|
||||||
((right_size.height - left_size.height) / 2.0, 0.0)
|
((right_size.height - left_size.height) / 2.0, 0.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
left_node.move_to(Point::new(0.0, left_y));
|
|
||||||
right_node.move_to(Point::new(left_size.width + spacing, right_y));
|
|
||||||
|
|
||||||
Node::with_children(
|
Node::with_children(
|
||||||
Size::new(
|
Size::new(
|
||||||
left_size.width + spacing + right_size.width,
|
left_size.width + spacing + right_size.width,
|
||||||
left_size.height.max(right_size.height),
|
left_size.height.max(right_size.height),
|
||||||
),
|
),
|
||||||
vec![left_node, right_node],
|
vec![
|
||||||
|
left_node.move_to(Point::new(0.0, left_y)),
|
||||||
|
right_node.move_to(Point::new(left_size.width + spacing, right_y)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the resulting [`Node`] that fits the [`Limits`] given
|
||||||
|
/// some width and height requirements and no intrinsic size.
|
||||||
|
pub fn atomic(
|
||||||
|
limits: &Limits,
|
||||||
|
width: impl Into<Length>,
|
||||||
|
height: impl Into<Length>,
|
||||||
|
) -> Node {
|
||||||
|
let width = width.into();
|
||||||
|
let height = height.into();
|
||||||
|
|
||||||
|
Node::new(limits.resolve(width, height, Size::ZERO))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the resulting [`Node`] that fits the [`Limits`] given
|
||||||
|
/// some width and height requirements and a closure that produces
|
||||||
|
/// the intrinsic [`Size`] inside the given [`Limits`].
|
||||||
|
pub fn sized(
|
||||||
|
limits: &Limits,
|
||||||
|
width: impl Into<Length>,
|
||||||
|
height: impl Into<Length>,
|
||||||
|
f: impl FnOnce(&Limits) -> Size,
|
||||||
|
) -> Node {
|
||||||
|
let width = width.into();
|
||||||
|
let height = height.into();
|
||||||
|
|
||||||
|
let limits = limits.width(width).height(height);
|
||||||
|
let intrinsic_size = f(&limits);
|
||||||
|
|
||||||
|
Node::new(limits.resolve(width, height, intrinsic_size))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the resulting [`Node`] that fits the [`Limits`] given
|
||||||
|
/// some width and height requirements and a closure that produces
|
||||||
|
/// the content [`Node`] inside the given [`Limits`].
|
||||||
|
pub fn contained(
|
||||||
|
limits: &Limits,
|
||||||
|
width: impl Into<Length>,
|
||||||
|
height: impl Into<Length>,
|
||||||
|
f: impl FnOnce(&Limits) -> Node,
|
||||||
|
) -> Node {
|
||||||
|
let width = width.into();
|
||||||
|
let height = height.into();
|
||||||
|
|
||||||
|
let limits = limits.width(width).height(height);
|
||||||
|
let content = f(&limits);
|
||||||
|
|
||||||
|
Node::with_children(
|
||||||
|
limits.resolve(width, height, content.size()),
|
||||||
|
vec![content],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the [`Node`] that fits the [`Limits`] given some width, height, and
|
||||||
|
/// [`Padding`] requirements and a closure that produces the content [`Node`]
|
||||||
|
/// inside the given [`Limits`].
|
||||||
|
pub fn padded(
|
||||||
|
limits: &Limits,
|
||||||
|
width: impl Into<Length>,
|
||||||
|
height: impl Into<Length>,
|
||||||
|
padding: impl Into<Padding>,
|
||||||
|
layout: impl FnOnce(&Limits) -> Node,
|
||||||
|
) -> Node {
|
||||||
|
positioned(limits, width, height, padding, layout, |content, _| content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes a [`padded`] [`Node`] with a positioning step.
|
||||||
|
pub fn positioned(
|
||||||
|
limits: &Limits,
|
||||||
|
width: impl Into<Length>,
|
||||||
|
height: impl Into<Length>,
|
||||||
|
padding: impl Into<Padding>,
|
||||||
|
layout: impl FnOnce(&Limits) -> Node,
|
||||||
|
position: impl FnOnce(Node, Size) -> Node,
|
||||||
|
) -> Node {
|
||||||
|
let width = width.into();
|
||||||
|
let height = height.into();
|
||||||
|
let padding = padding.into();
|
||||||
|
|
||||||
|
let limits = limits.width(width).height(height);
|
||||||
|
let content = layout(&limits.shrink(padding));
|
||||||
|
let padding = padding.fit(content.size(), limits.max());
|
||||||
|
|
||||||
|
let size = limits
|
||||||
|
.shrink(padding)
|
||||||
|
.resolve(width, height, content.size());
|
||||||
|
|
||||||
|
Node::with_children(
|
||||||
|
size.expand(padding),
|
||||||
|
vec![position(content.move_to((padding.left, padding.top)), size)],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use crate::Element;
|
||||||
|
|
||||||
use crate::layout::{Limits, Node};
|
use crate::layout::{Limits, Node};
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::{Alignment, Padding, Point, Size};
|
use crate::{Alignment, Length, Padding, Point, Size};
|
||||||
|
|
||||||
/// The main axis of a flex layout.
|
/// The main axis of a flex layout.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -47,7 +47,7 @@ impl Axis {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(&self, main: f32, cross: f32) -> (f32, f32) {
|
fn pack<T>(&self, main: T, cross: T) -> (T, T) {
|
||||||
match self {
|
match self {
|
||||||
Axis::Horizontal => (main, cross),
|
Axis::Horizontal => (main, cross),
|
||||||
Axis::Vertical => (cross, main),
|
Axis::Vertical => (cross, main),
|
||||||
|
|
@ -63,6 +63,8 @@ pub fn resolve<Message, Renderer>(
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &Limits,
|
limits: &Limits,
|
||||||
|
width: Length,
|
||||||
|
height: Length,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
align_items: Alignment,
|
align_items: Alignment,
|
||||||
|
|
@ -72,26 +74,64 @@ pub fn resolve<Message, Renderer>(
|
||||||
where
|
where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
let limits = limits.pad(padding);
|
let limits = limits.width(width).height(height).shrink(padding);
|
||||||
let total_spacing = spacing * items.len().saturating_sub(1) as f32;
|
let total_spacing = spacing * items.len().saturating_sub(1) as f32;
|
||||||
let max_cross = axis.cross(limits.max());
|
let max_cross = axis.cross(limits.max());
|
||||||
|
|
||||||
let mut fill_sum = 0;
|
let mut fill_main_sum = 0;
|
||||||
let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill()));
|
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let mut available = axis.main(limits.max()) - total_spacing;
|
let mut available = axis.main(limits.max()) - total_spacing;
|
||||||
|
|
||||||
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||||
nodes.resize(items.len(), Node::default());
|
nodes.resize(items.len(), Node::default());
|
||||||
|
|
||||||
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
|
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
|
||||||
let fill_factor = match axis {
|
let (fill_main_factor, fill_cross_factor) = {
|
||||||
Axis::Horizontal => child.as_widget().width(),
|
let size = child.as_widget().size();
|
||||||
Axis::Vertical => child.as_widget().height(),
|
|
||||||
}
|
|
||||||
.fill_factor();
|
|
||||||
|
|
||||||
if fill_factor == 0 {
|
axis.pack(size.width.fill_factor(), size.height.fill_factor())
|
||||||
let (max_width, max_height) = axis.pack(available, max_cross);
|
};
|
||||||
|
|
||||||
|
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 child_limits =
|
let child_limits =
|
||||||
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
||||||
|
|
@ -101,34 +141,47 @@ where
|
||||||
let size = layout.size();
|
let size = layout.size();
|
||||||
|
|
||||||
available -= axis.main(size);
|
available -= axis.main(size);
|
||||||
cross = cross.max(axis.cross(size));
|
cross = cross.max(axis.cross(layout.size()));
|
||||||
|
|
||||||
nodes[i] = layout;
|
nodes[i] = layout;
|
||||||
} else {
|
|
||||||
fill_sum += fill_factor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let remaining = available.max(0.0);
|
let remaining = match axis {
|
||||||
|
Axis::Horizontal => match width {
|
||||||
|
Length::Shrink => 0.0,
|
||||||
|
_ => available.max(0.0),
|
||||||
|
},
|
||||||
|
Axis::Vertical => match height {
|
||||||
|
Length::Shrink => 0.0,
|
||||||
|
_ => available.max(0.0),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
|
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
|
||||||
let fill_factor = match axis {
|
let (fill_main_factor, fill_cross_factor) = {
|
||||||
Axis::Horizontal => child.as_widget().width(),
|
let size = child.as_widget().size();
|
||||||
Axis::Vertical => child.as_widget().height(),
|
|
||||||
}
|
axis.pack(size.width.fill_factor(), size.height.fill_factor())
|
||||||
.fill_factor();
|
};
|
||||||
|
|
||||||
|
if fill_main_factor != 0 {
|
||||||
|
let max_main =
|
||||||
|
remaining * fill_main_factor as f32 / fill_main_sum as f32;
|
||||||
|
|
||||||
if fill_factor != 0 {
|
|
||||||
let max_main = remaining * fill_factor as f32 / fill_sum as f32;
|
|
||||||
let min_main = if max_main.is_infinite() {
|
let min_main = if max_main.is_infinite() {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
max_main
|
max_main
|
||||||
};
|
};
|
||||||
|
|
||||||
let (min_width, min_height) =
|
let max_cross = if fill_cross_factor == 0 {
|
||||||
axis.pack(min_main, axis.cross(limits.min()));
|
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, max_cross);
|
||||||
|
|
||||||
let child_limits = Limits::new(
|
let child_limits = Limits::new(
|
||||||
|
|
@ -154,18 +207,18 @@ where
|
||||||
|
|
||||||
let (x, y) = axis.pack(main, pad.1);
|
let (x, y) = axis.pack(main, pad.1);
|
||||||
|
|
||||||
node.move_to(Point::new(x, y));
|
node.move_to_mut(Point::new(x, y));
|
||||||
|
|
||||||
match axis {
|
match axis {
|
||||||
Axis::Horizontal => {
|
Axis::Horizontal => {
|
||||||
node.align(
|
node.align_mut(
|
||||||
Alignment::Start,
|
Alignment::Start,
|
||||||
align_items,
|
align_items,
|
||||||
Size::new(0.0, cross),
|
Size::new(0.0, cross),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Axis::Vertical => {
|
Axis::Vertical => {
|
||||||
node.align(
|
node.align_mut(
|
||||||
align_items,
|
align_items,
|
||||||
Alignment::Start,
|
Alignment::Start,
|
||||||
Size::new(cross, 0.0),
|
Size::new(cross, 0.0),
|
||||||
|
|
@ -178,8 +231,12 @@ where
|
||||||
main += axis.main(size);
|
main += axis.main(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (width, height) = axis.pack(main - pad.0, cross);
|
let (intrinsic_width, intrinsic_height) = axis.pack(main - pad.0, cross);
|
||||||
let size = limits.resolve(Size::new(width, height));
|
let size = limits.resolve(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
Size::new(intrinsic_width, intrinsic_height),
|
||||||
|
);
|
||||||
|
|
||||||
Node::with_children(size.pad(padding), nodes)
|
Node::with_children(size.expand(padding), nodes)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
#![allow(clippy::manual_clamp)]
|
#![allow(clippy::manual_clamp)]
|
||||||
use crate::{Length, Padding, Size};
|
use crate::{Length, Size};
|
||||||
|
|
||||||
/// A set of size constraints for layouting.
|
/// A set of size constraints for layouting.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Limits {
|
pub struct Limits {
|
||||||
min: Size,
|
min: Size,
|
||||||
max: Size,
|
max: Size,
|
||||||
fill: Size,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Limits {
|
impl Limits {
|
||||||
|
|
@ -14,16 +13,11 @@ impl Limits {
|
||||||
pub const NONE: Limits = Limits {
|
pub const NONE: Limits = Limits {
|
||||||
min: Size::ZERO,
|
min: Size::ZERO,
|
||||||
max: Size::INFINITY,
|
max: Size::INFINITY,
|
||||||
fill: Size::INFINITY,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creates new [`Limits`] with the given minimum and maximum [`Size`].
|
/// Creates new [`Limits`] with the given minimum and maximum [`Size`].
|
||||||
pub const fn new(min: Size, max: Size) -> Limits {
|
pub const fn new(min: Size, max: Size) -> Limits {
|
||||||
Limits {
|
Limits { min, max }
|
||||||
min,
|
|
||||||
max,
|
|
||||||
fill: Size::INFINITY,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the minimum [`Size`] of the [`Limits`].
|
/// Returns the minimum [`Size`] of the [`Limits`].
|
||||||
|
|
@ -36,26 +30,15 @@ impl Limits {
|
||||||
self.max
|
self.max
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the fill [`Size`] of the [`Limits`].
|
|
||||||
pub fn fill(&self) -> Size {
|
|
||||||
self.fill
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies a width constraint to the current [`Limits`].
|
/// Applies a width constraint to the current [`Limits`].
|
||||||
pub fn width(mut self, width: impl Into<Length>) -> Limits {
|
pub fn width(mut self, width: impl Into<Length>) -> Limits {
|
||||||
match width.into() {
|
match width.into() {
|
||||||
Length::Shrink => {
|
Length::Shrink | Length::Fill | Length::FillPortion(_) => {}
|
||||||
self.fill.width = self.min.width;
|
|
||||||
}
|
|
||||||
Length::Fill | Length::FillPortion(_) => {
|
|
||||||
self.fill.width = self.fill.width.min(self.max.width);
|
|
||||||
}
|
|
||||||
Length::Fixed(amount) => {
|
Length::Fixed(amount) => {
|
||||||
let new_width = amount.min(self.max.width).max(self.min.width);
|
let new_width = amount.min(self.max.width).max(self.min.width);
|
||||||
|
|
||||||
self.min.width = new_width;
|
self.min.width = new_width;
|
||||||
self.max.width = new_width;
|
self.max.width = new_width;
|
||||||
self.fill.width = new_width;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,19 +48,13 @@ impl Limits {
|
||||||
/// Applies a height constraint to the current [`Limits`].
|
/// Applies a height constraint to the current [`Limits`].
|
||||||
pub fn height(mut self, height: impl Into<Length>) -> Limits {
|
pub fn height(mut self, height: impl Into<Length>) -> Limits {
|
||||||
match height.into() {
|
match height.into() {
|
||||||
Length::Shrink => {
|
Length::Shrink | Length::Fill | Length::FillPortion(_) => {}
|
||||||
self.fill.height = self.min.height;
|
|
||||||
}
|
|
||||||
Length::Fill | Length::FillPortion(_) => {
|
|
||||||
self.fill.height = self.fill.height.min(self.max.height);
|
|
||||||
}
|
|
||||||
Length::Fixed(amount) => {
|
Length::Fixed(amount) => {
|
||||||
let new_height =
|
let new_height =
|
||||||
amount.min(self.max.height).max(self.min.height);
|
amount.min(self.max.height).max(self.min.height);
|
||||||
|
|
||||||
self.min.height = new_height;
|
self.min.height = new_height;
|
||||||
self.max.height = new_height;
|
self.max.height = new_height;
|
||||||
self.fill.height = new_height;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,13 +89,10 @@ impl Limits {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shrinks the current [`Limits`] to account for the given padding.
|
|
||||||
pub fn pad(&self, padding: Padding) -> Limits {
|
|
||||||
self.shrink(Size::new(padding.horizontal(), padding.vertical()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shrinks the current [`Limits`] by the given [`Size`].
|
/// Shrinks the current [`Limits`] by the given [`Size`].
|
||||||
pub fn shrink(&self, size: Size) -> Limits {
|
pub fn shrink(&self, size: impl Into<Size>) -> Limits {
|
||||||
|
let size = size.into();
|
||||||
|
|
||||||
let min = Size::new(
|
let min = Size::new(
|
||||||
(self.min().width - size.width).max(0.0),
|
(self.min().width - size.width).max(0.0),
|
||||||
(self.min().height - size.height).max(0.0),
|
(self.min().height - size.height).max(0.0),
|
||||||
|
|
@ -129,12 +103,7 @@ impl Limits {
|
||||||
(self.max().height - size.height).max(0.0),
|
(self.max().height - size.height).max(0.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let fill = Size::new(
|
Limits { min, max }
|
||||||
(self.fill.width - size.width).max(0.0),
|
|
||||||
(self.fill.height - size.height).max(0.0),
|
|
||||||
);
|
|
||||||
|
|
||||||
Limits { min, max, fill }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the minimum width constraint for the current [`Limits`].
|
/// Removes the minimum width constraint for the current [`Limits`].
|
||||||
|
|
@ -142,22 +111,39 @@ impl Limits {
|
||||||
Limits {
|
Limits {
|
||||||
min: Size::ZERO,
|
min: Size::ZERO,
|
||||||
max: self.max,
|
max: self.max,
|
||||||
fill: self.fill,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the resulting [`Size`] that fits the [`Limits`] given the
|
/// Computes the resulting [`Size`] that fits the [`Limits`] given
|
||||||
/// intrinsic size of some content.
|
/// some width and height requirements and the intrinsic size of
|
||||||
pub fn resolve(&self, intrinsic_size: Size) -> Size {
|
/// some content.
|
||||||
Size::new(
|
pub fn resolve(
|
||||||
intrinsic_size
|
&self,
|
||||||
.width
|
width: impl Into<Length>,
|
||||||
.min(self.max.width)
|
height: impl Into<Length>,
|
||||||
.max(self.fill.width),
|
intrinsic_size: Size,
|
||||||
intrinsic_size
|
) -> Size {
|
||||||
|
let width = match width.into() {
|
||||||
|
Length::Fill | Length::FillPortion(_) => self.max.width,
|
||||||
|
Length::Fixed(amount) => {
|
||||||
|
amount.min(self.max.width).max(self.min.width)
|
||||||
|
}
|
||||||
|
Length::Shrink => {
|
||||||
|
intrinsic_size.width.min(self.max.width).max(self.min.width)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let height = match height.into() {
|
||||||
|
Length::Fill | Length::FillPortion(_) => self.max.height,
|
||||||
|
Length::Fixed(amount) => {
|
||||||
|
amount.min(self.max.height).max(self.min.height)
|
||||||
|
}
|
||||||
|
Length::Shrink => intrinsic_size
|
||||||
.height
|
.height
|
||||||
.min(self.max.height)
|
.min(self.max.height)
|
||||||
.max(self.fill.height),
|
.max(self.min.height),
|
||||||
)
|
};
|
||||||
|
|
||||||
|
Size::new(width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Alignment, Point, Rectangle, Size, Vector};
|
use crate::{Alignment, Padding, Point, Rectangle, Size, Vector};
|
||||||
|
|
||||||
/// The bounds of an element and its children.
|
/// The bounds of an element and its children.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
@ -26,6 +26,14 @@ impl Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Node`] that wraps a single child with some [`Padding`].
|
||||||
|
pub fn container(child: Self, padding: Padding) -> Self {
|
||||||
|
Self::with_children(
|
||||||
|
child.bounds.size().expand(padding),
|
||||||
|
vec![child.move_to(Point::new(padding.left, padding.top))],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`Size`] of the [`Node`].
|
/// Returns the [`Size`] of the [`Node`].
|
||||||
pub fn size(&self) -> Size {
|
pub fn size(&self) -> Size {
|
||||||
Size::new(self.bounds.width, self.bounds.height)
|
Size::new(self.bounds.width, self.bounds.height)
|
||||||
|
|
@ -43,6 +51,17 @@ impl Node {
|
||||||
|
|
||||||
/// Aligns the [`Node`] in the given space.
|
/// Aligns the [`Node`] in the given space.
|
||||||
pub fn align(
|
pub fn align(
|
||||||
|
mut self,
|
||||||
|
horizontal_alignment: Alignment,
|
||||||
|
vertical_alignment: Alignment,
|
||||||
|
space: Size,
|
||||||
|
) -> Self {
|
||||||
|
self.align_mut(horizontal_alignment, vertical_alignment, space);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable reference version of [`Self::align`].
|
||||||
|
pub fn align_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
horizontal_alignment: Alignment,
|
horizontal_alignment: Alignment,
|
||||||
vertical_alignment: Alignment,
|
vertical_alignment: Alignment,
|
||||||
|
|
@ -70,13 +89,23 @@ impl Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the [`Node`] to the given position.
|
/// Moves the [`Node`] to the given position.
|
||||||
pub fn move_to(&mut self, position: Point) {
|
pub fn move_to(mut self, position: impl Into<Point>) -> Self {
|
||||||
|
self.move_to_mut(position);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable reference version of [`Self::move_to`].
|
||||||
|
pub fn move_to_mut(&mut self, position: impl Into<Point>) {
|
||||||
|
let position = position.into();
|
||||||
|
|
||||||
self.bounds.x = position.x;
|
self.bounds.x = position.x;
|
||||||
self.bounds.y = position.y;
|
self.bounds.y = position.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translates the [`Node`] by the given translation.
|
/// Translates the [`Node`] by the given translation.
|
||||||
pub fn translate(self, translation: Vector) -> Self {
|
pub fn translate(self, translation: impl Into<Vector>) -> Self {
|
||||||
|
let translation = translation.into();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
bounds: self.bounds + translation,
|
bounds: self.bounds + translation,
|
||||||
..self
|
..self
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,24 @@ impl Length {
|
||||||
Length::Fixed(_) => 0,
|
Length::Fixed(_) => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` iff the [`Length`] is either [`Length::Fill`] or
|
||||||
|
// [`Length::FillPortion`].
|
||||||
|
pub fn is_fill(&self) -> bool {
|
||||||
|
self.fill_factor() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the "fluid" variant of the [`Length`].
|
||||||
|
///
|
||||||
|
/// Specifically:
|
||||||
|
/// - [`Length::Shrink`] if [`Length::Shrink`] or [`Length::Fixed`].
|
||||||
|
/// - [`Length::Fill`] otherwise.
|
||||||
|
pub fn fluid(&self) -> Length {
|
||||||
|
match self {
|
||||||
|
Length::Fill | Length::FillPortion(_) => Length::Fill,
|
||||||
|
Length::Shrink | Length::Fixed(_) => Length::Shrink,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Pixels> for Length {
|
impl From<Pixels> for Length {
|
||||||
|
|
|
||||||
|
|
@ -75,3 +75,5 @@ pub use size::Size;
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
pub use vector::Vector;
|
pub use vector::Vector;
|
||||||
pub use widget::Widget;
|
pub use widget::Widget;
|
||||||
|
|
||||||
|
pub use smol_str::SmolStr;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ pub enum Button {
|
||||||
/// The middle (wheel) button.
|
/// The middle (wheel) button.
|
||||||
Middle,
|
Middle,
|
||||||
|
|
||||||
|
/// The back mouse button.
|
||||||
|
Back,
|
||||||
|
|
||||||
|
/// The forward mouse button.
|
||||||
|
Forward,
|
||||||
|
|
||||||
/// Some other button.
|
/// Some other button.
|
||||||
Other(u16),
|
Other(u16),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,11 @@ impl Click {
|
||||||
self.kind
|
self.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the position of the [`Click`].
|
||||||
|
pub fn position(&self) -> Point {
|
||||||
|
self.position
|
||||||
|
}
|
||||||
|
|
||||||
fn is_consecutive(&self, new_position: Point, time: Instant) -> bool {
|
fn is_consecutive(&self, new_position: Point, time: Instant) -> bool {
|
||||||
let duration = if time > self.time {
|
let duration = if time > self.time {
|
||||||
Some(time - self.time)
|
Some(time - self.time)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::mouse;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::widget::Tree;
|
use crate::widget::Tree;
|
||||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
|
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector};
|
||||||
|
|
||||||
/// An interactive component that can be displayed on top of other widgets.
|
/// An interactive component that can be displayed on top of other widgets.
|
||||||
pub trait Overlay<Message, Renderer>
|
pub trait Overlay<Message, Renderer>
|
||||||
|
|
@ -29,6 +29,7 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
position: Point,
|
position: Point,
|
||||||
|
translation: Vector,
|
||||||
) -> layout::Node;
|
) -> layout::Node;
|
||||||
|
|
||||||
/// Draws the [`Overlay`] using the associated `Renderer`.
|
/// Draws the [`Overlay`] using the associated `Renderer`.
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use std::any::Any;
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Element<'a, Message, Renderer> {
|
pub struct Element<'a, Message, Renderer> {
|
||||||
position: Point,
|
position: Point,
|
||||||
|
translation: Vector,
|
||||||
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,7 +26,11 @@ where
|
||||||
position: Point,
|
position: Point,
|
||||||
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { position, overlay }
|
Self {
|
||||||
|
position,
|
||||||
|
overlay,
|
||||||
|
translation: Vector::ZERO,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the position of the [`Element`].
|
/// Returns the position of the [`Element`].
|
||||||
|
|
@ -36,6 +41,7 @@ where
|
||||||
/// Translates the [`Element`].
|
/// Translates the [`Element`].
|
||||||
pub fn translate(mut self, translation: Vector) -> Self {
|
pub fn translate(mut self, translation: Vector) -> Self {
|
||||||
self.position = self.position + translation;
|
self.position = self.position + translation;
|
||||||
|
self.translation = self.translation + translation;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,6 +54,7 @@ where
|
||||||
{
|
{
|
||||||
Element {
|
Element {
|
||||||
position: self.position,
|
position: self.position,
|
||||||
|
translation: self.translation,
|
||||||
overlay: Box::new(Map::new(self.overlay, f)),
|
overlay: Box::new(Map::new(self.overlay, f)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +66,12 @@ where
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.overlay
|
self.overlay.layout(
|
||||||
.layout(renderer, bounds, self.position + translation)
|
renderer,
|
||||||
|
bounds,
|
||||||
|
self.position + translation,
|
||||||
|
self.translation + translation,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes a runtime [`Event`].
|
/// Processes a runtime [`Event`].
|
||||||
|
|
@ -154,8 +165,9 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
position: Point,
|
position: Point,
|
||||||
|
translation: Vector,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.content.layout(renderer, bounds, position)
|
self.content.layout(renderer, bounds, position, translation)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
|
use crate::{
|
||||||
|
Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size, Vector,
|
||||||
|
};
|
||||||
|
|
||||||
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
|
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
|
||||||
/// children.
|
/// children.
|
||||||
|
|
@ -64,10 +66,9 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
position: Point,
|
_position: Point,
|
||||||
|
translation: Vector,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let translation = position - Point::ORIGIN;
|
|
||||||
|
|
||||||
layout::Node::with_children(
|
layout::Node::with_children(
|
||||||
bounds,
|
bounds,
|
||||||
self.children
|
self.children
|
||||||
|
|
|
||||||
|
|
@ -154,3 +154,9 @@ impl From<[f32; 4]> for Padding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Padding> for Size {
|
||||||
|
fn from(padding: Padding) -> Self {
|
||||||
|
Self::new(padding.horizontal(), padding.vertical())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,34 @@
|
||||||
use crate::Vector;
|
use crate::Vector;
|
||||||
|
|
||||||
|
use num_traits::{Float, Num};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// A 2D point.
|
/// A 2D point.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
pub struct Point {
|
pub struct Point<T = f32> {
|
||||||
/// The X coordinate.
|
/// The X coordinate.
|
||||||
pub x: f32,
|
pub x: T,
|
||||||
|
|
||||||
/// The Y coordinate.
|
/// The Y coordinate.
|
||||||
pub y: f32,
|
pub y: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Point {
|
impl Point {
|
||||||
/// The origin (i.e. a [`Point`] at (0, 0)).
|
/// The origin (i.e. a [`Point`] at (0, 0)).
|
||||||
pub const ORIGIN: Point = Point::new(0.0, 0.0);
|
pub const ORIGIN: Self = Self::new(0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Num> Point<T> {
|
||||||
/// Creates a new [`Point`] with the given coordinates.
|
/// Creates a new [`Point`] with the given coordinates.
|
||||||
pub const fn new(x: f32, y: f32) -> Self {
|
pub const fn new(x: T, y: T) -> Self {
|
||||||
Self { x, y }
|
Self { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the distance to another [`Point`].
|
/// Computes the distance to another [`Point`].
|
||||||
pub fn distance(&self, to: Point) -> f32 {
|
pub fn distance(&self, to: Self) -> T
|
||||||
|
where
|
||||||
|
T: Float,
|
||||||
|
{
|
||||||
let a = self.x - to.x;
|
let a = self.x - to.x;
|
||||||
let b = self.y - to.y;
|
let b = self.y - to.y;
|
||||||
|
|
||||||
|
|
@ -28,28 +36,37 @@ impl Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[f32; 2]> for Point {
|
impl<T> From<[T; 2]> for Point<T>
|
||||||
fn from([x, y]: [f32; 2]) -> Self {
|
where
|
||||||
|
T: Num,
|
||||||
|
{
|
||||||
|
fn from([x, y]: [T; 2]) -> Self {
|
||||||
Point { x, y }
|
Point { x, y }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[u16; 2]> for Point {
|
impl<T> From<(T, T)> for Point<T>
|
||||||
fn from([x, y]: [u16; 2]) -> Self {
|
where
|
||||||
Point::new(x.into(), y.into())
|
T: Num,
|
||||||
|
{
|
||||||
|
fn from((x, y): (T, T)) -> Self {
|
||||||
|
Self { x, y }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Point> for [f32; 2] {
|
impl<T> From<Point<T>> for [T; 2] {
|
||||||
fn from(point: Point) -> [f32; 2] {
|
fn from(point: Point<T>) -> [T; 2] {
|
||||||
[point.x, point.y]
|
[point.x, point.y]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Add<Vector> for Point {
|
impl<T> std::ops::Add<Vector<T>> for Point<T>
|
||||||
|
where
|
||||||
|
T: std::ops::Add<Output = T>,
|
||||||
|
{
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn add(self, vector: Vector) -> Self {
|
fn add(self, vector: Vector<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: self.x + vector.x,
|
x: self.x + vector.x,
|
||||||
y: self.y + vector.y,
|
y: self.y + vector.y,
|
||||||
|
|
@ -57,10 +74,13 @@ impl std::ops::Add<Vector> for Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Sub<Vector> for Point {
|
impl<T> std::ops::Sub<Vector<T>> for Point<T>
|
||||||
|
where
|
||||||
|
T: std::ops::Sub<Output = T>,
|
||||||
|
{
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn sub(self, vector: Vector) -> Self {
|
fn sub(self, vector: Vector<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: self.x - vector.x,
|
x: self.x - vector.x,
|
||||||
y: self.y - vector.y,
|
y: self.y - vector.y,
|
||||||
|
|
@ -68,10 +88,22 @@ impl std::ops::Sub<Vector> for Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Sub<Point> for Point {
|
impl<T> std::ops::Sub<Point<T>> for Point<T>
|
||||||
type Output = Vector;
|
where
|
||||||
|
T: std::ops::Sub<Output = T>,
|
||||||
|
{
|
||||||
|
type Output = Vector<T>;
|
||||||
|
|
||||||
fn sub(self, point: Point) -> Vector {
|
fn sub(self, point: Self) -> Vector<T> {
|
||||||
Vector::new(self.x - point.x, self.y - point.y)
|
Vector::new(self.x - point.x, self.y - point.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Display for Point<T>
|
||||||
|
where
|
||||||
|
T: fmt::Display,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Point {{ x: {}, y: {} }}", self.x, self.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ impl Renderer for Null {
|
||||||
impl text::Renderer for Null {
|
impl text::Renderer for Null {
|
||||||
type Font = Font;
|
type Font = Font;
|
||||||
type Paragraph = ();
|
type Paragraph = ();
|
||||||
|
type Editor = ();
|
||||||
|
|
||||||
const ICON_FONT: Font = Font::DEFAULT;
|
const ICON_FONT: Font = Font::DEFAULT;
|
||||||
const CHECKMARK_ICON: char = '0';
|
const CHECKMARK_ICON: char = '0';
|
||||||
|
|
@ -58,21 +59,21 @@ impl text::Renderer for Null {
|
||||||
|
|
||||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||||
|
|
||||||
fn create_paragraph(&self, _text: Text<'_, Self::Font>) -> Self::Paragraph {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resize_paragraph(
|
|
||||||
&self,
|
|
||||||
_paragraph: &mut Self::Paragraph,
|
|
||||||
_new_bounds: Size,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_paragraph(
|
fn fill_paragraph(
|
||||||
&mut self,
|
&mut self,
|
||||||
_paragraph: &Self::Paragraph,
|
_paragraph: &Self::Paragraph,
|
||||||
_position: Point,
|
_position: Point,
|
||||||
_color: Color,
|
_color: Color,
|
||||||
|
_clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_editor(
|
||||||
|
&mut self,
|
||||||
|
_editor: &Self::Editor,
|
||||||
|
_position: Point,
|
||||||
|
_color: Color,
|
||||||
|
_clip_bounds: Rectangle,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,6 +82,7 @@ impl text::Renderer for Null {
|
||||||
_paragraph: Text<'_, Self::Font>,
|
_paragraph: Text<'_, Self::Font>,
|
||||||
_position: Point,
|
_position: Point,
|
||||||
_color: Color,
|
_color: Color,
|
||||||
|
_clip_bounds: Rectangle,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,24 +90,12 @@ impl text::Renderer for Null {
|
||||||
impl text::Paragraph for () {
|
impl text::Paragraph for () {
|
||||||
type Font = Font;
|
type Font = Font;
|
||||||
|
|
||||||
fn content(&self) -> &str {
|
fn with_text(_text: Text<'_, Self::Font>) -> Self {}
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
fn text_size(&self) -> Pixels {
|
fn resize(&mut self, _new_bounds: Size) {}
|
||||||
Pixels(16.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn font(&self) -> Self::Font {
|
fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference {
|
||||||
Font::default()
|
text::Difference::None
|
||||||
}
|
|
||||||
|
|
||||||
fn line_height(&self) -> text::LineHeight {
|
|
||||||
text::LineHeight::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shaping(&self) -> text::Shaping {
|
|
||||||
text::Shaping::default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn horizontal_alignment(&self) -> alignment::Horizontal {
|
fn horizontal_alignment(&self) -> alignment::Horizontal {
|
||||||
|
|
@ -120,10 +110,6 @@ impl text::Paragraph for () {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds(&self) -> Size {
|
|
||||||
Size::ZERO
|
|
||||||
}
|
|
||||||
|
|
||||||
fn min_bounds(&self) -> Size {
|
fn min_bounds(&self) -> Size {
|
||||||
Size::ZERO
|
Size::ZERO
|
||||||
}
|
}
|
||||||
|
|
@ -132,3 +118,55 @@ impl text::Paragraph for () {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl text::Editor for () {
|
||||||
|
type Font = Font;
|
||||||
|
|
||||||
|
fn with_text(_text: &str) -> Self {}
|
||||||
|
|
||||||
|
fn cursor(&self) -> text::editor::Cursor {
|
||||||
|
text::editor::Cursor::Caret(Point::ORIGIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_position(&self) -> (usize, usize) {
|
||||||
|
(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line(&self, _index: usize) -> Option<&str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_count(&self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform(&mut self, _action: text::editor::Action) {}
|
||||||
|
|
||||||
|
fn bounds(&self) -> Size {
|
||||||
|
Size::ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
_new_bounds: Size,
|
||||||
|
_new_font: Self::Font,
|
||||||
|
_new_size: Pixels,
|
||||||
|
_new_line_height: text::LineHeight,
|
||||||
|
_new_highlighter: &mut impl text::Highlighter,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight<H: text::Highlighter>(
|
||||||
|
&mut self,
|
||||||
|
_font: Self::Font,
|
||||||
|
_highlighter: &mut H,
|
||||||
|
_format_highlight: impl Fn(
|
||||||
|
&H::Highlight,
|
||||||
|
) -> text::highlighter::Format<Self::Font>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Padding, Vector};
|
use crate::Vector;
|
||||||
|
|
||||||
/// An amount of space in 2 dimensions.
|
/// An amount of space in 2 dimensions.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -26,15 +26,7 @@ impl Size {
|
||||||
/// A [`Size`] with infinite width and height.
|
/// A [`Size`] with infinite width and height.
|
||||||
pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY);
|
pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY);
|
||||||
|
|
||||||
/// Increments the [`Size`] to account for the given padding.
|
/// Returns the minimum of each component of this size and another.
|
||||||
pub fn pad(&self, padding: Padding) -> Self {
|
|
||||||
Size {
|
|
||||||
width: self.width + padding.horizontal(),
|
|
||||||
height: self.height + padding.vertical(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the minimum of each component of this size and another
|
|
||||||
pub fn min(self, other: Self) -> Self {
|
pub fn min(self, other: Self) -> Self {
|
||||||
Size {
|
Size {
|
||||||
width: self.width.min(other.width),
|
width: self.width.min(other.width),
|
||||||
|
|
@ -42,13 +34,23 @@ impl Size {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the maximum of each component of this size and another
|
/// Returns the maximum of each component of this size and another.
|
||||||
pub fn max(self, other: Self) -> Self {
|
pub fn max(self, other: Self) -> Self {
|
||||||
Size {
|
Size {
|
||||||
width: self.width.max(other.width),
|
width: self.width.max(other.width),
|
||||||
height: self.height.max(other.height),
|
height: self.height.max(other.height),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expands this [`Size`] by the given amount.
|
||||||
|
pub fn expand(self, other: impl Into<Size>) -> Self {
|
||||||
|
let other = other.into();
|
||||||
|
|
||||||
|
Size {
|
||||||
|
width: self.width + other.width,
|
||||||
|
height: self.height + other.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[f32; 2]> for Size {
|
impl From<[f32; 2]> for Size {
|
||||||
|
|
|
||||||
178
core/src/text.rs
|
|
@ -1,6 +1,15 @@
|
||||||
//! Draw and interact with text.
|
//! Draw and interact with text.
|
||||||
|
mod paragraph;
|
||||||
|
|
||||||
|
pub mod editor;
|
||||||
|
pub mod highlighter;
|
||||||
|
|
||||||
|
pub use editor::Editor;
|
||||||
|
pub use highlighter::Highlighter;
|
||||||
|
pub use paragraph::Paragraph;
|
||||||
|
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::{Color, Pixels, Point, Size};
|
use crate::{Color, Pixels, Point, Rectangle, Size};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
@ -126,6 +135,33 @@ impl Hit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The difference detected in some text.
|
||||||
|
///
|
||||||
|
/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some
|
||||||
|
/// [`Text`].
|
||||||
|
///
|
||||||
|
/// [`compare`]: Paragraph::compare
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Difference {
|
||||||
|
/// No difference.
|
||||||
|
///
|
||||||
|
/// The text can be reused as it is!
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// A bounds difference.
|
||||||
|
///
|
||||||
|
/// This normally means a relayout is necessary, but the shape of the text can
|
||||||
|
/// be reused.
|
||||||
|
Bounds,
|
||||||
|
|
||||||
|
/// A shape difference.
|
||||||
|
///
|
||||||
|
/// The contents, alignment, sizes, fonts, or any other essential attributes
|
||||||
|
/// of the shape of the text have changed. A complete reshape and relayout of
|
||||||
|
/// the text is necessary.
|
||||||
|
Shape,
|
||||||
|
}
|
||||||
|
|
||||||
/// A renderer capable of measuring and drawing [`Text`].
|
/// A renderer capable of measuring and drawing [`Text`].
|
||||||
pub trait Renderer: crate::Renderer {
|
pub trait Renderer: crate::Renderer {
|
||||||
/// The font type used.
|
/// The font type used.
|
||||||
|
|
@ -134,6 +170,9 @@ pub trait Renderer: crate::Renderer {
|
||||||
/// The [`Paragraph`] of this [`Renderer`].
|
/// The [`Paragraph`] of this [`Renderer`].
|
||||||
type Paragraph: Paragraph<Font = Self::Font> + 'static;
|
type Paragraph: Paragraph<Font = Self::Font> + 'static;
|
||||||
|
|
||||||
|
/// The [`Editor`] of this [`Renderer`].
|
||||||
|
type Editor: Editor<Font = Self::Font> + 'static;
|
||||||
|
|
||||||
/// The icon font of the backend.
|
/// The icon font of the backend.
|
||||||
const ICON_FONT: Self::Font;
|
const ICON_FONT: Self::Font;
|
||||||
|
|
||||||
|
|
@ -156,33 +195,6 @@ pub trait Renderer: crate::Renderer {
|
||||||
/// Loads a [`Self::Font`] from its bytes.
|
/// Loads a [`Self::Font`] from its bytes.
|
||||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
||||||
|
|
||||||
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
|
|
||||||
fn create_paragraph(&self, text: Text<'_, Self::Font>) -> Self::Paragraph;
|
|
||||||
|
|
||||||
/// Lays out the given [`Paragraph`] with some new boundaries.
|
|
||||||
fn resize_paragraph(
|
|
||||||
&self,
|
|
||||||
paragraph: &mut Self::Paragraph,
|
|
||||||
new_bounds: Size,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Updates a [`Paragraph`] to match the given [`Text`], if needed.
|
|
||||||
fn update_paragraph(
|
|
||||||
&self,
|
|
||||||
paragraph: &mut Self::Paragraph,
|
|
||||||
text: Text<'_, Self::Font>,
|
|
||||||
) {
|
|
||||||
match compare(paragraph, text) {
|
|
||||||
Difference::None => {}
|
|
||||||
Difference::Bounds => {
|
|
||||||
self.resize_paragraph(paragraph, text.bounds);
|
|
||||||
}
|
|
||||||
Difference::Shape => {
|
|
||||||
*paragraph = self.create_paragraph(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws the given [`Paragraph`] at the given position and with the given
|
/// Draws the given [`Paragraph`] at the given position and with the given
|
||||||
/// [`Color`].
|
/// [`Color`].
|
||||||
fn fill_paragraph(
|
fn fill_paragraph(
|
||||||
|
|
@ -190,6 +202,17 @@ pub trait Renderer: crate::Renderer {
|
||||||
text: &Self::Paragraph,
|
text: &Self::Paragraph,
|
||||||
position: Point,
|
position: Point,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Draws the given [`Editor`] at the given position and with the given
|
||||||
|
/// [`Color`].
|
||||||
|
fn fill_editor(
|
||||||
|
&mut self,
|
||||||
|
editor: &Self::Editor,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Draws the given [`Text`] at the given position and with the given
|
/// Draws the given [`Text`] at the given position and with the given
|
||||||
|
|
@ -199,103 +222,6 @@ pub trait Renderer: crate::Renderer {
|
||||||
text: Text<'_, Self::Font>,
|
text: Text<'_, Self::Font>,
|
||||||
position: Point,
|
position: Point,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/// A text paragraph.
|
|
||||||
pub trait Paragraph: Default {
|
|
||||||
/// The font of this [`Paragraph`].
|
|
||||||
type Font;
|
|
||||||
|
|
||||||
/// Returns the content of the [`Paragraph`].
|
|
||||||
fn content(&self) -> &str;
|
|
||||||
|
|
||||||
/// Returns the text size of the [`Paragraph`].
|
|
||||||
fn text_size(&self) -> Pixels;
|
|
||||||
|
|
||||||
/// Returns the [`LineHeight`] of the [`Paragraph`].
|
|
||||||
fn line_height(&self) -> LineHeight;
|
|
||||||
|
|
||||||
/// Returns the [`Self::Font`] of the [`Paragraph`].
|
|
||||||
fn font(&self) -> Self::Font;
|
|
||||||
|
|
||||||
/// Returns the [`Shaping`] strategy of the [`Paragraph`].
|
|
||||||
fn shaping(&self) -> Shaping;
|
|
||||||
|
|
||||||
/// Returns the horizontal alignment of the [`Paragraph`].
|
|
||||||
fn horizontal_alignment(&self) -> alignment::Horizontal;
|
|
||||||
|
|
||||||
/// Returns the vertical alignment of the [`Paragraph`].
|
|
||||||
fn vertical_alignment(&self) -> alignment::Vertical;
|
|
||||||
|
|
||||||
/// Returns the boundaries of the [`Paragraph`].
|
|
||||||
fn bounds(&self) -> Size;
|
|
||||||
|
|
||||||
/// Returns the minimum boundaries that can fit the contents of the
|
|
||||||
/// [`Paragraph`].
|
|
||||||
fn min_bounds(&self) -> Size;
|
|
||||||
|
|
||||||
/// Tests whether the provided point is within the boundaries of the
|
|
||||||
/// [`Paragraph`], returning information about the nearest character.
|
|
||||||
fn hit_test(&self, point: Point) -> Option<Hit>;
|
|
||||||
|
|
||||||
/// Returns the distance to the given grapheme index in the [`Paragraph`].
|
|
||||||
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
|
|
||||||
|
|
||||||
/// Returns the minimum width that can fit the contents of the [`Paragraph`].
|
|
||||||
fn min_width(&self) -> f32 {
|
|
||||||
self.min_bounds().width
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the minimum height that can fit the contents of the [`Paragraph`].
|
|
||||||
fn min_height(&self) -> f32 {
|
|
||||||
self.min_bounds().height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The difference detected in some text.
|
|
||||||
///
|
|
||||||
/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some
|
|
||||||
/// [`Text`].
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum Difference {
|
|
||||||
/// No difference.
|
|
||||||
///
|
|
||||||
/// The text can be reused as it is!
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// A bounds difference.
|
|
||||||
///
|
|
||||||
/// This normally means a relayout is necessary, but the shape of the text can
|
|
||||||
/// be reused.
|
|
||||||
Bounds,
|
|
||||||
|
|
||||||
/// A shape difference.
|
|
||||||
///
|
|
||||||
/// The contents, alignment, sizes, fonts, or any other essential attributes
|
|
||||||
/// of the shape of the text have changed. A complete reshape and relayout of
|
|
||||||
/// the text is necessary.
|
|
||||||
Shape,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compares a [`Paragraph`] with some desired [`Text`] and returns the
|
|
||||||
/// [`Difference`].
|
|
||||||
pub fn compare<Font: PartialEq>(
|
|
||||||
paragraph: &impl Paragraph<Font = Font>,
|
|
||||||
text: Text<'_, Font>,
|
|
||||||
) -> Difference {
|
|
||||||
if paragraph.content() != text.content
|
|
||||||
|| paragraph.text_size() != text.size
|
|
||||||
|| paragraph.line_height().to_absolute(text.size)
|
|
||||||
!= text.line_height.to_absolute(text.size)
|
|
||||||
|| paragraph.font() != text.font
|
|
||||||
|| paragraph.shaping() != text.shaping
|
|
||||||
|| paragraph.horizontal_alignment() != text.horizontal_alignment
|
|
||||||
|| paragraph.vertical_alignment() != text.vertical_alignment
|
|
||||||
{
|
|
||||||
Difference::Shape
|
|
||||||
} else if paragraph.bounds() != text.bounds {
|
|
||||||
Difference::Bounds
|
|
||||||
} else {
|
|
||||||
Difference::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
181
core/src/text/editor.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
//! Edit text.
|
||||||
|
use crate::text::highlighter::{self, Highlighter};
|
||||||
|
use crate::text::LineHeight;
|
||||||
|
use crate::{Pixels, Point, Rectangle, Size};
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A component that can be used by widgets to edit multi-line text.
|
||||||
|
pub trait Editor: Sized + Default {
|
||||||
|
/// The font of the [`Editor`].
|
||||||
|
type Font: Copy + PartialEq + Default;
|
||||||
|
|
||||||
|
/// Creates a new [`Editor`] laid out with the given text.
|
||||||
|
fn with_text(text: &str) -> Self;
|
||||||
|
|
||||||
|
/// Returns the current [`Cursor`] of the [`Editor`].
|
||||||
|
fn cursor(&self) -> Cursor;
|
||||||
|
|
||||||
|
/// Returns the current cursor position of the [`Editor`].
|
||||||
|
///
|
||||||
|
/// Line and column, respectively.
|
||||||
|
fn cursor_position(&self) -> (usize, usize);
|
||||||
|
|
||||||
|
/// Returns the current selected text of the [`Editor`].
|
||||||
|
fn selection(&self) -> Option<String>;
|
||||||
|
|
||||||
|
/// Returns the text of the given line in the [`Editor`], if it exists.
|
||||||
|
fn line(&self, index: usize) -> Option<&str>;
|
||||||
|
|
||||||
|
/// Returns the amount of lines in the [`Editor`].
|
||||||
|
fn line_count(&self) -> usize;
|
||||||
|
|
||||||
|
/// Performs an [`Action`] on the [`Editor`].
|
||||||
|
fn perform(&mut self, action: Action);
|
||||||
|
|
||||||
|
/// Returns the current boundaries of the [`Editor`].
|
||||||
|
fn bounds(&self) -> Size;
|
||||||
|
|
||||||
|
/// Updates the [`Editor`] with some new attributes.
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
new_bounds: Size,
|
||||||
|
new_font: Self::Font,
|
||||||
|
new_size: Pixels,
|
||||||
|
new_line_height: LineHeight,
|
||||||
|
new_highlighter: &mut impl Highlighter,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Runs a text [`Highlighter`] in the [`Editor`].
|
||||||
|
fn highlight<H: Highlighter>(
|
||||||
|
&mut self,
|
||||||
|
font: Self::Font,
|
||||||
|
highlighter: &mut H,
|
||||||
|
format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An interaction with an [`Editor`].
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Action {
|
||||||
|
/// Apply a [`Motion`].
|
||||||
|
Move(Motion),
|
||||||
|
/// Select text with a given [`Motion`].
|
||||||
|
Select(Motion),
|
||||||
|
/// Select the word at the current cursor.
|
||||||
|
SelectWord,
|
||||||
|
/// Select the line at the current cursor.
|
||||||
|
SelectLine,
|
||||||
|
/// Perform an [`Edit`].
|
||||||
|
Edit(Edit),
|
||||||
|
/// Click the [`Editor`] at the given [`Point`].
|
||||||
|
Click(Point),
|
||||||
|
/// Drag the mouse on the [`Editor`] to the given [`Point`].
|
||||||
|
Drag(Point),
|
||||||
|
/// Scroll the [`Editor`] a certain amount of lines.
|
||||||
|
Scroll {
|
||||||
|
/// The amount of lines to scroll.
|
||||||
|
lines: i32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Action {
|
||||||
|
/// Returns whether the [`Action`] is an editing action.
|
||||||
|
pub fn is_edit(&self) -> bool {
|
||||||
|
matches!(self, Self::Edit(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An action that edits text.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Edit {
|
||||||
|
/// Insert the given character.
|
||||||
|
Insert(char),
|
||||||
|
/// Paste the given text.
|
||||||
|
Paste(Arc<String>),
|
||||||
|
/// Break the current line.
|
||||||
|
Enter,
|
||||||
|
/// Delete the previous character.
|
||||||
|
Backspace,
|
||||||
|
/// Delete the next character.
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cursor movement.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum Motion {
|
||||||
|
/// Move left.
|
||||||
|
Left,
|
||||||
|
/// Move right.
|
||||||
|
Right,
|
||||||
|
/// Move up.
|
||||||
|
Up,
|
||||||
|
/// Move down.
|
||||||
|
Down,
|
||||||
|
/// Move to the left boundary of a word.
|
||||||
|
WordLeft,
|
||||||
|
/// Move to the right boundary of a word.
|
||||||
|
WordRight,
|
||||||
|
/// Move to the start of the line.
|
||||||
|
Home,
|
||||||
|
/// Move to the end of the line.
|
||||||
|
End,
|
||||||
|
/// Move to the start of the previous window.
|
||||||
|
PageUp,
|
||||||
|
/// Move to the start of the next window.
|
||||||
|
PageDown,
|
||||||
|
/// Move to the start of the text.
|
||||||
|
DocumentStart,
|
||||||
|
/// Move to the end of the text.
|
||||||
|
DocumentEnd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Motion {
|
||||||
|
/// Widens the [`Motion`], if possible.
|
||||||
|
pub fn widen(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Left => Self::WordLeft,
|
||||||
|
Self::Right => Self::WordRight,
|
||||||
|
Self::Home => Self::DocumentStart,
|
||||||
|
Self::End => Self::DocumentEnd,
|
||||||
|
_ => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [`Direction`] of the [`Motion`].
|
||||||
|
pub fn direction(&self) -> Direction {
|
||||||
|
match self {
|
||||||
|
Self::Left
|
||||||
|
| Self::Up
|
||||||
|
| Self::WordLeft
|
||||||
|
| Self::Home
|
||||||
|
| Self::PageUp
|
||||||
|
| Self::DocumentStart => Direction::Left,
|
||||||
|
Self::Right
|
||||||
|
| Self::Down
|
||||||
|
| Self::WordRight
|
||||||
|
| Self::End
|
||||||
|
| Self::PageDown
|
||||||
|
| Self::DocumentEnd => Direction::Right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A direction in some text.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Direction {
|
||||||
|
/// <-
|
||||||
|
Left,
|
||||||
|
/// ->
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The cursor of an [`Editor`].
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Cursor {
|
||||||
|
/// Cursor without a selection
|
||||||
|
Caret(Point),
|
||||||
|
|
||||||
|
/// Cursor selecting a range of text
|
||||||
|
Selection(Vec<Rectangle>),
|
||||||
|
}
|
||||||
88
core/src/text/highlighter.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
//! Highlight text.
|
||||||
|
use crate::Color;
|
||||||
|
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
/// A type capable of highlighting text.
|
||||||
|
///
|
||||||
|
/// A [`Highlighter`] highlights lines in sequence. When a line changes,
|
||||||
|
/// it must be notified and the lines after the changed one must be fed
|
||||||
|
/// again to the [`Highlighter`].
|
||||||
|
pub trait Highlighter: 'static {
|
||||||
|
/// The settings to configure the [`Highlighter`].
|
||||||
|
type Settings: PartialEq + Clone;
|
||||||
|
|
||||||
|
/// The output of the [`Highlighter`].
|
||||||
|
type Highlight;
|
||||||
|
|
||||||
|
/// The highlight iterator type.
|
||||||
|
type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
/// Creates a new [`Highlighter`] from its [`Self::Settings`].
|
||||||
|
fn new(settings: &Self::Settings) -> Self;
|
||||||
|
|
||||||
|
/// Updates the [`Highlighter`] with some new [`Self::Settings`].
|
||||||
|
fn update(&mut self, new_settings: &Self::Settings);
|
||||||
|
|
||||||
|
/// Notifies the [`Highlighter`] that the line at the given index has changed.
|
||||||
|
fn change_line(&mut self, line: usize);
|
||||||
|
|
||||||
|
/// Highlights the given line.
|
||||||
|
///
|
||||||
|
/// If a line changed prior to this, the first line provided here will be the
|
||||||
|
/// line that changed.
|
||||||
|
fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>;
|
||||||
|
|
||||||
|
/// Returns the current line of the [`Highlighter`].
|
||||||
|
///
|
||||||
|
/// If `change_line` has been called, this will normally be the least index
|
||||||
|
/// that changed.
|
||||||
|
fn current_line(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A highlighter that highlights nothing.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct PlainText;
|
||||||
|
|
||||||
|
impl Highlighter for PlainText {
|
||||||
|
type Settings = ();
|
||||||
|
type Highlight = ();
|
||||||
|
|
||||||
|
type Iterator<'a> = std::iter::Empty<(Range<usize>, Self::Highlight)>;
|
||||||
|
|
||||||
|
fn new(_settings: &Self::Settings) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _new_settings: &Self::Settings) {}
|
||||||
|
|
||||||
|
fn change_line(&mut self, _line: usize) {}
|
||||||
|
|
||||||
|
fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> {
|
||||||
|
std::iter::empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_line(&self) -> usize {
|
||||||
|
usize::MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The format of some text.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Format<Font> {
|
||||||
|
/// The [`Color`] of the text.
|
||||||
|
pub color: Option<Color>,
|
||||||
|
/// The `Font` of the text.
|
||||||
|
pub font: Option<Font>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Font> Default for Format<Font> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
color: None,
|
||||||
|
font: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
core/src/text/paragraph.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use crate::alignment;
|
||||||
|
use crate::text::{Difference, Hit, Text};
|
||||||
|
use crate::{Point, Size};
|
||||||
|
|
||||||
|
/// A text paragraph.
|
||||||
|
pub trait Paragraph: Sized + Default {
|
||||||
|
/// The font of this [`Paragraph`].
|
||||||
|
type Font: Copy + PartialEq;
|
||||||
|
|
||||||
|
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
|
||||||
|
fn with_text(text: Text<'_, Self::Font>) -> Self;
|
||||||
|
|
||||||
|
/// Lays out the [`Paragraph`] with some new boundaries.
|
||||||
|
fn resize(&mut self, new_bounds: Size);
|
||||||
|
|
||||||
|
/// Compares the [`Paragraph`] with some desired [`Text`] and returns the
|
||||||
|
/// [`Difference`].
|
||||||
|
fn compare(&self, text: Text<'_, Self::Font>) -> Difference;
|
||||||
|
|
||||||
|
/// Returns the horizontal alignment of the [`Paragraph`].
|
||||||
|
fn horizontal_alignment(&self) -> alignment::Horizontal;
|
||||||
|
|
||||||
|
/// Returns the vertical alignment of the [`Paragraph`].
|
||||||
|
fn vertical_alignment(&self) -> alignment::Vertical;
|
||||||
|
|
||||||
|
/// Returns the minimum boundaries that can fit the contents of the
|
||||||
|
/// [`Paragraph`].
|
||||||
|
fn min_bounds(&self) -> Size;
|
||||||
|
|
||||||
|
/// Tests whether the provided point is within the boundaries of the
|
||||||
|
/// [`Paragraph`], returning information about the nearest character.
|
||||||
|
fn hit_test(&self, point: Point) -> Option<Hit>;
|
||||||
|
|
||||||
|
/// Returns the distance to the given grapheme index in the [`Paragraph`].
|
||||||
|
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
|
||||||
|
|
||||||
|
/// Updates the [`Paragraph`] to match the given [`Text`], if needed.
|
||||||
|
fn update(&mut self, text: Text<'_, Self::Font>) {
|
||||||
|
match self.compare(text) {
|
||||||
|
Difference::None => {}
|
||||||
|
Difference::Bounds => {
|
||||||
|
self.resize(text.bounds);
|
||||||
|
}
|
||||||
|
Difference::Shape => {
|
||||||
|
*self = Self::with_text(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum width that can fit the contents of the [`Paragraph`].
|
||||||
|
fn min_width(&self) -> f32 {
|
||||||
|
self.min_bounds().width
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum height that can fit the contents of the [`Paragraph`].
|
||||||
|
fn min_height(&self) -> f32 {
|
||||||
|
self.min_bounds().height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,4 @@
|
||||||
//! Keep track of time, both in native and web platforms!
|
//! Keep track of time, both in native and web platforms!
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
pub use web_time::Duration;
|
||||||
pub use instant::Instant;
|
pub use web_time::Instant;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
pub use instant::Duration;
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub use std::time::Instant;
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub use std::time::Duration;
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use crate::layout::{self, Layout};
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::{Clipboard, Length, Rectangle, Shell};
|
use crate::{Clipboard, Length, Rectangle, Shell, Size};
|
||||||
|
|
||||||
/// A component that displays information and allows interaction.
|
/// A component that displays information and allows interaction.
|
||||||
///
|
///
|
||||||
|
|
@ -43,11 +43,16 @@ pub trait Widget<Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
/// Returns the width of the [`Widget`].
|
/// Returns the [`Size`] of the [`Widget`] in lengths.
|
||||||
fn width(&self) -> Length;
|
fn size(&self) -> Size<Length>;
|
||||||
|
|
||||||
/// Returns the height of the [`Widget`].
|
/// Returns a [`Size`] hint for laying out the [`Widget`].
|
||||||
fn height(&self) -> Length;
|
///
|
||||||
|
/// This hint may be used by some widget containers to adjust their sizing strategy
|
||||||
|
/// during construction.
|
||||||
|
fn size_hint(&self) -> Size<Length> {
|
||||||
|
self.size()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`layout::Node`] of the [`Widget`].
|
/// Returns the [`layout::Node`] of the [`Widget`].
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ use crate::mouse;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::text::{self, Paragraph};
|
use crate::text::{self, Paragraph};
|
||||||
use crate::widget::tree::{self, Tree};
|
use crate::widget::tree::{self, Tree};
|
||||||
use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget};
|
use crate::{
|
||||||
|
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -134,12 +136,11 @@ where
|
||||||
tree::State::new(State(Renderer::Paragraph::default()))
|
tree::State::new(State(Renderer::Paragraph::default()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(&self) -> Length {
|
fn size(&self) -> Size<Length> {
|
||||||
self.width
|
Size {
|
||||||
}
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
fn height(&self) -> Length {
|
}
|
||||||
self.height
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -172,7 +173,7 @@ where
|
||||||
style: &renderer::Style,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: mouse::Cursor,
|
_cursor_position: mouse::Cursor,
|
||||||
_viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||||
|
|
||||||
|
|
@ -182,6 +183,7 @@ where
|
||||||
layout,
|
layout,
|
||||||
state,
|
state,
|
||||||
theme.appearance(self.style.clone()),
|
theme.appearance(self.style.clone()),
|
||||||
|
viewport,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -204,17 +206,15 @@ pub fn layout<Renderer>(
|
||||||
where
|
where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
let limits = limits.width(width).height(height);
|
layout::sized(limits, width, height, |limits| {
|
||||||
let bounds = limits.max();
|
let bounds = limits.max();
|
||||||
|
|
||||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||||
|
|
||||||
let State(ref mut paragraph) = state;
|
let State(ref mut paragraph) = state;
|
||||||
|
|
||||||
renderer.update_paragraph(
|
paragraph.update(text::Text {
|
||||||
paragraph,
|
|
||||||
text::Text {
|
|
||||||
content,
|
content,
|
||||||
bounds,
|
bounds,
|
||||||
size,
|
size,
|
||||||
|
|
@ -223,12 +223,10 @@ where
|
||||||
horizontal_alignment,
|
horizontal_alignment,
|
||||||
vertical_alignment,
|
vertical_alignment,
|
||||||
shaping,
|
shaping,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
let size = limits.resolve(paragraph.min_bounds());
|
paragraph.min_bounds()
|
||||||
|
})
|
||||||
layout::Node::new(size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws text using the same logic as the [`Text`] widget.
|
/// Draws text using the same logic as the [`Text`] widget.
|
||||||
|
|
@ -247,6 +245,7 @@ pub fn draw<Renderer>(
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
state: &State<Renderer::Paragraph>,
|
state: &State<Renderer::Paragraph>,
|
||||||
appearance: Appearance,
|
appearance: Appearance,
|
||||||
|
viewport: &Rectangle,
|
||||||
) where
|
) where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
|
|
@ -269,6 +268,7 @@ pub fn draw<Renderer>(
|
||||||
paragraph,
|
paragraph,
|
||||||
Point::new(x, y),
|
Point::new(x, y),
|
||||||
appearance.color.unwrap_or(style.text_color),
|
appearance.color.unwrap_or(style.text_color),
|
||||||
|
*viewport,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ impl Tree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reconciliates the children of the tree with the provided list of widgets.
|
/// Reconciles the children of the tree with the provided list of widgets.
|
||||||
pub fn diff_children<'a, Message, Renderer>(
|
pub fn diff_children<'a, Message, Renderer>(
|
||||||
&mut self,
|
&mut self,
|
||||||
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
|
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
//! Build window-based GUI applications.
|
//! Build window-based GUI applications.
|
||||||
pub mod icon;
|
pub mod icon;
|
||||||
|
pub mod settings;
|
||||||
|
|
||||||
mod event;
|
mod event;
|
||||||
|
mod id;
|
||||||
mod level;
|
mod level;
|
||||||
mod mode;
|
mod mode;
|
||||||
|
mod position;
|
||||||
mod redraw_request;
|
mod redraw_request;
|
||||||
mod user_attention;
|
mod user_attention;
|
||||||
|
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
pub use icon::Icon;
|
pub use icon::Icon;
|
||||||
|
pub use id::Id;
|
||||||
pub use level::Level;
|
pub use level::Level;
|
||||||
pub use mode::Mode;
|
pub use mode::Mode;
|
||||||
|
pub use position::Position;
|
||||||
pub use redraw_request::RedrawRequest;
|
pub use redraw_request::RedrawRequest;
|
||||||
|
pub use settings::Settings;
|
||||||
pub use user_attention::UserAttention;
|
pub use user_attention::UserAttention;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,27 @@
|
||||||
use crate::time::Instant;
|
use crate::time::Instant;
|
||||||
|
use crate::{Point, Size};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// A window-related event.
|
/// A window-related event.
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
/// A window was opened.
|
||||||
|
Opened {
|
||||||
|
/// The position of the opened window. This is relative to the top-left corner of the desktop
|
||||||
|
/// the window is on, including virtual desktops. Refers to window's "inner" position,
|
||||||
|
/// or the client area, in logical pixels.
|
||||||
|
///
|
||||||
|
/// **Note**: Not available in Wayland.
|
||||||
|
position: Option<Point>,
|
||||||
|
/// The size of the created window. This is its "inner" size, or the size of the
|
||||||
|
/// client area, in logical pixels.
|
||||||
|
size: Size,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A window was closed.
|
||||||
|
Closed,
|
||||||
|
|
||||||
/// A window was moved.
|
/// A window was moved.
|
||||||
Moved {
|
Moved {
|
||||||
/// The new logical x location of the window
|
/// The new logical x location of the window
|
||||||
|
|
@ -27,9 +44,6 @@ pub enum Event {
|
||||||
RedrawRequested(Instant),
|
RedrawRequested(Instant),
|
||||||
|
|
||||||
/// The user has requested for the window to close.
|
/// The user has requested for the window to close.
|
||||||
///
|
|
||||||
/// Usually, you will want to terminate the execution whenever this event
|
|
||||||
/// occurs.
|
|
||||||
CloseRequested,
|
CloseRequested,
|
||||||
|
|
||||||
/// A window was focused.
|
/// A window was focused.
|
||||||
|
|
@ -44,7 +58,7 @@ pub enum Event {
|
||||||
/// for each file separately.
|
/// for each file separately.
|
||||||
FileHovered(PathBuf),
|
FileHovered(PathBuf),
|
||||||
|
|
||||||
/// A file has beend dropped into the window.
|
/// A file has been dropped into the window.
|
||||||
///
|
///
|
||||||
/// When the user drops multiple files at once, this event will be emitted
|
/// When the user drops multiple files at once, this event will be emitted
|
||||||
/// for each file separately.
|
/// for each file separately.
|
||||||
|
|
|
||||||
21
core/src/window/id.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use std::sync::atomic::{self, AtomicU64};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
/// The id of the window.
|
||||||
|
///
|
||||||
|
/// Internally Iced reserves `window::Id::MAIN` for the first window spawned.
|
||||||
|
pub struct Id(u64);
|
||||||
|
|
||||||
|
static COUNT: AtomicU64 = AtomicU64::new(1);
|
||||||
|
|
||||||
|
impl Id {
|
||||||
|
/// The reserved window [`Id`] for the first window in an Iced application.
|
||||||
|
pub const MAIN: Self = Id(0);
|
||||||
|
|
||||||
|
/// Creates a new unique window [`Id`].
|
||||||
|
pub fn unique() -> Id {
|
||||||
|
Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
use crate::Point;
|
||||||
|
|
||||||
/// The position of a window in a given screen.
|
/// The position of a window in a given screen.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum Position {
|
pub enum Position {
|
||||||
/// The platform-specific default position for a new window.
|
/// The platform-specific default position for a new window.
|
||||||
Default,
|
Default,
|
||||||
|
|
@ -12,7 +14,7 @@ pub enum Position {
|
||||||
/// position. So if you have decorations enabled and want the window to be
|
/// position. So if you have decorations enabled and want the window to be
|
||||||
/// at (0, 0) you would have to set the position to
|
/// at (0, 0) you would have to set the position to
|
||||||
/// `(PADDING_X, PADDING_Y)`.
|
/// `(PADDING_X, PADDING_Y)`.
|
||||||
Specific(i32, i32),
|
Specific(Point),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Position {
|
impl Default for Position {
|
||||||
|
|
@ -1,21 +1,47 @@
|
||||||
|
//! Configure your windows.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
#[path = "settings/windows.rs"]
|
||||||
|
mod platform;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[path = "settings/macos.rs"]
|
||||||
|
mod platform;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[path = "settings/linux.rs"]
|
||||||
|
mod platform;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
#[path = "settings/wasm.rs"]
|
||||||
|
mod platform;
|
||||||
|
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "windows",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "linux",
|
||||||
|
target_arch = "wasm32"
|
||||||
|
)))]
|
||||||
|
#[path = "settings/other.rs"]
|
||||||
|
mod platform;
|
||||||
|
|
||||||
use crate::window::{Icon, Level, Position};
|
use crate::window::{Icon, Level, Position};
|
||||||
|
use crate::Size;
|
||||||
|
|
||||||
pub use iced_winit::settings::PlatformSpecific;
|
pub use platform::PlatformSpecific;
|
||||||
|
|
||||||
/// The window settings of an application.
|
/// The window settings of an application.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
/// The initial size of the window.
|
/// The initial logical dimensions of the window.
|
||||||
pub size: (u32, u32),
|
pub size: Size,
|
||||||
|
|
||||||
/// The initial position of the window.
|
/// The initial position of the window.
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
|
|
||||||
/// The minimum size of the window.
|
/// The minimum size of the window.
|
||||||
pub min_size: Option<(u32, u32)>,
|
pub min_size: Option<Size>,
|
||||||
|
|
||||||
/// The maximum size of the window.
|
/// The maximum size of the window.
|
||||||
pub max_size: Option<(u32, u32)>,
|
pub max_size: Option<Size>,
|
||||||
|
|
||||||
/// Whether the window should be visible or not.
|
/// Whether the window should be visible or not.
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
|
|
@ -37,12 +63,22 @@ pub struct Settings {
|
||||||
|
|
||||||
/// Platform specific settings.
|
/// Platform specific settings.
|
||||||
pub platform_specific: PlatformSpecific,
|
pub platform_specific: PlatformSpecific,
|
||||||
|
|
||||||
|
/// Whether the window will close when the user requests it, e.g. when a user presses the
|
||||||
|
/// close button.
|
||||||
|
///
|
||||||
|
/// This can be useful if you want to have some behavior that executes before the window is
|
||||||
|
/// actually destroyed. If you disable this, you must manually close the window with the
|
||||||
|
/// `window::close` command.
|
||||||
|
///
|
||||||
|
/// By default this is enabled.
|
||||||
|
pub exit_on_close_request: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
fn default() -> Settings {
|
fn default() -> Self {
|
||||||
Settings {
|
Self {
|
||||||
size: (1024, 768),
|
size: Size::new(1024.0, 768.0),
|
||||||
position: Position::default(),
|
position: Position::default(),
|
||||||
min_size: None,
|
min_size: None,
|
||||||
max_size: None,
|
max_size: None,
|
||||||
|
|
@ -52,25 +88,8 @@ impl Default for Settings {
|
||||||
transparent: false,
|
transparent: false,
|
||||||
level: Level::default(),
|
level: Level::default(),
|
||||||
icon: None,
|
icon: None,
|
||||||
|
exit_on_close_request: true,
|
||||||
platform_specific: PlatformSpecific::default(),
|
platform_specific: PlatformSpecific::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Settings> for iced_winit::settings::Window {
|
|
||||||
fn from(settings: Settings) -> Self {
|
|
||||||
Self {
|
|
||||||
size: settings.size,
|
|
||||||
position: iced_winit::Position::from(settings.position),
|
|
||||||
min_size: settings.min_size,
|
|
||||||
max_size: settings.max_size,
|
|
||||||
visible: settings.visible,
|
|
||||||
resizable: settings.resizable,
|
|
||||||
decorations: settings.decorations,
|
|
||||||
transparent: settings.transparent,
|
|
||||||
level: settings.level,
|
|
||||||
icon: settings.icon.map(Icon::into),
|
|
||||||
platform_specific: settings.platform_specific,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -26,12 +26,11 @@ mod quad {
|
||||||
where
|
where
|
||||||
Renderer: renderer::Renderer,
|
Renderer: renderer::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn size(&self) -> Size<Length> {
|
||||||
Length::Shrink
|
Size {
|
||||||
}
|
width: Length::Shrink,
|
||||||
|
height: Length::Shrink,
|
||||||
fn height(&self) -> Length {
|
}
|
||||||
Length::Shrink
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
|
||||||
17
examples/custom_shader/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "custom_shader"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Bingus <shankern@protonmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced.workspace = true
|
||||||
|
iced.features = ["debug", "advanced"]
|
||||||
|
|
||||||
|
image.workspace = true
|
||||||
|
bytemuck.workspace = true
|
||||||
|
|
||||||
|
glam.workspace = true
|
||||||
|
glam.features = ["bytemuck"]
|
||||||
|
|
||||||
|
rand = "0.8.5"
|
||||||
163
examples/custom_shader/src/main.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
mod scene;
|
||||||
|
|
||||||
|
use scene::Scene;
|
||||||
|
|
||||||
|
use iced::executor;
|
||||||
|
use iced::time::Instant;
|
||||||
|
use iced::widget::shader::wgpu;
|
||||||
|
use iced::widget::{checkbox, column, container, row, shader, slider, text};
|
||||||
|
use iced::window;
|
||||||
|
use iced::{
|
||||||
|
Alignment, Application, Color, Command, Element, Length, Renderer,
|
||||||
|
Subscription, Theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() -> iced::Result {
|
||||||
|
IcedCubes::run(iced::Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IcedCubes {
|
||||||
|
start: Instant,
|
||||||
|
scene: Scene,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
CubeAmountChanged(u32),
|
||||||
|
CubeSizeChanged(f32),
|
||||||
|
Tick(Instant),
|
||||||
|
ShowDepthBuffer(bool),
|
||||||
|
LightColorChanged(Color),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for IcedCubes {
|
||||||
|
type Executor = executor::Default;
|
||||||
|
type Message = Message;
|
||||||
|
type Theme = Theme;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||||
|
(
|
||||||
|
Self {
|
||||||
|
start: Instant::now(),
|
||||||
|
scene: Scene::new(),
|
||||||
|
},
|
||||||
|
Command::none(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
"Iced Cubes".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||||
|
match message {
|
||||||
|
Message::CubeAmountChanged(amount) => {
|
||||||
|
self.scene.change_amount(amount);
|
||||||
|
}
|
||||||
|
Message::CubeSizeChanged(size) => {
|
||||||
|
self.scene.size = size;
|
||||||
|
}
|
||||||
|
Message::Tick(time) => {
|
||||||
|
self.scene.update(time - self.start);
|
||||||
|
}
|
||||||
|
Message::ShowDepthBuffer(show) => {
|
||||||
|
self.scene.show_depth_buffer = show;
|
||||||
|
}
|
||||||
|
Message::LightColorChanged(color) => {
|
||||||
|
self.scene.light_color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
|
||||||
|
let top_controls = row![
|
||||||
|
control(
|
||||||
|
"Amount",
|
||||||
|
slider(
|
||||||
|
1..=scene::MAX,
|
||||||
|
self.scene.cubes.len() as u32,
|
||||||
|
Message::CubeAmountChanged
|
||||||
|
)
|
||||||
|
.width(100)
|
||||||
|
),
|
||||||
|
control(
|
||||||
|
"Size",
|
||||||
|
slider(0.1..=0.25, self.scene.size, Message::CubeSizeChanged)
|
||||||
|
.step(0.01)
|
||||||
|
.width(100),
|
||||||
|
),
|
||||||
|
checkbox(
|
||||||
|
"Show Depth Buffer",
|
||||||
|
self.scene.show_depth_buffer,
|
||||||
|
Message::ShowDepthBuffer
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.spacing(40);
|
||||||
|
|
||||||
|
let bottom_controls = row![
|
||||||
|
control(
|
||||||
|
"R",
|
||||||
|
slider(0.0..=1.0, self.scene.light_color.r, move |r| {
|
||||||
|
Message::LightColorChanged(Color {
|
||||||
|
r,
|
||||||
|
..self.scene.light_color
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.step(0.01)
|
||||||
|
.width(100)
|
||||||
|
),
|
||||||
|
control(
|
||||||
|
"G",
|
||||||
|
slider(0.0..=1.0, self.scene.light_color.g, move |g| {
|
||||||
|
Message::LightColorChanged(Color {
|
||||||
|
g,
|
||||||
|
..self.scene.light_color
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.step(0.01)
|
||||||
|
.width(100)
|
||||||
|
),
|
||||||
|
control(
|
||||||
|
"B",
|
||||||
|
slider(0.0..=1.0, self.scene.light_color.b, move |b| {
|
||||||
|
Message::LightColorChanged(Color {
|
||||||
|
b,
|
||||||
|
..self.scene.light_color
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.step(0.01)
|
||||||
|
.width(100)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
.spacing(40);
|
||||||
|
|
||||||
|
let controls = column![top_controls, bottom_controls,]
|
||||||
|
.spacing(10)
|
||||||
|
.padding(20)
|
||||||
|
.align_items(Alignment::Center);
|
||||||
|
|
||||||
|
let shader =
|
||||||
|
shader(&self.scene).width(Length::Fill).height(Length::Fill);
|
||||||
|
|
||||||
|
container(column![shader, controls].align_items(Alignment::Center))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
|
window::frames().map(Message::Tick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control<'a>(
|
||||||
|
label: &'static str,
|
||||||
|
control: impl Into<Element<'a, Message>>,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
row![text(label), control.into()].spacing(10).into()
|
||||||
|
}
|
||||||
186
examples/custom_shader/src/scene.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
mod camera;
|
||||||
|
mod pipeline;
|
||||||
|
|
||||||
|
use camera::Camera;
|
||||||
|
use pipeline::Pipeline;
|
||||||
|
|
||||||
|
use crate::wgpu;
|
||||||
|
use pipeline::cube::{self, Cube};
|
||||||
|
|
||||||
|
use iced::mouse;
|
||||||
|
use iced::time::Duration;
|
||||||
|
use iced::widget::shader;
|
||||||
|
use iced::{Color, Rectangle, Size};
|
||||||
|
|
||||||
|
use glam::Vec3;
|
||||||
|
use rand::Rng;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
pub const MAX: u32 = 500;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Scene {
|
||||||
|
pub size: f32,
|
||||||
|
pub cubes: Vec<Cube>,
|
||||||
|
pub camera: Camera,
|
||||||
|
pub show_depth_buffer: bool,
|
||||||
|
pub light_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut scene = Self {
|
||||||
|
size: 0.2,
|
||||||
|
cubes: vec![],
|
||||||
|
camera: Camera::default(),
|
||||||
|
show_depth_buffer: false,
|
||||||
|
light_color: Color::WHITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
scene.change_amount(MAX);
|
||||||
|
|
||||||
|
scene
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, time: Duration) {
|
||||||
|
for cube in self.cubes.iter_mut() {
|
||||||
|
cube.update(self.size, time.as_secs_f32());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_amount(&mut self, amount: u32) {
|
||||||
|
let curr_cubes = self.cubes.len() as u32;
|
||||||
|
|
||||||
|
match amount.cmp(&curr_cubes) {
|
||||||
|
Ordering::Greater => {
|
||||||
|
// spawn
|
||||||
|
let cubes_2_spawn = (amount - curr_cubes) as usize;
|
||||||
|
|
||||||
|
let mut cubes = 0;
|
||||||
|
self.cubes.extend(iter::from_fn(|| {
|
||||||
|
if cubes < cubes_2_spawn {
|
||||||
|
cubes += 1;
|
||||||
|
Some(Cube::new(self.size, rnd_origin()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Ordering::Less => {
|
||||||
|
// chop
|
||||||
|
let cubes_2_cut = curr_cubes - amount;
|
||||||
|
let new_len = self.cubes.len() - cubes_2_cut as usize;
|
||||||
|
self.cubes.truncate(new_len);
|
||||||
|
}
|
||||||
|
Ordering::Equal => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> shader::Program<Message> for Scene {
|
||||||
|
type State = ();
|
||||||
|
type Primitive = Primitive;
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_state: &Self::State,
|
||||||
|
_cursor: mouse::Cursor,
|
||||||
|
bounds: Rectangle,
|
||||||
|
) -> Self::Primitive {
|
||||||
|
Primitive::new(
|
||||||
|
&self.cubes,
|
||||||
|
&self.camera,
|
||||||
|
bounds,
|
||||||
|
self.show_depth_buffer,
|
||||||
|
self.light_color,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collection of `Cube`s that can be rendered.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Primitive {
|
||||||
|
cubes: Vec<cube::Raw>,
|
||||||
|
uniforms: pipeline::Uniforms,
|
||||||
|
show_depth_buffer: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Primitive {
|
||||||
|
pub fn new(
|
||||||
|
cubes: &[Cube],
|
||||||
|
camera: &Camera,
|
||||||
|
bounds: Rectangle,
|
||||||
|
show_depth_buffer: bool,
|
||||||
|
light_color: Color,
|
||||||
|
) -> Self {
|
||||||
|
let uniforms = pipeline::Uniforms::new(camera, bounds, light_color);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
cubes: cubes
|
||||||
|
.iter()
|
||||||
|
.map(cube::Raw::from_cube)
|
||||||
|
.collect::<Vec<cube::Raw>>(),
|
||||||
|
uniforms,
|
||||||
|
show_depth_buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl shader::Primitive for Primitive {
|
||||||
|
fn prepare(
|
||||||
|
&self,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_bounds: Rectangle,
|
||||||
|
target_size: Size<u32>,
|
||||||
|
_scale_factor: f32,
|
||||||
|
storage: &mut shader::Storage,
|
||||||
|
) {
|
||||||
|
if !storage.has::<Pipeline>() {
|
||||||
|
storage.store(Pipeline::new(device, queue, format, target_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
let pipeline = storage.get_mut::<Pipeline>().unwrap();
|
||||||
|
|
||||||
|
//upload data to GPU
|
||||||
|
pipeline.update(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
target_size,
|
||||||
|
&self.uniforms,
|
||||||
|
self.cubes.len(),
|
||||||
|
&self.cubes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&self,
|
||||||
|
storage: &shader::Storage,
|
||||||
|
target: &wgpu::TextureView,
|
||||||
|
_target_size: Size<u32>,
|
||||||
|
viewport: Rectangle<u32>,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
) {
|
||||||
|
//at this point our pipeline should always be initialized
|
||||||
|
let pipeline = storage.get::<Pipeline>().unwrap();
|
||||||
|
|
||||||
|
//render primitive
|
||||||
|
pipeline.render(
|
||||||
|
target,
|
||||||
|
encoder,
|
||||||
|
viewport,
|
||||||
|
self.cubes.len() as u32,
|
||||||
|
self.show_depth_buffer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rnd_origin() -> Vec3 {
|
||||||
|
Vec3::new(
|
||||||
|
rand::thread_rng().gen_range(-4.0..4.0),
|
||||||
|
rand::thread_rng().gen_range(-4.0..4.0),
|
||||||
|
rand::thread_rng().gen_range(-4.0..2.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
53
examples/custom_shader/src/scene/camera.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
use glam::{mat4, vec3, vec4};
|
||||||
|
use iced::Rectangle;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Camera {
|
||||||
|
eye: glam::Vec3,
|
||||||
|
target: glam::Vec3,
|
||||||
|
up: glam::Vec3,
|
||||||
|
fov_y: f32,
|
||||||
|
near: f32,
|
||||||
|
far: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Camera {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
eye: vec3(0.0, 2.0, 3.0),
|
||||||
|
target: glam::Vec3::ZERO,
|
||||||
|
up: glam::Vec3::Y,
|
||||||
|
fov_y: 45.0,
|
||||||
|
near: 0.1,
|
||||||
|
far: 100.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = mat4(
|
||||||
|
vec4(1.0, 0.0, 0.0, 0.0),
|
||||||
|
vec4(0.0, 1.0, 0.0, 0.0),
|
||||||
|
vec4(0.0, 0.0, 0.5, 0.0),
|
||||||
|
vec4(0.0, 0.0, 0.5, 1.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Camera {
|
||||||
|
pub fn build_view_proj_matrix(&self, bounds: Rectangle) -> glam::Mat4 {
|
||||||
|
//TODO looks distorted without padding; base on surface texture size instead?
|
||||||
|
let aspect_ratio = bounds.width / (bounds.height + 150.0);
|
||||||
|
|
||||||
|
let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up);
|
||||||
|
let proj = glam::Mat4::perspective_rh(
|
||||||
|
self.fov_y,
|
||||||
|
aspect_ratio,
|
||||||
|
self.near,
|
||||||
|
self.far,
|
||||||
|
);
|
||||||
|
|
||||||
|
OPENGL_TO_WGPU_MATRIX * proj * view
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self) -> glam::Vec4 {
|
||||||
|
glam::Vec4::from((self.eye, 0.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
621
examples/custom_shader/src/scene/pipeline.rs
Normal file
|
|
@ -0,0 +1,621 @@
|
||||||
|
pub mod cube;
|
||||||
|
|
||||||
|
mod buffer;
|
||||||
|
mod uniforms;
|
||||||
|
mod vertex;
|
||||||
|
|
||||||
|
pub use uniforms::Uniforms;
|
||||||
|
|
||||||
|
use buffer::Buffer;
|
||||||
|
use vertex::Vertex;
|
||||||
|
|
||||||
|
use crate::wgpu;
|
||||||
|
use crate::wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
use iced::{Rectangle, Size};
|
||||||
|
|
||||||
|
const SKY_TEXTURE_SIZE: u32 = 128;
|
||||||
|
|
||||||
|
pub struct Pipeline {
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
vertices: wgpu::Buffer,
|
||||||
|
cubes: Buffer,
|
||||||
|
uniforms: wgpu::Buffer,
|
||||||
|
uniform_bind_group: wgpu::BindGroup,
|
||||||
|
depth_texture_size: Size<u32>,
|
||||||
|
depth_view: wgpu::TextureView,
|
||||||
|
depth_pipeline: DepthPipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pipeline {
|
||||||
|
pub fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
target_size: Size<u32>,
|
||||||
|
) -> Self {
|
||||||
|
//vertices of one cube
|
||||||
|
let vertices =
|
||||||
|
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("cubes vertex buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&cube::Raw::vertices()),
|
||||||
|
usage: wgpu::BufferUsages::VERTEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
//cube instance data
|
||||||
|
let cubes_buffer = Buffer::new(
|
||||||
|
device,
|
||||||
|
"cubes instance buffer",
|
||||||
|
std::mem::size_of::<cube::Raw>() as u64,
|
||||||
|
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||||
|
);
|
||||||
|
|
||||||
|
//uniforms for all cubes
|
||||||
|
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("cubes uniform buffer"),
|
||||||
|
size: std::mem::size_of::<Uniforms>() as u64,
|
||||||
|
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
//depth buffer
|
||||||
|
let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("cubes depth texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: target_size.width,
|
||||||
|
height: target_size.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||||
|
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let depth_view =
|
||||||
|
depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let normal_map_data = load_normal_map_data();
|
||||||
|
|
||||||
|
//normal map
|
||||||
|
let normal_texture = device.create_texture_with_data(
|
||||||
|
queue,
|
||||||
|
&wgpu::TextureDescriptor {
|
||||||
|
label: Some("cubes normal map texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: 1024,
|
||||||
|
height: 1024,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
},
|
||||||
|
wgpu::util::TextureDataOrder::LayerMajor,
|
||||||
|
&normal_map_data,
|
||||||
|
);
|
||||||
|
|
||||||
|
let normal_view =
|
||||||
|
normal_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
//skybox texture for reflection/refraction
|
||||||
|
let skybox_data = load_skybox_data();
|
||||||
|
|
||||||
|
let skybox_texture = device.create_texture_with_data(
|
||||||
|
queue,
|
||||||
|
&wgpu::TextureDescriptor {
|
||||||
|
label: Some("cubes skybox texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: SKY_TEXTURE_SIZE,
|
||||||
|
height: SKY_TEXTURE_SIZE,
|
||||||
|
depth_or_array_layers: 6, //one for each face of the cube
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
},
|
||||||
|
wgpu::util::TextureDataOrder::LayerMajor,
|
||||||
|
&skybox_data,
|
||||||
|
);
|
||||||
|
|
||||||
|
let sky_view =
|
||||||
|
skybox_texture.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
label: Some("cubes skybox texture view"),
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::Cube),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: Some("cubes skybox sampler"),
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Linear,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let uniform_bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("cubes uniform bind group layout"),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float {
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
view_dimension: wgpu::TextureViewDimension::Cube,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(
|
||||||
|
wgpu::SamplerBindingType::Filtering,
|
||||||
|
),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 3,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float {
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let uniform_bind_group =
|
||||||
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("cubes uniform bind group"),
|
||||||
|
layout: &uniform_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: uniforms.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&sky_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&sky_sampler),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 3,
|
||||||
|
resource: wgpu::BindingResource::TextureView(
|
||||||
|
&normal_view,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let layout =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("cubes pipeline layout"),
|
||||||
|
bind_group_layouts: &[&uniform_bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let shader =
|
||||||
|
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: Some("cubes shader"),
|
||||||
|
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||||
|
include_str!("../shaders/cubes.wgsl"),
|
||||||
|
)),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline =
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("cubes pipeline"),
|
||||||
|
layout: Some(&layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[Vertex::desc(), cube::Raw::desc()],
|
||||||
|
},
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
|
depth_write_enabled: true,
|
||||||
|
depth_compare: wgpu::CompareFunction::Less,
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias: wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: 1,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format,
|
||||||
|
blend: Some(wgpu::BlendState {
|
||||||
|
color: wgpu::BlendComponent {
|
||||||
|
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||||
|
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||||
|
operation: wgpu::BlendOperation::Add,
|
||||||
|
},
|
||||||
|
alpha: wgpu::BlendComponent {
|
||||||
|
src_factor: wgpu::BlendFactor::One,
|
||||||
|
dst_factor: wgpu::BlendFactor::One,
|
||||||
|
operation: wgpu::BlendOperation::Max,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
multiview: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let depth_pipeline = DepthPipeline::new(
|
||||||
|
device,
|
||||||
|
format,
|
||||||
|
depth_texture.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
pipeline,
|
||||||
|
cubes: cubes_buffer,
|
||||||
|
uniforms,
|
||||||
|
uniform_bind_group,
|
||||||
|
vertices,
|
||||||
|
depth_texture_size: target_size,
|
||||||
|
depth_view,
|
||||||
|
depth_pipeline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_depth_texture(&mut self, device: &wgpu::Device, size: Size<u32>) {
|
||||||
|
if self.depth_texture_size.height != size.height
|
||||||
|
|| self.depth_texture_size.width != size.width
|
||||||
|
{
|
||||||
|
let text = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("cubes depth texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||||
|
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
self.depth_view =
|
||||||
|
text.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
self.depth_texture_size = size;
|
||||||
|
|
||||||
|
self.depth_pipeline.update(device, &text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
target_size: Size<u32>,
|
||||||
|
uniforms: &Uniforms,
|
||||||
|
num_cubes: usize,
|
||||||
|
cubes: &[cube::Raw],
|
||||||
|
) {
|
||||||
|
//recreate depth texture if surface texture size has changed
|
||||||
|
self.update_depth_texture(device, target_size);
|
||||||
|
|
||||||
|
// update uniforms
|
||||||
|
queue.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms));
|
||||||
|
|
||||||
|
//resize cubes vertex buffer if cubes amount changed
|
||||||
|
let new_size = num_cubes * std::mem::size_of::<cube::Raw>();
|
||||||
|
self.cubes.resize(device, new_size as u64);
|
||||||
|
|
||||||
|
//always write new cube data since they are constantly rotating
|
||||||
|
queue.write_buffer(&self.cubes.raw, 0, bytemuck::cast_slice(cubes));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&self,
|
||||||
|
target: &wgpu::TextureView,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
viewport: Rectangle<u32>,
|
||||||
|
num_cubes: u32,
|
||||||
|
show_depth: bool,
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
let mut pass =
|
||||||
|
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("cubes.pipeline.pass"),
|
||||||
|
color_attachments: &[Some(
|
||||||
|
wgpu::RenderPassColorAttachment {
|
||||||
|
view: target,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
depth_stencil_attachment: Some(
|
||||||
|
wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
view: &self.depth_view,
|
||||||
|
depth_ops: Some(wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(1.0),
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
}),
|
||||||
|
stencil_ops: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
pass.set_scissor_rect(
|
||||||
|
viewport.x,
|
||||||
|
viewport.y,
|
||||||
|
viewport.width,
|
||||||
|
viewport.height,
|
||||||
|
);
|
||||||
|
pass.set_pipeline(&self.pipeline);
|
||||||
|
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||||
|
pass.set_vertex_buffer(0, self.vertices.slice(..));
|
||||||
|
pass.set_vertex_buffer(1, self.cubes.raw.slice(..));
|
||||||
|
pass.draw(0..36, 0..num_cubes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if show_depth {
|
||||||
|
self.depth_pipeline.render(encoder, target, viewport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DepthPipeline {
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group_layout: wgpu::BindGroupLayout,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
sampler: wgpu::Sampler,
|
||||||
|
depth_view: wgpu::TextureView,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DepthPipeline {
|
||||||
|
pub fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
depth_texture: wgpu::TextureView,
|
||||||
|
) -> Self {
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
label: Some("cubes.depth_pipeline.sampler"),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("cubes.depth_pipeline.bind_group_layout"),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(
|
||||||
|
wgpu::SamplerBindingType::NonFiltering,
|
||||||
|
),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float {
|
||||||
|
filterable: false,
|
||||||
|
},
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("cubes.depth_pipeline.bind_group"),
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(
|
||||||
|
&depth_texture,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let layout =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("cubes.depth_pipeline.layout"),
|
||||||
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let shader =
|
||||||
|
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: Some("cubes.depth_pipeline.shader"),
|
||||||
|
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||||
|
include_str!("../shaders/depth.wgsl"),
|
||||||
|
)),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline =
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("cubes.depth_pipeline.pipeline"),
|
||||||
|
layout: Some(&layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
|
depth_write_enabled: false,
|
||||||
|
depth_compare: wgpu::CompareFunction::Less,
|
||||||
|
stencil: wgpu::StencilState::default(),
|
||||||
|
bias: wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format,
|
||||||
|
blend: Some(wgpu::BlendState::REPLACE),
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
multiview: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
pipeline,
|
||||||
|
bind_group_layout,
|
||||||
|
bind_group,
|
||||||
|
sampler,
|
||||||
|
depth_view: depth_texture,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
depth_texture: &wgpu::Texture,
|
||||||
|
) {
|
||||||
|
self.depth_view =
|
||||||
|
depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
self.bind_group =
|
||||||
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("cubes.depth_pipeline.bind_group"),
|
||||||
|
layout: &self.bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(
|
||||||
|
&self.depth_view,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&self,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
target: &wgpu::TextureView,
|
||||||
|
viewport: Rectangle<u32>,
|
||||||
|
) {
|
||||||
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("cubes.pipeline.depth_pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: target,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: Some(
|
||||||
|
wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
view: &self.depth_view,
|
||||||
|
depth_ops: None,
|
||||||
|
stencil_ops: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
pass.set_scissor_rect(
|
||||||
|
viewport.x,
|
||||||
|
viewport.y,
|
||||||
|
viewport.width,
|
||||||
|
viewport.height,
|
||||||
|
);
|
||||||
|
pass.set_pipeline(&self.pipeline);
|
||||||
|
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
pass.draw(0..6, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_skybox_data() -> Vec<u8> {
|
||||||
|
let pos_x: &[u8] = include_bytes!("../../textures/skybox/pos_x.jpg");
|
||||||
|
let neg_x: &[u8] = include_bytes!("../../textures/skybox/neg_x.jpg");
|
||||||
|
let pos_y: &[u8] = include_bytes!("../../textures/skybox/pos_y.jpg");
|
||||||
|
let neg_y: &[u8] = include_bytes!("../../textures/skybox/neg_y.jpg");
|
||||||
|
let pos_z: &[u8] = include_bytes!("../../textures/skybox/pos_z.jpg");
|
||||||
|
let neg_z: &[u8] = include_bytes!("../../textures/skybox/neg_z.jpg");
|
||||||
|
|
||||||
|
let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z];
|
||||||
|
|
||||||
|
data.iter().fold(vec![], |mut acc, bytes| {
|
||||||
|
let i = image::load_from_memory_with_format(
|
||||||
|
bytes,
|
||||||
|
image::ImageFormat::Jpeg,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.to_rgba8()
|
||||||
|
.into_raw();
|
||||||
|
|
||||||
|
acc.extend(i);
|
||||||
|
acc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_normal_map_data() -> Vec<u8> {
|
||||||
|
let bytes: &[u8] = include_bytes!("../../textures/ice_cube_normal_map.png");
|
||||||
|
|
||||||
|
image::load_from_memory_with_format(bytes, image::ImageFormat::Png)
|
||||||
|
.unwrap()
|
||||||
|
.to_rgba8()
|
||||||
|
.into_raw()
|
||||||
|
}
|
||||||
41
examples/custom_shader/src/scene/pipeline/buffer.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::wgpu;
|
||||||
|
|
||||||
|
// A custom buffer container for dynamic resizing.
|
||||||
|
pub struct Buffer {
|
||||||
|
pub raw: wgpu::Buffer,
|
||||||
|
label: &'static str,
|
||||||
|
size: u64,
|
||||||
|
usage: wgpu::BufferUsages,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Buffer {
|
||||||
|
pub fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
label: &'static str,
|
||||||
|
size: u64,
|
||||||
|
usage: wgpu::BufferUsages,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
raw: device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some(label),
|
||||||
|
size,
|
||||||
|
usage,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
}),
|
||||||
|
label,
|
||||||
|
size,
|
||||||
|
usage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, device: &wgpu::Device, new_size: u64) {
|
||||||
|
if new_size > self.size {
|
||||||
|
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some(self.label),
|
||||||
|
size: new_size,
|
||||||
|
usage: self.usage,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
326
examples/custom_shader/src/scene/pipeline/cube.rs
Normal file
|
|
@ -0,0 +1,326 @@
|
||||||
|
use crate::scene::pipeline::Vertex;
|
||||||
|
use crate::wgpu;
|
||||||
|
|
||||||
|
use glam::{vec2, vec3, Vec3};
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
/// A single instance of a cube.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Cube {
|
||||||
|
pub rotation: glam::Quat,
|
||||||
|
pub position: Vec3,
|
||||||
|
pub size: f32,
|
||||||
|
rotation_dir: f32,
|
||||||
|
rotation_axis: glam::Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Cube {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
rotation: glam::Quat::IDENTITY,
|
||||||
|
position: glam::Vec3::ZERO,
|
||||||
|
size: 0.1,
|
||||||
|
rotation_dir: 1.0,
|
||||||
|
rotation_axis: glam::Vec3::Y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cube {
|
||||||
|
pub fn new(size: f32, origin: Vec3) -> Self {
|
||||||
|
let rnd = thread_rng().gen_range(0.0..=1.0f32);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
rotation: glam::Quat::IDENTITY,
|
||||||
|
position: origin + Vec3::new(0.1, 0.1, 0.1),
|
||||||
|
size,
|
||||||
|
rotation_dir: if rnd <= 0.5 { -1.0 } else { 1.0 },
|
||||||
|
rotation_axis: if rnd <= 0.33 {
|
||||||
|
glam::Vec3::Y
|
||||||
|
} else if rnd <= 0.66 {
|
||||||
|
glam::Vec3::X
|
||||||
|
} else {
|
||||||
|
glam::Vec3::Z
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, size: f32, time: f32) {
|
||||||
|
self.rotation = glam::Quat::from_axis_angle(
|
||||||
|
self.rotation_axis,
|
||||||
|
time / 2.0 * self.rotation_dir,
|
||||||
|
);
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Raw {
|
||||||
|
transformation: glam::Mat4,
|
||||||
|
normal: glam::Mat3,
|
||||||
|
_padding: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Raw {
|
||||||
|
const ATTRIBS: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![
|
||||||
|
//cube transformation matrix
|
||||||
|
4 => Float32x4,
|
||||||
|
5 => Float32x4,
|
||||||
|
6 => Float32x4,
|
||||||
|
7 => Float32x4,
|
||||||
|
//normal rotation matrix
|
||||||
|
8 => Float32x3,
|
||||||
|
9 => Float32x3,
|
||||||
|
10 => Float32x3,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
|
attributes: &Self::ATTRIBS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Raw {
|
||||||
|
pub fn from_cube(cube: &Cube) -> Raw {
|
||||||
|
Raw {
|
||||||
|
transformation: glam::Mat4::from_scale_rotation_translation(
|
||||||
|
glam::vec3(cube.size, cube.size, cube.size),
|
||||||
|
cube.rotation,
|
||||||
|
cube.position,
|
||||||
|
),
|
||||||
|
normal: glam::Mat3::from_quat(cube.rotation),
|
||||||
|
_padding: [0.0; 3],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vertices() -> [Vertex; 36] {
|
||||||
|
[
|
||||||
|
//face 1
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(0.0, 0.0, -1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(0.0, 0.0, -1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, -0.5),
|
||||||
|
normal: vec3(0.0, 0.0, -1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, -0.5),
|
||||||
|
normal: vec3(0.0, 0.0, -1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, 0.5, -0.5),
|
||||||
|
normal: vec3(0.0, 0.0, -1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(0.0, 0.0, -1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
//face 2
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, 0.5),
|
||||||
|
normal: vec3(0.0, 0.0, 1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, -0.5, 0.5),
|
||||||
|
normal: vec3(0.0, 0.0, 1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(0.0, 0.0, 1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(0.0, 0.0, 1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(0.0, 0.0, 1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, 0.5),
|
||||||
|
normal: vec3(0.0, 0.0, 1.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
//face 3
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(-1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, 0.5, -0.5),
|
||||||
|
normal: vec3(-1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(1.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(-1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(-1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, 0.5),
|
||||||
|
normal: vec3(-1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(0.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(-1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
//face 4
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, -0.5),
|
||||||
|
normal: vec3(1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(1.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, -0.5, 0.5),
|
||||||
|
normal: vec3(1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(0.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(1.0, 0.0, 0.0),
|
||||||
|
tangent: vec3(0.0, 0.0, -1.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
//face 5
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(0.0, -1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(0.0, -1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, -0.5, 0.5),
|
||||||
|
normal: vec3(0.0, -1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, -0.5, 0.5),
|
||||||
|
normal: vec3(0.0, -1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, 0.5),
|
||||||
|
normal: vec3(0.0, -1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, -0.5, -0.5),
|
||||||
|
normal: vec3(0.0, -1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
//face 6
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, 0.5, -0.5),
|
||||||
|
normal: vec3(0.0, 1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, -0.5),
|
||||||
|
normal: vec3(0.0, 1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 1.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(0.0, 1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(0.0, 1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(1.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, 0.5, 0.5),
|
||||||
|
normal: vec3(0.0, 1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 0.0),
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
pos: vec3(-0.5, 0.5, -0.5),
|
||||||
|
normal: vec3(0.0, 1.0, 0.0),
|
||||||
|
tangent: vec3(1.0, 0.0, 0.0),
|
||||||
|
uv: vec2(0.0, 1.0),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
23
examples/custom_shader/src/scene/pipeline/uniforms.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::scene::Camera;
|
||||||
|
|
||||||
|
use iced::{Color, Rectangle};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Uniforms {
|
||||||
|
camera_proj: glam::Mat4,
|
||||||
|
camera_pos: glam::Vec4,
|
||||||
|
light_color: glam::Vec4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Uniforms {
|
||||||
|
pub fn new(camera: &Camera, bounds: Rectangle, light_color: Color) -> Self {
|
||||||
|
let camera_proj = camera.build_view_proj_matrix(bounds);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
camera_proj,
|
||||||
|
camera_pos: camera.position(),
|
||||||
|
light_color: glam::Vec4::from(light_color.into_linear()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
examples/custom_shader/src/scene/pipeline/vertex.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use crate::wgpu;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Vertex {
|
||||||
|
pub pos: glam::Vec3,
|
||||||
|
pub normal: glam::Vec3,
|
||||||
|
pub tangent: glam::Vec3,
|
||||||
|
pub uv: glam::Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vertex {
|
||||||
|
const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
|
||||||
|
//position
|
||||||
|
0 => Float32x3,
|
||||||
|
//normal
|
||||||
|
1 => Float32x3,
|
||||||
|
//tangent
|
||||||
|
2 => Float32x3,
|
||||||
|
//uv
|
||||||
|
3 => Float32x2,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
|
||||||
|
step_mode: wgpu::VertexStepMode::Vertex,
|
||||||
|
attributes: &Self::ATTRIBS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
examples/custom_shader/src/shaders/cubes.wgsl
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
struct Uniforms {
|
||||||
|
projection: mat4x4<f32>,
|
||||||
|
camera_pos: vec4<f32>,
|
||||||
|
light_color: vec4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIGHT_POS: vec3<f32> = vec3<f32>(0.0, 3.0, 3.0);
|
||||||
|
|
||||||
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
||||||
|
@group(0) @binding(1) var sky_texture: texture_cube<f32>;
|
||||||
|
@group(0) @binding(2) var tex_sampler: sampler;
|
||||||
|
@group(0) @binding(3) var normal_texture: texture_2d<f32>;
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
@location(0) position: vec3<f32>,
|
||||||
|
@location(1) normal: vec3<f32>,
|
||||||
|
@location(2) tangent: vec3<f32>,
|
||||||
|
@location(3) uv: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cube {
|
||||||
|
@location(4) matrix_0: vec4<f32>,
|
||||||
|
@location(5) matrix_1: vec4<f32>,
|
||||||
|
@location(6) matrix_2: vec4<f32>,
|
||||||
|
@location(7) matrix_3: vec4<f32>,
|
||||||
|
@location(8) normal_matrix_0: vec3<f32>,
|
||||||
|
@location(9) normal_matrix_1: vec3<f32>,
|
||||||
|
@location(10) normal_matrix_2: vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Output {
|
||||||
|
@builtin(position) clip_pos: vec4<f32>,
|
||||||
|
@location(0) uv: vec2<f32>,
|
||||||
|
@location(1) tangent_pos: vec3<f32>,
|
||||||
|
@location(2) tangent_camera_pos: vec3<f32>,
|
||||||
|
@location(3) tangent_light_pos: vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(vertex: Vertex, cube: Cube) -> Output {
|
||||||
|
let cube_matrix = mat4x4<f32>(
|
||||||
|
cube.matrix_0,
|
||||||
|
cube.matrix_1,
|
||||||
|
cube.matrix_2,
|
||||||
|
cube.matrix_3,
|
||||||
|
);
|
||||||
|
|
||||||
|
let normal_matrix = mat3x3<f32>(
|
||||||
|
cube.normal_matrix_0,
|
||||||
|
cube.normal_matrix_1,
|
||||||
|
cube.normal_matrix_2,
|
||||||
|
);
|
||||||
|
|
||||||
|
//convert to tangent space to calculate lighting in same coordinate space as normal map sample
|
||||||
|
let tangent = normalize(normal_matrix * vertex.tangent);
|
||||||
|
let normal = normalize(normal_matrix * vertex.normal);
|
||||||
|
let bitangent = cross(tangent, normal);
|
||||||
|
|
||||||
|
//shift everything into tangent space
|
||||||
|
let tbn = transpose(mat3x3<f32>(tangent, bitangent, normal));
|
||||||
|
|
||||||
|
let world_pos = cube_matrix * vec4<f32>(vertex.position, 1.0);
|
||||||
|
|
||||||
|
var out: Output;
|
||||||
|
out.clip_pos = uniforms.projection * world_pos;
|
||||||
|
out.uv = vertex.uv;
|
||||||
|
out.tangent_pos = tbn * world_pos.xyz;
|
||||||
|
out.tangent_camera_pos = tbn * uniforms.camera_pos.xyz;
|
||||||
|
out.tangent_light_pos = tbn * LIGHT_POS;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
//cube properties
|
||||||
|
const CUBE_BASE_COLOR: vec4<f32> = vec4<f32>(0.294118, 0.462745, 0.611765, 0.6);
|
||||||
|
const SHINE_DAMPER: f32 = 1.0;
|
||||||
|
const REFLECTIVITY: f32 = 0.8;
|
||||||
|
const REFRACTION_INDEX: f32 = 1.31;
|
||||||
|
|
||||||
|
//fog, for the ~* cinematic effect *~
|
||||||
|
const FOG_DENSITY: f32 = 0.15;
|
||||||
|
const FOG_GRADIENT: f32 = 8.0;
|
||||||
|
const FOG_COLOR: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: Output) -> @location(0) vec4<f32> {
|
||||||
|
let to_camera = in.tangent_camera_pos - in.tangent_pos;
|
||||||
|
|
||||||
|
//normal sample from texture
|
||||||
|
var normal = textureSample(normal_texture, tex_sampler, in.uv).xyz;
|
||||||
|
normal = normal * 2.0 - 1.0;
|
||||||
|
|
||||||
|
//diffuse
|
||||||
|
let dir_to_light: vec3<f32> = normalize(in.tangent_light_pos - in.tangent_pos);
|
||||||
|
let brightness = max(dot(normal, dir_to_light), 0.0);
|
||||||
|
let diffuse: vec3<f32> = brightness * uniforms.light_color.xyz;
|
||||||
|
|
||||||
|
//specular
|
||||||
|
let dir_to_camera = normalize(to_camera);
|
||||||
|
let light_dir = -dir_to_light;
|
||||||
|
let reflected_light_dir = reflect(light_dir, normal);
|
||||||
|
let specular_factor = max(dot(reflected_light_dir, dir_to_camera), 0.0);
|
||||||
|
let damped_factor = pow(specular_factor, SHINE_DAMPER);
|
||||||
|
let specular: vec3<f32> = damped_factor * uniforms.light_color.xyz * REFLECTIVITY;
|
||||||
|
|
||||||
|
//fog
|
||||||
|
let distance = length(to_camera);
|
||||||
|
let visibility = clamp(exp(-pow((distance * FOG_DENSITY), FOG_GRADIENT)), 0.0, 1.0);
|
||||||
|
|
||||||
|
//reflection
|
||||||
|
let reflection_dir = reflect(dir_to_camera, normal);
|
||||||
|
let reflection_color = textureSample(sky_texture, tex_sampler, reflection_dir);
|
||||||
|
let refraction_dir = refract(dir_to_camera, normal, REFRACTION_INDEX);
|
||||||
|
let refraction_color = textureSample(sky_texture, tex_sampler, refraction_dir);
|
||||||
|
let final_reflect_color = mix(reflection_color, refraction_color, 0.5);
|
||||||
|
|
||||||
|
//mix it all together!
|
||||||
|
var color = vec4<f32>(CUBE_BASE_COLOR.xyz * diffuse + specular, CUBE_BASE_COLOR.w);
|
||||||
|
color = mix(color, final_reflect_color, 0.8);
|
||||||
|
color = mix(FOG_COLOR, color, visibility);
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
48
examples/custom_shader/src/shaders/depth.wgsl
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
var<private> positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
||||||
|
vec2<f32>(-1.0, 1.0),
|
||||||
|
vec2<f32>(-1.0, -1.0),
|
||||||
|
vec2<f32>(1.0, -1.0),
|
||||||
|
vec2<f32>(-1.0, 1.0),
|
||||||
|
vec2<f32>(1.0, 1.0),
|
||||||
|
vec2<f32>(1.0, -1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
||||||
|
vec2<f32>(0.0, 0.0),
|
||||||
|
vec2<f32>(0.0, 1.0),
|
||||||
|
vec2<f32>(1.0, 1.0),
|
||||||
|
vec2<f32>(0.0, 0.0),
|
||||||
|
vec2<f32>(1.0, 0.0),
|
||||||
|
vec2<f32>(1.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
@group(0) @binding(0) var depth_sampler: sampler;
|
||||||
|
@group(0) @binding(1) var depth_texture: texture_2d<f32>;
|
||||||
|
|
||||||
|
struct Output {
|
||||||
|
@builtin(position) position: vec4<f32>,
|
||||||
|
@location(0) uv: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) v_index: u32) -> Output {
|
||||||
|
var out: Output;
|
||||||
|
|
||||||
|
out.position = vec4<f32>(positions[v_index], 0.0, 1.0);
|
||||||
|
out.uv = uvs[v_index];
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(input: Output) -> @location(0) vec4<f32> {
|
||||||
|
let depth = textureSample(depth_texture, depth_sampler, input.uv).r;
|
||||||
|
|
||||||
|
if (depth > .9999) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = 1.0 - depth;
|
||||||
|
|
||||||
|
return vec4<f32>(c, c, c, 1.0);
|
||||||
|
}
|
||||||
BIN
examples/custom_shader/textures/ice_cube_normal_map.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
examples/custom_shader/textures/skybox/neg_x.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
examples/custom_shader/textures/skybox/neg_y.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
examples/custom_shader/textures/skybox/neg_z.jpg
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
examples/custom_shader/textures/skybox/pos_x.jpg
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
examples/custom_shader/textures/skybox/pos_y.jpg
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
examples/custom_shader/textures/skybox/pos_z.jpg
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
|
@ -33,12 +33,11 @@ mod circle {
|
||||||
where
|
where
|
||||||
Renderer: renderer::Renderer,
|
Renderer: renderer::Renderer,
|
||||||
{
|
{
|
||||||
fn width(&self) -> Length {
|
fn size(&self) -> Size<Length> {
|
||||||
Length::Shrink
|
Size {
|
||||||
}
|
width: Length::Shrink,
|
||||||
|
height: Length::Shrink,
|
||||||
fn height(&self) -> Length {
|
}
|
||||||
Length::Shrink
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
|
||||||
|
|
@ -73,16 +73,15 @@ impl Application for Example {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
let downloads = Column::with_children(
|
let downloads =
|
||||||
self.downloads.iter().map(Download::view).collect(),
|
Column::with_children(self.downloads.iter().map(Download::view))
|
||||||
)
|
.push(
|
||||||
.push(
|
button("Add another download")
|
||||||
button("Add another download")
|
.on_press(Message::Add)
|
||||||
.on_press(Message::Add)
|
.padding(10),
|
||||||
.padding(10),
|
)
|
||||||
)
|
.spacing(20)
|
||||||
.spacing(20)
|
.align_items(Alignment::End);
|
||||||
.align_items(Alignment::End);
|
|
||||||
|
|
||||||
container(downloads)
|
container(downloads)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
|
||||||
15
examples/editor/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "editor"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Héctor Ramón Jiménez <hector@hecrj.dev>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced.workspace = true
|
||||||
|
iced.features = ["highlighter", "tokio", "debug"]
|
||||||
|
|
||||||
|
tokio.workspace = true
|
||||||
|
tokio.features = ["fs"]
|
||||||
|
|
||||||
|
rfd = "0.13"
|
||||||
BIN
examples/editor/fonts/icons.ttf
Normal file
312
examples/editor/src/main.rs
Normal file
|
|
@ -0,0 +1,312 @@
|
||||||
|
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 std::ffi;
|
||||||
|
use std::io;
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Editor {
|
||||||
|
file: Option<PathBuf>,
|
||||||
|
content: text_editor::Content,
|
||||||
|
theme: highlighter::Theme,
|
||||||
|
is_loading: bool,
|
||||||
|
is_dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
ActionPerformed(text_editor::Action),
|
||||||
|
ThemeSelected(highlighter::Theme),
|
||||||
|
NewFile,
|
||||||
|
OpenFile,
|
||||||
|
FileOpened(Result<(PathBuf, Arc<String>), Error>),
|
||||||
|
SaveFile,
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Editor - Iced")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
match message {
|
||||||
|
Message::ActionPerformed(action) => {
|
||||||
|
self.is_dirty = self.is_dirty || action.is_edit();
|
||||||
|
|
||||||
|
self.content.perform(action);
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::ThemeSelected(theme) => {
|
||||||
|
self.theme = theme;
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::NewFile => {
|
||||||
|
if !self.is_loading {
|
||||||
|
self.file = None;
|
||||||
|
self.content = text_editor::Content::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::OpenFile => {
|
||||||
|
if self.is_loading {
|
||||||
|
Command::none()
|
||||||
|
} else {
|
||||||
|
self.is_loading = true;
|
||||||
|
|
||||||
|
Command::perform(open_file(), Message::FileOpened)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::FileOpened(result) => {
|
||||||
|
self.is_loading = false;
|
||||||
|
self.is_dirty = false;
|
||||||
|
|
||||||
|
if let Ok((path, contents)) = result {
|
||||||
|
self.file = Some(path);
|
||||||
|
self.content = text_editor::Content::with_text(&contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::SaveFile => {
|
||||||
|
if self.is_loading {
|
||||||
|
Command::none()
|
||||||
|
} else {
|
||||||
|
self.is_loading = true;
|
||||||
|
|
||||||
|
Command::perform(
|
||||||
|
save_file(self.file.clone(), self.content.text()),
|
||||||
|
Message::FileSaved,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::FileSaved(result) => {
|
||||||
|
self.is_loading = false;
|
||||||
|
|
||||||
|
if let Ok(path) = result {
|
||||||
|
self.file = Some(path);
|
||||||
|
self.is_dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
keyboard::on_key_press(|key, modifiers| match key.as_ref() {
|
||||||
|
keyboard::Key::Character("s") if modifiers.command() => {
|
||||||
|
Some(Message::SaveFile)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
let controls = row![
|
||||||
|
action(new_icon(), "New file", Some(Message::NewFile)),
|
||||||
|
action(
|
||||||
|
open_icon(),
|
||||||
|
"Open file",
|
||||||
|
(!self.is_loading).then_some(Message::OpenFile)
|
||||||
|
),
|
||||||
|
action(
|
||||||
|
save_icon(),
|
||||||
|
"Save file",
|
||||||
|
self.is_dirty.then_some(Message::SaveFile)
|
||||||
|
),
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
pick_list(
|
||||||
|
highlighter::Theme::ALL,
|
||||||
|
Some(self.theme),
|
||||||
|
Message::ThemeSelected
|
||||||
|
)
|
||||||
|
.text_size(14)
|
||||||
|
.padding([5, 10])
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.align_items(Alignment::Center);
|
||||||
|
|
||||||
|
let status = row![
|
||||||
|
text(if let Some(path) = &self.file {
|
||||||
|
let path = path.display().to_string();
|
||||||
|
|
||||||
|
if path.len() > 60 {
|
||||||
|
format!("...{}", &path[path.len() - 40..])
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::from("New file")
|
||||||
|
}),
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
text({
|
||||||
|
let (line, column) = self.content.cursor_position();
|
||||||
|
|
||||||
|
format!("{}:{}", line + 1, column + 1)
|
||||||
|
})
|
||||||
|
]
|
||||||
|
.spacing(10);
|
||||||
|
|
||||||
|
column![
|
||||||
|
controls,
|
||||||
|
text_editor(&self.content)
|
||||||
|
.on_action(Message::ActionPerformed)
|
||||||
|
.highlight::<Highlighter>(
|
||||||
|
highlighter::Settings {
|
||||||
|
theme: self.theme,
|
||||||
|
extension: self
|
||||||
|
.file
|
||||||
|
.as_deref()
|
||||||
|
.and_then(Path::extension)
|
||||||
|
.and_then(ffi::OsStr::to_str)
|
||||||
|
.map(str::to_string)
|
||||||
|
.unwrap_or(String::from("rs")),
|
||||||
|
},
|
||||||
|
|highlight, _theme| highlight.to_format()
|
||||||
|
),
|
||||||
|
status,
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.padding(10)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme(&self) -> Theme {
|
||||||
|
if self.theme.is_dark() {
|
||||||
|
Theme::Dark
|
||||||
|
} else {
|
||||||
|
Theme::Light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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...")
|
||||||
|
.pick_file()
|
||||||
|
.await
|
||||||
|
.ok_or(Error::DialogClosed)?;
|
||||||
|
|
||||||
|
load_file(picked_file.path().to_owned()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_file(path: PathBuf) -> Result<(PathBuf, Arc<String>), Error> {
|
||||||
|
let contents = tokio::fs::read_to_string(&path)
|
||||||
|
.await
|
||||||
|
.map(Arc::new)
|
||||||
|
.map_err(|error| Error::IoError(error.kind()))?;
|
||||||
|
|
||||||
|
Ok((path, contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_file(
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
contents: String,
|
||||||
|
) -> Result<PathBuf, Error> {
|
||||||
|
let path = if let Some(path) = path {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
rfd::AsyncFileDialog::new()
|
||||||
|
.save_file()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.map(rfd::FileHandle::path)
|
||||||
|
.map(Path::to_owned)
|
||||||
|
.ok_or(Error::DialogClosed)?
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::fs::write(&path, contents)
|
||||||
|
.await
|
||||||
|
.map_err(|error| Error::IoError(error.kind()))?;
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action<'a, Message: Clone + 'a>(
|
||||||
|
content: impl Into<Element<'a, Message>>,
|
||||||
|
label: &'a str,
|
||||||
|
on_press: Option<Message>,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
let action = button(container(content).width(30).center_x());
|
||||||
|
|
||||||
|
if let Some(on_press) = on_press {
|
||||||
|
tooltip(
|
||||||
|
action.on_press(on_press),
|
||||||
|
label,
|
||||||
|
tooltip::Position::FollowCursor,
|
||||||
|
)
|
||||||
|
.style(theme::Container::Box)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
action.style(theme::Button::Secondary).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_icon<'a, Message>() -> Element<'a, Message> {
|
||||||
|
icon('\u{0e800}')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_icon<'a, Message>() -> Element<'a, Message> {
|
||||||
|
icon('\u{0e801}')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_icon<'a, Message>() -> Element<'a, Message> {
|
||||||
|
icon('\u{0f115}')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn icon<'a, Message>(codepoint: char) -> Element<'a, Message> {
|
||||||
|
const ICON_FONT: Font = Font::with_name("editor-icons");
|
||||||
|
|
||||||
|
text(codepoint).font(ICON_FONT).into()
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,10 @@ use iced::{
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
Events::run(Settings {
|
Events::run(Settings {
|
||||||
exit_on_close_request: false,
|
window: window::Settings {
|
||||||
|
exit_on_close_request: false,
|
||||||
|
..window::Settings::default()
|
||||||
|
},
|
||||||
..Settings::default()
|
..Settings::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -54,8 +57,9 @@ impl Application for Events {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::EventOccurred(event) => {
|
Message::EventOccurred(event) => {
|
||||||
if let Event::Window(window::Event::CloseRequested) = event {
|
if let Event::Window(id, window::Event::CloseRequested) = event
|
||||||
window::close()
|
{
|
||||||
|
window::close(id)
|
||||||
} else {
|
} else {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +69,7 @@ impl Application for Events {
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::Exit => window::close(),
|
Message::Exit => window::close(window::Id::MAIN),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,8 +82,7 @@ impl Application for Events {
|
||||||
self.last
|
self.last
|
||||||
.iter()
|
.iter()
|
||||||
.map(|event| text(format!("{event:?}")).size(40))
|
.map(|event| text(format!("{event:?}")).size(40))
|
||||||
.map(Element::from)
|
.map(Element::from),
|
||||||
.collect(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let toggle = checkbox(
|
let toggle = checkbox(
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ impl Application for Exit {
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Confirm => window::close(),
|
Message::Confirm => window::close(window::Id::MAIN),
|
||||||
Message::Exit => {
|
Message::Exit => {
|
||||||
self.show_confirm = true;
|
self.show_confirm = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ publish = false
|
||||||
iced.workspace = true
|
iced.workspace = true
|
||||||
iced.features = ["debug", "canvas", "tokio"]
|
iced.features = ["debug", "canvas", "tokio"]
|
||||||
|
|
||||||
itertools = "0.11"
|
itertools = "0.12"
|
||||||
rustc-hash.workspace = true
|
rustc-hash.workspace = true
|
||||||
tokio = { workspace = true, features = ["sync"] }
|
tokio = { workspace = true, features = ["sync"] }
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,8 @@ impl Application for GameOfLife {
|
||||||
.view()
|
.view()
|
||||||
.map(move |message| Message::Grid(message, version)),
|
.map(move |message| Message::Grid(message, version)),
|
||||||
controls,
|
controls,
|
||||||
];
|
]
|
||||||
|
.height(Length::Fill);
|
||||||
|
|
||||||
container(content)
|
container(content)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -178,7 +179,6 @@ fn view_controls<'a>(
|
||||||
slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
|
slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
|
||||||
text(format!("x{speed}")).size(16),
|
text(format!("x{speed}")).size(16),
|
||||||
]
|
]
|
||||||
.width(Length::Fill)
|
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.spacing(10);
|
.spacing(10);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,11 @@ mod rainbow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> Widget<Message, Renderer> for Rainbow {
|
impl<Message> Widget<Message, Renderer> for Rainbow {
|
||||||
fn width(&self) -> Length {
|
fn size(&self) -> Size<Length> {
|
||||||
Length::Fill
|
Size {
|
||||||
}
|
width: Length::Fill,
|
||||||
|
height: Length::Shrink,
|
||||||
fn height(&self) -> Length {
|
}
|
||||||
Length::Shrink
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -30,9 +29,9 @@ mod rainbow {
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let size = limits.width(Length::Fill).resolve(Size::ZERO);
|
let width = limits.max().width;
|
||||||
|
|
||||||
layout::Node::new(Size::new(size.width, size.width))
|
layout::Node::new(Size::new(width, width))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -81,32 +81,25 @@ impl Program for Controls {
|
||||||
);
|
);
|
||||||
|
|
||||||
Row::new()
|
Row::new()
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.align_items(Alignment::End)
|
.align_items(Alignment::End)
|
||||||
.push(
|
.push(
|
||||||
Column::new()
|
Column::new().align_items(Alignment::End).push(
|
||||||
.width(Length::Fill)
|
Column::new()
|
||||||
.align_items(Alignment::End)
|
.padding(10)
|
||||||
.push(
|
.spacing(10)
|
||||||
Column::new()
|
.push(Text::new("Background color").style(Color::WHITE))
|
||||||
.padding(10)
|
.push(sliders)
|
||||||
.spacing(10)
|
.push(
|
||||||
.push(
|
Text::new(format!("{background_color:?}"))
|
||||||
Text::new("Background color")
|
.size(14)
|
||||||
.style(Color::WHITE),
|
.style(Color::WHITE),
|
||||||
)
|
)
|
||||||
.push(sliders)
|
.push(
|
||||||
.push(
|
text_input("Placeholder", text)
|
||||||
Text::new(format!("{background_color:?}"))
|
.on_input(Message::TextChanged),
|
||||||
.size(14)
|
),
|
||||||
.style(Color::WHITE),
|
),
|
||||||
)
|
|
||||||
.push(
|
|
||||||
text_input("Placeholder", text)
|
|
||||||
.on_input(Message::TextChanged),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,26 @@ use scene::Scene;
|
||||||
|
|
||||||
use iced_wgpu::graphics::Viewport;
|
use iced_wgpu::graphics::Viewport;
|
||||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
||||||
|
use iced_winit::conversion;
|
||||||
use iced_winit::core::mouse;
|
use iced_winit::core::mouse;
|
||||||
use iced_winit::core::renderer;
|
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};
|
||||||
|
use iced_winit::futures;
|
||||||
use iced_winit::runtime::program;
|
use iced_winit::runtime::program;
|
||||||
use iced_winit::runtime::Debug;
|
use iced_winit::runtime::Debug;
|
||||||
use iced_winit::style::Theme;
|
use iced_winit::style::Theme;
|
||||||
use iced_winit::{conversion, futures, winit, Clipboard};
|
use iced_winit::winit;
|
||||||
|
use iced_winit::Clipboard;
|
||||||
|
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, ModifiersState, WindowEvent},
|
event::{Event, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
|
keyboard::ModifiersState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
|
@ -44,7 +51,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
// Initialize winit
|
// Initialize winit
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new()?;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
let window = winit::window::WindowBuilder::new()
|
let window = winit::window::WindowBuilder::new()
|
||||||
|
|
@ -54,6 +61,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let window = winit::window::Window::new(&event_loop)?;
|
let window = winit::window::Window::new(&event_loop)?;
|
||||||
|
|
||||||
|
let window = Arc::new(window);
|
||||||
|
|
||||||
let physical_size = window.inner_size();
|
let physical_size = window.inner_size();
|
||||||
let mut viewport = Viewport::with_physical_size(
|
let mut viewport = Viewport::with_physical_size(
|
||||||
Size::new(physical_size.width, physical_size.height),
|
Size::new(physical_size.width, physical_size.height),
|
||||||
|
|
@ -76,7 +85,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
backends: backend,
|
backends: backend,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let surface = unsafe { instance.create_surface(&window) }?;
|
let surface = instance.create_surface(window.clone())?;
|
||||||
|
|
||||||
let (format, (device, queue)) =
|
let (format, (device, queue)) =
|
||||||
futures::futures::executor::block_on(async {
|
futures::futures::executor::block_on(async {
|
||||||
|
|
@ -110,9 +119,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.request_device(
|
.request_device(
|
||||||
&wgpu::DeviceDescriptor {
|
&wgpu::DeviceDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
features: adapter_features
|
required_features: adapter_features
|
||||||
& wgpu::Features::default(),
|
& wgpu::Features::default(),
|
||||||
limits: needed_limits,
|
required_limits: needed_limits,
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
@ -131,6 +140,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
present_mode: wgpu::PresentMode::AutoVsync,
|
present_mode: wgpu::PresentMode::AutoVsync,
|
||||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||||
view_formats: vec![],
|
view_formats: vec![],
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -156,66 +166,15 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Run event loop
|
// Run event loop
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, window_target| {
|
||||||
// You should change this if you want to render continuosly
|
// You should change this if you want to render continuosly
|
||||||
*control_flow = ControlFlow::Wait;
|
window_target.set_control_flow(ControlFlow::Wait);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::WindowEvent { event, .. } => {
|
Event::WindowEvent {
|
||||||
match event {
|
event: WindowEvent::RedrawRequested,
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
..
|
||||||
cursor_position = Some(position);
|
} => {
|
||||||
}
|
|
||||||
WindowEvent::ModifiersChanged(new_modifiers) => {
|
|
||||||
modifiers = new_modifiers;
|
|
||||||
}
|
|
||||||
WindowEvent::Resized(_) => {
|
|
||||||
resized = true;
|
|
||||||
}
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
*control_flow = ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map window event to iced event
|
|
||||||
if let Some(event) = iced_winit::conversion::window_event(
|
|
||||||
&event,
|
|
||||||
window.scale_factor(),
|
|
||||||
modifiers,
|
|
||||||
) {
|
|
||||||
state.queue_event(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::MainEventsCleared => {
|
|
||||||
// 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,
|
|
||||||
&mut debug,
|
|
||||||
);
|
|
||||||
|
|
||||||
// and request a redraw
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::RedrawRequested(_) => {
|
|
||||||
if resized {
|
if resized {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
|
|
||||||
|
|
@ -234,6 +193,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
present_mode: wgpu::PresentMode::AutoVsync,
|
present_mode: wgpu::PresentMode::AutoVsync,
|
||||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||||
view_formats: vec![],
|
view_formats: vec![],
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -271,6 +231,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
&queue,
|
&queue,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
None,
|
None,
|
||||||
|
frame.texture.format(),
|
||||||
&view,
|
&view,
|
||||||
primitive,
|
primitive,
|
||||||
&viewport,
|
&viewport,
|
||||||
|
|
@ -303,7 +264,60 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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,
|
||||||
|
&mut debug,
|
||||||
|
);
|
||||||
|
|
||||||
|
// and request a redraw
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,12 @@ impl Scene {
|
||||||
a: a as f64,
|
a: a as f64,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
store: true,
|
store: wgpu::StoreOp::Store,
|
||||||
},
|
},
|
||||||
})],
|
})],
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
9
examples/layout/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "layout"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../..", features = ["canvas"] }
|
||||||
371
examples/layout/src/main.rs
Normal file
|
|
@ -0,0 +1,371 @@
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
use iced::{
|
||||||
|
color, Alignment, Application, Color, Command, Element, Font, Length,
|
||||||
|
Point, Rectangle, Renderer, Settings, Subscription, Theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
Layout::run(Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Layout {
|
||||||
|
example: Example,
|
||||||
|
explain: bool,
|
||||||
|
theme: Theme,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
Next,
|
||||||
|
Previous,
|
||||||
|
ExplainToggled(bool),
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
format!("{} - Layout - Iced", self.example.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Message) -> Command<Message> {
|
||||||
|
match message {
|
||||||
|
Message::Next => {
|
||||||
|
self.example = self.example.next();
|
||||||
|
}
|
||||||
|
Message::Previous => {
|
||||||
|
self.example = self.example.previous();
|
||||||
|
}
|
||||||
|
Message::ExplainToggled(explain) => {
|
||||||
|
self.explain = explain;
|
||||||
|
}
|
||||||
|
Message::ThemeSelected(theme) => {
|
||||||
|
self.theme = theme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
use keyboard::key;
|
||||||
|
|
||||||
|
keyboard::on_key_release(|key, _modifiers| match key {
|
||||||
|
keyboard::Key::Named(key::Named::ArrowLeft) => {
|
||||||
|
Some(Message::Previous)
|
||||||
|
}
|
||||||
|
keyboard::Key::Named(key::Named::ArrowRight) => Some(Message::Next),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
let header = row![
|
||||||
|
text(self.example.title).size(20).font(Font::MONOSPACE),
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
checkbox("Explain", self.explain, Message::ExplainToggled),
|
||||||
|
pick_list(
|
||||||
|
Theme::ALL,
|
||||||
|
Some(self.theme.clone()),
|
||||||
|
Message::ThemeSelected
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.spacing(20)
|
||||||
|
.align_items(Alignment::Center);
|
||||||
|
|
||||||
|
let example = container(if self.explain {
|
||||||
|
self.example.view().explain(color!(0x0000ff))
|
||||||
|
} else {
|
||||||
|
self.example.view()
|
||||||
|
})
|
||||||
|
.style(|theme: &Theme| {
|
||||||
|
let palette = theme.extended_palette();
|
||||||
|
|
||||||
|
container::Appearance::default()
|
||||||
|
.with_border(palette.background.strong.color, 4.0)
|
||||||
|
})
|
||||||
|
.padding(4)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y();
|
||||||
|
|
||||||
|
let controls = row([
|
||||||
|
(!self.example.is_first()).then_some(
|
||||||
|
button("← Previous")
|
||||||
|
.padding([5, 10])
|
||||||
|
.on_press(Message::Previous)
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
Some(horizontal_space(Length::Fill).into()),
|
||||||
|
(!self.example.is_last()).then_some(
|
||||||
|
button("Next →")
|
||||||
|
.padding([5, 10])
|
||||||
|
.on_press(Message::Next)
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.flatten());
|
||||||
|
|
||||||
|
column![header, example, controls]
|
||||||
|
.spacing(10)
|
||||||
|
.padding(20)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme(&self) -> Theme {
|
||||||
|
self.theme.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
struct Example {
|
||||||
|
title: &'static str,
|
||||||
|
view: fn() -> Element<'static, Message>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Example {
|
||||||
|
const LIST: &'static [Self] = &[
|
||||||
|
Self {
|
||||||
|
title: "Centered",
|
||||||
|
view: centered,
|
||||||
|
},
|
||||||
|
Self {
|
||||||
|
title: "Column",
|
||||||
|
view: column_,
|
||||||
|
},
|
||||||
|
Self {
|
||||||
|
title: "Row",
|
||||||
|
view: row_,
|
||||||
|
},
|
||||||
|
Self {
|
||||||
|
title: "Space",
|
||||||
|
view: space,
|
||||||
|
},
|
||||||
|
Self {
|
||||||
|
title: "Application",
|
||||||
|
view: application,
|
||||||
|
},
|
||||||
|
Self {
|
||||||
|
title: "Nested Quotes",
|
||||||
|
view: nested_quotes,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
fn is_first(self) -> bool {
|
||||||
|
Self::LIST.first() == Some(&self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_last(self) -> bool {
|
||||||
|
Self::LIST.last() == Some(&self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn previous(self) -> Self {
|
||||||
|
let Some(index) =
|
||||||
|
Self::LIST.iter().position(|&example| example == self)
|
||||||
|
else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::LIST
|
||||||
|
.get(index.saturating_sub(1))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(self) -> Self {
|
||||||
|
let Some(index) =
|
||||||
|
Self::LIST.iter().position(|&example| example == self)
|
||||||
|
else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::LIST.get(index + 1).copied().unwrap_or(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
(self.view)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Example {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::LIST[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn centered<'a>() -> Element<'a, Message> {
|
||||||
|
container(text("I am centered!").size(50))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_<'a>() -> Element<'a, Message> {
|
||||||
|
column![
|
||||||
|
"A column can be used to",
|
||||||
|
"lay out widgets vertically.",
|
||||||
|
square(50),
|
||||||
|
square(50),
|
||||||
|
square(50),
|
||||||
|
"The amount of space between",
|
||||||
|
"elements can be configured!",
|
||||||
|
]
|
||||||
|
.spacing(40)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_<'a>() -> Element<'a, Message> {
|
||||||
|
row![
|
||||||
|
"A row works like a column...",
|
||||||
|
square(50),
|
||||||
|
square(50),
|
||||||
|
square(50),
|
||||||
|
"but lays out widgets horizontally!",
|
||||||
|
]
|
||||||
|
.spacing(40)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn space<'a>() -> Element<'a, Message> {
|
||||||
|
row!["Left!", horizontal_space(Length::Fill), "Right!"].into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn application<'a>() -> Element<'a, Message> {
|
||||||
|
let header = container(
|
||||||
|
row![
|
||||||
|
square(40),
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
"Header!",
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
square(40),
|
||||||
|
]
|
||||||
|
.padding(10)
|
||||||
|
.align_items(Alignment::Center),
|
||||||
|
)
|
||||||
|
.style(|theme: &Theme| {
|
||||||
|
let palette = theme.extended_palette();
|
||||||
|
|
||||||
|
container::Appearance::default()
|
||||||
|
.with_border(palette.background.strong.color, 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
let sidebar = container(
|
||||||
|
column!["Sidebar!", square(50), square(50)]
|
||||||
|
.spacing(40)
|
||||||
|
.padding(10)
|
||||||
|
.width(200)
|
||||||
|
.align_items(Alignment::Center),
|
||||||
|
)
|
||||||
|
.style(theme::Container::Box)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_y();
|
||||||
|
|
||||||
|
let content = container(
|
||||||
|
scrollable(
|
||||||
|
column![
|
||||||
|
"Content!",
|
||||||
|
square(400),
|
||||||
|
square(200),
|
||||||
|
square(400),
|
||||||
|
"The end"
|
||||||
|
]
|
||||||
|
.spacing(40)
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.height(Length::Fill),
|
||||||
|
)
|
||||||
|
.padding(10);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
impl canvas::Program<Message> for Square {
|
||||||
|
type State = ();
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_state: &Self::State,
|
||||||
|
renderer: &Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
bounds: Rectangle,
|
||||||
|
_cursor: mouse::Cursor,
|
||||||
|
) -> Vec<canvas::Geometry> {
|
||||||
|
let mut frame = canvas::Frame::new(renderer, bounds.size());
|
||||||
|
|
||||||
|
let palette = theme.extended_palette();
|
||||||
|
|
||||||
|
frame.fill_rectangle(
|
||||||
|
Point::ORIGIN,
|
||||||
|
bounds.size(),
|
||||||
|
palette.background.strong.color,
|
||||||
|
);
|
||||||
|
|
||||||
|
vec![frame.into_geometry()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas(Square).width(size).height(size).into()
|
||||||
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ enum Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
const ALL: &[Color] = &[
|
const ALL: &'static [Color] = &[
|
||||||
Color::Black,
|
Color::Black,
|
||||||
Color::Red,
|
Color::Red,
|
||||||
Color::Orange,
|
Color::Orange,
|
||||||
|
|
@ -178,35 +178,23 @@ impl Sandbox for App {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
column(
|
column(items.into_iter().map(|item| {
|
||||||
items
|
let button = button("Delete")
|
||||||
.into_iter()
|
.on_press(Message::DeleteItem(item.clone()))
|
||||||
.map(|item| {
|
.style(theme::Button::Destructive);
|
||||||
let button = button("Delete")
|
|
||||||
.on_press(Message::DeleteItem(item.clone()))
|
|
||||||
.style(theme::Button::Destructive);
|
|
||||||
|
|
||||||
row![
|
row![
|
||||||
text(&item.name)
|
text(&item.name)
|
||||||
.style(theme::Text::Color(item.color.into())),
|
.style(theme::Text::Color(item.color.into())),
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
pick_list(
|
pick_list(Color::ALL, Some(item.color), move |color| {
|
||||||
Color::ALL,
|
Message::ItemColorChanged(item.clone(), color)
|
||||||
Some(item.color),
|
}),
|
||||||
move |color| {
|
button
|
||||||
Message::ItemColorChanged(
|
]
|
||||||
item.clone(),
|
.spacing(20)
|
||||||
color,
|
.into()
|
||||||
)
|
}))
|
||||||
}
|
|
||||||
),
|
|
||||||
button
|
|
||||||
]
|
|
||||||
.spacing(20)
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -244,12 +244,11 @@ where
|
||||||
tree::State::new(State::default())
|
tree::State::new(State::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(&self) -> Length {
|
fn size(&self) -> Size<Length> {
|
||||||
Length::Fixed(self.size)
|
Size {
|
||||||
}
|
width: Length::Fixed(self.size),
|
||||||
|
height: Length::Fixed(self.size),
|
||||||
fn height(&self) -> Length {
|
}
|
||||||
Length::Fixed(self.size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -258,10 +257,7 @@ where
|
||||||
_renderer: &iced::Renderer<Theme>,
|
_renderer: &iced::Renderer<Theme>,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let limits = limits.width(self.size).height(self.size);
|
layout::atomic(limits, self.size, self.size)
|
||||||
let size = limits.resolve(Size::ZERO);
|
|
||||||
|
|
||||||
layout::Node::new(size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn on_event(
|
||||||
|
|
@ -275,11 +271,9 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
const FRAME_RATE: u64 = 60;
|
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
if let Event::Window(_, window::Event::RedrawRequested(now)) = event {
|
||||||
state.animation = state.animation.timed_transition(
|
state.animation = state.animation.timed_transition(
|
||||||
self.cycle_duration,
|
self.cycle_duration,
|
||||||
self.rotation_duration,
|
self.rotation_duration,
|
||||||
|
|
@ -287,9 +281,7 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
state.cache.clear();
|
state.cache.clear();
|
||||||
shell.request_redraw(RedrawRequest::At(
|
shell.request_redraw(RedrawRequest::NextFrame);
|
||||||
now + Duration::from_millis(1000 / FRAME_RATE),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
|
|
|
||||||
|
|
@ -165,12 +165,11 @@ where
|
||||||
tree::State::new(State::default())
|
tree::State::new(State::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(&self) -> Length {
|
fn size(&self) -> Size<Length> {
|
||||||
self.width
|
Size {
|
||||||
}
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
fn height(&self) -> Length {
|
}
|
||||||
self.height
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -179,10 +178,7 @@ where
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let limits = limits.width(self.width).height(self.height);
|
layout::atomic(limits, self.width, self.height)
|
||||||
let size = limits.resolve(Size::ZERO);
|
|
||||||
|
|
||||||
layout::Node::new(size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn on_event(
|
||||||
|
|
@ -196,16 +192,12 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
const FRAME_RATE: u64 = 60;
|
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
if let Event::Window(_, window::Event::RedrawRequested(now)) = event {
|
||||||
*state = state.timed_transition(self.cycle_duration, now);
|
*state = state.timed_transition(self.cycle_duration, now);
|
||||||
|
|
||||||
shell.request_redraw(RedrawRequest::At(
|
shell.request_redraw(RedrawRequest::NextFrame);
|
||||||
now + Duration::from_millis(1000 / FRAME_RATE),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
|
|
|
||||||
|
|
@ -96,15 +96,14 @@ impl Application for LoadingSpinners {
|
||||||
|
|
||||||
container(
|
container(
|
||||||
column.push(
|
column.push(
|
||||||
row(vec![
|
row![
|
||||||
text("Cycle duration:").into(),
|
text("Cycle duration:"),
|
||||||
slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| {
|
slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| {
|
||||||
Message::CycleDurationChanged(x / 100.0)
|
Message::CycleDurationChanged(x / 100.0)
|
||||||
})
|
})
|
||||||
.width(200.0)
|
.width(200.0),
|
||||||
.into(),
|
text(format!("{:.2}s", self.cycle_duration)),
|
||||||
text(format!("{:.2}s", self.cycle_duration)).into(),
|
]
|
||||||
])
|
|
||||||
.align_items(iced::Alignment::Center)
|
.align_items(iced::Alignment::Center)
|
||||||
.spacing(20.0),
|
.spacing(20.0),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use iced::event::{self, Event};
|
use iced::event::{self, Event};
|
||||||
use iced::executor;
|
use iced::executor;
|
||||||
use iced::keyboard;
|
use iced::keyboard;
|
||||||
|
use iced::keyboard::key;
|
||||||
use iced::theme;
|
use iced::theme;
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
self, button, column, container, horizontal_space, pick_list, row, text,
|
self, button, column, container, horizontal_space, pick_list, row, text,
|
||||||
|
|
@ -85,8 +86,9 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
Message::Event(event) => match event {
|
Message::Event(event) => match event {
|
||||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
key_code: keyboard::KeyCode::Tab,
|
key: keyboard::Key::Named(key::Named::Tab),
|
||||||
modifiers,
|
modifiers,
|
||||||
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if modifiers.shift() {
|
if modifiers.shift() {
|
||||||
widget::focus_previous()
|
widget::focus_previous()
|
||||||
|
|
@ -95,7 +97,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
key_code: keyboard::KeyCode::Escape,
|
key: keyboard::Key::Named(key::Named::Escape),
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
self.hide_modal();
|
self.hide_modal();
|
||||||
|
|
@ -205,7 +207,8 @@ enum Plan {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plan {
|
impl Plan {
|
||||||
pub const ALL: &[Self] = &[Self::Basic, Self::Pro, Self::Enterprise];
|
pub const ALL: &'static [Self] =
|
||||||
|
&[Self::Basic, Self::Pro, Self::Enterprise];
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Plan {
|
impl fmt::Display for Plan {
|
||||||
|
|
@ -230,6 +233,7 @@ mod modal {
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::{
|
use iced::{
|
||||||
BorderRadius, Color, Element, Event, Length, Point, Rectangle, Size,
|
BorderRadius, Color, Element, Event, Length, Point, Rectangle, Size,
|
||||||
|
Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A widget that centers a modal element over some base element
|
/// A widget that centers a modal element over some base element
|
||||||
|
|
@ -279,12 +283,8 @@ mod modal {
|
||||||
tree.diff_children(&[&self.base, &self.modal]);
|
tree.diff_children(&[&self.base, &self.modal]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(&self) -> Length {
|
fn size(&self) -> Size<Length> {
|
||||||
self.base.as_widget().width()
|
self.base.as_widget().size()
|
||||||
}
|
|
||||||
|
|
||||||
fn height(&self) -> Length {
|
|
||||||
self.base.as_widget().height()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -412,22 +412,20 @@ mod modal {
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
_bounds: Size,
|
_bounds: Size,
|
||||||
position: Point,
|
position: Point,
|
||||||
|
_translation: Vector,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let limits = layout::Limits::new(Size::ZERO, self.size)
|
let limits = layout::Limits::new(Size::ZERO, self.size)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill);
|
.height(Length::Fill);
|
||||||
|
|
||||||
let mut child = self
|
let child = self
|
||||||
.content
|
.content
|
||||||
.as_widget()
|
.as_widget()
|
||||||
.layout(self.tree, renderer, &limits);
|
.layout(self.tree, renderer, &limits)
|
||||||
|
.align(Alignment::Center, Alignment::Center, limits.max());
|
||||||
|
|
||||||
child.align(Alignment::Center, Alignment::Center, limits.max());
|
layout::Node::with_children(self.size, vec![child])
|
||||||
|
.move_to(position)
|
||||||
let mut node = layout::Node::with_children(self.size, vec![child]);
|
|
||||||
node.move_to(position);
|
|
||||||
|
|
||||||
node
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn on_event(
|
||||||
|
|
|
||||||
9
examples/multi_window/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "multi_window"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Bingus <shankern@protonmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../..", features = ["debug", "multi-window"] }
|
||||||
215
examples/multi_window/src/main.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
use iced::event;
|
||||||
|
use iced::executor;
|
||||||
|
use iced::multi_window::{self, Application};
|
||||||
|
use iced::widget::{button, column, container, scrollable, text, text_input};
|
||||||
|
use iced::window;
|
||||||
|
use iced::{
|
||||||
|
Alignment, Command, Element, Length, Point, Settings, Subscription, Theme,
|
||||||
|
Vector,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn main() -> iced::Result {
|
||||||
|
Example::run(Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Example {
|
||||||
|
windows: HashMap<window::Id, Window>,
|
||||||
|
next_window_pos: window::Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Window {
|
||||||
|
title: String,
|
||||||
|
scale_input: String,
|
||||||
|
current_scale: f64,
|
||||||
|
theme: Theme,
|
||||||
|
input_id: iced::widget::text_input::Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
ScaleInputChanged(window::Id, String),
|
||||||
|
ScaleChanged(window::Id, String),
|
||||||
|
TitleChanged(window::Id, String),
|
||||||
|
CloseWindow(window::Id),
|
||||||
|
WindowOpened(window::Id, Option<Point>),
|
||||||
|
WindowClosed(window::Id),
|
||||||
|
NewWindow,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl multi_window::Application for Example {
|
||||||
|
type Executor = executor::Default;
|
||||||
|
type Message = Message;
|
||||||
|
type Theme = Theme;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||||
|
(
|
||||||
|
Example {
|
||||||
|
windows: HashMap::from([(window::Id::MAIN, Window::new(1))]),
|
||||||
|
next_window_pos: window::Position::Default,
|
||||||
|
},
|
||||||
|
Command::none(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self, window: window::Id) -> String {
|
||||||
|
self.windows
|
||||||
|
.get(&window)
|
||||||
|
.map(|window| window.title.clone())
|
||||||
|
.unwrap_or("Example".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
match message {
|
||||||
|
Message::ScaleInputChanged(id, scale) => {
|
||||||
|
let window =
|
||||||
|
self.windows.get_mut(&id).expect("Window not found!");
|
||||||
|
window.scale_input = scale;
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::ScaleChanged(id, scale) => {
|
||||||
|
let window =
|
||||||
|
self.windows.get_mut(&id).expect("Window not found!");
|
||||||
|
|
||||||
|
window.current_scale = scale
|
||||||
|
.parse::<f64>()
|
||||||
|
.unwrap_or(window.current_scale)
|
||||||
|
.clamp(0.5, 5.0);
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::TitleChanged(id, title) => {
|
||||||
|
let window =
|
||||||
|
self.windows.get_mut(&id).expect("Window not found.");
|
||||||
|
|
||||||
|
window.title = title;
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::CloseWindow(id) => window::close(id),
|
||||||
|
Message::WindowClosed(id) => {
|
||||||
|
self.windows.remove(&id);
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::WindowOpened(id, position) => {
|
||||||
|
if let Some(position) = position {
|
||||||
|
self.next_window_pos = window::Position::Specific(
|
||||||
|
position + Vector::new(20.0, 20.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(window) = self.windows.get(&id) {
|
||||||
|
text_input::focus(window.input_id.clone())
|
||||||
|
} else {
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::NewWindow => {
|
||||||
|
let count = self.windows.len() + 1;
|
||||||
|
|
||||||
|
let (id, spawn_window) = window::spawn(window::Settings {
|
||||||
|
position: self.next_window_pos,
|
||||||
|
exit_on_close_request: count % 2 == 0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
self.windows.insert(id, Window::new(count));
|
||||||
|
|
||||||
|
spawn_window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme(&self, window: window::Id) -> Self::Theme {
|
||||||
|
self.windows.get(&window).unwrap().theme.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_factor(&self, window: window::Id) -> f64 {
|
||||||
|
self.windows
|
||||||
|
.get(&window)
|
||||||
|
.map(|window| window.current_scale)
|
||||||
|
.unwrap_or(1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
|
event::listen_with(|event, _| {
|
||||||
|
if let iced::Event::Window(id, window_event) = event {
|
||||||
|
match window_event {
|
||||||
|
window::Event::CloseRequested => {
|
||||||
|
Some(Message::CloseWindow(id))
|
||||||
|
}
|
||||||
|
window::Event::Opened { position, .. } => {
|
||||||
|
Some(Message::WindowOpened(id, position))
|
||||||
|
}
|
||||||
|
window::Event::Closed => Some(Message::WindowClosed(id)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
fn new(count: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
title: format!("Window_{}", count),
|
||||||
|
scale_input: "1.0".to_string(),
|
||||||
|
current_scale: 1.0,
|
||||||
|
theme: if count % 2 == 0 {
|
||||||
|
Theme::Light
|
||||||
|
} else {
|
||||||
|
Theme::Dark
|
||||||
|
},
|
||||||
|
input_id: text_input::Id::unique(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
|
let scale_input = column![
|
||||||
|
text("Window scale factor:"),
|
||||||
|
text_input("Window Scale", &self.scale_input)
|
||||||
|
.on_input(move |msg| { Message::ScaleInputChanged(id, msg) })
|
||||||
|
.on_submit(Message::ScaleChanged(
|
||||||
|
id,
|
||||||
|
self.scale_input.to_string()
|
||||||
|
))
|
||||||
|
];
|
||||||
|
|
||||||
|
let title_input = column![
|
||||||
|
text("Window title:"),
|
||||||
|
text_input("Window Title", &self.title)
|
||||||
|
.on_input(move |msg| { Message::TitleChanged(id, msg) })
|
||||||
|
.id(self.input_id.clone())
|
||||||
|
];
|
||||||
|
|
||||||
|
let new_window_button =
|
||||||
|
button(text("New Window")).on_press(Message::NewWindow);
|
||||||
|
|
||||||
|
let content = scrollable(
|
||||||
|
column![scale_input, title_input, new_window_button]
|
||||||
|
.spacing(50)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.align_items(Alignment::Center),
|
||||||
|
);
|
||||||
|
|
||||||
|
container(content).width(200).center_x().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -220,23 +220,26 @@ const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb(
|
||||||
0x47 as f32 / 255.0,
|
0x47 as f32 / 255.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
|
fn handle_hotkey(key: keyboard::Key) -> Option<Message> {
|
||||||
use keyboard::KeyCode;
|
use keyboard::key::{self, Key};
|
||||||
use pane_grid::{Axis, Direction};
|
use pane_grid::{Axis, Direction};
|
||||||
|
|
||||||
let direction = match key_code {
|
match key.as_ref() {
|
||||||
KeyCode::Up => Some(Direction::Up),
|
Key::Character("v") => Some(Message::SplitFocused(Axis::Vertical)),
|
||||||
KeyCode::Down => Some(Direction::Down),
|
Key::Character("h") => Some(Message::SplitFocused(Axis::Horizontal)),
|
||||||
KeyCode::Left => Some(Direction::Left),
|
Key::Character("w") => Some(Message::CloseFocused),
|
||||||
KeyCode::Right => Some(Direction::Right),
|
Key::Named(key) => {
|
||||||
_ => None,
|
let direction = match key {
|
||||||
};
|
key::Named::ArrowUp => Some(Direction::Up),
|
||||||
|
key::Named::ArrowDown => Some(Direction::Down),
|
||||||
|
key::Named::ArrowLeft => Some(Direction::Left),
|
||||||
|
key::Named::ArrowRight => Some(Direction::Right),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
match key_code {
|
direction.map(Message::FocusAdjacent)
|
||||||
KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)),
|
}
|
||||||
KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)),
|
_ => None,
|
||||||
KeyCode::W => Some(Message::CloseFocused),
|
|
||||||
_ => direction.map(Message::FocusAdjacent),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,7 +300,6 @@ fn view_content<'a>(
|
||||||
text(format!("{}x{}", size.width, size.height)).size(24),
|
text(format!("{}x{}", size.width, size.height)).size(24),
|
||||||
controls,
|
controls,
|
||||||
]
|
]
|
||||||
.width(Length::Fill)
|
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_items(Alignment::Center);
|
.align_items(Alignment::Center);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use iced::widget::{column, container, pick_list, scrollable, vertical_space};
|
use iced::widget::{column, pick_list, scrollable, vertical_space};
|
||||||
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
|
|
@ -52,12 +52,7 @@ impl Sandbox for Example {
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.spacing(10);
|
.spacing(10);
|
||||||
|
|
||||||
container(scrollable(content))
|
scrollable(content).into()
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Fill)
|
|
||||||
.center_x()
|
|
||||||
.center_y()
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ A simple progress bar that can be filled by using a slider.
|
||||||
The __[`main`]__ file contains all the code of the example.
|
The __[`main`]__ file contains all the code of the example.
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://iced.rs/examples/pokedex.gif">
|
<img src="https://iced.rs/examples/progress_bar.gif">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can run it with `cargo run`:
|
You can run it with `cargo run`:
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use iced::alignment;
|
use iced::alignment;
|
||||||
use iced::keyboard::KeyCode;
|
use iced::executor;
|
||||||
use iced::theme::{Button, Container};
|
use iced::keyboard;
|
||||||
|
use iced::theme;
|
||||||
use iced::widget::{button, column, container, image, row, text, text_input};
|
use iced::widget::{button, column, container, image, row, text, text_input};
|
||||||
|
use iced::window;
|
||||||
use iced::window::screenshot::{self, Screenshot};
|
use iced::window::screenshot::{self, Screenshot};
|
||||||
use iced::{
|
use iced::{
|
||||||
event, executor, keyboard, Alignment, Application, Command, ContentFit,
|
Alignment, Application, Command, ContentFit, Element, Length, Rectangle,
|
||||||
Element, Event, Length, Rectangle, Renderer, Subscription, Theme,
|
Renderer, Subscription, Theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ::image as img;
|
use ::image as img;
|
||||||
|
|
@ -70,7 +72,10 @@ impl Application for Example {
|
||||||
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Screenshot => {
|
Message::Screenshot => {
|
||||||
return iced::window::screenshot(Message::ScreenshotData);
|
return iced::window::screenshot(
|
||||||
|
window::Id::MAIN,
|
||||||
|
Message::ScreenshotData,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Message::ScreenshotData(screenshot) => {
|
Message::ScreenshotData(screenshot) => {
|
||||||
self.screenshot = Some(screenshot);
|
self.screenshot = Some(screenshot);
|
||||||
|
|
@ -144,7 +149,7 @@ impl Application for Example {
|
||||||
|
|
||||||
let image = container(image)
|
let image = container(image)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.style(Container::Box)
|
.style(theme::Container::Box)
|
||||||
.width(Length::FillPortion(2))
|
.width(Length::FillPortion(2))
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.center_x()
|
.center_x()
|
||||||
|
|
@ -199,9 +204,10 @@ impl Application for Example {
|
||||||
self.screenshot.is_some().then(|| Message::Png),
|
self.screenshot.is_some().then(|| Message::Png),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
button(centered_text("Saving...")).style(Button::Secondary)
|
button(centered_text("Saving..."))
|
||||||
|
.style(theme::Button::Secondary)
|
||||||
}
|
}
|
||||||
.style(Button::Secondary)
|
.style(theme::Button::Secondary)
|
||||||
.padding([10, 20, 10, 20])
|
.padding([10, 20, 10, 20])
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
]
|
]
|
||||||
|
|
@ -210,7 +216,7 @@ impl Application for Example {
|
||||||
crop_controls,
|
crop_controls,
|
||||||
button(centered_text("Crop"))
|
button(centered_text("Crop"))
|
||||||
.on_press(Message::Crop)
|
.on_press(Message::Crop)
|
||||||
.style(Button::Destructive)
|
.style(theme::Button::Destructive)
|
||||||
.padding([10, 20, 10, 20])
|
.padding([10, 20, 10, 20])
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
]
|
]
|
||||||
|
|
@ -253,16 +259,10 @@ impl Application for Example {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Self::Message> {
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
event::listen_with(|event, status| {
|
use keyboard::key;
|
||||||
if let event::Status::Captured = status {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Event::Keyboard(keyboard::Event::KeyPressed {
|
keyboard::on_key_press(|key, _modifiers| {
|
||||||
key_code: KeyCode::F5,
|
if let keyboard::Key::Named(key::Named::F5) = key {
|
||||||
..
|
|
||||||
}) = event
|
|
||||||
{
|
|
||||||
Some(Message::Screenshot)
|
Some(Message::Screenshot)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -298,10 +298,7 @@ fn numeric_input(
|
||||||
) -> Element<'_, Option<u32>> {
|
) -> Element<'_, Option<u32>> {
|
||||||
text_input(
|
text_input(
|
||||||
placeholder,
|
placeholder,
|
||||||
&value
|
&value.as_ref().map(ToString::to_string).unwrap_or_default(),
|
||||||
.as_ref()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.unwrap_or_else(String::new),
|
|
||||||
)
|
)
|
||||||
.on_input(move |text| {
|
.on_input(move |text| {
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
|
|
|
||||||