Merge branch 'master' into remove-vertex-indexing
|
|
@ -17,8 +17,6 @@ clippy --workspace --no-deps -- \
|
|||
-D clippy::useless_conversion
|
||||
"""
|
||||
|
||||
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
|
||||
|
||||
nitpick = """
|
||||
clippy --workspace --no-deps -- \
|
||||
-D warnings \
|
||||
|
|
|
|||
1
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
|
|
@ -25,7 +25,6 @@ body:
|
|||
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 `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!
|
||||
|
||||
|
|
|
|||
6
.github/workflows/audit.yml
vendored
|
|
@ -12,6 +12,8 @@ jobs:
|
|||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
- uses: actions/checkout@master
|
||||
- name: Resolve dependencies
|
||||
run: cargo update
|
||||
- name: Audit vulnerabilities
|
||||
run: cargo audit
|
||||
|
||||
|
|
@ -22,5 +24,7 @@ jobs:
|
|||
- name: Install cargo-outdated
|
||||
run: cargo install cargo-outdated
|
||||
- uses: actions/checkout@master
|
||||
- name: Delete `web-sys` dependency from `integration` example
|
||||
run: sed -i '$d' examples/integration/Cargo.toml
|
||||
- name: Find outdated dependencies
|
||||
run: cargo outdated --workspace --exit-code 1
|
||||
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:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: nightly
|
||||
rust-version: nightly-2023-12-11
|
||||
- uses: actions/checkout@v2
|
||||
- name: Generate documentation
|
||||
run: |
|
||||
RUSTDOCFLAGS="--cfg docsrs" \
|
||||
cargo doc --no-deps --all-features \
|
||||
-p iced_core \
|
||||
-p iced_highlighter \
|
||||
-p iced_style \
|
||||
-p iced_futures \
|
||||
-p iced_runtime \
|
||||
|
|
|
|||
2
.github/workflows/lint.yml
vendored
|
|
@ -2,7 +2,7 @@ name: Lint
|
|||
on: [push, pull_request]
|
||||
jobs:
|
||||
all:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
|
|
|
|||
25
.github/workflows/test.yml
vendored
|
|
@ -1,8 +1,10 @@
|
|||
name: Test
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
native:
|
||||
all:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
RUSTFLAGS: --deny warnings
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
|
|
@ -17,27 +19,8 @@ jobs:
|
|||
run: |
|
||||
export DEBIAN_FRONTED=noninteractive
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -y libxkbcommon-dev
|
||||
sudo apt-get install -y libxkbcommon-dev libgtk-3-dev
|
||||
- name: Run tests
|
||||
run: |
|
||||
cargo test --verbose --workspace
|
||||
cargo test --verbose --workspace --all-features
|
||||
|
||||
web:
|
||||
runs-on: ubuntu-latest
|
||||
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]
|
||||
default = ["wgpu"]
|
||||
# Enable the `wgpu` GPU-accelerated renderer backend
|
||||
wgpu = ["iced_renderer/wgpu"]
|
||||
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
|
||||
# Enables the `Image` widget
|
||||
image = ["iced_widget/image", "dep:image"]
|
||||
# Enables the `Svg` widget
|
||||
|
|
@ -47,6 +47,10 @@ system = ["iced_winit/system"]
|
|||
web-colors = ["iced_renderer/web-colors"]
|
||||
# Enables the WebGL backend, replacing WebGPU
|
||||
webgl = ["iced_renderer/webgl"]
|
||||
# Enables the syntax `highlighter` module
|
||||
highlighter = ["iced_highlighter"]
|
||||
# Enables experimental multi-window support.
|
||||
multi-window = ["iced_winit/multi-window"]
|
||||
# Enables the advanced module
|
||||
advanced = []
|
||||
|
||||
|
|
@ -58,6 +62,9 @@ iced_widget.workspace = true
|
|||
iced_winit.features = ["application"]
|
||||
iced_winit.workspace = true
|
||||
|
||||
iced_highlighter.workspace = true
|
||||
iced_highlighter.optional = true
|
||||
|
||||
thiserror.workspace = true
|
||||
|
||||
image.workspace = true
|
||||
|
|
@ -78,8 +85,9 @@ members = [
|
|||
"core",
|
||||
"futures",
|
||||
"graphics",
|
||||
"runtime",
|
||||
"highlighter",
|
||||
"renderer",
|
||||
"runtime",
|
||||
"style",
|
||||
"tiny_skia",
|
||||
"wgpu",
|
||||
|
|
@ -103,6 +111,7 @@ iced = { version = "0.12", path = "." }
|
|||
iced_core = { version = "0.12", path = "core" }
|
||||
iced_futures = { version = "0.12", path = "futures" }
|
||||
iced_graphics = { version = "0.12", path = "graphics" }
|
||||
iced_highlighter = { version = "0.12", path = "highlighter" }
|
||||
iced_renderer = { version = "0.12", path = "renderer" }
|
||||
iced_runtime = { version = "0.12", path = "runtime" }
|
||||
iced_style = { version = "0.12", path = "style" }
|
||||
|
|
@ -114,14 +123,13 @@ iced_winit = { version = "0.12", path = "winit" }
|
|||
async-std = "1.0"
|
||||
bitflags = "1.0"
|
||||
bytemuck = { version = "1.0", features = ["derive"] }
|
||||
cosmic-text = "0.9"
|
||||
cosmic-text = "0.10"
|
||||
futures = "0.3"
|
||||
glam = "0.24"
|
||||
glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "20f0f8fa80e0d0df4c63634ce9176fa489546ca9" }
|
||||
glyphon = "0.5"
|
||||
guillotiere = "0.6"
|
||||
half = "2.2"
|
||||
image = "0.24"
|
||||
instant = "0.1"
|
||||
kamadak-exif = "0.5"
|
||||
kurbo = "0.9"
|
||||
log = "0.4"
|
||||
|
|
@ -132,22 +140,25 @@ once_cell = "1.0"
|
|||
ouroboros = "0.17"
|
||||
palette = "0.7"
|
||||
qrcode = { version = "0.12", default-features = false }
|
||||
raw-window-handle = "0.5"
|
||||
resvg = "0.35"
|
||||
raw-window-handle = "0.6"
|
||||
resvg = "0.36"
|
||||
rustc-hash = "1.0"
|
||||
smol = "1.0"
|
||||
softbuffer = "0.2"
|
||||
smol_str = "0.2"
|
||||
softbuffer = "0.4"
|
||||
syntect = "5.1"
|
||||
sysinfo = "0.28"
|
||||
thiserror = "1.0"
|
||||
tiny-skia = "0.10"
|
||||
tiny-skia = "0.11"
|
||||
tokio = "1.0"
|
||||
tracing = "0.1"
|
||||
twox-hash = { version = "1.0", default-features = false }
|
||||
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
||||
unicode-segmentation = "1.0"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
wasm-timer = "0.2"
|
||||
web-sys = "0.3"
|
||||
wgpu = "0.17"
|
||||
web-time = "0.2"
|
||||
wgpu = "0.19"
|
||||
winapi = "0.3"
|
||||
window_clipboard = "0.3"
|
||||
winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e", default-features = false }
|
||||
window_clipboard = "0.4"
|
||||
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:
|
||||
|
||||
- [`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.
|
||||
|
||||
|
|
@ -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].
|
||||
|
||||
As of now, there are two official shells:
|
||||
|
||||
- [`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].
|
||||
As of now, there is one official shell: [`iced_winit`] implements a shell runtime on top of [`winit`].
|
||||
|
||||
## The web target
|
||||
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
|
||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||
[`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/
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
[](https://github.com/iced-rs/iced/blob/master/LICENSE)
|
||||
[](https://crates.io/crates/iced)
|
||||
[](https://github.com/iced-rs/iced/actions)
|
||||
[](https://discourse.iced.rs/)
|
||||
[](https://discourse.iced.rs/)
|
||||
[](https://discord.gg/3xZJ65GAhd)
|
||||
|
||||
A cross-platform GUI library for Rust focused on simplicity and type-safety.
|
||||
|
|
|
|||
|
|
@ -13,15 +13,17 @@ keywords.workspace = true
|
|||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
log.workspace = true
|
||||
thiserror.workspace = true
|
||||
twox-hash.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.optional = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
instant.workspace = true
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
raw-window-handle.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
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.
|
||||
#[must_use]
|
||||
pub fn into_rgba8(self) -> [u8; 4] {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::renderer;
|
|||
use crate::widget;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget,
|
||||
Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
|
||||
};
|
||||
|
||||
use std::any::Any;
|
||||
|
|
@ -296,12 +296,8 @@ where
|
|||
self.widget.diff(tree);
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.widget.width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.widget.height()
|
||||
fn size(&self) -> Size<Length> {
|
||||
self.widget.size()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -466,12 +462,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
|||
where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.element.widget.width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.element.widget.height()
|
||||
fn size(&self) -> Size<Length> {
|
||||
self.element.widget.size()
|
||||
}
|
||||
|
||||
fn tag(&self) -> tree::Tag {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ pub enum Event {
|
|||
Mouse(mouse::Event),
|
||||
|
||||
/// A window event
|
||||
Window(window::Event),
|
||||
Window(window::Id, window::Event),
|
||||
|
||||
/// A touch event
|
||||
Touch(touch::Event),
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ pub struct Font {
|
|||
pub stretch: Stretch,
|
||||
/// The [`Style`] of the [`Font`].
|
||||
pub style: Style,
|
||||
/// Whether if the [`Font`] is monospaced or not.
|
||||
pub monospaced: bool,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
|
|
@ -23,13 +21,11 @@ impl Font {
|
|||
weight: Weight::Normal,
|
||||
stretch: Stretch::Normal,
|
||||
style: Style::Normal,
|
||||
monospaced: false,
|
||||
};
|
||||
|
||||
/// A monospaced font with normal [`Weight`].
|
||||
pub const MONOSPACE: Font = Font {
|
||||
family: Family::Monospace,
|
||||
monospaced: true,
|
||||
..Self::DEFAULT
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/// The hasher used to compare layouts.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Hasher(twox_hash::XxHash64);
|
||||
#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways.
|
||||
#[derive(Default)]
|
||||
pub struct Hasher(xxhash_rust::xxh3::Xxh3);
|
||||
|
||||
impl core::hash::Hasher for Hasher {
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,16 @@ impl std::fmt::Debug for Data {
|
|||
}
|
||||
}
|
||||
|
||||
/// Image filtering strategy.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub enum FilterMethod {
|
||||
/// Bilinear interpolation.
|
||||
#[default]
|
||||
Linear,
|
||||
/// Nearest neighbor.
|
||||
Nearest,
|
||||
}
|
||||
|
||||
/// A [`Renderer`] that can render raster graphics.
|
||||
///
|
||||
/// [renderer]: crate::renderer
|
||||
|
|
@ -178,5 +188,10 @@ pub trait Renderer: crate::Renderer {
|
|||
|
||||
/// Draws an image with the given [`Handle`] and inside the provided
|
||||
/// `bounds`.
|
||||
fn draw(&mut self, handle: Self::Handle, bounds: Rectangle);
|
||||
fn draw(
|
||||
&mut self,
|
||||
handle: Self::Handle,
|
||||
filter_method: FilterMethod,
|
||||
bounds: Rectangle,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
//! Listen to keyboard events.
|
||||
pub mod key;
|
||||
|
||||
mod event;
|
||||
mod key_code;
|
||||
mod location;
|
||||
mod modifiers;
|
||||
|
||||
pub use event::Event;
|
||||
pub use key_code::KeyCode;
|
||||
pub use key::Key;
|
||||
pub use location::Location;
|
||||
pub use modifiers::Modifiers;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use super::{KeyCode, Modifiers};
|
||||
use crate::keyboard::{Key, Location, Modifiers};
|
||||
use crate::SmolStr;
|
||||
|
||||
/// A keyboard event.
|
||||
///
|
||||
|
|
@ -6,29 +7,35 @@ use super::{KeyCode, Modifiers};
|
|||
/// additional events, feel free to [open an issue] and share your use case!_
|
||||
///
|
||||
/// [open an issue]: https://github.com/iced-rs/iced/issues
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
/// A keyboard key was pressed.
|
||||
KeyPressed {
|
||||
/// The key identifier
|
||||
key_code: KeyCode,
|
||||
/// The key pressed.
|
||||
key: Key,
|
||||
|
||||
/// The state of the modifier keys
|
||||
/// The location of the key.
|
||||
location: Location,
|
||||
|
||||
/// The state of the modifier keys.
|
||||
modifiers: Modifiers,
|
||||
|
||||
/// The text produced by the key press, if any.
|
||||
text: Option<SmolStr>,
|
||||
},
|
||||
|
||||
/// A keyboard key was released.
|
||||
KeyReleased {
|
||||
/// The key identifier
|
||||
key_code: KeyCode,
|
||||
/// The key released.
|
||||
key: Key,
|
||||
|
||||
/// The state of the modifier keys
|
||||
/// The location of the key.
|
||||
location: Location,
|
||||
|
||||
/// The state of the modifier keys.
|
||||
modifiers: Modifiers,
|
||||
},
|
||||
|
||||
/// A unicode character was received.
|
||||
CharacterReceived(char),
|
||||
|
||||
/// The keyboard modifiers have changed.
|
||||
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 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.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -71,12 +71,12 @@ pub fn next_to_each_other(
|
|||
left: impl FnOnce(&Limits) -> Node,
|
||||
right: impl FnOnce(&Limits) -> Node,
|
||||
) -> Node {
|
||||
let mut left_node = left(limits);
|
||||
let left_node = left(limits);
|
||||
let left_size = left_node.size();
|
||||
|
||||
let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0));
|
||||
|
||||
let mut right_node = right(&right_limits);
|
||||
let right_node = right(&right_limits);
|
||||
let right_size = right_node.size();
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
left_node.move_to(Point::new(0.0, left_y));
|
||||
right_node.move_to(Point::new(left_size.width + spacing, right_y));
|
||||
|
||||
Node::with_children(
|
||||
Size::new(
|
||||
left_size.width + spacing + right_size.width,
|
||||
left_size.height.max(right_size.height),
|
||||
),
|
||||
vec![left_node, right_node],
|
||||
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::widget;
|
||||
use crate::{Alignment, Padding, Point, Size};
|
||||
use crate::{Alignment, Length, Padding, Point, Size};
|
||||
|
||||
/// The main axis of a flex layout.
|
||||
#[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 {
|
||||
Axis::Horizontal => (main, cross),
|
||||
Axis::Vertical => (cross, main),
|
||||
|
|
@ -63,6 +63,8 @@ pub fn resolve<Message, Renderer>(
|
|||
axis: Axis,
|
||||
renderer: &Renderer,
|
||||
limits: &Limits,
|
||||
width: Length,
|
||||
height: Length,
|
||||
padding: Padding,
|
||||
spacing: f32,
|
||||
align_items: Alignment,
|
||||
|
|
@ -72,26 +74,64 @@ pub fn resolve<Message, Renderer>(
|
|||
where
|
||||
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 max_cross = axis.cross(limits.max());
|
||||
|
||||
let mut fill_sum = 0;
|
||||
let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill()));
|
||||
let mut fill_main_sum = 0;
|
||||
let mut cross = match axis {
|
||||
Axis::Horizontal => match height {
|
||||
Length::Shrink => 0.0,
|
||||
_ => max_cross,
|
||||
},
|
||||
Axis::Vertical => match width {
|
||||
Length::Shrink => 0.0,
|
||||
_ => max_cross,
|
||||
},
|
||||
};
|
||||
|
||||
let mut available = axis.main(limits.max()) - total_spacing;
|
||||
|
||||
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||
nodes.resize(items.len(), Node::default());
|
||||
|
||||
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
}
|
||||
.fill_factor();
|
||||
let (fill_main_factor, fill_cross_factor) = {
|
||||
let size = child.as_widget().size();
|
||||
|
||||
if fill_factor == 0 {
|
||||
let (max_width, max_height) = axis.pack(available, max_cross);
|
||||
axis.pack(size.width.fill_factor(), size.height.fill_factor())
|
||||
};
|
||||
|
||||
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 =
|
||||
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
||||
|
|
@ -101,34 +141,47 @@ where
|
|||
let size = layout.size();
|
||||
|
||||
available -= axis.main(size);
|
||||
cross = cross.max(axis.cross(size));
|
||||
cross = cross.max(axis.cross(layout.size()));
|
||||
|
||||
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() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
}
|
||||
.fill_factor();
|
||||
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 {
|
||||
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() {
|
||||
0.0
|
||||
} else {
|
||||
max_main
|
||||
};
|
||||
|
||||
let (min_width, min_height) =
|
||||
axis.pack(min_main, axis.cross(limits.min()));
|
||||
let max_cross = if fill_cross_factor == 0 {
|
||||
max_cross
|
||||
} else {
|
||||
cross
|
||||
};
|
||||
|
||||
let (min_width, min_height) = axis.pack(min_main, 0.0);
|
||||
let (max_width, max_height) = axis.pack(max_main, max_cross);
|
||||
|
||||
let child_limits = Limits::new(
|
||||
|
|
@ -154,18 +207,18 @@ where
|
|||
|
||||
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 {
|
||||
Axis::Horizontal => {
|
||||
node.align(
|
||||
node.align_mut(
|
||||
Alignment::Start,
|
||||
align_items,
|
||||
Size::new(0.0, cross),
|
||||
);
|
||||
}
|
||||
Axis::Vertical => {
|
||||
node.align(
|
||||
node.align_mut(
|
||||
align_items,
|
||||
Alignment::Start,
|
||||
Size::new(cross, 0.0),
|
||||
|
|
@ -178,8 +231,12 @@ where
|
|||
main += axis.main(size);
|
||||
}
|
||||
|
||||
let (width, height) = axis.pack(main - pad.0, cross);
|
||||
let size = limits.resolve(Size::new(width, height));
|
||||
let (intrinsic_width, intrinsic_height) = axis.pack(main - pad.0, cross);
|
||||
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)]
|
||||
use crate::{Length, Padding, Size};
|
||||
use crate::{Length, Size};
|
||||
|
||||
/// A set of size constraints for layouting.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Limits {
|
||||
min: Size,
|
||||
max: Size,
|
||||
fill: Size,
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
|
|
@ -14,16 +13,11 @@ impl Limits {
|
|||
pub const NONE: Limits = Limits {
|
||||
min: Size::ZERO,
|
||||
max: Size::INFINITY,
|
||||
fill: Size::INFINITY,
|
||||
};
|
||||
|
||||
/// Creates new [`Limits`] with the given minimum and maximum [`Size`].
|
||||
pub const fn new(min: Size, max: Size) -> Limits {
|
||||
Limits {
|
||||
min,
|
||||
max,
|
||||
fill: Size::INFINITY,
|
||||
}
|
||||
Limits { min, max }
|
||||
}
|
||||
|
||||
/// Returns the minimum [`Size`] of the [`Limits`].
|
||||
|
|
@ -36,26 +30,15 @@ impl Limits {
|
|||
self.max
|
||||
}
|
||||
|
||||
/// Returns the fill [`Size`] of the [`Limits`].
|
||||
pub fn fill(&self) -> Size {
|
||||
self.fill
|
||||
}
|
||||
|
||||
/// Applies a width constraint to the current [`Limits`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Limits {
|
||||
match width.into() {
|
||||
Length::Shrink => {
|
||||
self.fill.width = self.min.width;
|
||||
}
|
||||
Length::Fill | Length::FillPortion(_) => {
|
||||
self.fill.width = self.fill.width.min(self.max.width);
|
||||
}
|
||||
Length::Shrink | Length::Fill | Length::FillPortion(_) => {}
|
||||
Length::Fixed(amount) => {
|
||||
let new_width = amount.min(self.max.width).max(self.min.width);
|
||||
|
||||
self.min.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`].
|
||||
pub fn height(mut self, height: impl Into<Length>) -> Limits {
|
||||
match height.into() {
|
||||
Length::Shrink => {
|
||||
self.fill.height = self.min.height;
|
||||
}
|
||||
Length::Fill | Length::FillPortion(_) => {
|
||||
self.fill.height = self.fill.height.min(self.max.height);
|
||||
}
|
||||
Length::Shrink | Length::Fill | Length::FillPortion(_) => {}
|
||||
Length::Fixed(amount) => {
|
||||
let new_height =
|
||||
amount.min(self.max.height).max(self.min.height);
|
||||
|
||||
self.min.height = new_height;
|
||||
self.max.height = new_height;
|
||||
self.fill.height = new_height;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,13 +89,10 @@ impl Limits {
|
|||
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`].
|
||||
pub fn shrink(&self, size: Size) -> Limits {
|
||||
pub fn shrink(&self, size: impl Into<Size>) -> Limits {
|
||||
let size = size.into();
|
||||
|
||||
let min = Size::new(
|
||||
(self.min().width - size.width).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),
|
||||
);
|
||||
|
||||
let fill = Size::new(
|
||||
(self.fill.width - size.width).max(0.0),
|
||||
(self.fill.height - size.height).max(0.0),
|
||||
);
|
||||
|
||||
Limits { min, max, fill }
|
||||
Limits { min, max }
|
||||
}
|
||||
|
||||
/// Removes the minimum width constraint for the current [`Limits`].
|
||||
|
|
@ -142,22 +111,39 @@ impl Limits {
|
|||
Limits {
|
||||
min: Size::ZERO,
|
||||
max: self.max,
|
||||
fill: self.fill,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the resulting [`Size`] that fits the [`Limits`] given the
|
||||
/// intrinsic size of some content.
|
||||
pub fn resolve(&self, intrinsic_size: Size) -> Size {
|
||||
Size::new(
|
||||
intrinsic_size
|
||||
.width
|
||||
.min(self.max.width)
|
||||
.max(self.fill.width),
|
||||
intrinsic_size
|
||||
/// Computes the resulting [`Size`] that fits the [`Limits`] given
|
||||
/// some width and height requirements and the intrinsic size of
|
||||
/// some content.
|
||||
pub fn resolve(
|
||||
&self,
|
||||
width: impl Into<Length>,
|
||||
height: impl Into<Length>,
|
||||
intrinsic_size: 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
|
||||
.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.
|
||||
#[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`].
|
||||
pub fn size(&self) -> Size {
|
||||
Size::new(self.bounds.width, self.bounds.height)
|
||||
|
|
@ -43,6 +51,17 @@ impl Node {
|
|||
|
||||
/// Aligns the [`Node`] in the given space.
|
||||
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,
|
||||
horizontal_alignment: Alignment,
|
||||
vertical_alignment: Alignment,
|
||||
|
|
@ -70,13 +89,23 @@ impl Node {
|
|||
}
|
||||
|
||||
/// 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.y = position.y;
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
bounds: self.bounds + translation,
|
||||
..self
|
||||
|
|
|
|||
|
|
@ -36,6 +36,24 @@ impl Length {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -75,3 +75,5 @@ pub use size::Size;
|
|||
pub use text::Text;
|
||||
pub use vector::Vector;
|
||||
pub use widget::Widget;
|
||||
|
||||
pub use smol_str::SmolStr;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ pub enum Button {
|
|||
/// The middle (wheel) button.
|
||||
Middle,
|
||||
|
||||
/// The back mouse button.
|
||||
Back,
|
||||
|
||||
/// The forward mouse button.
|
||||
Forward,
|
||||
|
||||
/// Some other button.
|
||||
Other(u16),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ impl Click {
|
|||
self.kind
|
||||
}
|
||||
|
||||
/// Returns the position of the [`Click`].
|
||||
pub fn position(&self) -> Point {
|
||||
self.position
|
||||
}
|
||||
|
||||
fn is_consecutive(&self, new_position: Point, time: Instant) -> bool {
|
||||
let duration = if time > self.time {
|
||||
Some(time - self.time)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use crate::mouse;
|
|||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::widget::Tree;
|
||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
|
||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector};
|
||||
|
||||
/// An interactive component that can be displayed on top of other widgets.
|
||||
pub trait Overlay<Message, Renderer>
|
||||
|
|
@ -29,6 +29,7 @@ where
|
|||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
translation: Vector,
|
||||
) -> layout::Node;
|
||||
|
||||
/// Draws the [`Overlay`] using the associated `Renderer`.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use std::any::Any;
|
|||
#[allow(missing_debug_implementations)]
|
||||
pub struct Element<'a, Message, Renderer> {
|
||||
position: Point,
|
||||
translation: Vector,
|
||||
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +26,11 @@ where
|
|||
position: Point,
|
||||
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
|
||||
) -> Self {
|
||||
Self { position, overlay }
|
||||
Self {
|
||||
position,
|
||||
overlay,
|
||||
translation: Vector::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the position of the [`Element`].
|
||||
|
|
@ -36,6 +41,7 @@ where
|
|||
/// Translates the [`Element`].
|
||||
pub fn translate(mut self, translation: Vector) -> Self {
|
||||
self.position = self.position + translation;
|
||||
self.translation = self.translation + translation;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +54,7 @@ where
|
|||
{
|
||||
Element {
|
||||
position: self.position,
|
||||
translation: self.translation,
|
||||
overlay: Box::new(Map::new(self.overlay, f)),
|
||||
}
|
||||
}
|
||||
|
|
@ -59,8 +66,12 @@ where
|
|||
bounds: Size,
|
||||
translation: Vector,
|
||||
) -> layout::Node {
|
||||
self.overlay
|
||||
.layout(renderer, bounds, self.position + translation)
|
||||
self.overlay.layout(
|
||||
renderer,
|
||||
bounds,
|
||||
self.position + translation,
|
||||
self.translation + translation,
|
||||
)
|
||||
}
|
||||
|
||||
/// Processes a runtime [`Event`].
|
||||
|
|
@ -154,8 +165,9 @@ where
|
|||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
translation: Vector,
|
||||
) -> layout::Node {
|
||||
self.content.layout(renderer, bounds, position)
|
||||
self.content.layout(renderer, bounds, position, translation)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use crate::mouse;
|
|||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
|
||||
use crate::{
|
||||
Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size, Vector,
|
||||
};
|
||||
|
||||
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
|
||||
/// children.
|
||||
|
|
@ -64,10 +66,9 @@ where
|
|||
&mut self,
|
||||
renderer: &Renderer,
|
||||
bounds: Size,
|
||||
position: Point,
|
||||
_position: Point,
|
||||
translation: Vector,
|
||||
) -> layout::Node {
|
||||
let translation = position - Point::ORIGIN;
|
||||
|
||||
layout::Node::with_children(
|
||||
bounds,
|
||||
self.children
|
||||
|
|
|
|||
|
|
@ -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 num_traits::{Float, Num};
|
||||
use std::fmt;
|
||||
|
||||
/// A 2D point.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct Point {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Point<T = f32> {
|
||||
/// The X coordinate.
|
||||
pub x: f32,
|
||||
pub x: T,
|
||||
|
||||
/// The Y coordinate.
|
||||
pub y: f32,
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
/// 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.
|
||||
pub const fn new(x: f32, y: f32) -> Self {
|
||||
pub const fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// 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 b = self.y - to.y;
|
||||
|
||||
|
|
@ -28,28 +36,37 @@ impl Point {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<[f32; 2]> for Point {
|
||||
fn from([x, y]: [f32; 2]) -> Self {
|
||||
impl<T> From<[T; 2]> for Point<T>
|
||||
where
|
||||
T: Num,
|
||||
{
|
||||
fn from([x, y]: [T; 2]) -> Self {
|
||||
Point { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u16; 2]> for Point {
|
||||
fn from([x, y]: [u16; 2]) -> Self {
|
||||
Point::new(x.into(), y.into())
|
||||
impl<T> From<(T, T)> for Point<T>
|
||||
where
|
||||
T: Num,
|
||||
{
|
||||
fn from((x, y): (T, T)) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point> for [f32; 2] {
|
||||
fn from(point: Point) -> [f32; 2] {
|
||||
impl<T> From<Point<T>> for [T; 2] {
|
||||
fn from(point: Point<T>) -> [T; 2] {
|
||||
[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;
|
||||
|
||||
fn add(self, vector: Vector) -> Self {
|
||||
fn add(self, vector: Vector<T>) -> Self {
|
||||
Self {
|
||||
x: self.x + vector.x,
|
||||
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;
|
||||
|
||||
fn sub(self, vector: Vector) -> Self {
|
||||
fn sub(self, vector: Vector<T>) -> Self {
|
||||
Self {
|
||||
x: self.x - vector.x,
|
||||
y: self.y - vector.y,
|
||||
|
|
@ -68,10 +88,22 @@ impl std::ops::Sub<Vector> for Point {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<Point> for Point {
|
||||
type Output = Vector;
|
||||
impl<T> std::ops::Sub<Point<T>> for Point<T>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
type Font = Font;
|
||||
type Paragraph = ();
|
||||
type Editor = ();
|
||||
|
||||
const ICON_FONT: Font = Font::DEFAULT;
|
||||
const CHECKMARK_ICON: char = '0';
|
||||
|
|
@ -58,21 +59,21 @@ impl text::Renderer for Null {
|
|||
|
||||
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(
|
||||
&mut self,
|
||||
_paragraph: &Self::Paragraph,
|
||||
_position: Point,
|
||||
_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>,
|
||||
_position: Point,
|
||||
_color: Color,
|
||||
_clip_bounds: Rectangle,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -88,24 +90,12 @@ impl text::Renderer for Null {
|
|||
impl text::Paragraph for () {
|
||||
type Font = Font;
|
||||
|
||||
fn content(&self) -> &str {
|
||||
""
|
||||
}
|
||||
fn with_text(_text: Text<'_, Self::Font>) -> Self {}
|
||||
|
||||
fn text_size(&self) -> Pixels {
|
||||
Pixels(16.0)
|
||||
}
|
||||
fn resize(&mut self, _new_bounds: Size) {}
|
||||
|
||||
fn font(&self) -> Self::Font {
|
||||
Font::default()
|
||||
}
|
||||
|
||||
fn line_height(&self) -> text::LineHeight {
|
||||
text::LineHeight::default()
|
||||
}
|
||||
|
||||
fn shaping(&self) -> text::Shaping {
|
||||
text::Shaping::default()
|
||||
fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference {
|
||||
text::Difference::None
|
||||
}
|
||||
|
||||
fn horizontal_alignment(&self) -> alignment::Horizontal {
|
||||
|
|
@ -120,10 +110,6 @@ impl text::Paragraph for () {
|
|||
None
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Size {
|
||||
Size::ZERO
|
||||
}
|
||||
|
||||
fn min_bounds(&self) -> Size {
|
||||
Size::ZERO
|
||||
}
|
||||
|
|
@ -132,3 +118,55 @@ impl text::Paragraph for () {
|
|||
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.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
@ -26,15 +26,7 @@ impl Size {
|
|||
/// A [`Size`] with infinite width and height.
|
||||
pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY);
|
||||
|
||||
/// Increments the [`Size`] to account for the given padding.
|
||||
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
|
||||
/// Returns the minimum of each component of this size and another.
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
Size {
|
||||
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 {
|
||||
Size {
|
||||
width: self.width.max(other.width),
|
||||
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 {
|
||||
|
|
|
|||
178
core/src/text.rs
|
|
@ -1,6 +1,15 @@
|
|||
//! Draw and interact with text.
|
||||
mod paragraph;
|
||||
|
||||
pub mod editor;
|
||||
pub mod highlighter;
|
||||
|
||||
pub use editor::Editor;
|
||||
pub use highlighter::Highlighter;
|
||||
pub use paragraph::Paragraph;
|
||||
|
||||
use crate::alignment;
|
||||
use crate::{Color, Pixels, Point, Size};
|
||||
use crate::{Color, Pixels, Point, Rectangle, Size};
|
||||
|
||||
use std::borrow::Cow;
|
||||
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`].
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The font type used.
|
||||
|
|
@ -134,6 +170,9 @@ pub trait Renderer: crate::Renderer {
|
|||
/// The [`Paragraph`] of this [`Renderer`].
|
||||
type Paragraph: Paragraph<Font = Self::Font> + 'static;
|
||||
|
||||
/// The [`Editor`] of this [`Renderer`].
|
||||
type Editor: Editor<Font = Self::Font> + 'static;
|
||||
|
||||
/// The icon font of the backend.
|
||||
const ICON_FONT: Self::Font;
|
||||
|
||||
|
|
@ -156,33 +195,6 @@ pub trait Renderer: crate::Renderer {
|
|||
/// Loads a [`Self::Font`] from its bytes.
|
||||
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
|
||||
/// [`Color`].
|
||||
fn fill_paragraph(
|
||||
|
|
@ -190,6 +202,17 @@ pub trait Renderer: crate::Renderer {
|
|||
text: &Self::Paragraph,
|
||||
position: Point,
|
||||
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
|
||||
|
|
@ -199,103 +222,6 @@ pub trait Renderer: crate::Renderer {
|
|||
text: Text<'_, Self::Font>,
|
||||
position: Point,
|
||||
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!
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use instant::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;
|
||||
pub use web_time::Duration;
|
||||
pub use web_time::Instant;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use crate::layout::{self, Layout};
|
|||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::{Clipboard, Length, Rectangle, Shell};
|
||||
use crate::{Clipboard, Length, Rectangle, Shell, Size};
|
||||
|
||||
/// A component that displays information and allows interaction.
|
||||
///
|
||||
|
|
@ -43,11 +43,16 @@ pub trait Widget<Message, Renderer>
|
|||
where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
/// Returns the width of the [`Widget`].
|
||||
fn width(&self) -> Length;
|
||||
/// Returns the [`Size`] of the [`Widget`] in lengths.
|
||||
fn size(&self) -> Size<Length>;
|
||||
|
||||
/// Returns the height of the [`Widget`].
|
||||
fn height(&self) -> Length;
|
||||
/// Returns a [`Size`] hint for laying out the [`Widget`].
|
||||
///
|
||||
/// 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`].
|
||||
///
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ use crate::mouse;
|
|||
use crate::renderer;
|
||||
use crate::text::{self, Paragraph};
|
||||
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;
|
||||
|
||||
|
|
@ -134,12 +136,11 @@ where
|
|||
tree::State::new(State(Renderer::Paragraph::default()))
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -172,7 +173,7 @@ where
|
|||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor_position: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||
|
||||
|
|
@ -182,6 +183,7 @@ where
|
|||
layout,
|
||||
state,
|
||||
theme.appearance(self.style.clone()),
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -204,17 +206,15 @@ pub fn layout<Renderer>(
|
|||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let limits = limits.width(width).height(height);
|
||||
let bounds = limits.max();
|
||||
layout::sized(limits, width, height, |limits| {
|
||||
let bounds = limits.max();
|
||||
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
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,
|
||||
text::Text {
|
||||
paragraph.update(text::Text {
|
||||
content,
|
||||
bounds,
|
||||
size,
|
||||
|
|
@ -223,12 +223,10 @@ where
|
|||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
shaping,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let size = limits.resolve(paragraph.min_bounds());
|
||||
|
||||
layout::Node::new(size)
|
||||
paragraph.min_bounds()
|
||||
})
|
||||
}
|
||||
|
||||
/// Draws text using the same logic as the [`Text`] widget.
|
||||
|
|
@ -247,6 +245,7 @@ pub fn draw<Renderer>(
|
|||
layout: Layout<'_>,
|
||||
state: &State<Renderer::Paragraph>,
|
||||
appearance: Appearance,
|
||||
viewport: &Rectangle,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
|
|
@ -269,6 +268,7 @@ pub fn draw<Renderer>(
|
|||
paragraph,
|
||||
Point::new(x, y),
|
||||
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>(
|
||||
&mut self,
|
||||
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
//! Build window-based GUI applications.
|
||||
pub mod icon;
|
||||
pub mod settings;
|
||||
|
||||
mod event;
|
||||
mod id;
|
||||
mod level;
|
||||
mod mode;
|
||||
mod position;
|
||||
mod redraw_request;
|
||||
mod user_attention;
|
||||
|
||||
pub use event::Event;
|
||||
pub use icon::Icon;
|
||||
pub use id::Id;
|
||||
pub use level::Level;
|
||||
pub use mode::Mode;
|
||||
pub use position::Position;
|
||||
pub use redraw_request::RedrawRequest;
|
||||
pub use settings::Settings;
|
||||
pub use user_attention::UserAttention;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,27 @@
|
|||
use crate::time::Instant;
|
||||
use crate::{Point, Size};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// A window-related event.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
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.
|
||||
Moved {
|
||||
/// The new logical x location of the window
|
||||
|
|
@ -27,9 +44,6 @@ pub enum Event {
|
|||
RedrawRequested(Instant),
|
||||
|
||||
/// The user has requested for the window to close.
|
||||
///
|
||||
/// Usually, you will want to terminate the execution whenever this event
|
||||
/// occurs.
|
||||
CloseRequested,
|
||||
|
||||
/// A window was focused.
|
||||
|
|
@ -44,7 +58,7 @@ pub enum Event {
|
|||
/// for each file separately.
|
||||
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
|
||||
/// 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.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Position {
|
||||
/// The platform-specific default position for a new window.
|
||||
Default,
|
||||
|
|
@ -12,7 +14,7 @@ pub enum Position {
|
|||
/// position. So if you have decorations enabled and want the window to be
|
||||
/// at (0, 0) you would have to set the position to
|
||||
/// `(PADDING_X, PADDING_Y)`.
|
||||
Specific(i32, i32),
|
||||
Specific(Point),
|
||||
}
|
||||
|
||||
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::Size;
|
||||
|
||||
pub use iced_winit::settings::PlatformSpecific;
|
||||
|
||||
pub use platform::PlatformSpecific;
|
||||
/// The window settings of an application.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Settings {
|
||||
/// The initial size of the window.
|
||||
pub size: (u32, u32),
|
||||
/// The initial logical dimensions of the window.
|
||||
pub size: Size,
|
||||
|
||||
/// The initial position of the window.
|
||||
pub position: Position,
|
||||
|
||||
/// The minimum size of the window.
|
||||
pub min_size: Option<(u32, u32)>,
|
||||
pub min_size: Option<Size>,
|
||||
|
||||
/// 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.
|
||||
pub visible: bool,
|
||||
|
|
@ -37,12 +63,22 @@ pub struct Settings {
|
|||
|
||||
/// Platform specific settings.
|
||||
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 {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
size: (1024, 768),
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: Size::new(1024.0, 768.0),
|
||||
position: Position::default(),
|
||||
min_size: None,
|
||||
max_size: None,
|
||||
|
|
@ -52,25 +88,8 @@ impl Default for Settings {
|
|||
transparent: false,
|
||||
level: Level::default(),
|
||||
icon: None,
|
||||
exit_on_close_request: true,
|
||||
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
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
|
|||
|
|
@ -73,16 +73,15 @@ impl Application for Example {
|
|||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let downloads = Column::with_children(
|
||||
self.downloads.iter().map(Download::view).collect(),
|
||||
)
|
||||
.push(
|
||||
button("Add another download")
|
||||
.on_press(Message::Add)
|
||||
.padding(10),
|
||||
)
|
||||
.spacing(20)
|
||||
.align_items(Alignment::End);
|
||||
let downloads =
|
||||
Column::with_children(self.downloads.iter().map(Download::view))
|
||||
.push(
|
||||
button("Add another download")
|
||||
.on_press(Message::Add)
|
||||
.padding(10),
|
||||
)
|
||||
.spacing(20)
|
||||
.align_items(Alignment::End);
|
||||
|
||||
container(downloads)
|
||||
.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 {
|
||||
Events::run(Settings {
|
||||
exit_on_close_request: false,
|
||||
window: window::Settings {
|
||||
exit_on_close_request: false,
|
||||
..window::Settings::default()
|
||||
},
|
||||
..Settings::default()
|
||||
})
|
||||
}
|
||||
|
|
@ -54,8 +57,9 @@ impl Application for Events {
|
|||
Command::none()
|
||||
}
|
||||
Message::EventOccurred(event) => {
|
||||
if let Event::Window(window::Event::CloseRequested) = event {
|
||||
window::close()
|
||||
if let Event::Window(id, window::Event::CloseRequested) = event
|
||||
{
|
||||
window::close(id)
|
||||
} else {
|
||||
Command::none()
|
||||
}
|
||||
|
|
@ -65,7 +69,7 @@ impl Application for Events {
|
|||
|
||||
Command::none()
|
||||
}
|
||||
Message::Exit => window::close(),
|
||||
Message::Exit => window::close(window::Id::MAIN),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,8 +82,7 @@ impl Application for Events {
|
|||
self.last
|
||||
.iter()
|
||||
.map(|event| text(format!("{event:?}")).size(40))
|
||||
.map(Element::from)
|
||||
.collect(),
|
||||
.map(Element::from),
|
||||
);
|
||||
|
||||
let toggle = checkbox(
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl Application for Exit {
|
|||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::Confirm => window::close(),
|
||||
Message::Confirm => window::close(window::Id::MAIN),
|
||||
Message::Exit => {
|
||||
self.show_confirm = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ publish = false
|
|||
iced.workspace = true
|
||||
iced.features = ["debug", "canvas", "tokio"]
|
||||
|
||||
itertools = "0.11"
|
||||
itertools = "0.12"
|
||||
rustc-hash.workspace = true
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
tracing-subscriber = "0.3"
|
||||
|
|
|
|||
|
|
@ -146,7 +146,8 @@ impl Application for GameOfLife {
|
|||
.view()
|
||||
.map(move |message| Message::Grid(message, version)),
|
||||
controls,
|
||||
];
|
||||
]
|
||||
.height(Length::Fill);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
|
|
@ -178,7 +179,6 @@ fn view_controls<'a>(
|
|||
slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),
|
||||
text(format!("x{speed}")).size(16),
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,11 @@ mod rainbow {
|
|||
}
|
||||
|
||||
impl<Message> Widget<Message, Renderer> for Rainbow {
|
||||
fn width(&self) -> Length {
|
||||
Length::Fill
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: Length::Fill,
|
||||
height: Length::Shrink,
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -30,9 +29,9 @@ mod rainbow {
|
|||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> 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(
|
||||
|
|
|
|||
|
|
@ -81,32 +81,25 @@ impl Program for Controls {
|
|||
);
|
||||
|
||||
Row::new()
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.align_items(Alignment::End)
|
||||
.push(
|
||||
Column::new()
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::End)
|
||||
.push(
|
||||
Column::new()
|
||||
.padding(10)
|
||||
.spacing(10)
|
||||
.push(
|
||||
Text::new("Background color")
|
||||
.style(Color::WHITE),
|
||||
)
|
||||
.push(sliders)
|
||||
.push(
|
||||
Text::new(format!("{background_color:?}"))
|
||||
.size(14)
|
||||
.style(Color::WHITE),
|
||||
)
|
||||
.push(
|
||||
text_input("Placeholder", text)
|
||||
.on_input(Message::TextChanged),
|
||||
),
|
||||
),
|
||||
Column::new().align_items(Alignment::End).push(
|
||||
Column::new()
|
||||
.padding(10)
|
||||
.spacing(10)
|
||||
.push(Text::new("Background color").style(Color::WHITE))
|
||||
.push(sliders)
|
||||
.push(
|
||||
Text::new(format!("{background_color:?}"))
|
||||
.size(14)
|
||||
.style(Color::WHITE),
|
||||
)
|
||||
.push(
|
||||
text_input("Placeholder", text)
|
||||
.on_input(Message::TextChanged),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,19 +6,26 @@ use scene::Scene;
|
|||
|
||||
use iced_wgpu::graphics::Viewport;
|
||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
||||
use iced_winit::conversion;
|
||||
use iced_winit::core::mouse;
|
||||
use iced_winit::core::renderer;
|
||||
use iced_winit::core::window;
|
||||
use iced_winit::core::{Color, Font, Pixels, Size};
|
||||
use iced_winit::futures;
|
||||
use iced_winit::runtime::program;
|
||||
use iced_winit::runtime::Debug;
|
||||
use iced_winit::style::Theme;
|
||||
use iced_winit::{conversion, futures, winit, Clipboard};
|
||||
use iced_winit::winit;
|
||||
use iced_winit::Clipboard;
|
||||
|
||||
use winit::{
|
||||
event::{Event, ModifiersState, WindowEvent},
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::ModifiersState,
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsCast;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
|
@ -44,7 +51,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Initialize winit
|
||||
let event_loop = EventLoop::new();
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
|
|
@ -54,6 +61,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let window = winit::window::Window::new(&event_loop)?;
|
||||
|
||||
let window = Arc::new(window);
|
||||
|
||||
let physical_size = window.inner_size();
|
||||
let mut viewport = Viewport::with_physical_size(
|
||||
Size::new(physical_size.width, physical_size.height),
|
||||
|
|
@ -76,7 +85,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
backends: backend,
|
||||
..Default::default()
|
||||
});
|
||||
let surface = unsafe { instance.create_surface(&window) }?;
|
||||
let surface = instance.create_surface(window.clone())?;
|
||||
|
||||
let (format, (device, queue)) =
|
||||
futures::futures::executor::block_on(async {
|
||||
|
|
@ -110,9 +119,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: adapter_features
|
||||
required_features: adapter_features
|
||||
& wgpu::Features::default(),
|
||||
limits: needed_limits,
|
||||
required_limits: needed_limits,
|
||||
},
|
||||
None,
|
||||
)
|
||||
|
|
@ -131,6 +140,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -156,66 +166,15 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
);
|
||||
|
||||
// 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
|
||||
*control_flow = ControlFlow::Wait;
|
||||
window_target.set_control_flow(ControlFlow::Wait);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
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(_) => {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
if resized {
|
||||
let size = window.inner_size();
|
||||
|
||||
|
|
@ -234,6 +193,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -271,6 +231,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
&queue,
|
||||
&mut encoder,
|
||||
None,
|
||||
frame.texture.format(),
|
||||
&view,
|
||||
primitive,
|
||||
&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,
|
||||
}
|
||||
}),
|
||||
store: true,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
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 {
|
||||
const ALL: &[Color] = &[
|
||||
const ALL: &'static [Color] = &[
|
||||
Color::Black,
|
||||
Color::Red,
|
||||
Color::Orange,
|
||||
|
|
@ -178,35 +178,23 @@ impl Sandbox for App {
|
|||
}
|
||||
});
|
||||
|
||||
column(
|
||||
items
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
let button = button("Delete")
|
||||
.on_press(Message::DeleteItem(item.clone()))
|
||||
.style(theme::Button::Destructive);
|
||||
column(items.into_iter().map(|item| {
|
||||
let button = button("Delete")
|
||||
.on_press(Message::DeleteItem(item.clone()))
|
||||
.style(theme::Button::Destructive);
|
||||
|
||||
row![
|
||||
text(&item.name)
|
||||
.style(theme::Text::Color(item.color.into())),
|
||||
horizontal_space(Length::Fill),
|
||||
pick_list(
|
||||
Color::ALL,
|
||||
Some(item.color),
|
||||
move |color| {
|
||||
Message::ItemColorChanged(
|
||||
item.clone(),
|
||||
color,
|
||||
)
|
||||
}
|
||||
),
|
||||
button
|
||||
]
|
||||
.spacing(20)
|
||||
.into()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
row![
|
||||
text(&item.name)
|
||||
.style(theme::Text::Color(item.color.into())),
|
||||
horizontal_space(Length::Fill),
|
||||
pick_list(Color::ALL, Some(item.color), move |color| {
|
||||
Message::ItemColorChanged(item.clone(), color)
|
||||
}),
|
||||
button
|
||||
]
|
||||
.spacing(20)
|
||||
.into()
|
||||
}))
|
||||
.spacing(10)
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -244,12 +244,11 @@ where
|
|||
tree::State::new(State::default())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
Length::Fixed(self.size)
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Fixed(self.size)
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: Length::Fixed(self.size),
|
||||
height: Length::Fixed(self.size),
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -258,10 +257,7 @@ where
|
|||
_renderer: &iced::Renderer<Theme>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.size).height(self.size);
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
layout::Node::new(size)
|
||||
layout::atomic(limits, self.size, self.size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -275,11 +271,9 @@ where
|
|||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
const FRAME_RATE: u64 = 60;
|
||||
|
||||
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(
|
||||
self.cycle_duration,
|
||||
self.rotation_duration,
|
||||
|
|
@ -287,9 +281,7 @@ where
|
|||
);
|
||||
|
||||
state.cache.clear();
|
||||
shell.request_redraw(RedrawRequest::At(
|
||||
now + Duration::from_millis(1000 / FRAME_RATE),
|
||||
));
|
||||
shell.request_redraw(RedrawRequest::NextFrame);
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
|
|
|
|||
|
|
@ -165,12 +165,11 @@ where
|
|||
tree::State::new(State::default())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -179,10 +178,7 @@ where
|
|||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
layout::Node::new(size)
|
||||
layout::atomic(limits, self.width, self.height)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -196,16 +192,12 @@ where
|
|||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
const FRAME_RATE: u64 = 60;
|
||||
|
||||
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);
|
||||
|
||||
shell.request_redraw(RedrawRequest::At(
|
||||
now + Duration::from_millis(1000 / FRAME_RATE),
|
||||
));
|
||||
shell.request_redraw(RedrawRequest::NextFrame);
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
|
|
|
|||
|
|
@ -96,15 +96,14 @@ impl Application for LoadingSpinners {
|
|||
|
||||
container(
|
||||
column.push(
|
||||
row(vec![
|
||||
text("Cycle duration:").into(),
|
||||
row![
|
||||
text("Cycle duration:"),
|
||||
slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| {
|
||||
Message::CycleDurationChanged(x / 100.0)
|
||||
})
|
||||
.width(200.0)
|
||||
.into(),
|
||||
text(format!("{:.2}s", self.cycle_duration)).into(),
|
||||
])
|
||||
.width(200.0),
|
||||
text(format!("{:.2}s", self.cycle_duration)),
|
||||
]
|
||||
.align_items(iced::Alignment::Center)
|
||||
.spacing(20.0),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use iced::event::{self, Event};
|
||||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::keyboard::key;
|
||||
use iced::theme;
|
||||
use iced::widget::{
|
||||
self, button, column, container, horizontal_space, pick_list, row, text,
|
||||
|
|
@ -85,8 +86,9 @@ impl Application for App {
|
|||
}
|
||||
Message::Event(event) => match event {
|
||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
key_code: keyboard::KeyCode::Tab,
|
||||
key: keyboard::Key::Named(key::Named::Tab),
|
||||
modifiers,
|
||||
..
|
||||
}) => {
|
||||
if modifiers.shift() {
|
||||
widget::focus_previous()
|
||||
|
|
@ -95,7 +97,7 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
key_code: keyboard::KeyCode::Escape,
|
||||
key: keyboard::Key::Named(key::Named::Escape),
|
||||
..
|
||||
}) => {
|
||||
self.hide_modal();
|
||||
|
|
@ -205,7 +207,8 @@ enum 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 {
|
||||
|
|
@ -230,6 +233,7 @@ mod modal {
|
|||
use iced::mouse;
|
||||
use iced::{
|
||||
BorderRadius, Color, Element, Event, Length, Point, Rectangle, Size,
|
||||
Vector,
|
||||
};
|
||||
|
||||
/// A widget that centers a modal element over some base element
|
||||
|
|
@ -279,12 +283,8 @@ mod modal {
|
|||
tree.diff_children(&[&self.base, &self.modal]);
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.base.as_widget().width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.base.as_widget().height()
|
||||
fn size(&self) -> Size<Length> {
|
||||
self.base.as_widget().size()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -412,22 +412,20 @@ mod modal {
|
|||
renderer: &Renderer,
|
||||
_bounds: Size,
|
||||
position: Point,
|
||||
_translation: Vector,
|
||||
) -> layout::Node {
|
||||
let limits = layout::Limits::new(Size::ZERO, self.size)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
let mut child = self
|
||||
let child = self
|
||||
.content
|
||||
.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());
|
||||
|
||||
let mut node = layout::Node::with_children(self.size, vec![child]);
|
||||
node.move_to(position);
|
||||
|
||||
node
|
||||
layout::Node::with_children(self.size, vec![child])
|
||||
.move_to(position)
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
|
||||
use keyboard::KeyCode;
|
||||
fn handle_hotkey(key: keyboard::Key) -> Option<Message> {
|
||||
use keyboard::key::{self, Key};
|
||||
use pane_grid::{Axis, Direction};
|
||||
|
||||
let direction = match key_code {
|
||||
KeyCode::Up => Some(Direction::Up),
|
||||
KeyCode::Down => Some(Direction::Down),
|
||||
KeyCode::Left => Some(Direction::Left),
|
||||
KeyCode::Right => Some(Direction::Right),
|
||||
_ => None,
|
||||
};
|
||||
match key.as_ref() {
|
||||
Key::Character("v") => Some(Message::SplitFocused(Axis::Vertical)),
|
||||
Key::Character("h") => Some(Message::SplitFocused(Axis::Horizontal)),
|
||||
Key::Character("w") => Some(Message::CloseFocused),
|
||||
Key::Named(key) => {
|
||||
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 {
|
||||
KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)),
|
||||
KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)),
|
||||
KeyCode::W => Some(Message::CloseFocused),
|
||||
_ => direction.map(Message::FocusAdjacent),
|
||||
direction.map(Message::FocusAdjacent)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +300,6 @@ fn view_content<'a>(
|
|||
text(format!("{}x{}", size.width, size.height)).size(24),
|
||||
controls,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.spacing(10)
|
||||
.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};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
|
|
@ -52,12 +52,7 @@ impl Sandbox for Example {
|
|||
.align_items(Alignment::Center)
|
||||
.spacing(10);
|
||||
|
||||
container(scrollable(content))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
scrollable(content).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.
|
||||
|
||||
<div align="center">
|
||||
<img src="https://iced.rs/examples/pokedex.gif">
|
||||
<img src="https://iced.rs/examples/progress_bar.gif">
|
||||
</div>
|
||||
|
||||
You can run it with `cargo run`:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
use iced::alignment;
|
||||
use iced::keyboard::KeyCode;
|
||||
use iced::theme::{Button, Container};
|
||||
use iced::executor;
|
||||
use iced::keyboard;
|
||||
use iced::theme;
|
||||
use iced::widget::{button, column, container, image, row, text, text_input};
|
||||
use iced::window;
|
||||
use iced::window::screenshot::{self, Screenshot};
|
||||
use iced::{
|
||||
event, executor, keyboard, Alignment, Application, Command, ContentFit,
|
||||
Element, Event, Length, Rectangle, Renderer, Subscription, Theme,
|
||||
Alignment, Application, Command, ContentFit, Element, Length, Rectangle,
|
||||
Renderer, Subscription, Theme,
|
||||
};
|
||||
|
||||
use ::image as img;
|
||||
|
|
@ -70,7 +72,10 @@ impl Application for Example {
|
|||
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||
match message {
|
||||
Message::Screenshot => {
|
||||
return iced::window::screenshot(Message::ScreenshotData);
|
||||
return iced::window::screenshot(
|
||||
window::Id::MAIN,
|
||||
Message::ScreenshotData,
|
||||
);
|
||||
}
|
||||
Message::ScreenshotData(screenshot) => {
|
||||
self.screenshot = Some(screenshot);
|
||||
|
|
@ -144,7 +149,7 @@ impl Application for Example {
|
|||
|
||||
let image = container(image)
|
||||
.padding(10)
|
||||
.style(Container::Box)
|
||||
.style(theme::Container::Box)
|
||||
.width(Length::FillPortion(2))
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
|
|
@ -199,9 +204,10 @@ impl Application for Example {
|
|||
self.screenshot.is_some().then(|| Message::Png),
|
||||
)
|
||||
} 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])
|
||||
.width(Length::Fill)
|
||||
]
|
||||
|
|
@ -210,7 +216,7 @@ impl Application for Example {
|
|||
crop_controls,
|
||||
button(centered_text("Crop"))
|
||||
.on_press(Message::Crop)
|
||||
.style(Button::Destructive)
|
||||
.style(theme::Button::Destructive)
|
||||
.padding([10, 20, 10, 20])
|
||||
.width(Length::Fill),
|
||||
]
|
||||
|
|
@ -253,16 +259,10 @@ impl Application for Example {
|
|||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Self::Message> {
|
||||
event::listen_with(|event, status| {
|
||||
if let event::Status::Captured = status {
|
||||
return None;
|
||||
}
|
||||
use keyboard::key;
|
||||
|
||||
if let Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
key_code: KeyCode::F5,
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
keyboard::on_key_press(|key, _modifiers| {
|
||||
if let keyboard::Key::Named(key::Named::F5) = key {
|
||||
Some(Message::Screenshot)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -298,10 +298,7 @@ fn numeric_input(
|
|||
) -> Element<'_, Option<u32>> {
|
||||
text_input(
|
||||
placeholder,
|
||||
&value
|
||||
.as_ref()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(String::new),
|
||||
&value.as_ref().map(ToString::to_string).unwrap_or_default(),
|
||||
)
|
||||
.on_input(move |text| {
|
||||
if text.is_empty() {
|
||||
|
|
|
|||