Merge branch 'master' of https://github.com/hecrj/iced into wgpu_outdatedframe
This commit is contained in:
commit
e822f654e4
219 changed files with 6266 additions and 1761 deletions
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
|
@ -1 +1,2 @@
|
||||||
|
github: hecrj
|
||||||
ko_fi: hecrj_
|
ko_fi: hecrj_
|
||||||
|
|
|
||||||
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
|
|
@ -11,6 +11,11 @@ jobs:
|
||||||
- name: Install cargo-deb
|
- name: Install cargo-deb
|
||||||
run: cargo install cargo-deb
|
run: cargo install cargo-deb
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
export DEBIAN_FRONTED=noninteractive
|
||||||
|
sudo apt-get -qq update
|
||||||
|
sudo apt-get install -y libxkbcommon-dev
|
||||||
- name: Enable Link Time Optimizations
|
- name: Enable Link Time Optimizations
|
||||||
run: |
|
run: |
|
||||||
echo "[profile.release]" >> Cargo.toml
|
echo "[profile.release]" >> Cargo.toml
|
||||||
|
|
@ -67,6 +72,8 @@ jobs:
|
||||||
env:
|
env:
|
||||||
MACOSX_DEPLOYMENT_TARGET: 10.14
|
MACOSX_DEPLOYMENT_TARGET: 10.14
|
||||||
run: cargo build --verbose --release --package todos
|
run: cargo build --verbose --release --package todos
|
||||||
|
- name: Open binary via double-click
|
||||||
|
run: chmod +x target/release/todos
|
||||||
- name: Archive todos binary
|
- name: Archive todos binary
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
|
|
@ -12,6 +12,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
rust-version: ${{ matrix.rust }}
|
rust-version: ${{ matrix.rust }}
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
|
- name: Install dependencies
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
export DEBIAN_FRONTED=noninteractive
|
||||||
|
sudo apt-get -qq update
|
||||||
|
sudo apt-get install -y libxkbcommon-dev
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
cargo test --verbose --all
|
cargo test --verbose --all
|
||||||
|
|
@ -29,5 +35,5 @@ jobs:
|
||||||
run: cargo check --package iced --target wasm32-unknown-unknown
|
run: cargo check --package iced --target wasm32-unknown-unknown
|
||||||
- name: Check compilation of `tour` example
|
- name: Check compilation of `tour` example
|
||||||
run: cargo build --package tour --target wasm32-unknown-unknown
|
run: cargo build --package tour --target wasm32-unknown-unknown
|
||||||
- name: Check compilation of `pokedex` example
|
- name: Check compilation of `todos` example
|
||||||
run: cargo build --package pokedex --target wasm32-unknown-unknown
|
run: cargo build --package todos --target wasm32-unknown-unknown
|
||||||
|
|
|
||||||
117
CHANGELOG.md
117
CHANGELOG.md
|
|
@ -5,10 +5,115 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
|
||||||
- `"system_font"` feature gates reading system fonts. [#370]
|
|
||||||
|
|
||||||
[#370]: https://github.com/hecrj/iced/pull/370
|
## [0.3.0] - 2021-03-31
|
||||||
|
### Added
|
||||||
|
- Touch support. [#57] [#650] (thanks to @simlay and @discordance!)
|
||||||
|
- Clipboard write access for
|
||||||
|
- `TextInput` widget. [#770]
|
||||||
|
- `Application::update`. [#773]
|
||||||
|
- `image::Viewer` widget. It allows panning and scaling of an image. [#319] (thanks to @tarkah!)
|
||||||
|
- `Tooltip` widget. It annotates content with some text on mouse hover. [#465] (thanks to @yusdacra!)
|
||||||
|
- Support for the [`smol`] async runtime. [#699] (thanks to @JayceFayne!)
|
||||||
|
- Support for graceful exiting when using the `Application` trait. [#804]
|
||||||
|
- Image format features in [`iced_wgpu`] to reduce code bloat. [#392] (thanks to @unrelentingtech!)
|
||||||
|
- `Focused` and `Unfocused` variant to `window::Event`. [#701] (thanks to @cossonleo!)
|
||||||
|
- `WGPU_BACKEND` environment variable to configure the internal graphics backend of `iced_wgpu`. [#789] (thanks to @Cupnfish!)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- The `TitleBar` of a `PaneGrid` now supports generic elements. [#657] (thanks to @clarkmoody!)
|
||||||
|
- The `Error` type now implements `Send` and `Sync`. [#719] (thanks to @taiki-e!)
|
||||||
|
- The `Style` types in `iced_style` now implement `Clone` and `Copy`. [#720] (thanks to @taiki-e!)
|
||||||
|
- The following dependencies have been updated:
|
||||||
|
- [`font-kit`] → `0.10` [#669]
|
||||||
|
- [`glutin`] → `0.26` [#658]
|
||||||
|
- [`resvg`] → `0.12` [#669]
|
||||||
|
- [`tokio`] → `1.0` [#672] (thanks to @yusdacra!)
|
||||||
|
- [`winit`] → `0.24` [#658]
|
||||||
|
- [`wgpu`] → `0.7` [#725] (thanks to @PolyMeilex)
|
||||||
|
- The following examples were improved:
|
||||||
|
- `download_progress` now showcases multiple file downloads at once. [#283] (thanks to @Folyd!)
|
||||||
|
- `solar_system` uses the new `rand` API. [#760] (thanks to @TriedAngle!)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Button events not being propagated to contents. [#668]
|
||||||
|
- Incorrect overlay implementation for the `Button` widget. [#764]
|
||||||
|
- `Viewport::physical_width` returning the wrong value. [#700]
|
||||||
|
- Outdated documentation for the `Sandbox` trait. [#710]
|
||||||
|
|
||||||
|
[#57]: https://github.com/hecrj/iced/pull/57
|
||||||
|
[#283]: https://github.com/hecrj/iced/pull/283
|
||||||
|
[#319]: https://github.com/hecrj/iced/pull/319
|
||||||
|
[#392]: https://github.com/hecrj/iced/pull/392
|
||||||
|
[#465]: https://github.com/hecrj/iced/pull/465
|
||||||
|
[#650]: https://github.com/hecrj/iced/pull/650
|
||||||
|
[#657]: https://github.com/hecrj/iced/pull/657
|
||||||
|
[#658]: https://github.com/hecrj/iced/pull/658
|
||||||
|
[#668]: https://github.com/hecrj/iced/pull/668
|
||||||
|
[#669]: https://github.com/hecrj/iced/pull/669
|
||||||
|
[#672]: https://github.com/hecrj/iced/pull/672
|
||||||
|
[#699]: https://github.com/hecrj/iced/pull/699
|
||||||
|
[#700]: https://github.com/hecrj/iced/pull/700
|
||||||
|
[#701]: https://github.com/hecrj/iced/pull/701
|
||||||
|
[#710]: https://github.com/hecrj/iced/pull/710
|
||||||
|
[#719]: https://github.com/hecrj/iced/pull/719
|
||||||
|
[#720]: https://github.com/hecrj/iced/pull/720
|
||||||
|
[#725]: https://github.com/hecrj/iced/pull/725
|
||||||
|
[#760]: https://github.com/hecrj/iced/pull/760
|
||||||
|
[#764]: https://github.com/hecrj/iced/pull/764
|
||||||
|
[#770]: https://github.com/hecrj/iced/pull/770
|
||||||
|
[#773]: https://github.com/hecrj/iced/pull/773
|
||||||
|
[#789]: https://github.com/hecrj/iced/pull/789
|
||||||
|
[#804]: https://github.com/hecrj/iced/pull/804
|
||||||
|
[`smol`]: https://github.com/smol-rs/smol
|
||||||
|
[`winit`]: https://github.com/rust-windowing/winit
|
||||||
|
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||||
|
[`font-kit`]: https://github.com/servo/font-kit
|
||||||
|
|
||||||
|
## [0.2.0] - 2020-11-26
|
||||||
|
### Added
|
||||||
|
- __[`Canvas` interactivity][canvas]__ (#325)
|
||||||
|
A trait-based approach to react to mouse and keyboard interactions in [the `Canvas` widget][#193].
|
||||||
|
|
||||||
|
- __[`iced_graphics` subcrate][opengl]__ (#354)
|
||||||
|
A backend-agnostic graphics subcrate that can be leveraged to build new renderers.
|
||||||
|
|
||||||
|
- __[OpenGL renderer][opengl]__ (#354)
|
||||||
|
An OpenGL renderer powered by [`iced_graphics`], [`glow`], and [`glutin`]. It is an alternative to the default [`wgpu`] renderer.
|
||||||
|
|
||||||
|
- __[Overlay support][pick_list]__ (#444)
|
||||||
|
Basic support for superpositioning interactive widgets on top of other widgets.
|
||||||
|
|
||||||
|
- __[Faster event loop][view]__ (#597)
|
||||||
|
The event loop now takes advantage of the data dependencies in [The Elm Architecture] and leverages the borrow checker to keep the widget tree alive between iterations, avoiding unnecessary rebuilds.
|
||||||
|
|
||||||
|
- __[Event capturing][event]__ (#614)
|
||||||
|
The runtime now can tell whether a widget has handled an event or not, easing [integration with existing applications].
|
||||||
|
|
||||||
|
- __[`PickList` widget][pick_list]__ (#444)
|
||||||
|
A drop-down selector widget built on top of the new overlay support.
|
||||||
|
|
||||||
|
- __[`QRCode` widget][qr_code]__ (#622)
|
||||||
|
A widget that displays a QR code, powered by [the `qrcode` crate].
|
||||||
|
|
||||||
|
[canvas]: https://github.com/hecrj/iced/pull/325
|
||||||
|
[opengl]: https://github.com/hecrj/iced/pull/354
|
||||||
|
[`iced_graphics`]: https://github.com/hecrj/iced/pull/354
|
||||||
|
[pane_grid]: https://github.com/hecrj/iced/pull/397
|
||||||
|
[pick_list]: https://github.com/hecrj/iced/pull/444
|
||||||
|
[error]: https://github.com/hecrj/iced/pull/514
|
||||||
|
[view]: https://github.com/hecrj/iced/pull/597
|
||||||
|
[event]: https://github.com/hecrj/iced/pull/614
|
||||||
|
[color]: https://github.com/hecrj/iced/pull/200
|
||||||
|
[qr_code]: https://github.com/hecrj/iced/pull/622
|
||||||
|
[#193]: https://github.com/hecrj/iced/pull/193
|
||||||
|
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||||
|
[`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||||
|
[`glow`]: https://github.com/grovesNL/glow
|
||||||
|
[the `qrcode` crate]: https://docs.rs/qrcode/0.12.0/qrcode/
|
||||||
|
[integration with existing applications]: https://github.com/hecrj/iced/pull/183
|
||||||
|
[The Elm Architecture]: https://guide.elm-lang.org/architecture/
|
||||||
|
|
||||||
|
|
||||||
## [0.1.1] - 2020-04-15
|
## [0.1.1] - 2020-04-15
|
||||||
### Added
|
### Added
|
||||||
|
|
@ -102,7 +207,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
[`wasm-bindgen-futures`]: https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures
|
[`wasm-bindgen-futures`]: https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures
|
||||||
[`resvg`]: https://github.com/RazrFalcon/resvg
|
[`resvg`]: https://github.com/RazrFalcon/resvg
|
||||||
[`raqote`]: https://github.com/jrmuizel/raqote
|
[`raqote`]: https://github.com/jrmuizel/raqote
|
||||||
[`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.1/wgpu
|
[`iced_wgpu`]: https://github.com/hecrj/iced/tree/master/wgpu
|
||||||
|
|
||||||
|
|
||||||
## [0.1.0-beta] - 2019-11-25
|
## [0.1.0-beta] - 2019-11-25
|
||||||
|
|
@ -114,7 +219,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Added
|
### Added
|
||||||
- First release! :tada:
|
- First release! :tada:
|
||||||
|
|
||||||
[Unreleased]: https://github.com/hecrj/iced/compare/0.1.1...HEAD
|
[Unreleased]: https://github.com/hecrj/iced/compare/0.3.0...HEAD
|
||||||
|
[0.3.0]: https://github.com/hecrj/iced/compare/0.2.0...0.3.0
|
||||||
|
[0.2.0]: https://github.com/hecrj/iced/compare/0.1.1...0.2.0
|
||||||
[0.1.1]: https://github.com/hecrj/iced/compare/0.1.0...0.1.1
|
[0.1.1]: https://github.com/hecrj/iced/compare/0.1.0...0.1.1
|
||||||
[0.1.0]: https://github.com/hecrj/iced/compare/0.1.0-beta...0.1.0
|
[0.1.0]: https://github.com/hecrj/iced/compare/0.1.0-beta...0.1.0
|
||||||
[0.1.0-beta]: https://github.com/hecrj/iced/compare/0.1.0-alpha...0.1.0-beta
|
[0.1.0-beta]: https://github.com/hecrj/iced/compare/0.1.0-alpha...0.1.0-beta
|
||||||
|
|
|
||||||
23
Cargo.toml
23
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced"
|
name = "iced"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A cross-platform GUI library inspired by Elm"
|
description = "A cross-platform GUI library inspired by Elm"
|
||||||
|
|
@ -41,6 +41,8 @@ tokio = ["iced_futures/tokio"]
|
||||||
tokio_old = ["iced_futures/tokio_old"]
|
tokio_old = ["iced_futures/tokio_old"]
|
||||||
# Enables `async-std` as the `executor::Default` on native platforms
|
# Enables `async-std` as the `executor::Default` on native platforms
|
||||||
async-std = ["iced_futures/async-std"]
|
async-std = ["iced_futures/async-std"]
|
||||||
|
# Enables `smol` as the `executor::Default` on native platforms
|
||||||
|
smol = ["iced_futures/smol"]
|
||||||
# Enables advanced color conversion via `palette`
|
# Enables advanced color conversion via `palette`
|
||||||
palette = ["iced_core/palette"]
|
palette = ["iced_core/palette"]
|
||||||
|
|
||||||
|
|
@ -81,22 +83,25 @@ members = [
|
||||||
"examples/svg",
|
"examples/svg",
|
||||||
"examples/todos",
|
"examples/todos",
|
||||||
"examples/tour",
|
"examples/tour",
|
||||||
|
"examples/tooltip",
|
||||||
|
"examples/url_handler",
|
||||||
|
"examples/menu",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_core = { version = "0.3", path = "core" }
|
iced_core = { version = "0.4", path = "core" }
|
||||||
iced_futures = { version = "0.2", path = "futures" }
|
iced_futures = { version = "0.3", path = "futures" }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
iced_winit = { version = "0.2", path = "winit" }
|
iced_winit = { version = "0.3", path = "winit" }
|
||||||
iced_glutin = { version = "0.1", path = "glutin", optional = true }
|
iced_glutin = { version = "0.2", path = "glutin", optional = true }
|
||||||
iced_wgpu = { version = "0.3", path = "wgpu", optional = true }
|
iced_wgpu = { version = "0.4", path = "wgpu", optional = true }
|
||||||
iced_glow = { version = "0.1", path = "glow", optional = true}
|
iced_glow = { version = "0.2", path = "glow", optional = true}
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
iced_web = { version = "0.3", path = "web" }
|
iced_web = { version = "0.4", path = "web" }
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
features = ["image", "svg", "canvas"]
|
features = ["image", "svg", "canvas", "qr_code"]
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap],
|
||||||
Add `iced` as a dependency in your `Cargo.toml`:
|
Add `iced` as a dependency in your `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
iced = "0.2"
|
iced = "0.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||||
|
|
@ -168,7 +168,7 @@ Browse the [documentation] and the [examples] to learn more!
|
||||||
Iced was originally born as an attempt at bringing the simplicity of [Elm] and
|
Iced was originally born as an attempt at bringing the simplicity of [Elm] and
|
||||||
[The Elm Architecture] into [Coffee], a 2D game engine I am working on.
|
[The Elm Architecture] into [Coffee], a 2D game engine I am working on.
|
||||||
|
|
||||||
The core of the library was implemented during May in [this pull request].
|
The core of the library was implemented during May 2019 in [this pull request].
|
||||||
[The first alpha version] was eventually released as
|
[The first alpha version] was eventually released as
|
||||||
[a renderer-agnostic GUI library]. The library did not provide a renderer and
|
[a renderer-agnostic GUI library]. The library did not provide a renderer and
|
||||||
implemented the current [tour example] on top of [`ggez`], a game library.
|
implemented the current [tour example] on top of [`ggez`], a game library.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_core"
|
name = "iced_core"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "The essential concepts of Iced"
|
description = "The essential concepts of Iced"
|
||||||
|
|
@ -8,6 +8,7 @@ license = "MIT"
|
||||||
repository = "https://github.com/hecrj/iced"
|
repository = "https://github.com/hecrj/iced"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitflags = "1.2"
|
||||||
|
|
||||||
[dependencies.palette]
|
[dependencies.palette]
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ This crate is meant to be a starting point for an Iced runtime.
|
||||||
Add `iced_core` as a dependency in your `Cargo.toml`:
|
Add `iced_core` as a dependency in your `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
iced_core = "0.3"
|
iced_core = "0.4"
|
||||||
```
|
```
|
||||||
|
|
||||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
//! Reuse basic keyboard types.
|
//! Reuse basic keyboard types.
|
||||||
mod event;
|
mod event;
|
||||||
|
mod hotkey;
|
||||||
mod key_code;
|
mod key_code;
|
||||||
mod modifiers;
|
mod modifiers;
|
||||||
|
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
|
pub use hotkey::Hotkey;
|
||||||
pub use key_code::KeyCode;
|
pub use key_code::KeyCode;
|
||||||
pub use modifiers::Modifiers;
|
pub use modifiers::Modifiers;
|
||||||
|
|
|
||||||
18
core/src/keyboard/hotkey.rs
Normal file
18
core/src/keyboard/hotkey.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use crate::keyboard::{KeyCode, Modifiers};
|
||||||
|
|
||||||
|
/// Representation of a hotkey, consists on the combination of a [`KeyCode`] and [`Modifiers`].
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Hotkey {
|
||||||
|
/// The key that represents this hotkey.
|
||||||
|
pub key: KeyCode,
|
||||||
|
|
||||||
|
/// The list of modifiers that represents this hotkey.
|
||||||
|
pub modifiers: Modifiers,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hotkey {
|
||||||
|
/// Creates a new [`Hotkey`] with the given [`Modifiers`] and [`KeyCode`].
|
||||||
|
pub fn new(modifiers: Modifiers, key: KeyCode) -> Self {
|
||||||
|
Self { modifiers, key }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,53 @@
|
||||||
/// The current state of the keyboard modifiers.
|
use bitflags::bitflags;
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
||||||
pub struct Modifiers {
|
|
||||||
/// Whether a shift key is pressed
|
|
||||||
pub shift: bool,
|
|
||||||
|
|
||||||
/// Whether a control key is pressed
|
bitflags! {
|
||||||
pub control: bool,
|
/// The current state of the keyboard modifiers.
|
||||||
|
#[derive(Default)]
|
||||||
/// Whether an alt key is pressed
|
pub struct Modifiers: u32{
|
||||||
pub alt: bool,
|
/// The "shift" key.
|
||||||
|
const SHIFT = 0b100 << 0;
|
||||||
/// Whether a logo key is pressed (e.g. windows key, command key...)
|
// const LSHIFT = 0b010 << 0;
|
||||||
pub logo: bool,
|
// const RSHIFT = 0b001 << 0;
|
||||||
|
//
|
||||||
|
/// The "control" key.
|
||||||
|
const CTRL = 0b100 << 3;
|
||||||
|
// const LCTRL = 0b010 << 3;
|
||||||
|
// const RCTRL = 0b001 << 3;
|
||||||
|
//
|
||||||
|
/// The "alt" key.
|
||||||
|
const ALT = 0b100 << 6;
|
||||||
|
// const LALT = 0b010 << 6;
|
||||||
|
// const RALT = 0b001 << 6;
|
||||||
|
//
|
||||||
|
/// The "windows" key on Windows, "command" key on Mac, and
|
||||||
|
/// "super" key on Linux.
|
||||||
|
const LOGO = 0b100 << 9;
|
||||||
|
// const LLOGO = 0b010 << 9;
|
||||||
|
// const RLOGO = 0b001 << 9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Modifiers {
|
impl Modifiers {
|
||||||
|
/// Returns true if the [`SHIFT`] key is pressed in the [`Modifiers`].
|
||||||
|
pub fn shift(self) -> bool {
|
||||||
|
self.contains(Self::SHIFT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the [`CTRL`] key is pressed in the [`Modifiers`].
|
||||||
|
pub fn control(self) -> bool {
|
||||||
|
self.contains(Self::CTRL)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the [`ALT`] key is pressed in the [`Modifiers`].
|
||||||
|
pub fn alt(self) -> bool {
|
||||||
|
self.contains(Self::ALT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the [`LOGO`] key is pressed in the [`Modifiers`].
|
||||||
|
pub fn logo(self) -> bool {
|
||||||
|
self.contains(Self::LOGO)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if a "command key" is pressed in the [`Modifiers`].
|
/// Returns true if a "command key" is pressed in the [`Modifiers`].
|
||||||
///
|
///
|
||||||
/// The "command key" is the main modifier key used to issue commands in the
|
/// The "command key" is the main modifier key used to issue commands in the
|
||||||
|
|
@ -22,24 +55,13 @@ impl Modifiers {
|
||||||
///
|
///
|
||||||
/// - It is the `logo` or command key (⌘) on macOS
|
/// - It is the `logo` or command key (⌘) on macOS
|
||||||
/// - It is the `control` key on other platforms
|
/// - It is the `control` key on other platforms
|
||||||
pub fn is_command_pressed(self) -> bool {
|
pub fn command(self) -> bool {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let is_pressed = self.logo;
|
let is_pressed = self.logo();
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
let is_pressed = self.control;
|
let is_pressed = self.control();
|
||||||
|
|
||||||
is_pressed
|
is_pressed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the current [`Modifiers`] have at least the same
|
|
||||||
/// keys pressed as the provided ones, and false otherwise.
|
|
||||||
pub fn matches(&self, modifiers: Self) -> bool {
|
|
||||||
let shift = !modifiers.shift || self.shift;
|
|
||||||
let control = !modifiers.control || self.control;
|
|
||||||
let alt = !modifiers.alt || self.alt;
|
|
||||||
let logo = !modifiers.logo || self.logo;
|
|
||||||
|
|
||||||
shift && control && alt && logo
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![forbid(rust_2018_idioms)]
|
#![forbid(rust_2018_idioms)]
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
|
pub mod menu;
|
||||||
pub mod mouse;
|
pub mod mouse;
|
||||||
|
|
||||||
mod align;
|
mod align;
|
||||||
|
|
@ -22,6 +23,7 @@ mod background;
|
||||||
mod color;
|
mod color;
|
||||||
mod font;
|
mod font;
|
||||||
mod length;
|
mod length;
|
||||||
|
mod padding;
|
||||||
mod point;
|
mod point;
|
||||||
mod rectangle;
|
mod rectangle;
|
||||||
mod size;
|
mod size;
|
||||||
|
|
@ -32,6 +34,8 @@ pub use background::Background;
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
pub use font::Font;
|
pub use font::Font;
|
||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
|
pub use menu::Menu;
|
||||||
|
pub use padding::Padding;
|
||||||
pub use point::Point;
|
pub use point::Point;
|
||||||
pub use rectangle::Rectangle;
|
pub use rectangle::Rectangle;
|
||||||
pub use size::Size;
|
pub use size::Size;
|
||||||
|
|
|
||||||
145
core/src/menu.rs
Normal file
145
core/src/menu.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
//! Build menus for your application.
|
||||||
|
use crate::keyboard::Hotkey;
|
||||||
|
|
||||||
|
/// Menu representation.
|
||||||
|
///
|
||||||
|
/// This can be used by `shell` implementations to create a menu.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Menu<Message> {
|
||||||
|
entries: Vec<Entry<Message>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> PartialEq for Menu<Message> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.entries == other.entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> Menu<Message> {
|
||||||
|
/// Creates an empty [`Menu`].
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::with_entries(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Menu`] with the given entries.
|
||||||
|
pub fn with_entries(entries: Vec<Entry<Message>>) -> Self {
|
||||||
|
Self { entries }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [`MenuEntry`] iterator.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &Entry<Message>> {
|
||||||
|
self.entries.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an [`Entry`] to the [`Menu`].
|
||||||
|
pub fn push(mut self, entry: Entry<Message>) -> Self {
|
||||||
|
self.entries.push(entry);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps the `Message` of the [`Menu`] using the provided function.
|
||||||
|
///
|
||||||
|
/// This is useful to compose menus and split them into different
|
||||||
|
/// abstraction levels.
|
||||||
|
pub fn map<B>(self, f: impl Fn(Message) -> B + Copy) -> Menu<B> {
|
||||||
|
// TODO: Use a boxed trait to avoid reallocation of entries
|
||||||
|
Menu {
|
||||||
|
entries: self
|
||||||
|
.entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| entry.map(f))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents one of the possible entries used to build a [`Menu`].
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Entry<Message> {
|
||||||
|
/// Item for a [`Menu`]
|
||||||
|
Item {
|
||||||
|
/// The title of the item
|
||||||
|
title: String,
|
||||||
|
/// The [`Hotkey`] to activate the item, if any
|
||||||
|
hotkey: Option<Hotkey>,
|
||||||
|
/// The message generated when the item is activated
|
||||||
|
on_activation: Message,
|
||||||
|
},
|
||||||
|
/// Dropdown for a [`Menu`]
|
||||||
|
Dropdown {
|
||||||
|
/// Title of the dropdown
|
||||||
|
title: String,
|
||||||
|
/// The submenu of the dropdown
|
||||||
|
submenu: Menu<Message>,
|
||||||
|
},
|
||||||
|
/// Separator for a [`Menu`]
|
||||||
|
Separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> Entry<Message> {
|
||||||
|
/// Creates an [`Entry::Item`].
|
||||||
|
pub fn item<S: Into<String>>(
|
||||||
|
title: S,
|
||||||
|
hotkey: impl Into<Option<Hotkey>>,
|
||||||
|
on_activation: Message,
|
||||||
|
) -> Self {
|
||||||
|
let title = title.into();
|
||||||
|
let hotkey = hotkey.into();
|
||||||
|
|
||||||
|
Self::Item {
|
||||||
|
title,
|
||||||
|
hotkey,
|
||||||
|
on_activation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an [`Entry::Dropdown`].
|
||||||
|
pub fn dropdown<S: Into<String>>(title: S, submenu: Menu<Message>) -> Self {
|
||||||
|
let title = title.into();
|
||||||
|
|
||||||
|
Self::Dropdown { title, submenu }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map<B>(self, f: impl Fn(Message) -> B + Copy) -> Entry<B> {
|
||||||
|
match self {
|
||||||
|
Self::Item {
|
||||||
|
title,
|
||||||
|
hotkey,
|
||||||
|
on_activation,
|
||||||
|
} => Entry::Item {
|
||||||
|
title,
|
||||||
|
hotkey,
|
||||||
|
on_activation: f(on_activation),
|
||||||
|
},
|
||||||
|
Self::Dropdown { title, submenu } => Entry::Dropdown {
|
||||||
|
title,
|
||||||
|
submenu: submenu.map(f),
|
||||||
|
},
|
||||||
|
Self::Separator => Entry::Separator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> PartialEq for Entry<Message> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(
|
||||||
|
Entry::Item { title, hotkey, .. },
|
||||||
|
Entry::Item {
|
||||||
|
title: other_title,
|
||||||
|
hotkey: other_hotkey,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => title == other_title && hotkey == other_hotkey,
|
||||||
|
(
|
||||||
|
Entry::Dropdown { title, submenu },
|
||||||
|
Entry::Dropdown {
|
||||||
|
title: other_title,
|
||||||
|
submenu: other_submenu,
|
||||||
|
},
|
||||||
|
) => title == other_title && submenu == other_submenu,
|
||||||
|
(Entry::Separator, Entry::Separator) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::Point;
|
||||||
|
|
||||||
use super::Button;
|
use super::Button;
|
||||||
|
|
||||||
/// A mouse event.
|
/// A mouse event.
|
||||||
|
|
@ -16,11 +18,8 @@ pub enum Event {
|
||||||
|
|
||||||
/// The mouse cursor was moved
|
/// The mouse cursor was moved
|
||||||
CursorMoved {
|
CursorMoved {
|
||||||
/// The X coordinate of the mouse position
|
/// The new position of the mouse cursor
|
||||||
x: f32,
|
position: Point,
|
||||||
|
|
||||||
/// The Y coordinate of the mouse position
|
|
||||||
y: f32,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A mouse button was pressed.
|
/// A mouse button was pressed.
|
||||||
|
|
|
||||||
107
core/src/padding.rs
Normal file
107
core/src/padding.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
/// An amount of space to pad for each side of a box
|
||||||
|
///
|
||||||
|
/// You can leverage the `From` trait to build [`Padding`] conveniently:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use iced_core::Padding;
|
||||||
|
/// #
|
||||||
|
/// let padding = Padding::from(20); // 20px on all sides
|
||||||
|
/// let padding = Padding::from([10, 20]); // top/bottom, left/right
|
||||||
|
/// let padding = Padding::from([5, 10, 15, 20]); // top, right, bottom, left
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Normally, the `padding` method of a widget will ask for an `Into<Padding>`,
|
||||||
|
/// so you can easily write:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use iced_core::Padding;
|
||||||
|
/// #
|
||||||
|
/// # struct Widget;
|
||||||
|
/// #
|
||||||
|
/// impl Widget {
|
||||||
|
/// # pub fn new() -> Self { Self }
|
||||||
|
/// #
|
||||||
|
/// pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||||
|
/// // ...
|
||||||
|
/// self
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let widget = Widget::new().padding(20); // 20px on all sides
|
||||||
|
/// let widget = Widget::new().padding([10, 20]); // top/bottom, left/right
|
||||||
|
/// let widget = Widget::new().padding([5, 10, 15, 20]); // top, right, bottom, left
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Hash, Copy, Clone)]
|
||||||
|
pub struct Padding {
|
||||||
|
/// Top padding
|
||||||
|
pub top: u16,
|
||||||
|
/// Right padding
|
||||||
|
pub right: u16,
|
||||||
|
/// Bottom padding
|
||||||
|
pub bottom: u16,
|
||||||
|
/// Left padding
|
||||||
|
pub left: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Padding {
|
||||||
|
/// Padding of zero
|
||||||
|
pub const ZERO: Padding = Padding {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create a Padding that is equal on all sides
|
||||||
|
pub const fn new(padding: u16) -> Padding {
|
||||||
|
Padding {
|
||||||
|
top: padding,
|
||||||
|
right: padding,
|
||||||
|
bottom: padding,
|
||||||
|
left: padding,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the total amount of vertical [`Padding`].
|
||||||
|
pub fn vertical(self) -> u16 {
|
||||||
|
self.top + self.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the total amount of horizontal [`Padding`].
|
||||||
|
pub fn horizontal(self) -> u16 {
|
||||||
|
self.left + self.right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<u16> for Padding {
|
||||||
|
fn from(p: u16) -> Self {
|
||||||
|
Padding {
|
||||||
|
top: p,
|
||||||
|
right: p,
|
||||||
|
bottom: p,
|
||||||
|
left: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<[u16; 2]> for Padding {
|
||||||
|
fn from(p: [u16; 2]) -> Self {
|
||||||
|
Padding {
|
||||||
|
top: p[0],
|
||||||
|
right: p[1],
|
||||||
|
bottom: p[0],
|
||||||
|
left: p[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<[u16; 4]> for Padding {
|
||||||
|
fn from(p: [u16; 4]) -> Self {
|
||||||
|
Padding {
|
||||||
|
top: p[0],
|
||||||
|
right: p[1],
|
||||||
|
bottom: p[2],
|
||||||
|
left: p[3],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -105,8 +105,8 @@ impl Rectangle<f32> {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: self.x as u32,
|
x: self.x as u32,
|
||||||
y: self.y as u32,
|
y: self.y as u32,
|
||||||
width: self.width.ceil() as u32,
|
width: self.width as u32,
|
||||||
height: self.height.ceil() as u32,
|
height: self.height as u32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::Vector;
|
use crate::{Padding, Vector};
|
||||||
use std::f32;
|
use std::f32;
|
||||||
|
|
||||||
/// An amount of space in 2 dimensions.
|
/// An amount of space in 2 dimensions.
|
||||||
|
|
@ -28,10 +28,10 @@ impl Size {
|
||||||
pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY);
|
pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY);
|
||||||
|
|
||||||
/// Increments the [`Size`] to account for the given padding.
|
/// Increments the [`Size`] to account for the given padding.
|
||||||
pub fn pad(&self, padding: f32) -> Self {
|
pub fn pad(&self, padding: Padding) -> Self {
|
||||||
Size {
|
Size {
|
||||||
width: self.width + padding * 2.0,
|
width: self.width + padding.horizontal() as f32,
|
||||||
height: self.height + padding * 2.0,
|
height: self.height + padding.vertical() as f32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ cargo run --package <example>
|
||||||
[Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
|
[Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
|
||||||
|
|
||||||
## [Coffee]
|
## [Coffee]
|
||||||
Since [Iced was born in May], it has been powering the user interfaces in
|
Since [Iced was born in May 2019], it has been powering the user interfaces in
|
||||||
[Coffee], an experimental 2D game engine.
|
[Coffee], an experimental 2D game engine.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -128,6 +128,6 @@ Since [Iced was born in May], it has been powering the user interfaces in
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[Iced was born in May]: https://github.com/hecrj/coffee/pull/35
|
[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35
|
||||||
[`ui` module]: https://docs.rs/coffee/0.3.2/coffee/ui/index.html
|
[`ui` module]: https://docs.rs/coffee/0.3.2/coffee/ui/index.html
|
||||||
[Coffee]: https://github.com/hecrj/coffee
|
[Coffee]: https://github.com/hecrj/coffee
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke},
|
canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke},
|
||||||
executor, time, Application, Color, Command, Container, Element, Length,
|
executor, time, Application, Clipboard, Color, Command, Container, Element,
|
||||||
Point, Rectangle, Settings, Subscription, Vector,
|
Length, Point, Rectangle, Settings, Subscription, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
|
|
@ -40,7 +40,11 @@ impl Application for Clock {
|
||||||
String::from("Clock - Iced")
|
String::from("Clock - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Tick(local_time) => {
|
Message::Tick(local_time) => {
|
||||||
let now = local_time;
|
let now = local_time;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "download_progress"
|
name = "download_progress"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Songtronix <contact@songtronix.com>"]
|
authors = ["Songtronix <contact@songtronix.com>", "Folyd <lyshuhow@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["tokio_old"] }
|
iced = { path = "../..", features = ["tokio"] }
|
||||||
iced_native = { path = "../../native" }
|
iced_native = { path = "../../native" }
|
||||||
iced_futures = { path = "../../futures" }
|
iced_futures = { path = "../../futures" }
|
||||||
reqwest = "0.10"
|
reqwest = "0.11"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
## Download progress
|
## Download progress
|
||||||
|
|
||||||
A basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
|
A basic application that asynchronously downloads multiple dummy files of 100 MB and tracks the download progress.
|
||||||
|
|
||||||
The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress.
|
The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,46 @@
|
||||||
use iced_futures::futures;
|
use iced_futures::futures;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
// Just a little utility function
|
// Just a little utility function
|
||||||
pub fn file<T: ToString>(url: T) -> iced::Subscription<Progress> {
|
pub fn file<I: 'static + Hash + Copy + Send, T: ToString>(
|
||||||
|
id: I,
|
||||||
|
url: T,
|
||||||
|
) -> iced::Subscription<(I, Progress)> {
|
||||||
iced::Subscription::from_recipe(Download {
|
iced::Subscription::from_recipe(Download {
|
||||||
|
id,
|
||||||
url: url.to_string(),
|
url: url.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Download {
|
pub struct Download<I> {
|
||||||
|
id: I,
|
||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure iced can use our download stream
|
// Make sure iced can use our download stream
|
||||||
impl<H, I> iced_native::subscription::Recipe<H, I> for Download
|
impl<H, I, T> iced_native::subscription::Recipe<H, I> for Download<T>
|
||||||
where
|
where
|
||||||
H: std::hash::Hasher,
|
T: 'static + Hash + Copy + Send,
|
||||||
|
H: Hasher,
|
||||||
{
|
{
|
||||||
type Output = Progress;
|
type Output = (T, Progress);
|
||||||
|
|
||||||
fn hash(&self, state: &mut H) {
|
fn hash(&self, state: &mut H) {
|
||||||
use std::hash::Hash;
|
struct Marker;
|
||||||
|
std::any::TypeId::of::<Marker>().hash(state);
|
||||||
|
|
||||||
std::any::TypeId::of::<Self>().hash(state);
|
self.id.hash(state);
|
||||||
self.url.hash(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stream(
|
fn stream(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
_input: futures::stream::BoxStream<'static, I>,
|
_input: futures::stream::BoxStream<'static, I>,
|
||||||
) -> futures::stream::BoxStream<'static, Self::Output> {
|
) -> futures::stream::BoxStream<'static, Self::Output> {
|
||||||
|
let id = self.id;
|
||||||
|
|
||||||
Box::pin(futures::stream::unfold(
|
Box::pin(futures::stream::unfold(
|
||||||
State::Ready(self.url),
|
State::Ready(self.url),
|
||||||
|state| async move {
|
move |state| async move {
|
||||||
match state {
|
match state {
|
||||||
State::Ready(url) => {
|
State::Ready(url) => {
|
||||||
let response = reqwest::get(&url).await;
|
let response = reqwest::get(&url).await;
|
||||||
|
|
@ -40,7 +49,7 @@ where
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if let Some(total) = response.content_length() {
|
if let Some(total) = response.content_length() {
|
||||||
Some((
|
Some((
|
||||||
Progress::Started,
|
(id, Progress::Started),
|
||||||
State::Downloading {
|
State::Downloading {
|
||||||
response,
|
response,
|
||||||
total,
|
total,
|
||||||
|
|
@ -48,11 +57,14 @@ where
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Some((Progress::Errored, State::Finished))
|
Some((
|
||||||
|
(id, Progress::Errored),
|
||||||
|
State::Finished,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
Some((Progress::Errored, State::Finished))
|
Some(((id, Progress::Errored), State::Finished))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +80,7 @@ where
|
||||||
(downloaded as f32 / total as f32) * 100.0;
|
(downloaded as f32 / total as f32) * 100.0;
|
||||||
|
|
||||||
Some((
|
Some((
|
||||||
Progress::Advanced(percentage),
|
(id, Progress::Advanced(percentage)),
|
||||||
State::Downloading {
|
State::Downloading {
|
||||||
response,
|
response,
|
||||||
total,
|
total,
|
||||||
|
|
@ -76,8 +88,12 @@ where
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Ok(None) => Some((Progress::Finished, State::Finished)),
|
Ok(None) => {
|
||||||
Err(_) => Some((Progress::Errored, State::Finished)),
|
Some(((id, Progress::Finished), State::Finished))
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
Some(((id, Progress::Errored), State::Finished))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
State::Finished => {
|
State::Finished => {
|
||||||
// We do not let the stream die, as it would start a
|
// We do not let the stream die, as it would start a
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
button, executor, Align, Application, Button, Column, Command, Container,
|
button, executor, Align, Application, Button, Clipboard, Column, Command,
|
||||||
Element, Length, ProgressBar, Settings, Subscription, Text,
|
Container, Element, Length, ProgressBar, Settings, Subscription, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod download;
|
mod download;
|
||||||
|
|
@ -10,17 +10,17 @@ pub fn main() -> iced::Result {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Example {
|
struct Example {
|
||||||
Idle { button: button::State },
|
downloads: Vec<Download>,
|
||||||
Downloading { progress: f32 },
|
last_id: usize,
|
||||||
Finished { button: button::State },
|
add: button::State,
|
||||||
Errored { button: button::State },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Download,
|
Add,
|
||||||
DownloadProgressed(download::Progress),
|
Download(usize),
|
||||||
|
DownloadProgressed((usize, download::Progress)),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Example {
|
impl Application for Example {
|
||||||
|
|
@ -30,8 +30,10 @@ impl Application for Example {
|
||||||
|
|
||||||
fn new(_flags: ()) -> (Example, Command<Message>) {
|
fn new(_flags: ()) -> (Example, Command<Message>) {
|
||||||
(
|
(
|
||||||
Example::Idle {
|
Example {
|
||||||
button: button::State::new(),
|
downloads: vec![Download::new(0)],
|
||||||
|
last_id: 0,
|
||||||
|
add: button::State::new(),
|
||||||
},
|
},
|
||||||
Command::none(),
|
Command::none(),
|
||||||
)
|
)
|
||||||
|
|
@ -41,104 +43,177 @@ impl Application for Example {
|
||||||
String::from("Download progress - Iced")
|
String::from("Download progress - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Download => match self {
|
Message::Add => {
|
||||||
Example::Idle { .. }
|
self.last_id = self.last_id + 1;
|
||||||
| Example::Finished { .. }
|
|
||||||
| Example::Errored { .. } => {
|
self.downloads.push(Download::new(self.last_id));
|
||||||
*self = Example::Downloading { progress: 0.0 };
|
}
|
||||||
|
Message::Download(index) => {
|
||||||
|
if let Some(download) = self.downloads.get_mut(index) {
|
||||||
|
download.start();
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
},
|
Message::DownloadProgressed((id, progress)) => {
|
||||||
Message::DownloadProgressed(message) => match self {
|
if let Some(download) =
|
||||||
Example::Downloading { progress } => match message {
|
self.downloads.iter_mut().find(|download| download.id == id)
|
||||||
download::Progress::Started => {
|
{
|
||||||
*progress = 0.0;
|
download.progress(progress);
|
||||||
}
|
}
|
||||||
download::Progress::Advanced(percentage) => {
|
}
|
||||||
*progress = percentage;
|
|
||||||
}
|
|
||||||
download::Progress::Finished => {
|
|
||||||
*self = Example::Finished {
|
|
||||||
button: button::State::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
download::Progress::Errored => {
|
|
||||||
*self = Example::Errored {
|
|
||||||
button: button::State::new(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
match self {
|
Subscription::batch(self.downloads.iter().map(Download::subscription))
|
||||||
Example::Downloading { .. } => {
|
}
|
||||||
download::file("https://speed.hetzner.de/100MB.bin")
|
|
||||||
|
fn view(&mut self) -> Element<Message> {
|
||||||
|
let downloads = self
|
||||||
|
.downloads
|
||||||
|
.iter_mut()
|
||||||
|
.fold(Column::new().spacing(20), |column, download| {
|
||||||
|
column.push(download.view())
|
||||||
|
})
|
||||||
|
.push(
|
||||||
|
Button::new(&mut self.add, Text::new("Add another download"))
|
||||||
|
.on_press(Message::Add)
|
||||||
|
.padding(10),
|
||||||
|
)
|
||||||
|
.align_items(Align::End);
|
||||||
|
|
||||||
|
Container::new(downloads)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.padding(20)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Download {
|
||||||
|
id: usize,
|
||||||
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum State {
|
||||||
|
Idle { button: button::State },
|
||||||
|
Downloading { progress: f32 },
|
||||||
|
Finished { button: button::State },
|
||||||
|
Errored { button: button::State },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Download {
|
||||||
|
pub fn new(id: usize) -> Self {
|
||||||
|
Download {
|
||||||
|
id,
|
||||||
|
state: State::Idle {
|
||||||
|
button: button::State::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
match self.state {
|
||||||
|
State::Idle { .. }
|
||||||
|
| State::Finished { .. }
|
||||||
|
| State::Errored { .. } => {
|
||||||
|
self.state = State::Downloading { progress: 0.0 };
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn progress(&mut self, new_progress: download::Progress) {
|
||||||
|
match &mut self.state {
|
||||||
|
State::Downloading { progress } => match new_progress {
|
||||||
|
download::Progress::Started => {
|
||||||
|
*progress = 0.0;
|
||||||
|
}
|
||||||
|
download::Progress::Advanced(percentage) => {
|
||||||
|
*progress = percentage;
|
||||||
|
}
|
||||||
|
download::Progress::Finished => {
|
||||||
|
self.state = State::Finished {
|
||||||
|
button: button::State::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
download::Progress::Errored => {
|
||||||
|
self.state = State::Errored {
|
||||||
|
button: button::State::new(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscription(&self) -> Subscription<Message> {
|
||||||
|
match self.state {
|
||||||
|
State::Downloading { .. } => {
|
||||||
|
download::file(self.id, "https://speed.hetzner.de/100MB.bin?")
|
||||||
.map(Message::DownloadProgressed)
|
.map(Message::DownloadProgressed)
|
||||||
}
|
}
|
||||||
_ => Subscription::none(),
|
_ => Subscription::none(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Message> {
|
pub fn view(&mut self) -> Element<Message> {
|
||||||
let current_progress = match self {
|
let current_progress = match &self.state {
|
||||||
Example::Idle { .. } => 0.0,
|
State::Idle { .. } => 0.0,
|
||||||
Example::Downloading { progress } => *progress,
|
State::Downloading { progress } => *progress,
|
||||||
Example::Finished { .. } => 100.0,
|
State::Finished { .. } => 100.0,
|
||||||
Example::Errored { .. } => 0.0,
|
State::Errored { .. } => 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let progress_bar = ProgressBar::new(0.0..=100.0, current_progress);
|
let progress_bar = ProgressBar::new(0.0..=100.0, current_progress);
|
||||||
|
|
||||||
let control: Element<_> = match self {
|
let control: Element<_> = match &mut self.state {
|
||||||
Example::Idle { button } => {
|
State::Idle { button } => {
|
||||||
Button::new(button, Text::new("Start the download!"))
|
Button::new(button, Text::new("Start the download!"))
|
||||||
.on_press(Message::Download)
|
.on_press(Message::Download(self.id))
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
Example::Finished { button } => Column::new()
|
State::Finished { button } => Column::new()
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_items(Align::Center)
|
.align_items(Align::Center)
|
||||||
.push(Text::new("Download finished!"))
|
.push(Text::new("Download finished!"))
|
||||||
.push(
|
.push(
|
||||||
Button::new(button, Text::new("Start again"))
|
Button::new(button, Text::new("Start again"))
|
||||||
.on_press(Message::Download),
|
.on_press(Message::Download(self.id)),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
Example::Downloading { .. } => {
|
State::Downloading { .. } => {
|
||||||
Text::new(format!("Downloading... {:.2}%", current_progress))
|
Text::new(format!("Downloading... {:.2}%", current_progress))
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
Example::Errored { button } => Column::new()
|
State::Errored { button } => Column::new()
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_items(Align::Center)
|
.align_items(Align::Center)
|
||||||
.push(Text::new("Something went wrong :("))
|
.push(Text::new("Something went wrong :("))
|
||||||
.push(
|
.push(
|
||||||
Button::new(button, Text::new("Try again"))
|
Button::new(button, Text::new("Try again"))
|
||||||
.on_press(Message::Download),
|
.on_press(Message::Download(self.id)),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = Column::new()
|
Column::new()
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.align_items(Align::Center)
|
.align_items(Align::Center)
|
||||||
.push(progress_bar)
|
.push(progress_bar)
|
||||||
.push(control);
|
.push(control)
|
||||||
|
|
||||||
Container::new(content)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Fill)
|
|
||||||
.center_x()
|
|
||||||
.center_y()
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,30 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
executor, Align, Application, Checkbox, Column, Command, Container,
|
button, executor, Align, Application, Button, Checkbox, Clipboard, Column,
|
||||||
Element, Length, Settings, Subscription, Text,
|
Command, Container, Element, HorizontalAlignment, Length, Settings,
|
||||||
|
Subscription, Text,
|
||||||
};
|
};
|
||||||
|
use iced_native::{window, Event};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
Events::run(Settings::default())
|
Events::run(Settings {
|
||||||
|
exit_on_close_request: false,
|
||||||
|
..Settings::default()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Events {
|
struct Events {
|
||||||
last: Vec<iced_native::Event>,
|
last: Vec<iced_native::Event>,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
|
exit: button::State,
|
||||||
|
should_exit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
EventOccurred(iced_native::Event),
|
EventOccurred(iced_native::Event),
|
||||||
Toggled(bool),
|
Toggled(bool),
|
||||||
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Events {
|
impl Application for Events {
|
||||||
|
|
@ -32,29 +40,41 @@ impl Application for Events {
|
||||||
String::from("Events - Iced")
|
String::from("Events - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::EventOccurred(event) => {
|
Message::EventOccurred(event) if self.enabled => {
|
||||||
self.last.push(event);
|
self.last.push(event);
|
||||||
|
|
||||||
if self.last.len() > 5 {
|
if self.last.len() > 5 {
|
||||||
let _ = self.last.remove(0);
|
let _ = self.last.remove(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::EventOccurred(event) => {
|
||||||
|
if let Event::Window(window::Event::CloseRequested) = event {
|
||||||
|
self.should_exit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::Toggled(enabled) => {
|
Message::Toggled(enabled) => {
|
||||||
self.enabled = enabled;
|
self.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
Message::Exit => {
|
||||||
|
self.should_exit = true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
if self.enabled {
|
iced_native::subscription::events().map(Message::EventOccurred)
|
||||||
iced_native::subscription::events().map(Message::EventOccurred)
|
}
|
||||||
} else {
|
|
||||||
Subscription::none()
|
fn should_exit(&self) -> bool {
|
||||||
}
|
self.should_exit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Message> {
|
fn view(&mut self) -> Element<Message> {
|
||||||
|
|
@ -71,11 +91,22 @@ impl Application for Events {
|
||||||
Message::Toggled,
|
Message::Toggled,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let exit = Button::new(
|
||||||
|
&mut self.exit,
|
||||||
|
Text::new("Exit")
|
||||||
|
.width(Length::Fill)
|
||||||
|
.horizontal_alignment(HorizontalAlignment::Center),
|
||||||
|
)
|
||||||
|
.width(Length::Units(100))
|
||||||
|
.padding(10)
|
||||||
|
.on_press(Message::Exit);
|
||||||
|
|
||||||
let content = Column::new()
|
let content = Column::new()
|
||||||
.align_items(Align::Center)
|
.align_items(Align::Center)
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.push(events)
|
.push(events)
|
||||||
.push(toggle);
|
.push(toggle)
|
||||||
|
.push(exit);
|
||||||
|
|
||||||
Container::new(content)
|
Container::new(content)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||||
tokio = { version = "0.3", features = ["sync"] }
|
tokio = { version = "1.0", features = ["sync"] }
|
||||||
itertools = "0.9"
|
itertools = "0.9"
|
||||||
rustc-hash = "1.1"
|
rustc-hash = "1.1"
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ mod style;
|
||||||
use grid::Grid;
|
use grid::Grid;
|
||||||
use iced::button::{self, Button};
|
use iced::button::{self, Button};
|
||||||
use iced::executor;
|
use iced::executor;
|
||||||
|
use iced::menu::{self, Menu};
|
||||||
use iced::pick_list::{self, PickList};
|
use iced::pick_list::{self, PickList};
|
||||||
use iced::slider::{self, Slider};
|
use iced::slider::{self, Slider};
|
||||||
use iced::time;
|
use iced::time;
|
||||||
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
Align, Application, Checkbox, Column, Command, Container, Element, Length,
|
Align, Application, Checkbox, Clipboard, Column, Command, Container,
|
||||||
Row, Settings, Subscription, Text,
|
Element, Length, Row, Settings, Subscription, Text,
|
||||||
};
|
};
|
||||||
use preset::Preset;
|
use preset::Preset;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
@ -19,6 +21,10 @@ use std::time::{Duration, Instant};
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
GameOfLife::run(Settings {
|
GameOfLife::run(Settings {
|
||||||
antialiasing: true,
|
antialiasing: true,
|
||||||
|
window: window::Settings {
|
||||||
|
position: window::Position::Centered,
|
||||||
|
..window::Settings::default()
|
||||||
|
},
|
||||||
..Settings::default()
|
..Settings::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +71,11 @@ impl Application for GameOfLife {
|
||||||
String::from("Game of Life - Iced")
|
String::from("Game of Life - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Grid(message, version) => {
|
Message::Grid(message, version) => {
|
||||||
if version == self.version {
|
if version == self.version {
|
||||||
|
|
@ -124,6 +134,13 @@ impl Application for GameOfLife {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn menu(&self) -> Menu<Message> {
|
||||||
|
Menu::with_entries(vec![menu::Entry::dropdown(
|
||||||
|
"Presets",
|
||||||
|
Preset::menu().map(Message::PresetPicked),
|
||||||
|
)])
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Message> {
|
fn view(&mut self) -> Element<Message> {
|
||||||
let version = self.version;
|
let version = self.version;
|
||||||
let selected_speed = self.next_speed.unwrap_or(self.speed);
|
let selected_speed = self.next_speed.unwrap_or(self.speed);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use iced::menu::{self, Menu};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Preset {
|
pub enum Preset {
|
||||||
Custom,
|
Custom,
|
||||||
|
|
@ -26,6 +28,17 @@ pub static ALL: &[Preset] = &[
|
||||||
];
|
];
|
||||||
|
|
||||||
impl Preset {
|
impl Preset {
|
||||||
|
pub fn menu() -> Menu<Self> {
|
||||||
|
Menu::with_entries(
|
||||||
|
ALL.iter()
|
||||||
|
.copied()
|
||||||
|
.map(|preset| {
|
||||||
|
menu::Entry::item(preset.to_string(), None, preset)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn life(self) -> Vec<(isize, isize)> {
|
pub fn life(self) -> Vec<(isize, isize)> {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let cells = match self {
|
let cells = match self {
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,7 @@ impl pick_list::StyleSheet for PickList {
|
||||||
},
|
},
|
||||||
border_radius: 2.0,
|
border_radius: 2.0,
|
||||||
icon_size: 0.5,
|
icon_size: 0.5,
|
||||||
|
..pick_list::Style::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use iced_wgpu::Renderer;
|
use iced_wgpu::Renderer;
|
||||||
use iced_winit::{
|
use iced_winit::{
|
||||||
slider, Align, Color, Column, Command, Element, Length, Program, Row,
|
slider, Align, Clipboard, Color, Column, Command, Element, Length, Program,
|
||||||
Slider, Text,
|
Row, Slider, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Controls {
|
pub struct Controls {
|
||||||
|
|
@ -30,8 +30,13 @@ impl Controls {
|
||||||
impl Program for Controls {
|
impl Program for Controls {
|
||||||
type Renderer = Renderer;
|
type Renderer = Renderer;
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
type Clipboard = Clipboard;
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::BackgroundColorChanged(color) => {
|
Message::BackgroundColorChanged(color) => {
|
||||||
self.background_color = color;
|
self.background_color = color;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use controls::Controls;
|
||||||
use scene::Scene;
|
use scene::Scene;
|
||||||
|
|
||||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
|
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
|
||||||
use iced_winit::{conversion, futures, program, winit, Debug, Size};
|
use iced_winit::{conversion, futures, program, winit, Clipboard, Debug, Size};
|
||||||
|
|
||||||
use futures::task::SpawnExt;
|
use futures::task::SpawnExt;
|
||||||
use winit::{
|
use winit::{
|
||||||
|
|
@ -28,6 +28,7 @@ pub fn main() {
|
||||||
);
|
);
|
||||||
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
|
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
|
||||||
let mut modifiers = ModifiersState::default();
|
let mut modifiers = ModifiersState::default();
|
||||||
|
let mut clipboard = Clipboard::connect(&window);
|
||||||
|
|
||||||
// Initialize wgpu
|
// Initialize wgpu
|
||||||
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
|
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
|
||||||
|
|
@ -36,7 +37,7 @@ pub fn main() {
|
||||||
let (mut device, queue) = futures::executor::block_on(async {
|
let (mut device, queue) = futures::executor::block_on(async {
|
||||||
let adapter = instance
|
let adapter = instance
|
||||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
power_preference: wgpu::PowerPreference::Default,
|
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||||
compatible_surface: Some(&surface),
|
compatible_surface: Some(&surface),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
@ -45,9 +46,9 @@ pub fn main() {
|
||||||
adapter
|
adapter
|
||||||
.request_device(
|
.request_device(
|
||||||
&wgpu::DeviceDescriptor {
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
features: wgpu::Features::empty(),
|
features: wgpu::Features::empty(),
|
||||||
limits: wgpu::Limits::default(),
|
limits: wgpu::Limits::default(),
|
||||||
shader_validation: false,
|
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
@ -63,7 +64,7 @@ pub fn main() {
|
||||||
device.create_swap_chain(
|
device.create_swap_chain(
|
||||||
&surface,
|
&surface,
|
||||||
&wgpu::SwapChainDescriptor {
|
&wgpu::SwapChainDescriptor {
|
||||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
format: format,
|
format: format,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
|
|
@ -141,8 +142,8 @@ pub fn main() {
|
||||||
cursor_position,
|
cursor_position,
|
||||||
viewport.scale_factor(),
|
viewport.scale_factor(),
|
||||||
),
|
),
|
||||||
None,
|
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
|
&mut clipboard,
|
||||||
&mut debug,
|
&mut debug,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -157,7 +158,7 @@ pub fn main() {
|
||||||
swap_chain = device.create_swap_chain(
|
swap_chain = device.create_swap_chain(
|
||||||
&surface,
|
&surface,
|
||||||
&wgpu::SwapChainDescriptor {
|
&wgpu::SwapChainDescriptor {
|
||||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||||
format: format,
|
format: format,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,9 @@ impl Scene {
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
) -> wgpu::RenderPass<'a> {
|
) -> wgpu::RenderPass<'a> {
|
||||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
label: None,
|
||||||
attachment: target,
|
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||||
|
view: target,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear({
|
load: wgpu::LoadOp::Clear({
|
||||||
|
|
@ -48,10 +49,10 @@ impl Scene {
|
||||||
|
|
||||||
fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
|
fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
|
||||||
let vs_module =
|
let vs_module =
|
||||||
device.create_shader_module(wgpu::include_spirv!("shader/vert.spv"));
|
device.create_shader_module(&wgpu::include_spirv!("shader/vert.spv"));
|
||||||
|
|
||||||
let fs_module =
|
let fs_module =
|
||||||
device.create_shader_module(wgpu::include_spirv!("shader/frag.spv"));
|
device.create_shader_module(&wgpu::include_spirv!("shader/frag.spv"));
|
||||||
|
|
||||||
let pipeline_layout =
|
let pipeline_layout =
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
|
@ -64,34 +65,34 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
|
||||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
layout: Some(&pipeline_layout),
|
layout: Some(&pipeline_layout),
|
||||||
vertex_stage: wgpu::ProgrammableStageDescriptor {
|
vertex: wgpu::VertexState {
|
||||||
module: &vs_module,
|
module: &vs_module,
|
||||||
entry_point: "main",
|
entry_point: "main",
|
||||||
|
buffers: &[],
|
||||||
},
|
},
|
||||||
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
|
fragment: Some(wgpu::FragmentState {
|
||||||
module: &fs_module,
|
module: &fs_module,
|
||||||
entry_point: "main",
|
entry_point: "main",
|
||||||
|
targets: &[wgpu::ColorTargetState {
|
||||||
|
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||||
|
blend: Some(wgpu::BlendState {
|
||||||
|
color: wgpu::BlendComponent::REPLACE,
|
||||||
|
alpha: wgpu::BlendComponent::REPLACE,
|
||||||
|
}),
|
||||||
|
write_mask: wgpu::ColorWrite::ALL,
|
||||||
|
}],
|
||||||
}),
|
}),
|
||||||
rasterization_state: Some(wgpu::RasterizationStateDescriptor {
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
front_face: wgpu::FrontFace::Ccw,
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
cull_mode: wgpu::CullMode::None,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
|
||||||
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
|
|
||||||
color_states: &[wgpu::ColorStateDescriptor {
|
|
||||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
|
||||||
color_blend: wgpu::BlendDescriptor::REPLACE,
|
|
||||||
alpha_blend: wgpu::BlendDescriptor::REPLACE,
|
|
||||||
write_mask: wgpu::ColorWrite::ALL,
|
|
||||||
}],
|
|
||||||
depth_stencil_state: None,
|
|
||||||
vertex_state: wgpu::VertexStateDescriptor {
|
|
||||||
index_format: wgpu::IndexFormat::Uint16,
|
|
||||||
vertex_buffers: &[],
|
|
||||||
},
|
},
|
||||||
sample_count: 1,
|
depth_stencil: None,
|
||||||
sample_mask: !0,
|
multisample: wgpu::MultisampleState {
|
||||||
alpha_to_coverage_enabled: false,
|
count: 1,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
|
|
|
||||||
10
examples/menu/Cargo.toml
Normal file
10
examples/menu/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "menu"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../.." }
|
||||||
|
iced_native = { path = "../../native" }
|
||||||
117
examples/menu/src/main.rs
Normal file
117
examples/menu/src/main.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
use iced::menu::{self, Menu};
|
||||||
|
use iced::{
|
||||||
|
executor, Application, Clipboard, Command, Container, Element, Length,
|
||||||
|
Settings, Text,
|
||||||
|
};
|
||||||
|
use iced_native::keyboard::{Hotkey, KeyCode, Modifiers};
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
App::run(Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct App {
|
||||||
|
selected: Option<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Entry {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Three,
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
MenuActivated(Entry),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for App {
|
||||||
|
type Executor = executor::Default;
|
||||||
|
type Message = Message;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
fn new(_flags: ()) -> (App, Command<Message>) {
|
||||||
|
(App::default(), Command::none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Menu - Iced")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn menu(&self) -> Menu<Message> {
|
||||||
|
let alt = Modifiers::ALT;
|
||||||
|
let ctrl_shift = Modifiers::CTRL | Modifiers::SHIFT;
|
||||||
|
|
||||||
|
Menu::with_entries(vec![
|
||||||
|
menu::Entry::dropdown(
|
||||||
|
"First",
|
||||||
|
Menu::with_entries(vec![
|
||||||
|
menu::Entry::item(
|
||||||
|
"One",
|
||||||
|
Hotkey::new(alt, KeyCode::F1),
|
||||||
|
Message::MenuActivated(Entry::One),
|
||||||
|
),
|
||||||
|
menu::Entry::item(
|
||||||
|
"Two",
|
||||||
|
Hotkey::new(alt, KeyCode::F2),
|
||||||
|
Message::MenuActivated(Entry::Two),
|
||||||
|
),
|
||||||
|
menu::Entry::Separator,
|
||||||
|
menu::Entry::item(
|
||||||
|
"Three",
|
||||||
|
Hotkey::new(alt, KeyCode::F3),
|
||||||
|
Message::MenuActivated(Entry::Three),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
menu::Entry::dropdown(
|
||||||
|
"Second",
|
||||||
|
Menu::with_entries(vec![
|
||||||
|
menu::Entry::item(
|
||||||
|
"A",
|
||||||
|
Hotkey::new(ctrl_shift, KeyCode::A),
|
||||||
|
Message::MenuActivated(Entry::A),
|
||||||
|
),
|
||||||
|
menu::Entry::item(
|
||||||
|
"B",
|
||||||
|
Hotkey::new(ctrl_shift, KeyCode::B),
|
||||||
|
Message::MenuActivated(Entry::B),
|
||||||
|
),
|
||||||
|
menu::Entry::Separator,
|
||||||
|
menu::Entry::item(
|
||||||
|
"C",
|
||||||
|
Hotkey::new(ctrl_shift, KeyCode::C),
|
||||||
|
Message::MenuActivated(Entry::C),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
|
match message {
|
||||||
|
Message::MenuActivated(entry) => self.selected = Some(entry),
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&mut self) -> Element<Message> {
|
||||||
|
Container::new(
|
||||||
|
Text::new(format!("Selected {:?}", self.selected)).size(48),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
button, executor, keyboard, pane_grid, scrollable, Align, Application,
|
button, executor, keyboard, pane_grid, scrollable, Align, Application,
|
||||||
Button, Column, Command, Container, Element, HorizontalAlignment, Length,
|
Button, Clipboard, Color, Column, Command, Container, Element,
|
||||||
PaneGrid, Scrollable, Settings, Subscription, Text,
|
HorizontalAlignment, Length, PaneGrid, Row, Scrollable, Settings,
|
||||||
|
Subscription, Text,
|
||||||
};
|
};
|
||||||
use iced_native::{event, subscription, Event};
|
use iced_native::{event, subscription, Event};
|
||||||
|
|
||||||
|
|
@ -10,7 +11,7 @@ pub fn main() -> iced::Result {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Example {
|
struct Example {
|
||||||
panes: pane_grid::State<Content>,
|
panes: pane_grid::State<Pane>,
|
||||||
panes_created: usize,
|
panes_created: usize,
|
||||||
focus: Option<pane_grid::Pane>,
|
focus: Option<pane_grid::Pane>,
|
||||||
}
|
}
|
||||||
|
|
@ -23,6 +24,7 @@ enum Message {
|
||||||
Clicked(pane_grid::Pane),
|
Clicked(pane_grid::Pane),
|
||||||
Dragged(pane_grid::DragEvent),
|
Dragged(pane_grid::DragEvent),
|
||||||
Resized(pane_grid::ResizeEvent),
|
Resized(pane_grid::ResizeEvent),
|
||||||
|
TogglePin(pane_grid::Pane),
|
||||||
Close(pane_grid::Pane),
|
Close(pane_grid::Pane),
|
||||||
CloseFocused,
|
CloseFocused,
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +35,7 @@ impl Application for Example {
|
||||||
type Flags = ();
|
type Flags = ();
|
||||||
|
|
||||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||||
let (panes, _) = pane_grid::State::new(Content::new(0));
|
let (panes, _) = pane_grid::State::new(Pane::new(0));
|
||||||
|
|
||||||
(
|
(
|
||||||
Example {
|
Example {
|
||||||
|
|
@ -49,13 +51,17 @@ impl Application for Example {
|
||||||
String::from("Pane grid - Iced")
|
String::from("Pane grid - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Split(axis, pane) => {
|
Message::Split(axis, pane) => {
|
||||||
let result = self.panes.split(
|
let result = self.panes.split(
|
||||||
axis,
|
axis,
|
||||||
&pane,
|
&pane,
|
||||||
Content::new(self.panes_created),
|
Pane::new(self.panes_created),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some((pane, _)) = result {
|
if let Some((pane, _)) = result {
|
||||||
|
|
@ -69,7 +75,7 @@ impl Application for Example {
|
||||||
let result = self.panes.split(
|
let result = self.panes.split(
|
||||||
axis,
|
axis,
|
||||||
&pane,
|
&pane,
|
||||||
Content::new(self.panes_created),
|
Pane::new(self.panes_created),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some((pane, _)) = result {
|
if let Some((pane, _)) = result {
|
||||||
|
|
@ -101,6 +107,12 @@ impl Application for Example {
|
||||||
self.panes.swap(&pane, &target);
|
self.panes.swap(&pane, &target);
|
||||||
}
|
}
|
||||||
Message::Dragged(_) => {}
|
Message::Dragged(_) => {}
|
||||||
|
Message::TogglePin(pane) => {
|
||||||
|
if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane)
|
||||||
|
{
|
||||||
|
*is_pinned = !*is_pinned;
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::Close(pane) => {
|
Message::Close(pane) => {
|
||||||
if let Some((_, sibling)) = self.panes.close(&pane) {
|
if let Some((_, sibling)) = self.panes.close(&pane) {
|
||||||
self.focus = Some(sibling);
|
self.focus = Some(sibling);
|
||||||
|
|
@ -108,8 +120,14 @@ impl Application for Example {
|
||||||
}
|
}
|
||||||
Message::CloseFocused => {
|
Message::CloseFocused => {
|
||||||
if let Some(pane) = self.focus {
|
if let Some(pane) = self.focus {
|
||||||
if let Some((_, sibling)) = self.panes.close(&pane) {
|
if let Some(Pane { is_pinned, .. }) = self.panes.get(&pane)
|
||||||
self.focus = Some(sibling);
|
{
|
||||||
|
if !is_pinned {
|
||||||
|
if let Some((_, sibling)) = self.panes.close(&pane)
|
||||||
|
{
|
||||||
|
self.focus = Some(sibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +146,7 @@ impl Application for Example {
|
||||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
modifiers,
|
modifiers,
|
||||||
key_code,
|
key_code,
|
||||||
}) if modifiers.is_command_pressed() => handle_hotkey(key_code),
|
}) if modifiers.command() => handle_hotkey(key_code),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -138,17 +156,41 @@ impl Application for Example {
|
||||||
let focus = self.focus;
|
let focus = self.focus;
|
||||||
let total_panes = self.panes.len();
|
let total_panes = self.panes.len();
|
||||||
|
|
||||||
let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| {
|
let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| {
|
||||||
let is_focused = focus == Some(pane);
|
let is_focused = focus == Some(id);
|
||||||
|
|
||||||
let title_bar =
|
let text = if pane.is_pinned { "Unpin" } else { "Pin" };
|
||||||
pane_grid::TitleBar::new(format!("Pane {}", content.id))
|
let pin_button =
|
||||||
.padding(10)
|
Button::new(&mut pane.pin_button, Text::new(text).size(14))
|
||||||
.style(style::TitleBar { is_focused });
|
.on_press(Message::TogglePin(id))
|
||||||
|
.style(style::Button::Pin)
|
||||||
|
.padding(3);
|
||||||
|
|
||||||
pane_grid::Content::new(content.view(pane, total_panes))
|
let title = Row::with_children(vec![
|
||||||
.title_bar(title_bar)
|
pin_button.into(),
|
||||||
.style(style::Pane { is_focused })
|
Text::new("Pane").into(),
|
||||||
|
Text::new(pane.content.id.to_string())
|
||||||
|
.color(if is_focused {
|
||||||
|
PANE_ID_COLOR_FOCUSED
|
||||||
|
} else {
|
||||||
|
PANE_ID_COLOR_UNFOCUSED
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.spacing(5);
|
||||||
|
|
||||||
|
let title_bar = pane_grid::TitleBar::new(title)
|
||||||
|
.controls(pane.controls.view(id, total_panes, pane.is_pinned))
|
||||||
|
.padding(10)
|
||||||
|
.style(style::TitleBar { is_focused });
|
||||||
|
|
||||||
|
pane_grid::Content::new(pane.content.view(
|
||||||
|
id,
|
||||||
|
total_panes,
|
||||||
|
pane.is_pinned,
|
||||||
|
))
|
||||||
|
.title_bar(title_bar)
|
||||||
|
.style(style::Pane { is_focused })
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
|
|
@ -165,6 +207,17 @@ impl Application for Example {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb(
|
||||||
|
0xFF as f32 / 255.0,
|
||||||
|
0xC7 as f32 / 255.0,
|
||||||
|
0xC7 as f32 / 255.0,
|
||||||
|
);
|
||||||
|
const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb(
|
||||||
|
0xFF as f32 / 255.0,
|
||||||
|
0x47 as f32 / 255.0,
|
||||||
|
0x47 as f32 / 255.0,
|
||||||
|
);
|
||||||
|
|
||||||
fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
|
fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
|
||||||
use keyboard::KeyCode;
|
use keyboard::KeyCode;
|
||||||
use pane_grid::{Axis, Direction};
|
use pane_grid::{Axis, Direction};
|
||||||
|
|
@ -185,6 +238,13 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Pane {
|
||||||
|
pub is_pinned: bool,
|
||||||
|
pub pin_button: button::State,
|
||||||
|
pub content: Content,
|
||||||
|
pub controls: Controls,
|
||||||
|
}
|
||||||
|
|
||||||
struct Content {
|
struct Content {
|
||||||
id: usize,
|
id: usize,
|
||||||
scroll: scrollable::State,
|
scroll: scrollable::State,
|
||||||
|
|
@ -193,6 +253,21 @@ struct Content {
|
||||||
close: button::State,
|
close: button::State,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Controls {
|
||||||
|
close: button::State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pane {
|
||||||
|
fn new(id: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
is_pinned: false,
|
||||||
|
pin_button: button::State::new(),
|
||||||
|
content: Content::new(id),
|
||||||
|
controls: Controls::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Content {
|
impl Content {
|
||||||
fn new(id: usize) -> Self {
|
fn new(id: usize) -> Self {
|
||||||
Content {
|
Content {
|
||||||
|
|
@ -207,6 +282,7 @@ impl Content {
|
||||||
&mut self,
|
&mut self,
|
||||||
pane: pane_grid::Pane,
|
pane: pane_grid::Pane,
|
||||||
total_panes: usize,
|
total_panes: usize,
|
||||||
|
is_pinned: bool,
|
||||||
) -> Element<Message> {
|
) -> Element<Message> {
|
||||||
let Content {
|
let Content {
|
||||||
scroll,
|
scroll,
|
||||||
|
|
@ -246,7 +322,7 @@ impl Content {
|
||||||
style::Button::Primary,
|
style::Button::Primary,
|
||||||
));
|
));
|
||||||
|
|
||||||
if total_panes > 1 {
|
if total_panes > 1 && !is_pinned {
|
||||||
controls = controls.push(button(
|
controls = controls.push(button(
|
||||||
close,
|
close,
|
||||||
"Close",
|
"Close",
|
||||||
|
|
@ -270,7 +346,32 @@ impl Content {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Controls {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
close: button::State::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(
|
||||||
|
&mut self,
|
||||||
|
pane: pane_grid::Pane,
|
||||||
|
total_panes: usize,
|
||||||
|
is_pinned: bool,
|
||||||
|
) -> Element<Message> {
|
||||||
|
let mut button =
|
||||||
|
Button::new(&mut self.close, Text::new("Close").size(14))
|
||||||
|
.style(style::Button::Control)
|
||||||
|
.padding(3);
|
||||||
|
if total_panes > 1 && !is_pinned {
|
||||||
|
button = button.on_press(Message::Close(pane));
|
||||||
|
}
|
||||||
|
button.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod style {
|
mod style {
|
||||||
|
use crate::PANE_ID_COLOR_FOCUSED;
|
||||||
use iced::{button, container, Background, Color, Vector};
|
use iced::{button, container, Background, Color, Vector};
|
||||||
|
|
||||||
const SURFACE: Color = Color::from_rgb(
|
const SURFACE: Color = Color::from_rgb(
|
||||||
|
|
@ -332,6 +433,8 @@ mod style {
|
||||||
pub enum Button {
|
pub enum Button {
|
||||||
Primary,
|
Primary,
|
||||||
Destructive,
|
Destructive,
|
||||||
|
Control,
|
||||||
|
Pin,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl button::StyleSheet for Button {
|
impl button::StyleSheet for Button {
|
||||||
|
|
@ -341,6 +444,8 @@ mod style {
|
||||||
Button::Destructive => {
|
Button::Destructive => {
|
||||||
(None, Color::from_rgb8(0xFF, 0x47, 0x47))
|
(None, Color::from_rgb8(0xFF, 0x47, 0x47))
|
||||||
}
|
}
|
||||||
|
Button::Control => (Some(PANE_ID_COLOR_FOCUSED), Color::WHITE),
|
||||||
|
Button::Pin => (Some(ACTIVE), Color::WHITE),
|
||||||
};
|
};
|
||||||
|
|
||||||
button::Style {
|
button::Style {
|
||||||
|
|
@ -361,6 +466,8 @@ mod style {
|
||||||
a: 0.2,
|
a: 0.2,
|
||||||
..active.text_color
|
..active.text_color
|
||||||
}),
|
}),
|
||||||
|
Button::Control => Some(PANE_ID_COLOR_FOCUSED),
|
||||||
|
Button::Pin => Some(HOVERED),
|
||||||
};
|
};
|
||||||
|
|
||||||
button::Style {
|
button::Style {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub fn main() -> iced::Result {
|
||||||
struct Example {
|
struct Example {
|
||||||
scroll: scrollable::State,
|
scroll: scrollable::State,
|
||||||
pick_list: pick_list::State<Language>,
|
pick_list: pick_list::State<Language>,
|
||||||
selected_language: Language,
|
selected_language: Option<Language>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -33,7 +33,7 @@ impl Sandbox for Example {
|
||||||
fn update(&mut self, message: Message) {
|
fn update(&mut self, message: Message) {
|
||||||
match message {
|
match message {
|
||||||
Message::LanguageSelected(language) => {
|
Message::LanguageSelected(language) => {
|
||||||
self.selected_language = language;
|
self.selected_language = Some(language);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,9 +42,10 @@ impl Sandbox for Example {
|
||||||
let pick_list = PickList::new(
|
let pick_list = PickList::new(
|
||||||
&mut self.pick_list,
|
&mut self.pick_list,
|
||||||
&Language::ALL[..],
|
&Language::ALL[..],
|
||||||
Some(self.selected_language),
|
self.selected_language,
|
||||||
Message::LanguageSelected,
|
Message::LanguageSelected,
|
||||||
);
|
)
|
||||||
|
.placeholder("Choose a language...");
|
||||||
|
|
||||||
let mut content = Scrollable::new(&mut self.scroll)
|
let mut content = Scrollable::new(&mut self.scroll)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
button, futures, image, Align, Application, Button, Column, Command,
|
button, futures, image, Align, Application, Button, Clipboard, Column,
|
||||||
Container, Element, Image, Length, Row, Settings, Text,
|
Command, Container, Element, Length, Row, Settings, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
|
|
@ -48,7 +48,11 @@ impl Application for Pokedex {
|
||||||
format!("{} - Pokédex", subtitle)
|
format!("{} - Pokédex", subtitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::PokemonFound(Ok(pokemon)) => {
|
Message::PokemonFound(Ok(pokemon)) => {
|
||||||
*self = Pokedex::Loaded {
|
*self = Pokedex::Loaded {
|
||||||
|
|
@ -112,16 +116,20 @@ struct Pokemon {
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
image: image::Handle,
|
image: image::Handle,
|
||||||
|
image_viewer: image::viewer::State,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pokemon {
|
impl Pokemon {
|
||||||
const TOTAL: u16 = 807;
|
const TOTAL: u16 = 807;
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&mut self) -> Element<Message> {
|
||||||
Row::new()
|
Row::new()
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.align_items(Align::Center)
|
.align_items(Align::Center)
|
||||||
.push(Image::new(self.image.clone()))
|
.push(image::Viewer::new(
|
||||||
|
&mut self.image_viewer,
|
||||||
|
self.image.clone(),
|
||||||
|
))
|
||||||
.push(
|
.push(
|
||||||
Column::new()
|
Column::new()
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
|
|
@ -200,11 +208,15 @@ impl Pokemon {
|
||||||
.map(|c| if c.is_control() { ' ' } else { c })
|
.map(|c| if c.is_control() { ' ' } else { c })
|
||||||
.collect(),
|
.collect(),
|
||||||
image,
|
image,
|
||||||
|
image_viewer: image::viewer::State::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_image(id: u16) -> Result<image::Handle, reqwest::Error> {
|
async fn fetch_image(id: u16) -> Result<image::Handle, reqwest::Error> {
|
||||||
let url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", id);
|
let url = format!(
|
||||||
|
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png",
|
||||||
|
id
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ edition = "2018"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../.." }
|
iced = { path = "../..", features = ["debug"] }
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
mod style;
|
mod style;
|
||||||
|
|
||||||
use iced::{
|
use iced::{
|
||||||
scrollable, Column, Container, Element, Length, Radio, Row, Rule, Sandbox,
|
button, scrollable, Button, Column, Container, Element, Length,
|
||||||
Scrollable, Settings, Space, Text,
|
ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Space, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
|
|
@ -17,6 +17,9 @@ struct ScrollableDemo {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
ThemeChanged(style::Theme),
|
ThemeChanged(style::Theme),
|
||||||
|
ScrollToTop(usize),
|
||||||
|
ScrollToBottom(usize),
|
||||||
|
Scrolled(usize, f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sandbox for ScrollableDemo {
|
impl Sandbox for ScrollableDemo {
|
||||||
|
|
@ -36,6 +39,25 @@ impl Sandbox for ScrollableDemo {
|
||||||
fn update(&mut self, message: Message) {
|
fn update(&mut self, message: Message) {
|
||||||
match message {
|
match message {
|
||||||
Message::ThemeChanged(theme) => self.theme = theme,
|
Message::ThemeChanged(theme) => self.theme = theme,
|
||||||
|
Message::ScrollToTop(i) => {
|
||||||
|
if let Some(variant) = self.variants.get_mut(i) {
|
||||||
|
variant.scrollable.snap_to(0.0);
|
||||||
|
|
||||||
|
variant.latest_offset = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::ScrollToBottom(i) => {
|
||||||
|
if let Some(variant) = self.variants.get_mut(i) {
|
||||||
|
variant.scrollable.snap_to(1.0);
|
||||||
|
|
||||||
|
variant.latest_offset = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::Scrolled(i, offset) => {
|
||||||
|
if let Some(variant) = self.variants.get_mut(i) {
|
||||||
|
variant.latest_offset = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,13 +84,28 @@ impl Sandbox for ScrollableDemo {
|
||||||
let scrollable_row = Row::with_children(
|
let scrollable_row = Row::with_children(
|
||||||
variants
|
variants
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|variant| {
|
.enumerate()
|
||||||
let mut scrollable = Scrollable::new(&mut variant.state)
|
.map(|(i, variant)| {
|
||||||
.padding(10)
|
let mut scrollable =
|
||||||
.width(Length::Fill)
|
Scrollable::new(&mut variant.scrollable)
|
||||||
.height(Length::Fill)
|
.padding(10)
|
||||||
.style(*theme)
|
.spacing(10)
|
||||||
.push(Text::new(variant.title));
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.on_scroll(move |offset| {
|
||||||
|
Message::Scrolled(i, offset)
|
||||||
|
})
|
||||||
|
.style(*theme)
|
||||||
|
.push(Text::new(variant.title))
|
||||||
|
.push(
|
||||||
|
Button::new(
|
||||||
|
&mut variant.scroll_to_bottom,
|
||||||
|
Text::new("Scroll to bottom"),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.padding(10)
|
||||||
|
.on_press(Message::ScrollToBottom(i)),
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(scrollbar_width) = variant.scrollbar_width {
|
if let Some(scrollbar_width) = variant.scrollbar_width {
|
||||||
scrollable = scrollable
|
scrollable = scrollable
|
||||||
|
|
@ -108,12 +145,31 @@ impl Sandbox for ScrollableDemo {
|
||||||
.push(Space::with_height(Length::Units(1200)))
|
.push(Space::with_height(Length::Units(1200)))
|
||||||
.push(Text::new("Middle"))
|
.push(Text::new("Middle"))
|
||||||
.push(Space::with_height(Length::Units(1200)))
|
.push(Space::with_height(Length::Units(1200)))
|
||||||
.push(Text::new("The End."));
|
.push(Text::new("The End."))
|
||||||
|
.push(
|
||||||
|
Button::new(
|
||||||
|
&mut variant.scroll_to_top,
|
||||||
|
Text::new("Scroll to top"),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.padding(10)
|
||||||
|
.on_press(Message::ScrollToTop(i)),
|
||||||
|
);
|
||||||
|
|
||||||
Container::new(scrollable)
|
Column::new()
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.style(*theme)
|
.spacing(10)
|
||||||
|
.push(
|
||||||
|
Container::new(scrollable)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.style(*theme),
|
||||||
|
)
|
||||||
|
.push(ProgressBar::new(
|
||||||
|
0.0..=1.0,
|
||||||
|
variant.latest_offset,
|
||||||
|
))
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
@ -142,10 +198,13 @@ impl Sandbox for ScrollableDemo {
|
||||||
/// A version of a scrollable
|
/// A version of a scrollable
|
||||||
struct Variant {
|
struct Variant {
|
||||||
title: &'static str,
|
title: &'static str,
|
||||||
state: scrollable::State,
|
scrollable: scrollable::State,
|
||||||
|
scroll_to_top: button::State,
|
||||||
|
scroll_to_bottom: button::State,
|
||||||
scrollbar_width: Option<u16>,
|
scrollbar_width: Option<u16>,
|
||||||
scrollbar_margin: Option<u16>,
|
scrollbar_margin: Option<u16>,
|
||||||
scroller_width: Option<u16>,
|
scroller_width: Option<u16>,
|
||||||
|
latest_offset: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Variant {
|
impl Variant {
|
||||||
|
|
@ -153,31 +212,43 @@ impl Variant {
|
||||||
vec![
|
vec![
|
||||||
Self {
|
Self {
|
||||||
title: "Default Scrollbar",
|
title: "Default Scrollbar",
|
||||||
state: scrollable::State::new(),
|
scrollable: scrollable::State::new(),
|
||||||
|
scroll_to_top: button::State::new(),
|
||||||
|
scroll_to_bottom: button::State::new(),
|
||||||
scrollbar_width: None,
|
scrollbar_width: None,
|
||||||
scrollbar_margin: None,
|
scrollbar_margin: None,
|
||||||
scroller_width: None,
|
scroller_width: None,
|
||||||
|
latest_offset: 0.0,
|
||||||
},
|
},
|
||||||
Self {
|
Self {
|
||||||
title: "Slimmed & Margin",
|
title: "Slimmed & Margin",
|
||||||
state: scrollable::State::new(),
|
scrollable: scrollable::State::new(),
|
||||||
|
scroll_to_top: button::State::new(),
|
||||||
|
scroll_to_bottom: button::State::new(),
|
||||||
scrollbar_width: Some(4),
|
scrollbar_width: Some(4),
|
||||||
scrollbar_margin: Some(3),
|
scrollbar_margin: Some(3),
|
||||||
scroller_width: Some(4),
|
scroller_width: Some(4),
|
||||||
|
latest_offset: 0.0,
|
||||||
},
|
},
|
||||||
Self {
|
Self {
|
||||||
title: "Wide Scroller",
|
title: "Wide Scroller",
|
||||||
state: scrollable::State::new(),
|
scrollable: scrollable::State::new(),
|
||||||
|
scroll_to_top: button::State::new(),
|
||||||
|
scroll_to_bottom: button::State::new(),
|
||||||
scrollbar_width: Some(4),
|
scrollbar_width: Some(4),
|
||||||
scrollbar_margin: None,
|
scrollbar_margin: None,
|
||||||
scroller_width: Some(10),
|
scroller_width: Some(10),
|
||||||
|
latest_offset: 0.0,
|
||||||
},
|
},
|
||||||
Self {
|
Self {
|
||||||
title: "Narrow Scroller",
|
title: "Narrow Scroller",
|
||||||
state: scrollable::State::new(),
|
scrollable: scrollable::State::new(),
|
||||||
|
scroll_to_top: button::State::new(),
|
||||||
|
scroll_to_bottom: button::State::new(),
|
||||||
scrollbar_width: Some(10),
|
scrollbar_width: Some(10),
|
||||||
scrollbar_margin: None,
|
scrollbar_margin: None,
|
||||||
scroller_width: Some(4),
|
scroller_width: Some(4),
|
||||||
|
latest_offset: 0.0,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||||
rand = "0.7"
|
rand = "0.8.3"
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
|
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
|
||||||
use iced::{
|
use iced::{
|
||||||
canvas::{self, Cursor, Path, Stroke},
|
canvas::{self, Cursor, Path, Stroke},
|
||||||
executor, time, window, Application, Canvas, Color, Command, Element,
|
executor, time, window, Application, Canvas, Clipboard, Color, Command,
|
||||||
Length, Point, Rectangle, Settings, Size, Subscription, Vector,
|
Element, Length, Point, Rectangle, Settings, Size, Subscription, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
@ -48,7 +48,11 @@ impl Application for SolarSystem {
|
||||||
String::from("Solar system - Iced")
|
String::from("Solar system - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Tick(instant) => {
|
Message::Tick(instant) => {
|
||||||
self.state.update(instant);
|
self.state.update(instant);
|
||||||
|
|
@ -117,15 +121,13 @@ impl State {
|
||||||
(
|
(
|
||||||
Point::new(
|
Point::new(
|
||||||
rng.gen_range(
|
rng.gen_range(
|
||||||
-(width as f32) / 2.0,
|
(-(width as f32) / 2.0)..(width as f32 / 2.0),
|
||||||
width as f32 / 2.0,
|
|
||||||
),
|
),
|
||||||
rng.gen_range(
|
rng.gen_range(
|
||||||
-(height as f32) / 2.0,
|
(-(height as f32) / 2.0)..(height as f32 / 2.0),
|
||||||
height as f32 / 2.0,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
rng.gen_range(0.5, 1.0),
|
rng.gen_range(0.5..1.0),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ edition = "2018"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["tokio"] }
|
iced = { path = "../..", features = ["smol"] }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
button, executor, time, Align, Application, Button, Column, Command,
|
button, executor, time, Align, Application, Button, Clipboard, Column,
|
||||||
Container, Element, HorizontalAlignment, Length, Row, Settings,
|
Command, Container, Element, HorizontalAlignment, Length, Row, Settings,
|
||||||
Subscription, Text,
|
Subscription, Text,
|
||||||
};
|
};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
@ -49,7 +49,11 @@ impl Application for Stopwatch {
|
||||||
String::from("Stopwatch - Iced")
|
String::from("Stopwatch - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Toggle => match self.state {
|
Message::Toggle => match self.state {
|
||||||
State::Idle => {
|
State::Idle => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
button, scrollable, slider, text_input, Align, Button, Checkbox, Column,
|
button, scrollable, slider, text_input, Align, Button, Checkbox, Column,
|
||||||
Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox,
|
Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox,
|
||||||
Scrollable, Settings, Slider, Space, Text, TextInput,
|
Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
|
|
@ -17,7 +17,8 @@ struct Styling {
|
||||||
button: button::State,
|
button: button::State,
|
||||||
slider: slider::State,
|
slider: slider::State,
|
||||||
slider_value: f32,
|
slider_value: f32,
|
||||||
toggle_value: bool,
|
checkbox_value: bool,
|
||||||
|
toggler_value: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -27,6 +28,7 @@ enum Message {
|
||||||
ButtonPressed,
|
ButtonPressed,
|
||||||
SliderChanged(f32),
|
SliderChanged(f32),
|
||||||
CheckboxToggled(bool),
|
CheckboxToggled(bool),
|
||||||
|
TogglerToggled(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sandbox for Styling {
|
impl Sandbox for Styling {
|
||||||
|
|
@ -44,9 +46,10 @@ impl Sandbox for Styling {
|
||||||
match message {
|
match message {
|
||||||
Message::ThemeChanged(theme) => self.theme = theme,
|
Message::ThemeChanged(theme) => self.theme = theme,
|
||||||
Message::InputChanged(value) => self.input_value = value,
|
Message::InputChanged(value) => self.input_value = value,
|
||||||
Message::ButtonPressed => (),
|
Message::ButtonPressed => {}
|
||||||
Message::SliderChanged(value) => self.slider_value = value,
|
Message::SliderChanged(value) => self.slider_value = value,
|
||||||
Message::CheckboxToggled(value) => self.toggle_value = value,
|
Message::CheckboxToggled(value) => self.checkbox_value = value,
|
||||||
|
Message::TogglerToggled(value) => self.toggler_value = value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,11 +104,19 @@ impl Sandbox for Styling {
|
||||||
.push(Text::new("You did it!"));
|
.push(Text::new("You did it!"));
|
||||||
|
|
||||||
let checkbox = Checkbox::new(
|
let checkbox = Checkbox::new(
|
||||||
self.toggle_value,
|
self.checkbox_value,
|
||||||
"Toggle me!",
|
"Check me!",
|
||||||
Message::CheckboxToggled,
|
Message::CheckboxToggled,
|
||||||
)
|
)
|
||||||
.width(Length::Fill)
|
.style(self.theme);
|
||||||
|
|
||||||
|
let toggler = Toggler::new(
|
||||||
|
self.toggler_value,
|
||||||
|
String::from("Toggle me!"),
|
||||||
|
Message::TogglerToggled,
|
||||||
|
)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.spacing(10)
|
||||||
.style(self.theme);
|
.style(self.theme);
|
||||||
|
|
||||||
let content = Column::new()
|
let content = Column::new()
|
||||||
|
|
@ -124,7 +135,13 @@ impl Sandbox for Styling {
|
||||||
.align_items(Align::Center)
|
.align_items(Align::Center)
|
||||||
.push(scrollable)
|
.push(scrollable)
|
||||||
.push(Rule::vertical(38).style(self.theme))
|
.push(Rule::vertical(38).style(self.theme))
|
||||||
.push(checkbox),
|
.push(
|
||||||
|
Column::new()
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.spacing(20)
|
||||||
|
.push(checkbox)
|
||||||
|
.push(toggler),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Container::new(content)
|
Container::new(content)
|
||||||
|
|
@ -140,7 +157,7 @@ impl Sandbox for Styling {
|
||||||
mod style {
|
mod style {
|
||||||
use iced::{
|
use iced::{
|
||||||
button, checkbox, container, progress_bar, radio, rule, scrollable,
|
button, checkbox, container, progress_bar, radio, rule, scrollable,
|
||||||
slider, text_input,
|
slider, text_input, toggler,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -231,6 +248,15 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Theme> for Box<dyn toggler::StyleSheet> {
|
||||||
|
fn from(theme: Theme) -> Self {
|
||||||
|
match theme {
|
||||||
|
Theme::Light => Default::default(),
|
||||||
|
Theme::Dark => dark::Toggler.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Theme> for Box<dyn rule::StyleSheet> {
|
impl From<Theme> for Box<dyn rule::StyleSheet> {
|
||||||
fn from(theme: Theme) -> Self {
|
fn from(theme: Theme) -> Self {
|
||||||
match theme {
|
match theme {
|
||||||
|
|
@ -269,7 +295,7 @@ mod style {
|
||||||
mod dark {
|
mod dark {
|
||||||
use iced::{
|
use iced::{
|
||||||
button, checkbox, container, progress_bar, radio, rule, scrollable,
|
button, checkbox, container, progress_bar, radio, rule, scrollable,
|
||||||
slider, text_input, Color,
|
slider, text_input, toggler, Color,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SURFACE: Color = Color::from_rgb(
|
const SURFACE: Color = Color::from_rgb(
|
||||||
|
|
@ -520,6 +546,35 @@ mod style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Toggler;
|
||||||
|
|
||||||
|
impl toggler::StyleSheet for Toggler {
|
||||||
|
fn active(&self, is_active: bool) -> toggler::Style {
|
||||||
|
toggler::Style {
|
||||||
|
background: if is_active { ACTIVE } else { SURFACE },
|
||||||
|
background_border: None,
|
||||||
|
foreground: if is_active { Color::WHITE } else { ACTIVE },
|
||||||
|
foreground_border: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hovered(&self, is_active: bool) -> toggler::Style {
|
||||||
|
toggler::Style {
|
||||||
|
background: if is_active { ACTIVE } else { SURFACE },
|
||||||
|
background_border: None,
|
||||||
|
foreground: if is_active {
|
||||||
|
Color {
|
||||||
|
a: 0.5,
|
||||||
|
..Color::WHITE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color { a: 0.5, ..ACTIVE }
|
||||||
|
},
|
||||||
|
foreground_border: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Rule;
|
pub struct Rule;
|
||||||
|
|
||||||
impl rule::StyleSheet for Rule {
|
impl rule::StyleSheet for Rule {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
button, scrollable, text_input, Align, Application, Button, Checkbox,
|
button, scrollable, text_input, Align, Application, Button, Checkbox,
|
||||||
Column, Command, Container, Element, Font, HorizontalAlignment, Length,
|
Clipboard, Column, Command, Container, Element, Font, HorizontalAlignment,
|
||||||
Row, Scrollable, Settings, Text, TextInput,
|
Length, Row, Scrollable, Settings, Text, TextInput,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -58,7 +58,11 @@ impl Application for Todos {
|
||||||
format!("Todos{} - Iced", if dirty { "*" } else { "" })
|
format!("Todos{} - Iced", if dirty { "*" } else { "" })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
match self {
|
match self {
|
||||||
Todos::Loading => {
|
Todos::Loading => {
|
||||||
match message {
|
match message {
|
||||||
|
|
@ -261,8 +265,11 @@ impl Task {
|
||||||
self.completed = completed;
|
self.completed = completed;
|
||||||
}
|
}
|
||||||
TaskMessage::Edit => {
|
TaskMessage::Edit => {
|
||||||
|
let mut text_input = text_input::State::focused();
|
||||||
|
text_input.select_all();
|
||||||
|
|
||||||
self.state = TaskState::Editing {
|
self.state = TaskState::Editing {
|
||||||
text_input: text_input::State::focused(),
|
text_input,
|
||||||
delete_button: button::State::new(),
|
delete_button: button::State::new(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -489,7 +496,6 @@ enum LoadError {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum SaveError {
|
enum SaveError {
|
||||||
DirectoryError,
|
|
||||||
FileError,
|
FileError,
|
||||||
WriteError,
|
WriteError,
|
||||||
FormatError,
|
FormatError,
|
||||||
|
|
@ -538,7 +544,7 @@ impl SavedState {
|
||||||
if let Some(dir) = path.parent() {
|
if let Some(dir) = path.parent() {
|
||||||
async_std::fs::create_dir_all(dir)
|
async_std::fs::create_dir_all(dir)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| SaveError::DirectoryError)?;
|
.map_err(|_| SaveError::FileError)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
||||||
9
examples/tooltip/Cargo.toml
Normal file
9
examples/tooltip/Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "tooltip"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Yusuf Bera Ertan <y.bera003.06@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../..", features = ["debug"] }
|
||||||
14
examples/tooltip/README.md
Normal file
14
examples/tooltip/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
## Tooltip
|
||||||
|
|
||||||
|
A tooltip.
|
||||||
|
|
||||||
|
It displays and positions a widget on another based on cursor position.
|
||||||
|
|
||||||
|
The __[`main`]__ file contains all the code of the example.
|
||||||
|
|
||||||
|
You can run it with `cargo run`:
|
||||||
|
```
|
||||||
|
cargo run --package tooltip
|
||||||
|
```
|
||||||
|
|
||||||
|
[`main`]: src/main.rs
|
||||||
138
examples/tooltip/src/main.rs
Normal file
138
examples/tooltip/src/main.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
use iced::tooltip::{self, Tooltip};
|
||||||
|
use iced::{
|
||||||
|
button, Button, Column, Container, Element, HorizontalAlignment, Length,
|
||||||
|
Row, Sandbox, Settings, Text, VerticalAlignment,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
Example::run(Settings::default()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Example {
|
||||||
|
top: button::State,
|
||||||
|
bottom: button::State,
|
||||||
|
right: button::State,
|
||||||
|
left: button::State,
|
||||||
|
follow_cursor: button::State,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Message;
|
||||||
|
|
||||||
|
impl Sandbox for Example {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Tooltip - Iced")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _message: Message) {}
|
||||||
|
|
||||||
|
fn view(&mut self) -> Element<Message> {
|
||||||
|
let top =
|
||||||
|
tooltip("Tooltip at top", &mut self.top, tooltip::Position::Top);
|
||||||
|
|
||||||
|
let bottom = tooltip(
|
||||||
|
"Tooltip at bottom",
|
||||||
|
&mut self.bottom,
|
||||||
|
tooltip::Position::Bottom,
|
||||||
|
);
|
||||||
|
|
||||||
|
let left =
|
||||||
|
tooltip("Tooltip at left", &mut self.left, tooltip::Position::Left);
|
||||||
|
|
||||||
|
let right = tooltip(
|
||||||
|
"Tooltip at right",
|
||||||
|
&mut self.right,
|
||||||
|
tooltip::Position::Right,
|
||||||
|
);
|
||||||
|
|
||||||
|
let fixed_tooltips = Row::with_children(vec![
|
||||||
|
top.into(),
|
||||||
|
bottom.into(),
|
||||||
|
left.into(),
|
||||||
|
right.into(),
|
||||||
|
])
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.align_items(iced::Align::Center)
|
||||||
|
.spacing(50);
|
||||||
|
|
||||||
|
let follow_cursor = tooltip(
|
||||||
|
"Tooltip follows cursor",
|
||||||
|
&mut self.follow_cursor,
|
||||||
|
tooltip::Position::FollowCursor,
|
||||||
|
);
|
||||||
|
|
||||||
|
let content = Column::with_children(vec![
|
||||||
|
Container::new(fixed_tooltips)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.into(),
|
||||||
|
follow_cursor.into(),
|
||||||
|
])
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.spacing(50);
|
||||||
|
|
||||||
|
Container::new(content)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.padding(50)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tooltip<'a>(
|
||||||
|
label: &str,
|
||||||
|
button_state: &'a mut button::State,
|
||||||
|
position: tooltip::Position,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
Tooltip::new(
|
||||||
|
Button::new(
|
||||||
|
button_state,
|
||||||
|
Text::new(label)
|
||||||
|
.size(40)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.horizontal_alignment(HorizontalAlignment::Center)
|
||||||
|
.vertical_alignment(VerticalAlignment::Center),
|
||||||
|
)
|
||||||
|
.on_press(Message)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill),
|
||||||
|
"Tooltip",
|
||||||
|
position,
|
||||||
|
)
|
||||||
|
.gap(5)
|
||||||
|
.padding(10)
|
||||||
|
.style(style::Tooltip)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
mod style {
|
||||||
|
use iced::container;
|
||||||
|
use iced::Color;
|
||||||
|
|
||||||
|
pub struct Tooltip;
|
||||||
|
|
||||||
|
impl container::StyleSheet for Tooltip {
|
||||||
|
fn style(&self) -> container::Style {
|
||||||
|
container::Style {
|
||||||
|
text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)),
|
||||||
|
background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()),
|
||||||
|
border_radius: 12.0,
|
||||||
|
..container::Style::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
|
button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
|
||||||
Container, Element, HorizontalAlignment, Image, Length, Radio, Row,
|
Container, Element, HorizontalAlignment, Image, Length, Radio, Row,
|
||||||
Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput,
|
Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
|
|
@ -135,6 +135,9 @@ impl Steps {
|
||||||
color: Color::BLACK,
|
color: Color::BLACK,
|
||||||
},
|
},
|
||||||
Step::Radio { selection: None },
|
Step::Radio { selection: None },
|
||||||
|
Step::Toggler {
|
||||||
|
can_continue: false,
|
||||||
|
},
|
||||||
Step::Image {
|
Step::Image {
|
||||||
width: 300,
|
width: 300,
|
||||||
slider: slider::State::new(),
|
slider: slider::State::new(),
|
||||||
|
|
@ -206,6 +209,9 @@ enum Step {
|
||||||
Radio {
|
Radio {
|
||||||
selection: Option<Language>,
|
selection: Option<Language>,
|
||||||
},
|
},
|
||||||
|
Toggler {
|
||||||
|
can_continue: bool,
|
||||||
|
},
|
||||||
Image {
|
Image {
|
||||||
width: u16,
|
width: u16,
|
||||||
slider: slider::State,
|
slider: slider::State,
|
||||||
|
|
@ -232,6 +238,7 @@ pub enum StepMessage {
|
||||||
InputChanged(String),
|
InputChanged(String),
|
||||||
ToggleSecureInput(bool),
|
ToggleSecureInput(bool),
|
||||||
DebugToggled(bool),
|
DebugToggled(bool),
|
||||||
|
TogglerChanged(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Step {
|
impl<'a> Step {
|
||||||
|
|
@ -287,6 +294,11 @@ impl<'a> Step {
|
||||||
*is_secure = toggle;
|
*is_secure = toggle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
StepMessage::TogglerChanged(value) => {
|
||||||
|
if let Step::Toggler { can_continue, .. } = self {
|
||||||
|
*can_continue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,6 +306,7 @@ impl<'a> Step {
|
||||||
match self {
|
match self {
|
||||||
Step::Welcome => "Welcome",
|
Step::Welcome => "Welcome",
|
||||||
Step::Radio { .. } => "Radio button",
|
Step::Radio { .. } => "Radio button",
|
||||||
|
Step::Toggler { .. } => "Toggler",
|
||||||
Step::Slider { .. } => "Slider",
|
Step::Slider { .. } => "Slider",
|
||||||
Step::Text { .. } => "Text",
|
Step::Text { .. } => "Text",
|
||||||
Step::Image { .. } => "Image",
|
Step::Image { .. } => "Image",
|
||||||
|
|
@ -309,6 +322,7 @@ impl<'a> Step {
|
||||||
match self {
|
match self {
|
||||||
Step::Welcome => true,
|
Step::Welcome => true,
|
||||||
Step::Radio { selection } => *selection == Some(Language::Rust),
|
Step::Radio { selection } => *selection == Some(Language::Rust),
|
||||||
|
Step::Toggler { can_continue } => *can_continue,
|
||||||
Step::Slider { .. } => true,
|
Step::Slider { .. } => true,
|
||||||
Step::Text { .. } => true,
|
Step::Text { .. } => true,
|
||||||
Step::Image { .. } => true,
|
Step::Image { .. } => true,
|
||||||
|
|
@ -324,6 +338,7 @@ impl<'a> Step {
|
||||||
match self {
|
match self {
|
||||||
Step::Welcome => Self::welcome(),
|
Step::Welcome => Self::welcome(),
|
||||||
Step::Radio { selection } => Self::radio(*selection),
|
Step::Radio { selection } => Self::radio(*selection),
|
||||||
|
Step::Toggler { can_continue } => Self::toggler(*can_continue),
|
||||||
Step::Slider { state, value } => Self::slider(state, *value),
|
Step::Slider { state, value } => Self::slider(state, *value),
|
||||||
Step::Text {
|
Step::Text {
|
||||||
size_slider,
|
size_slider,
|
||||||
|
|
@ -545,6 +560,21 @@ impl<'a> Step {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
|
||||||
|
Self::container("Toggler")
|
||||||
|
.push(Text::new(
|
||||||
|
"A toggler is mostly used to enable or disable something.",
|
||||||
|
))
|
||||||
|
.push(
|
||||||
|
Container::new(Toggler::new(
|
||||||
|
can_continue,
|
||||||
|
String::from("Toggle me to continue..."),
|
||||||
|
StepMessage::TogglerChanged,
|
||||||
|
))
|
||||||
|
.padding([0, 40]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn image(
|
fn image(
|
||||||
width: u16,
|
width: u16,
|
||||||
slider: &'a mut slider::State,
|
slider: &'a mut slider::State,
|
||||||
|
|
|
||||||
10
examples/url_handler/Cargo.toml
Normal file
10
examples/url_handler/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "url_handler"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../.." }
|
||||||
|
iced_native = { path = "../../native" }
|
||||||
73
examples/url_handler/src/main.rs
Normal file
73
examples/url_handler/src/main.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
use iced::{
|
||||||
|
executor, Application, Clipboard, Command, Container, Element, Length,
|
||||||
|
Settings, Subscription, Text,
|
||||||
|
};
|
||||||
|
use iced_native::{
|
||||||
|
event::{MacOS, PlatformSpecific},
|
||||||
|
Event,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
App::run(Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct App {
|
||||||
|
url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
EventOccurred(iced_native::Event),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for App {
|
||||||
|
type Executor = executor::Default;
|
||||||
|
type Message = Message;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
fn new(_flags: ()) -> (App, Command<Message>) {
|
||||||
|
(App::default(), Command::none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Url - Iced")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Message,
|
||||||
|
_clipboard: &mut Clipboard,
|
||||||
|
) -> Command<Message> {
|
||||||
|
match message {
|
||||||
|
Message::EventOccurred(event) => {
|
||||||
|
if let Event::PlatformSpecific(PlatformSpecific::MacOS(
|
||||||
|
MacOS::ReceivedUrl(url),
|
||||||
|
)) = event
|
||||||
|
{
|
||||||
|
self.url = Some(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
iced_native::subscription::events().map(Message::EventOccurred)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&mut self) -> Element<Message> {
|
||||||
|
let content = match &self.url {
|
||||||
|
Some(url) => Text::new(format!("{}", url)),
|
||||||
|
None => Text::new("No URL received yet!"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Container::new(content.size(48))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_futures"
|
name = "iced_futures"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Commands, subscriptions, and runtimes for Iced"
|
description = "Commands, subscriptions, and runtimes for Iced"
|
||||||
|
|
@ -26,15 +26,20 @@ optional = true
|
||||||
features = ["rt-core", "rt-threaded", "time", "stream"]
|
features = ["rt-core", "rt-threaded", "time", "stream"]
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
|
||||||
version = "0.3"
|
package = "tokio"
|
||||||
|
version = "1.0"
|
||||||
optional = true
|
optional = true
|
||||||
features = ["rt-multi-thread", "time", "stream"]
|
features = ["rt", "rt-multi-thread", "time"]
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.async-std]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.async-std]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
optional = true
|
optional = true
|
||||||
features = ["unstable"]
|
features = ["unstable"]
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.smol]
|
||||||
|
version = "1.2"
|
||||||
|
optional = true
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ mod tokio_old;
|
||||||
#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))]
|
#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))]
|
||||||
mod async_std;
|
mod async_std;
|
||||||
|
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))]
|
||||||
|
mod smol;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
mod wasm_bindgen;
|
mod wasm_bindgen;
|
||||||
|
|
||||||
|
|
@ -30,6 +33,9 @@ pub use self::tokio_old::TokioOld;
|
||||||
#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))]
|
#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))]
|
||||||
pub use self::async_std::AsyncStd;
|
pub use self::async_std::AsyncStd;
|
||||||
|
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))]
|
||||||
|
pub use self::smol::Smol;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub use wasm_bindgen::WasmBindgen;
|
pub use wasm_bindgen::WasmBindgen;
|
||||||
|
|
||||||
|
|
|
||||||
18
futures/src/executor/smol.rs
Normal file
18
futures/src/executor/smol.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use crate::Executor;
|
||||||
|
|
||||||
|
use futures::Future;
|
||||||
|
|
||||||
|
/// A `smol` runtime.
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "smol")))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Smol;
|
||||||
|
|
||||||
|
impl Executor for Smol {
|
||||||
|
fn new() -> Result<Self, futures::io::Error> {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
|
||||||
|
smol::spawn(future).detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,10 +17,22 @@ pub mod executor;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
any(feature = "tokio", feature = "tokio_old", feature = "async-std"),
|
any(
|
||||||
|
feature = "tokio",
|
||||||
|
feature = "tokio_old",
|
||||||
|
feature = "async-std",
|
||||||
|
feature = "smol"
|
||||||
|
),
|
||||||
not(target_arch = "wasm32")
|
not(target_arch = "wasm32")
|
||||||
))]
|
))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(
|
||||||
|
feature = "tokio",
|
||||||
|
feature = "async-std",
|
||||||
|
feature = "smol"
|
||||||
|
)))
|
||||||
|
)]
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
|
||||||
pub use command::Command;
|
pub use command::Command;
|
||||||
|
|
|
||||||
|
|
@ -125,9 +125,9 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> {
|
||||||
/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how
|
/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how
|
||||||
/// to listen to time.
|
/// to listen to time.
|
||||||
///
|
///
|
||||||
/// [examples]: https://github.com/hecrj/iced/tree/0.2/examples
|
/// [examples]: https://github.com/hecrj/iced/tree/0.3/examples
|
||||||
/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.2/examples/download_progress
|
/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.3/examples/download_progress
|
||||||
/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.2/examples/stopwatch
|
/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.3/examples/stopwatch
|
||||||
pub trait Recipe<Hasher: std::hash::Hasher, Event> {
|
pub trait Recipe<Hasher: std::hash::Hasher, Event> {
|
||||||
/// The events that will be produced by a [`Subscription`] with this
|
/// The events that will be produced by a [`Subscription`] with this
|
||||||
/// [`Recipe`].
|
/// [`Recipe`].
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ where
|
||||||
.filter_map(|connection| connection.listener.as_mut())
|
.filter_map(|connection| connection.listener.as_mut())
|
||||||
.for_each(|listener| {
|
.for_each(|listener| {
|
||||||
if let Err(error) = listener.try_send(event.clone()) {
|
if let Err(error) = listener.try_send(event.clone()) {
|
||||||
log::error!(
|
log::warn!(
|
||||||
"Error sending event to subscription: {:?}",
|
"Error sending event to subscription: {:?}",
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,33 @@ pub fn every<H: std::hash::Hasher, E>(
|
||||||
|
|
||||||
struct Every(std::time::Duration);
|
struct Every(std::time::Duration);
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
not(any(feature = "tokio_old", feature = "tokio", feature = "async-std")),
|
||||||
|
feature = "smol"
|
||||||
|
))]
|
||||||
|
impl<H, E> subscription::Recipe<H, E> for Every
|
||||||
|
where
|
||||||
|
H: std::hash::Hasher,
|
||||||
|
{
|
||||||
|
type Output = std::time::Instant;
|
||||||
|
|
||||||
|
fn hash(&self, state: &mut H) {
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
std::any::TypeId::of::<Self>().hash(state);
|
||||||
|
self.0.hash(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream(
|
||||||
|
self: Box<Self>,
|
||||||
|
_input: futures::stream::BoxStream<'static, E>,
|
||||||
|
) -> futures::stream::BoxStream<'static, Self::Output> {
|
||||||
|
use futures::stream::StreamExt;
|
||||||
|
|
||||||
|
smol::Timer::interval(self.0).boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async-std")]
|
#[cfg(feature = "async-std")]
|
||||||
impl<H, E> subscription::Recipe<H, E> for Every
|
impl<H, E> subscription::Recipe<H, E> for Every
|
||||||
where
|
where
|
||||||
|
|
@ -41,7 +68,7 @@ where
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
any(feature = "tokio", feature = "tokio_old"),
|
any(feature = "tokio", feature = "tokio_old"),
|
||||||
not(feature = "async-std")
|
not(any(feature = "async-std", feature = "smol"))
|
||||||
))]
|
))]
|
||||||
impl<H, E> subscription::Recipe<H, E> for Every
|
impl<H, E> subscription::Recipe<H, E> for Every
|
||||||
where
|
where
|
||||||
|
|
@ -67,8 +94,20 @@ where
|
||||||
|
|
||||||
let start = tokio::time::Instant::now() + self.0;
|
let start = tokio::time::Instant::now() + self.0;
|
||||||
|
|
||||||
tokio::time::interval_at(start, self.0)
|
let stream = {
|
||||||
.map(|_| std::time::Instant::now())
|
#[cfg(feature = "tokio")]
|
||||||
.boxed()
|
{
|
||||||
|
futures::stream::unfold(
|
||||||
|
tokio::time::interval_at(start, self.0),
|
||||||
|
|mut interval| async move {
|
||||||
|
Some((interval.tick().await, interval))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tokio_old")]
|
||||||
|
tokio::time::interval_at(start, self.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.map(tokio::time::Instant::into_std).boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_glow"
|
name = "iced_glow"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A glow renderer for iced"
|
description = "A glow renderer for iced"
|
||||||
|
|
@ -24,11 +24,11 @@ bytemuck = "1.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
[dependencies.iced_native]
|
[dependencies.iced_native]
|
||||||
version = "0.3"
|
version = "0.4"
|
||||||
path = "../native"
|
path = "../native"
|
||||||
|
|
||||||
[dependencies.iced_graphics]
|
[dependencies.iced_graphics]
|
||||||
version = "0.1"
|
version = "0.2"
|
||||||
path = "../graphics"
|
path = "../graphics"
|
||||||
features = ["font-fallback", "font-icons", "opengl"]
|
features = ["font-fallback", "font-icons", "opengl"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,12 @@ pub struct Backend {
|
||||||
impl Backend {
|
impl Backend {
|
||||||
/// Creates a new [`Backend`].
|
/// Creates a new [`Backend`].
|
||||||
pub fn new(gl: &glow::Context, settings: Settings) -> Self {
|
pub fn new(gl: &glow::Context, settings: Settings) -> Self {
|
||||||
let text_pipeline = text::Pipeline::new(gl, settings.default_font);
|
let text_pipeline = text::Pipeline::new(
|
||||||
|
gl,
|
||||||
|
settings.default_font,
|
||||||
|
settings.text_multithreading,
|
||||||
|
);
|
||||||
|
|
||||||
let quad_pipeline = quad::Pipeline::new(gl);
|
let quad_pipeline = quad::Pipeline::new(gl);
|
||||||
let triangle_pipeline = triangle::Pipeline::new(gl);
|
let triangle_pipeline = triangle::Pipeline::new(gl);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ pub unsafe fn create(
|
||||||
gl.compile_shader(shader);
|
gl.compile_shader(shader);
|
||||||
|
|
||||||
if !gl.get_shader_compile_status(shader) {
|
if !gl.get_shader_compile_status(shader) {
|
||||||
panic!(gl.get_shader_info_log(shader));
|
panic!("{}", gl.get_shader_info_log(shader));
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.attach_shader(program, shader);
|
gl.attach_shader(program, shader);
|
||||||
|
|
@ -27,7 +27,7 @@ pub unsafe fn create(
|
||||||
|
|
||||||
gl.link_program(program);
|
gl.link_program(program);
|
||||||
if !gl.get_program_link_status(program) {
|
if !gl.get_program_link_status(program) {
|
||||||
panic!(gl.get_program_info_log(program));
|
panic!("{}", gl.get_program_info_log(program));
|
||||||
}
|
}
|
||||||
|
|
||||||
for shader in shaders {
|
for shader in shaders {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,15 @@ pub struct Settings {
|
||||||
/// By default, it will be set to 20.
|
/// By default, it will be set to 20.
|
||||||
pub default_text_size: u16,
|
pub default_text_size: u16,
|
||||||
|
|
||||||
|
/// If enabled, spread text workload in multiple threads when multiple cores
|
||||||
|
/// are available.
|
||||||
|
///
|
||||||
|
/// By default, it is disabled.
|
||||||
|
pub text_multithreading: bool,
|
||||||
|
|
||||||
/// The antialiasing strategy that will be used for triangle primitives.
|
/// The antialiasing strategy that will be used for triangle primitives.
|
||||||
|
///
|
||||||
|
/// By default, it is `None`.
|
||||||
pub antialiasing: Option<Antialiasing>,
|
pub antialiasing: Option<Antialiasing>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,7 +33,17 @@ impl Default for Settings {
|
||||||
Settings {
|
Settings {
|
||||||
default_font: None,
|
default_font: None,
|
||||||
default_text_size: 20,
|
default_text_size: 20,
|
||||||
|
text_multithreading: false,
|
||||||
antialiasing: None,
|
antialiasing: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
/// Creates new [`Settings`] using environment configuration.
|
||||||
|
///
|
||||||
|
/// Currently, this is equivalent to calling [`Settings::default`].
|
||||||
|
pub fn from_env() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,11 @@ pub struct Pipeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pipeline {
|
impl Pipeline {
|
||||||
pub fn new(gl: &glow::Context, default_font: Option<&[u8]>) -> Self {
|
pub fn new(
|
||||||
|
gl: &glow::Context,
|
||||||
|
default_font: Option<&[u8]>,
|
||||||
|
multithreading: bool,
|
||||||
|
) -> Self {
|
||||||
let default_font = default_font.map(|slice| slice.to_vec());
|
let default_font = default_font.map(|slice| slice.to_vec());
|
||||||
|
|
||||||
// TODO: Font customization
|
// TODO: Font customization
|
||||||
|
|
@ -41,7 +45,7 @@ impl Pipeline {
|
||||||
let draw_brush =
|
let draw_brush =
|
||||||
glow_glyph::GlyphBrushBuilder::using_font(font.clone())
|
glow_glyph::GlyphBrushBuilder::using_font(font.clone())
|
||||||
.initial_cache_size((2048, 2048))
|
.initial_cache_size((2048, 2048))
|
||||||
.draw_cache_multithread(false) // TODO: Expose as a configuration flag
|
.draw_cache_multithread(multithreading)
|
||||||
.build(&gl);
|
.build(&gl);
|
||||||
|
|
||||||
let measure_brush =
|
let measure_brush =
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ pub mod rule;
|
||||||
pub mod scrollable;
|
pub mod scrollable;
|
||||||
pub mod slider;
|
pub mod slider;
|
||||||
pub mod text_input;
|
pub mod text_input;
|
||||||
|
pub mod toggler;
|
||||||
|
pub mod tooltip;
|
||||||
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use button::Button;
|
pub use button::Button;
|
||||||
|
|
@ -43,6 +45,10 @@ pub use scrollable::Scrollable;
|
||||||
pub use slider::Slider;
|
pub use slider::Slider;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use text_input::TextInput;
|
pub use text_input::TextInput;
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use toggler::Toggler;
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use tooltip::Tooltip;
|
||||||
|
|
||||||
#[cfg(feature = "canvas")]
|
#[cfg(feature = "canvas")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))]
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@
|
||||||
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
|
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
|
||||||
//! drag and drop, and hotkey support.
|
//! drag and drop, and hotkey support.
|
||||||
//!
|
//!
|
||||||
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid
|
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
|
||||||
use crate::Renderer;
|
use crate::Renderer;
|
||||||
|
|
||||||
pub use iced_native::pane_grid::{
|
pub use iced_graphics::pane_grid::{
|
||||||
Axis, Configuration, Direction, DragEvent, Node, Pane, ResizeEvent, Split,
|
Axis, Configuration, Direction, DragEvent, Line, Node, Pane, ResizeEvent,
|
||||||
State,
|
Split, State, StyleSheet,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A collection of panes distributed using either vertical or horizontal splits
|
/// A collection of panes distributed using either vertical or horizontal splits
|
||||||
|
|
|
||||||
9
glow/src/widget/toggler.rs
Normal file
9
glow/src/widget/toggler.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
//! Show toggle controls using togglers.
|
||||||
|
use crate::Renderer;
|
||||||
|
|
||||||
|
pub use iced_graphics::toggler::{Style, StyleSheet};
|
||||||
|
|
||||||
|
/// A toggler that can be toggled.
|
||||||
|
///
|
||||||
|
/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
|
||||||
|
pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;
|
||||||
6
glow/src/widget/tooltip.rs
Normal file
6
glow/src/widget/tooltip.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
//! Display a widget over another.
|
||||||
|
/// A widget allowing the selection of a single value from a list of options.
|
||||||
|
pub type Tooltip<'a, Message> =
|
||||||
|
iced_native::Tooltip<'a, Message, crate::Renderer>;
|
||||||
|
|
||||||
|
pub use iced_native::tooltip::Position;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_glutin"
|
name = "iced_glutin"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A glutin runtime for Iced"
|
description = "A glutin runtime for Iced"
|
||||||
|
|
@ -13,18 +13,20 @@ categories = ["gui"]
|
||||||
[features]
|
[features]
|
||||||
debug = ["iced_winit/debug"]
|
debug = ["iced_winit/debug"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies.glutin]
|
||||||
glutin = "0.26"
|
version = "0.27"
|
||||||
|
git = "https://github.com/iced-rs/glutin"
|
||||||
|
rev = "03437d8a1826d83c62017b2bb7bf18bfc9e352cc"
|
||||||
|
|
||||||
[dependencies.iced_native]
|
[dependencies.iced_native]
|
||||||
version = "0.3"
|
version = "0.4"
|
||||||
path = "../native"
|
path = "../native"
|
||||||
|
|
||||||
[dependencies.iced_winit]
|
[dependencies.iced_winit]
|
||||||
version = "0.2"
|
version = "0.3"
|
||||||
path = "../winit"
|
path = "../winit"
|
||||||
|
|
||||||
[dependencies.iced_graphics]
|
[dependencies.iced_graphics]
|
||||||
version = "0.1"
|
version = "0.2"
|
||||||
path = "../graphics"
|
path = "../graphics"
|
||||||
features = ["opengl"]
|
features = ["opengl"]
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t
|
||||||
Add `iced_glutin` as a dependency in your `Cargo.toml`:
|
Add `iced_glutin` as a dependency in your `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
iced_glutin = "0.1"
|
iced_glutin = "0.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,14 @@ where
|
||||||
runtime.track(subscription);
|
runtime.track(subscription);
|
||||||
|
|
||||||
let context = {
|
let context = {
|
||||||
let builder = settings.window.into_builder(
|
let builder = settings
|
||||||
&application.title(),
|
.window
|
||||||
application.mode(),
|
.into_builder(
|
||||||
event_loop.primary_monitor(),
|
&application.title(),
|
||||||
);
|
application.mode(),
|
||||||
|
event_loop.primary_monitor(),
|
||||||
|
)
|
||||||
|
.with_menu(Some(conversion::menu(&application.menu())));
|
||||||
|
|
||||||
let context = ContextBuilder::new()
|
let context = ContextBuilder::new()
|
||||||
.with_vsync(true)
|
.with_vsync(true)
|
||||||
|
|
@ -92,10 +95,11 @@ where
|
||||||
application,
|
application,
|
||||||
compositor,
|
compositor,
|
||||||
renderer,
|
renderer,
|
||||||
context,
|
|
||||||
runtime,
|
runtime,
|
||||||
debug,
|
debug,
|
||||||
receiver,
|
receiver,
|
||||||
|
context,
|
||||||
|
settings.exit_on_close_request,
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut context = task::Context::from_waker(task::noop_waker_ref());
|
let mut context = task::Context::from_waker(task::noop_waker_ref());
|
||||||
|
|
@ -107,7 +111,22 @@ where
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(event) = event.to_static() {
|
let event = match event {
|
||||||
|
glutin::event::Event::WindowEvent {
|
||||||
|
event:
|
||||||
|
glutin::event::WindowEvent::ScaleFactorChanged {
|
||||||
|
new_inner_size,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
window_id,
|
||||||
|
} => Some(glutin::event::Event::WindowEvent {
|
||||||
|
event: glutin::event::WindowEvent::Resized(*new_inner_size),
|
||||||
|
window_id,
|
||||||
|
}),
|
||||||
|
_ => event.to_static(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(event) = event {
|
||||||
sender.start_send(event).expect("Send event");
|
sender.start_send(event).expect("Send event");
|
||||||
|
|
||||||
let poll = instance.as_mut().poll(&mut context);
|
let poll = instance.as_mut().poll(&mut context);
|
||||||
|
|
@ -124,10 +143,11 @@ async fn run_instance<A, E, C>(
|
||||||
mut application: A,
|
mut application: A,
|
||||||
mut compositor: C,
|
mut compositor: C,
|
||||||
mut renderer: A::Renderer,
|
mut renderer: A::Renderer,
|
||||||
context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>,
|
|
||||||
mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
|
mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
|
||||||
mut debug: Debug,
|
mut debug: Debug,
|
||||||
mut receiver: mpsc::UnboundedReceiver<glutin::event::Event<'_, A::Message>>,
|
mut receiver: mpsc::UnboundedReceiver<glutin::event::Event<'_, A::Message>>,
|
||||||
|
context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>,
|
||||||
|
exit_on_close_request: bool,
|
||||||
) where
|
) where
|
||||||
A: Application + 'static,
|
A: Application + 'static,
|
||||||
E: Executor + 'static,
|
E: Executor + 'static,
|
||||||
|
|
@ -136,7 +156,7 @@ async fn run_instance<A, E, C>(
|
||||||
use glutin::event;
|
use glutin::event;
|
||||||
use iced_winit::futures::stream::StreamExt;
|
use iced_winit::futures::stream::StreamExt;
|
||||||
|
|
||||||
let clipboard = Clipboard::new(context.window());
|
let mut clipboard = Clipboard::connect(context.window());
|
||||||
|
|
||||||
let mut state = application::State::new(&application, context.window());
|
let mut state = application::State::new(&application, context.window());
|
||||||
let mut viewport_version = state.viewport_version();
|
let mut viewport_version = state.viewport_version();
|
||||||
|
|
@ -170,8 +190,8 @@ async fn run_instance<A, E, C>(
|
||||||
let statuses = user_interface.update(
|
let statuses = user_interface.update(
|
||||||
&events,
|
&events,
|
||||||
state.cursor_position(),
|
state.cursor_position(),
|
||||||
clipboard.as_ref().map(|c| c as _),
|
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
|
&mut clipboard,
|
||||||
&mut messages,
|
&mut messages,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -190,12 +210,15 @@ async fn run_instance<A, E, C>(
|
||||||
&mut application,
|
&mut application,
|
||||||
&mut runtime,
|
&mut runtime,
|
||||||
&mut debug,
|
&mut debug,
|
||||||
|
&mut clipboard,
|
||||||
&mut messages,
|
&mut messages,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update window
|
// Update window
|
||||||
state.synchronize(&application, context.window());
|
state.synchronize(&application, context.window());
|
||||||
|
|
||||||
|
let should_exit = application.should_exit();
|
||||||
|
|
||||||
user_interface =
|
user_interface =
|
||||||
ManuallyDrop::new(application::build_user_interface(
|
ManuallyDrop::new(application::build_user_interface(
|
||||||
&mut application,
|
&mut application,
|
||||||
|
|
@ -204,6 +227,10 @@ async fn run_instance<A, E, C>(
|
||||||
state.logical_size(),
|
state.logical_size(),
|
||||||
&mut debug,
|
&mut debug,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if should_exit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.draw_started();
|
debug.draw_started();
|
||||||
|
|
@ -213,6 +240,16 @@ async fn run_instance<A, E, C>(
|
||||||
|
|
||||||
context.window().request_redraw();
|
context.window().request_redraw();
|
||||||
}
|
}
|
||||||
|
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
|
||||||
|
event::MacOS::ReceivedUrl(url),
|
||||||
|
)) => {
|
||||||
|
use iced_native::event;
|
||||||
|
events.push(iced_native::Event::PlatformSpecific(
|
||||||
|
event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(
|
||||||
|
url,
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
}
|
||||||
event::Event::UserEvent(message) => {
|
event::Event::UserEvent(message) => {
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
}
|
}
|
||||||
|
|
@ -269,11 +306,22 @@ async fn run_instance<A, E, C>(
|
||||||
// TODO: Handle animations!
|
// TODO: Handle animations!
|
||||||
// Maybe we can use `ControlFlow::WaitUntil` for this.
|
// Maybe we can use `ControlFlow::WaitUntil` for this.
|
||||||
}
|
}
|
||||||
|
event::Event::WindowEvent {
|
||||||
|
event: event::WindowEvent::MenuEntryActivated(entry_id),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let Some(message) =
|
||||||
|
conversion::menu_message(state.menu(), entry_id)
|
||||||
|
{
|
||||||
|
messages.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
event::Event::WindowEvent {
|
event::Event::WindowEvent {
|
||||||
event: window_event,
|
event: window_event,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if application::requests_exit(&window_event, state.modifiers())
|
if application::requests_exit(&window_event, state.modifiers())
|
||||||
|
&& exit_on_close_request
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ pub use iced_native::*;
|
||||||
pub mod application;
|
pub mod application;
|
||||||
|
|
||||||
pub use iced_winit::settings;
|
pub use iced_winit::settings;
|
||||||
pub use iced_winit::{Error, Mode};
|
pub use iced_winit::{Clipboard, Error, Mode};
|
||||||
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use application::Application;
|
pub use application::Application;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_graphics"
|
name = "iced_graphics"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced"
|
description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced"
|
||||||
|
|
@ -28,11 +28,11 @@ version = "1.4"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
|
|
||||||
[dependencies.iced_native]
|
[dependencies.iced_native]
|
||||||
version = "0.3"
|
version = "0.4"
|
||||||
path = "../native"
|
path = "../native"
|
||||||
|
|
||||||
[dependencies.iced_style]
|
[dependencies.iced_style]
|
||||||
version = "0.2"
|
version = "0.3"
|
||||||
path = "../style"
|
path = "../style"
|
||||||
|
|
||||||
[dependencies.lyon]
|
[dependencies.lyon]
|
||||||
|
|
@ -42,9 +42,10 @@ optional = true
|
||||||
[dependencies.qrcode]
|
[dependencies.qrcode]
|
||||||
version = "0.12"
|
version = "0.12"
|
||||||
optional = true
|
optional = true
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[dependencies.font-kit]
|
[dependencies.font-kit]
|
||||||
version = "0.8"
|
version = "0.10"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,12 @@ impl<'a> Layer<'a> {
|
||||||
|
|
||||||
let mut layers = vec![first_layer];
|
let mut layers = vec![first_layer];
|
||||||
|
|
||||||
Self::process_primitive(&mut layers, Vector::new(0.0, 0.0), primitive);
|
Self::process_primitive(
|
||||||
|
&mut layers,
|
||||||
|
Vector::new(0.0, 0.0),
|
||||||
|
primitive,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
layers
|
layers
|
||||||
}
|
}
|
||||||
|
|
@ -91,13 +96,19 @@ impl<'a> Layer<'a> {
|
||||||
layers: &mut Vec<Self>,
|
layers: &mut Vec<Self>,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
primitive: &'a Primitive,
|
primitive: &'a Primitive,
|
||||||
|
current_layer: usize,
|
||||||
) {
|
) {
|
||||||
match primitive {
|
match primitive {
|
||||||
Primitive::None => {}
|
Primitive::None => {}
|
||||||
Primitive::Group { primitives } => {
|
Primitive::Group { primitives } => {
|
||||||
// TODO: Inspect a bit and regroup (?)
|
// TODO: Inspect a bit and regroup (?)
|
||||||
for primitive in primitives {
|
for primitive in primitives {
|
||||||
Self::process_primitive(layers, translation, primitive)
|
Self::process_primitive(
|
||||||
|
layers,
|
||||||
|
translation,
|
||||||
|
primitive,
|
||||||
|
current_layer,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Primitive::Text {
|
Primitive::Text {
|
||||||
|
|
@ -109,7 +120,7 @@ impl<'a> Layer<'a> {
|
||||||
horizontal_alignment,
|
horizontal_alignment,
|
||||||
vertical_alignment,
|
vertical_alignment,
|
||||||
} => {
|
} => {
|
||||||
let layer = layers.last_mut().unwrap();
|
let layer = &mut layers[current_layer];
|
||||||
|
|
||||||
layer.text.push(Text {
|
layer.text.push(Text {
|
||||||
content,
|
content,
|
||||||
|
|
@ -128,7 +139,7 @@ impl<'a> Layer<'a> {
|
||||||
border_width,
|
border_width,
|
||||||
border_color,
|
border_color,
|
||||||
} => {
|
} => {
|
||||||
let layer = layers.last_mut().unwrap();
|
let layer = &mut layers[current_layer];
|
||||||
|
|
||||||
// TODO: Move some of these computations to the GPU (?)
|
// TODO: Move some of these computations to the GPU (?)
|
||||||
layer.quads.push(Quad {
|
layer.quads.push(Quad {
|
||||||
|
|
@ -146,7 +157,7 @@ impl<'a> Layer<'a> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Primitive::Mesh2D { buffers, size } => {
|
Primitive::Mesh2D { buffers, size } => {
|
||||||
let layer = layers.last_mut().unwrap();
|
let layer = &mut layers[current_layer];
|
||||||
|
|
||||||
let bounds = Rectangle::new(
|
let bounds = Rectangle::new(
|
||||||
Point::new(translation.x, translation.y),
|
Point::new(translation.x, translation.y),
|
||||||
|
|
@ -167,7 +178,7 @@ impl<'a> Layer<'a> {
|
||||||
offset,
|
offset,
|
||||||
content,
|
content,
|
||||||
} => {
|
} => {
|
||||||
let layer = layers.last_mut().unwrap();
|
let layer = &mut layers[current_layer];
|
||||||
let translated_bounds = *bounds + translation;
|
let translated_bounds = *bounds + translation;
|
||||||
|
|
||||||
// Only draw visible content
|
// Only draw visible content
|
||||||
|
|
@ -175,16 +186,15 @@ impl<'a> Layer<'a> {
|
||||||
layer.bounds.intersection(&translated_bounds)
|
layer.bounds.intersection(&translated_bounds)
|
||||||
{
|
{
|
||||||
let clip_layer = Layer::new(clip_bounds);
|
let clip_layer = Layer::new(clip_bounds);
|
||||||
let new_layer = Layer::new(layer.bounds);
|
|
||||||
|
|
||||||
layers.push(clip_layer);
|
layers.push(clip_layer);
|
||||||
|
|
||||||
Self::process_primitive(
|
Self::process_primitive(
|
||||||
layers,
|
layers,
|
||||||
translation
|
translation
|
||||||
- Vector::new(offset.x as f32, offset.y as f32),
|
- Vector::new(offset.x as f32, offset.y as f32),
|
||||||
content,
|
content,
|
||||||
|
layers.len() - 1,
|
||||||
);
|
);
|
||||||
layers.push(new_layer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Primitive::Translate {
|
Primitive::Translate {
|
||||||
|
|
@ -195,13 +205,19 @@ impl<'a> Layer<'a> {
|
||||||
layers,
|
layers,
|
||||||
translation + *new_translation,
|
translation + *new_translation,
|
||||||
&content,
|
&content,
|
||||||
|
current_layer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Primitive::Cached { cache } => {
|
Primitive::Cached { cache } => {
|
||||||
Self::process_primitive(layers, translation, &cache);
|
Self::process_primitive(
|
||||||
|
layers,
|
||||||
|
translation,
|
||||||
|
&cache,
|
||||||
|
current_layer,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Primitive::Image { handle, bounds } => {
|
Primitive::Image { handle, bounds } => {
|
||||||
let layer = layers.last_mut().unwrap();
|
let layer = &mut layers[current_layer];
|
||||||
|
|
||||||
layer.images.push(Image::Raster {
|
layer.images.push(Image::Raster {
|
||||||
handle: handle.clone(),
|
handle: handle.clone(),
|
||||||
|
|
@ -209,7 +225,7 @@ impl<'a> Layer<'a> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Primitive::Svg { handle, bounds } => {
|
Primitive::Svg { handle, bounds } => {
|
||||||
let layer = layers.last_mut().unwrap();
|
let layer = &mut layers[current_layer];
|
||||||
|
|
||||||
layer.images.push(Image::Vector {
|
layer.images.push(Image::Vector {
|
||||||
handle: handle.clone(),
|
handle: handle.clone(),
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
use crate::backend::{self, Backend};
|
use crate::backend::{self, Backend};
|
||||||
use crate::{Primitive, Renderer};
|
use crate::{Primitive, Renderer};
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
mouse, overlay, Color, Font, HorizontalAlignment, Point, Rectangle,
|
mouse, overlay, Color, Font, HorizontalAlignment, Padding, Point,
|
||||||
VerticalAlignment,
|
Rectangle, VerticalAlignment,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use iced_style::menu::Style;
|
pub use iced_style::menu::Style;
|
||||||
|
|
@ -45,7 +45,7 @@ where
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
options: &[T],
|
options: &[T],
|
||||||
hovered_option: Option<usize>,
|
hovered_option: Option<usize>,
|
||||||
padding: u16,
|
padding: Padding,
|
||||||
text_size: u16,
|
text_size: u16,
|
||||||
font: Font,
|
font: Font,
|
||||||
style: &Style,
|
style: &Style,
|
||||||
|
|
@ -53,7 +53,7 @@ where
|
||||||
use std::f32;
|
use std::f32;
|
||||||
|
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
let option_height = text_size as usize + padding as usize * 2;
|
let option_height = (text_size + padding.vertical()) as usize;
|
||||||
|
|
||||||
let mut primitives = Vec::new();
|
let mut primitives = Vec::new();
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ where
|
||||||
x: bounds.x,
|
x: bounds.x,
|
||||||
y: bounds.y + (option_height * i) as f32,
|
y: bounds.y + (option_height * i) as f32,
|
||||||
width: bounds.width,
|
width: bounds.width,
|
||||||
height: f32::from(text_size + padding * 2),
|
height: f32::from(text_size + padding.vertical()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_selected {
|
if is_selected {
|
||||||
|
|
@ -88,7 +88,7 @@ where
|
||||||
primitives.push(Primitive::Text {
|
primitives.push(Primitive::Text {
|
||||||
content: option.to_string(),
|
content: option.to_string(),
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + f32::from(padding),
|
x: bounds.x + padding.left as f32,
|
||||||
y: bounds.center_y(),
|
y: bounds.center_y(),
|
||||||
width: f32::INFINITY,
|
width: f32::INFINITY,
|
||||||
..bounds
|
..bounds
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl Viewport {
|
||||||
|
|
||||||
/// Returns the physical width of the [`Viewport`].
|
/// Returns the physical width of the [`Viewport`].
|
||||||
pub fn physical_width(&self) -> u32 {
|
pub fn physical_width(&self) -> u32 {
|
||||||
self.physical_size.height
|
self.physical_size.width
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the physical height of the [`Viewport`].
|
/// Returns the physical height of the [`Viewport`].
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ pub mod scrollable;
|
||||||
pub mod slider;
|
pub mod slider;
|
||||||
pub mod svg;
|
pub mod svg;
|
||||||
pub mod text_input;
|
pub mod text_input;
|
||||||
|
pub mod toggler;
|
||||||
|
pub mod tooltip;
|
||||||
|
|
||||||
mod column;
|
mod column;
|
||||||
mod row;
|
mod row;
|
||||||
|
|
@ -48,6 +50,10 @@ pub use scrollable::Scrollable;
|
||||||
pub use slider::Slider;
|
pub use slider::Slider;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use text_input::TextInput;
|
pub use text_input::TextInput;
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use toggler::Toggler;
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use tooltip::Tooltip;
|
||||||
|
|
||||||
pub use column::Column;
|
pub use column::Column;
|
||||||
pub use image::Image;
|
pub use image::Image;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::defaults::{self, Defaults};
|
||||||
use crate::{Backend, Primitive, Renderer};
|
use crate::{Backend, Primitive, Renderer};
|
||||||
use iced_native::mouse;
|
use iced_native::mouse;
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
Background, Color, Element, Layout, Point, Rectangle, Vector,
|
Background, Color, Element, Layout, Padding, Point, Rectangle, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use iced_native::button::State;
|
pub use iced_native::button::State;
|
||||||
|
|
@ -21,7 +21,7 @@ impl<B> iced_native::button::Renderer for Renderer<B>
|
||||||
where
|
where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
const DEFAULT_PADDING: u16 = 5;
|
const DEFAULT_PADDING: Padding = Padding::new(5);
|
||||||
|
|
||||||
type Style = Box<dyn StyleSheet>;
|
type Style = Box<dyn StyleSheet>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,9 +154,9 @@ where
|
||||||
event: iced_native::Event,
|
event: iced_native::Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
messages: &mut Vec<Message>,
|
|
||||||
_renderer: &Renderer<B>,
|
_renderer: &Renderer<B>,
|
||||||
_clipboard: Option<&dyn Clipboard>,
|
_clipboard: &mut dyn Clipboard,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ impl Frame {
|
||||||
self.size.width
|
self.size.width
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the width of the [`Frame`].
|
/// Returns the height of the [`Frame`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn height(&self) -> f32 {
|
pub fn height(&self) -> f32 {
|
||||||
self.size.height
|
self.size.height
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ pub trait Program<Message> {
|
||||||
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
|
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
|
||||||
/// [`Cache`].
|
/// [`Cache`].
|
||||||
///
|
///
|
||||||
/// [`Frame`]: crate::widget::canvas::Cache
|
/// [`Frame`]: crate::widget::canvas::Frame
|
||||||
/// [`Cache`]: crate::widget::canvas::Cache
|
/// [`Cache`]: crate::widget::canvas::Cache
|
||||||
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>;
|
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
//! Display images in your user interface.
|
//! Display images in your user interface.
|
||||||
|
pub mod viewer;
|
||||||
|
|
||||||
use crate::backend::{self, Backend};
|
use crate::backend::{self, Backend};
|
||||||
|
|
||||||
use crate::{Primitive, Renderer};
|
use crate::{Primitive, Renderer};
|
||||||
use iced_native::image;
|
use iced_native::image;
|
||||||
use iced_native::mouse;
|
use iced_native::mouse;
|
||||||
use iced_native::Layout;
|
use iced_native::Layout;
|
||||||
|
|
||||||
pub use iced_native::image::{Handle, Image};
|
pub use iced_native::image::{Handle, Image, Viewer};
|
||||||
|
|
||||||
impl<B> image::Renderer for Renderer<B>
|
impl<B> image::Renderer for Renderer<B>
|
||||||
where
|
where
|
||||||
|
|
|
||||||
55
graphics/src/widget/image/viewer.rs
Normal file
55
graphics/src/widget/image/viewer.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
//! Zoom and pan on an image.
|
||||||
|
use crate::backend::{self, Backend};
|
||||||
|
use crate::{Primitive, Renderer};
|
||||||
|
|
||||||
|
use iced_native::image;
|
||||||
|
use iced_native::image::viewer;
|
||||||
|
use iced_native::mouse;
|
||||||
|
use iced_native::{Rectangle, Size, Vector};
|
||||||
|
|
||||||
|
impl<B> viewer::Renderer for Renderer<B>
|
||||||
|
where
|
||||||
|
B: Backend + backend::Image,
|
||||||
|
{
|
||||||
|
fn draw(
|
||||||
|
&mut self,
|
||||||
|
state: &viewer::State,
|
||||||
|
bounds: Rectangle,
|
||||||
|
image_size: Size,
|
||||||
|
translation: Vector,
|
||||||
|
handle: image::Handle,
|
||||||
|
is_mouse_over: bool,
|
||||||
|
) -> Self::Output {
|
||||||
|
(
|
||||||
|
{
|
||||||
|
Primitive::Clip {
|
||||||
|
bounds,
|
||||||
|
content: Box::new(Primitive::Translate {
|
||||||
|
translation,
|
||||||
|
content: Box::new(Primitive::Image {
|
||||||
|
handle,
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y,
|
||||||
|
..Rectangle::with_size(image_size)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
offset: Vector::new(0, 0),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
if state.is_cursor_grabbed() {
|
||||||
|
mouse::Interaction::Grabbing
|
||||||
|
} else if is_mouse_over
|
||||||
|
&& (image_size.width > bounds.width
|
||||||
|
|| image_size.height > bounds.height)
|
||||||
|
{
|
||||||
|
mouse::Interaction::Grab
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::Idle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,23 +6,21 @@
|
||||||
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
|
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
|
||||||
//! drag and drop, and hotkey support.
|
//! drag and drop, and hotkey support.
|
||||||
//!
|
//!
|
||||||
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid
|
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
|
||||||
use crate::backend::{self, Backend};
|
|
||||||
use crate::defaults;
|
use crate::defaults;
|
||||||
use crate::{Primitive, Renderer};
|
use crate::{Backend, Color, Primitive, Renderer};
|
||||||
|
use iced_native::container;
|
||||||
use iced_native::mouse;
|
use iced_native::mouse;
|
||||||
use iced_native::pane_grid;
|
use iced_native::pane_grid;
|
||||||
use iced_native::text;
|
use iced_native::{Element, Layout, Point, Rectangle, Vector};
|
||||||
use iced_native::{
|
|
||||||
Element, HorizontalAlignment, Layout, Point, Rectangle, Vector,
|
|
||||||
VerticalAlignment,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use iced_native::pane_grid::{
|
pub use iced_native::pane_grid::{
|
||||||
Axis, Configuration, Content, Direction, DragEvent, Pane, ResizeEvent,
|
Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
|
||||||
Split, State, TitleBar,
|
ResizeEvent, Split, State, TitleBar,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use iced_style::pane_grid::{Line, StyleSheet};
|
||||||
|
|
||||||
/// A collection of panes distributed using either vertical or horizontal splits
|
/// A collection of panes distributed using either vertical or horizontal splits
|
||||||
/// to completely fill the space available.
|
/// to completely fill the space available.
|
||||||
///
|
///
|
||||||
|
|
@ -34,16 +32,20 @@ pub type PaneGrid<'a, Message, Backend> =
|
||||||
|
|
||||||
impl<B> pane_grid::Renderer for Renderer<B>
|
impl<B> pane_grid::Renderer for Renderer<B>
|
||||||
where
|
where
|
||||||
B: Backend + backend::Text,
|
B: Backend,
|
||||||
{
|
{
|
||||||
|
type Style = Box<dyn StyleSheet>;
|
||||||
|
|
||||||
fn draw<Message>(
|
fn draw<Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
defaults: &Self::Defaults,
|
defaults: &Self::Defaults,
|
||||||
content: &[(Pane, Content<'_, Message, Self>)],
|
content: &[(Pane, Content<'_, Message, Self>)],
|
||||||
dragging: Option<(Pane, Point)>,
|
dragging: Option<(Pane, Point)>,
|
||||||
resizing: Option<Axis>,
|
resizing: Option<(Axis, Rectangle, bool)>,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
style_sheet: &<Self as pane_grid::Renderer>::Style,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let pane_cursor_position = if dragging.is_some() {
|
let pane_cursor_position = if dragging.is_some() {
|
||||||
// TODO: Remove once cursor availability is encoded in the type
|
// TODO: Remove once cursor availability is encoded in the type
|
||||||
|
|
@ -61,8 +63,13 @@ where
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, ((id, pane), layout))| {
|
.map(|(i, ((id, pane), layout))| {
|
||||||
let (primitive, new_mouse_interaction) =
|
let (primitive, new_mouse_interaction) = pane.draw(
|
||||||
pane.draw(self, defaults, layout, pane_cursor_position);
|
self,
|
||||||
|
defaults,
|
||||||
|
layout,
|
||||||
|
pane_cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
|
||||||
if new_mouse_interaction > mouse_interaction {
|
if new_mouse_interaction > mouse_interaction {
|
||||||
mouse_interaction = new_mouse_interaction;
|
mouse_interaction = new_mouse_interaction;
|
||||||
|
|
@ -78,7 +85,8 @@ where
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let primitives = if let Some((index, layout, origin)) = dragged_pane {
|
let mut primitives = if let Some((index, layout, origin)) = dragged_pane
|
||||||
|
{
|
||||||
let pane = panes.remove(index);
|
let pane = panes.remove(index);
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
|
|
@ -108,15 +116,62 @@ where
|
||||||
panes
|
panes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (primitives, mouse_interaction) =
|
||||||
|
if let Some((axis, split_region, is_picked)) = resizing {
|
||||||
|
let highlight = if is_picked {
|
||||||
|
style_sheet.picked_split()
|
||||||
|
} else {
|
||||||
|
style_sheet.hovered_split()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(highlight) = highlight {
|
||||||
|
primitives.push(Primitive::Quad {
|
||||||
|
bounds: match axis {
|
||||||
|
Axis::Horizontal => Rectangle {
|
||||||
|
x: split_region.x,
|
||||||
|
y: (split_region.y
|
||||||
|
+ (split_region.height - highlight.width)
|
||||||
|
/ 2.0)
|
||||||
|
.round(),
|
||||||
|
width: split_region.width,
|
||||||
|
height: highlight.width,
|
||||||
|
},
|
||||||
|
Axis::Vertical => Rectangle {
|
||||||
|
x: (split_region.x
|
||||||
|
+ (split_region.width - highlight.width)
|
||||||
|
/ 2.0)
|
||||||
|
.round(),
|
||||||
|
y: split_region.y,
|
||||||
|
width: highlight.width,
|
||||||
|
height: split_region.height,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
background: highlight.color.into(),
|
||||||
|
border_radius: 0.0,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
primitives,
|
||||||
|
match axis {
|
||||||
|
Axis::Horizontal => {
|
||||||
|
mouse::Interaction::ResizingVertically
|
||||||
|
}
|
||||||
|
Axis::Vertical => {
|
||||||
|
mouse::Interaction::ResizingHorizontally
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(primitives, mouse_interaction)
|
||||||
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
Primitive::Group { primitives },
|
Primitive::Group { primitives },
|
||||||
if dragging.is_some() {
|
if dragging.is_some() {
|
||||||
mouse::Interaction::Grabbing
|
mouse::Interaction::Grabbing
|
||||||
} else if let Some(axis) = resizing {
|
|
||||||
match axis {
|
|
||||||
Axis::Horizontal => mouse::Interaction::ResizingVertically,
|
|
||||||
Axis::Vertical => mouse::Interaction::ResizingHorizontally,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
mouse_interaction
|
mouse_interaction
|
||||||
},
|
},
|
||||||
|
|
@ -127,16 +182,17 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
defaults: &Self::Defaults,
|
defaults: &Self::Defaults,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
style_sheet: &Self::Style,
|
style_sheet: &<Self as container::Renderer>::Style,
|
||||||
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
|
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
|
||||||
body: (&Element<'_, Message, Self>, Layout<'_>),
|
body: (&Element<'_, Message, Self>, Layout<'_>),
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let style = style_sheet.style();
|
let style = style_sheet.style();
|
||||||
let (body, body_layout) = body;
|
let (body, body_layout) = body;
|
||||||
|
|
||||||
let (body_primitive, body_interaction) =
|
let (body_primitive, body_interaction) =
|
||||||
body.draw(self, defaults, body_layout, cursor_position, &bounds);
|
body.draw(self, defaults, body_layout, cursor_position, viewport);
|
||||||
|
|
||||||
let background = crate::widget::container::background(bounds, &style);
|
let background = crate::widget::container::background(bounds, &style);
|
||||||
|
|
||||||
|
|
@ -150,6 +206,7 @@ where
|
||||||
defaults,
|
defaults,
|
||||||
title_bar_layout,
|
title_bar_layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
|
viewport,
|
||||||
show_controls,
|
show_controls,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -161,10 +218,10 @@ where
|
||||||
body_primitive,
|
body_primitive,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
if is_over_pick_area {
|
if title_bar_interaction > body_interaction {
|
||||||
mouse::Interaction::Grab
|
|
||||||
} else if title_bar_interaction > body_interaction {
|
|
||||||
title_bar_interaction
|
title_bar_interaction
|
||||||
|
} else if is_over_pick_area {
|
||||||
|
mouse::Interaction::Grab
|
||||||
} else {
|
} else {
|
||||||
body_interaction
|
body_interaction
|
||||||
},
|
},
|
||||||
|
|
@ -187,15 +244,14 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
defaults: &Self::Defaults,
|
defaults: &Self::Defaults,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
style_sheet: &Self::Style,
|
style_sheet: &<Self as container::Renderer>::Style,
|
||||||
title: &str,
|
content: (&Element<'_, Message, Self>, Layout<'_>),
|
||||||
title_size: u16,
|
|
||||||
title_font: Self::Font,
|
|
||||||
title_bounds: Rectangle,
|
|
||||||
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
|
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let style = style_sheet.style();
|
let style = style_sheet.style();
|
||||||
|
let (title_content, title_layout) = content;
|
||||||
|
|
||||||
let defaults = Self::Defaults {
|
let defaults = Self::Defaults {
|
||||||
text: defaults::Text {
|
text: defaults::Text {
|
||||||
|
|
@ -205,16 +261,12 @@ where
|
||||||
|
|
||||||
let background = crate::widget::container::background(bounds, &style);
|
let background = crate::widget::container::background(bounds, &style);
|
||||||
|
|
||||||
let (title_primitive, _) = text::Renderer::draw(
|
let (title_primitive, title_interaction) = title_content.draw(
|
||||||
self,
|
self,
|
||||||
&defaults,
|
&defaults,
|
||||||
title_bounds,
|
title_layout,
|
||||||
title,
|
cursor_position,
|
||||||
title_size,
|
viewport,
|
||||||
title_font,
|
|
||||||
None,
|
|
||||||
HorizontalAlignment::Left,
|
|
||||||
VerticalAlignment::Top,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some((controls, controls_layout)) = controls {
|
if let Some((controls, controls_layout)) = controls {
|
||||||
|
|
@ -223,7 +275,7 @@ where
|
||||||
&defaults,
|
&defaults,
|
||||||
controls_layout,
|
controls_layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
&bounds,
|
viewport,
|
||||||
);
|
);
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
@ -234,7 +286,7 @@ where
|
||||||
controls_primitive,
|
controls_primitive,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
controls_interaction,
|
controls_interaction.max(title_interaction),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
|
|
@ -245,7 +297,7 @@ where
|
||||||
} else {
|
} else {
|
||||||
title_primitive
|
title_primitive
|
||||||
},
|
},
|
||||||
mouse::Interaction::default(),
|
title_interaction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
use crate::backend::{self, Backend};
|
use crate::backend::{self, Backend};
|
||||||
use crate::{Primitive, Renderer};
|
use crate::{Primitive, Renderer};
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment,
|
mouse, Font, HorizontalAlignment, Padding, Point, Rectangle,
|
||||||
|
VerticalAlignment,
|
||||||
};
|
};
|
||||||
use iced_style::menu;
|
use iced_style::menu;
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ where
|
||||||
{
|
{
|
||||||
type Style = Box<dyn StyleSheet>;
|
type Style = Box<dyn StyleSheet>;
|
||||||
|
|
||||||
const DEFAULT_PADDING: u16 = 5;
|
const DEFAULT_PADDING: Padding = Padding::new(5);
|
||||||
|
|
||||||
fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
|
fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
|
||||||
style.menu()
|
style.menu()
|
||||||
|
|
@ -30,12 +31,14 @@ where
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
selected: Option<String>,
|
selected: Option<String>,
|
||||||
padding: u16,
|
placeholder: Option<&str>,
|
||||||
|
padding: Padding,
|
||||||
text_size: u16,
|
text_size: u16,
|
||||||
font: Font,
|
font: Font,
|
||||||
style: &Box<dyn StyleSheet>,
|
style: &Box<dyn StyleSheet>,
|
||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let is_mouse_over = bounds.contains(cursor_position);
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
let is_selected = selected.is_some();
|
||||||
|
|
||||||
let style = if is_mouse_over {
|
let style = if is_mouse_over {
|
||||||
style.hovered()
|
style.hovered()
|
||||||
|
|
@ -56,7 +59,7 @@ where
|
||||||
font: B::ICON_FONT,
|
font: B::ICON_FONT,
|
||||||
size: bounds.height * style.icon_size,
|
size: bounds.height * style.icon_size,
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + bounds.width - f32::from(padding) * 2.0,
|
x: bounds.x + bounds.width - f32::from(padding.horizontal()),
|
||||||
y: bounds.center_y(),
|
y: bounds.center_y(),
|
||||||
..bounds
|
..bounds
|
||||||
},
|
},
|
||||||
|
|
@ -67,14 +70,18 @@ where
|
||||||
|
|
||||||
(
|
(
|
||||||
Primitive::Group {
|
Primitive::Group {
|
||||||
primitives: if let Some(label) = selected {
|
primitives: if let Some(label) =
|
||||||
|
selected.or_else(|| placeholder.map(str::to_string))
|
||||||
|
{
|
||||||
let label = Primitive::Text {
|
let label = Primitive::Text {
|
||||||
content: label,
|
content: label,
|
||||||
size: f32::from(text_size),
|
size: f32::from(text_size),
|
||||||
font,
|
font,
|
||||||
color: style.text_color,
|
color: is_selected
|
||||||
|
.then(|| style.text_color)
|
||||||
|
.unwrap_or(style.placeholder_color),
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + f32::from(padding),
|
x: bounds.x + f32::from(padding.left),
|
||||||
y: bounds.center_y(),
|
y: bounds.center_y(),
|
||||||
..bounds
|
..bounds
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -134,8 +134,16 @@ where
|
||||||
Primitive::None
|
Primitive::None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let scroll = Primitive::Clip {
|
||||||
|
bounds,
|
||||||
|
offset: Vector::new(0, 0),
|
||||||
|
content: Box::new(Primitive::Group {
|
||||||
|
primitives: vec![scrollbar, scroller],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
Primitive::Group {
|
Primitive::Group {
|
||||||
primitives: vec![clip, scrollbar, scroller],
|
primitives: vec![clip, scroll],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
|
|
|
||||||
99
graphics/src/widget/toggler.rs
Normal file
99
graphics/src/widget/toggler.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
//! Show toggle controls using togglers.
|
||||||
|
use crate::backend::{self, Backend};
|
||||||
|
use crate::{Primitive, Renderer};
|
||||||
|
use iced_native::mouse;
|
||||||
|
use iced_native::toggler;
|
||||||
|
use iced_native::Rectangle;
|
||||||
|
|
||||||
|
pub use iced_style::toggler::{Style, StyleSheet};
|
||||||
|
|
||||||
|
/// Makes sure that the border radius of the toggler looks good at every size.
|
||||||
|
const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
|
||||||
|
|
||||||
|
/// The space ratio between the background Quad and the Toggler bounds, and
|
||||||
|
/// between the background Quad and foreground Quad.
|
||||||
|
const SPACE_RATIO: f32 = 0.05;
|
||||||
|
|
||||||
|
/// A toggler that can be toggled.
|
||||||
|
///
|
||||||
|
/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
|
||||||
|
pub type Toggler<Message, Backend> =
|
||||||
|
iced_native::Toggler<Message, Renderer<Backend>>;
|
||||||
|
|
||||||
|
impl<B> toggler::Renderer for Renderer<B>
|
||||||
|
where
|
||||||
|
B: Backend + backend::Text,
|
||||||
|
{
|
||||||
|
type Style = Box<dyn StyleSheet>;
|
||||||
|
|
||||||
|
const DEFAULT_SIZE: u16 = 20;
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&mut self,
|
||||||
|
bounds: Rectangle,
|
||||||
|
is_active: bool,
|
||||||
|
is_mouse_over: bool,
|
||||||
|
label: Option<Self::Output>,
|
||||||
|
style_sheet: &Self::Style,
|
||||||
|
) -> Self::Output {
|
||||||
|
let style = if is_mouse_over {
|
||||||
|
style_sheet.hovered(is_active)
|
||||||
|
} else {
|
||||||
|
style_sheet.active(is_active)
|
||||||
|
};
|
||||||
|
|
||||||
|
let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
|
||||||
|
let space = SPACE_RATIO * bounds.height as f32;
|
||||||
|
|
||||||
|
let toggler_background_bounds = Rectangle {
|
||||||
|
x: bounds.x + space,
|
||||||
|
y: bounds.y + space,
|
||||||
|
width: bounds.width - (2.0 * space),
|
||||||
|
height: bounds.height - (2.0 * space),
|
||||||
|
};
|
||||||
|
|
||||||
|
let toggler_background = Primitive::Quad {
|
||||||
|
bounds: toggler_background_bounds,
|
||||||
|
background: style.background.into(),
|
||||||
|
border_radius,
|
||||||
|
border_width: 1.0,
|
||||||
|
border_color: style.background_border.unwrap_or(style.background),
|
||||||
|
};
|
||||||
|
|
||||||
|
let toggler_foreground_bounds = Rectangle {
|
||||||
|
x: bounds.x
|
||||||
|
+ if is_active {
|
||||||
|
bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
|
||||||
|
} else {
|
||||||
|
2.0 * space
|
||||||
|
},
|
||||||
|
y: bounds.y + (2.0 * space),
|
||||||
|
width: bounds.height - (4.0 * space),
|
||||||
|
height: bounds.height - (4.0 * space),
|
||||||
|
};
|
||||||
|
|
||||||
|
let toggler_foreground = Primitive::Quad {
|
||||||
|
bounds: toggler_foreground_bounds,
|
||||||
|
background: style.foreground.into(),
|
||||||
|
border_radius,
|
||||||
|
border_width: 1.0,
|
||||||
|
border_color: style.foreground_border.unwrap_or(style.foreground),
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
Primitive::Group {
|
||||||
|
primitives: match label {
|
||||||
|
Some((l, _)) => {
|
||||||
|
vec![l, toggler_background, toggler_foreground]
|
||||||
|
}
|
||||||
|
None => vec![toggler_background, toggler_foreground],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
if is_mouse_over {
|
||||||
|
mouse::Interaction::Pointer
|
||||||
|
} else {
|
||||||
|
mouse::Interaction::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
168
graphics/src/widget/tooltip.rs
Normal file
168
graphics/src/widget/tooltip.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
//! Decorate content and apply alignment.
|
||||||
|
use crate::backend::{self, Backend};
|
||||||
|
use crate::defaults::{self, Defaults};
|
||||||
|
use crate::{Primitive, Renderer, Vector};
|
||||||
|
|
||||||
|
use iced_native::container;
|
||||||
|
use iced_native::layout::{self, Layout};
|
||||||
|
use iced_native::{Element, Padding, Point, Rectangle, Size, Text};
|
||||||
|
|
||||||
|
/// An element decorating some content.
|
||||||
|
///
|
||||||
|
/// This is an alias of an `iced_native` tooltip with a default
|
||||||
|
/// `Renderer`.
|
||||||
|
pub type Tooltip<'a, Message, Backend> =
|
||||||
|
iced_native::Tooltip<'a, Message, Renderer<Backend>>;
|
||||||
|
|
||||||
|
pub use iced_native::tooltip::Position;
|
||||||
|
|
||||||
|
impl<B> iced_native::tooltip::Renderer for Renderer<B>
|
||||||
|
where
|
||||||
|
B: Backend + backend::Text,
|
||||||
|
{
|
||||||
|
const DEFAULT_PADDING: u16 = 5;
|
||||||
|
|
||||||
|
fn draw<Message>(
|
||||||
|
&mut self,
|
||||||
|
defaults: &Defaults,
|
||||||
|
cursor_position: Point,
|
||||||
|
content_layout: Layout<'_>,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
content: &Element<'_, Message, Self>,
|
||||||
|
tooltip: &Text<Self>,
|
||||||
|
position: Position,
|
||||||
|
style_sheet: &<Self as container::Renderer>::Style,
|
||||||
|
gap: u16,
|
||||||
|
padding: u16,
|
||||||
|
) -> Self::Output {
|
||||||
|
let (content, mouse_interaction) = content.draw(
|
||||||
|
self,
|
||||||
|
&defaults,
|
||||||
|
content_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
let bounds = content_layout.bounds();
|
||||||
|
|
||||||
|
if bounds.contains(cursor_position) {
|
||||||
|
use iced_native::Widget;
|
||||||
|
|
||||||
|
let gap = f32::from(gap);
|
||||||
|
let style = style_sheet.style();
|
||||||
|
|
||||||
|
let defaults = Defaults {
|
||||||
|
text: defaults::Text {
|
||||||
|
color: style.text_color.unwrap_or(defaults.text.color),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let text_layout = Widget::<(), Self>::layout(
|
||||||
|
tooltip,
|
||||||
|
self,
|
||||||
|
&layout::Limits::new(Size::ZERO, viewport.size())
|
||||||
|
.pad(Padding::new(padding)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let padding = f32::from(padding);
|
||||||
|
let text_bounds = text_layout.bounds();
|
||||||
|
let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
|
||||||
|
let y_center =
|
||||||
|
bounds.y + (bounds.height - text_bounds.height) / 2.0;
|
||||||
|
|
||||||
|
let mut tooltip_bounds = {
|
||||||
|
let offset = match position {
|
||||||
|
Position::Top => Vector::new(
|
||||||
|
x_center,
|
||||||
|
bounds.y - text_bounds.height - gap - padding,
|
||||||
|
),
|
||||||
|
Position::Bottom => Vector::new(
|
||||||
|
x_center,
|
||||||
|
bounds.y + bounds.height + gap + padding,
|
||||||
|
),
|
||||||
|
Position::Left => Vector::new(
|
||||||
|
bounds.x - text_bounds.width - gap - padding,
|
||||||
|
y_center,
|
||||||
|
),
|
||||||
|
Position::Right => Vector::new(
|
||||||
|
bounds.x + bounds.width + gap + padding,
|
||||||
|
y_center,
|
||||||
|
),
|
||||||
|
Position::FollowCursor => Vector::new(
|
||||||
|
cursor_position.x,
|
||||||
|
cursor_position.y - text_bounds.height,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: offset.x - padding,
|
||||||
|
y: offset.y - padding,
|
||||||
|
width: text_bounds.width + padding * 2.0,
|
||||||
|
height: text_bounds.height + padding * 2.0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if tooltip_bounds.x < viewport.x {
|
||||||
|
tooltip_bounds.x = viewport.x;
|
||||||
|
} else if viewport.x + viewport.width
|
||||||
|
< tooltip_bounds.x + tooltip_bounds.width
|
||||||
|
{
|
||||||
|
tooltip_bounds.x =
|
||||||
|
viewport.x + viewport.width - tooltip_bounds.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if tooltip_bounds.y < viewport.y {
|
||||||
|
tooltip_bounds.y = viewport.y;
|
||||||
|
} else if viewport.y + viewport.height
|
||||||
|
< tooltip_bounds.y + tooltip_bounds.height
|
||||||
|
{
|
||||||
|
tooltip_bounds.y =
|
||||||
|
viewport.y + viewport.height - tooltip_bounds.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (tooltip, _) = Widget::<(), Self>::draw(
|
||||||
|
tooltip,
|
||||||
|
self,
|
||||||
|
&defaults,
|
||||||
|
Layout::with_offset(
|
||||||
|
Vector::new(
|
||||||
|
tooltip_bounds.x + padding,
|
||||||
|
tooltip_bounds.y + padding,
|
||||||
|
),
|
||||||
|
&text_layout,
|
||||||
|
),
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
Primitive::Group {
|
||||||
|
primitives: vec![
|
||||||
|
content,
|
||||||
|
Primitive::Clip {
|
||||||
|
bounds: *viewport,
|
||||||
|
offset: Vector::new(0, 0),
|
||||||
|
content: Box::new(
|
||||||
|
if let Some(background) =
|
||||||
|
crate::container::background(
|
||||||
|
tooltip_bounds,
|
||||||
|
&style,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Primitive::Group {
|
||||||
|
primitives: vec![background, tooltip],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tooltip
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
mouse_interaction,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(content, mouse_interaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,10 @@ pub trait Compositor: Sized {
|
||||||
type SwapChain;
|
type SwapChain;
|
||||||
|
|
||||||
/// Creates a new [`Compositor`].
|
/// Creates a new [`Compositor`].
|
||||||
fn new(settings: Self::Settings) -> Result<(Self, Self::Renderer), Error>;
|
fn new<W: HasRawWindowHandle>(
|
||||||
|
settings: Self::Settings,
|
||||||
|
compatible_window: Option<&W>,
|
||||||
|
) -> Result<(Self, Self::Renderer), Error>;
|
||||||
|
|
||||||
/// Crates a new [`Surface`] for the given window.
|
/// Crates a new [`Surface`] for the given window.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_native"
|
name = "iced_native"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A renderer-agnostic library for native GUIs"
|
description = "A renderer-agnostic library for native GUIs"
|
||||||
|
|
@ -16,10 +16,10 @@ unicode-segmentation = "1.6"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
|
||||||
[dependencies.iced_core]
|
[dependencies.iced_core]
|
||||||
version = "0.3"
|
version = "0.4"
|
||||||
path = "../core"
|
path = "../core"
|
||||||
|
|
||||||
[dependencies.iced_futures]
|
[dependencies.iced_futures]
|
||||||
version = "0.2"
|
version = "0.3"
|
||||||
path = "../futures"
|
path = "../futures"
|
||||||
features = ["thread-pool"]
|
features = ["thread-pool"]
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ To achieve this, it introduces a bunch of reusable interfaces:
|
||||||
Add `iced_native` as a dependency in your `Cargo.toml`:
|
Add `iced_native` as a dependency in your `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
iced_native = "0.3"
|
iced_native = "0.4"
|
||||||
```
|
```
|
||||||
|
|
||||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,23 @@
|
||||||
|
//! Access the clipboard.
|
||||||
|
|
||||||
/// A buffer for short-term storage and transfer within and between
|
/// A buffer for short-term storage and transfer within and between
|
||||||
/// applications.
|
/// applications.
|
||||||
pub trait Clipboard {
|
pub trait Clipboard {
|
||||||
/// Returns the current content of the [`Clipboard`] as text.
|
/// Reads the current content of the [`Clipboard`] as text.
|
||||||
fn content(&self) -> Option<String>;
|
fn read(&self) -> Option<String>;
|
||||||
|
|
||||||
|
/// Writes the given text contents to the [`Clipboard`].
|
||||||
|
fn write(&mut self, contents: String);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A null implementation of the [`Clipboard`] trait.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Null;
|
||||||
|
|
||||||
|
impl Clipboard for Null {
|
||||||
|
fn read(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, _contents: String) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -223,17 +223,17 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
messages: &mut Vec<Message>,
|
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: Option<&dyn Clipboard>,
|
clipboard: &mut dyn Clipboard,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
self.widget.on_event(
|
self.widget.on_event(
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
messages,
|
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
|
messages,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,9 +311,9 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
messages: &mut Vec<B>,
|
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: Option<&dyn Clipboard>,
|
clipboard: &mut dyn Clipboard,
|
||||||
|
messages: &mut Vec<B>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
let mut original_messages = Vec::new();
|
let mut original_messages = Vec::new();
|
||||||
|
|
||||||
|
|
@ -321,9 +321,9 @@ where
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
&mut original_messages,
|
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
|
&mut original_messages,
|
||||||
);
|
);
|
||||||
|
|
||||||
original_messages
|
original_messages
|
||||||
|
|
@ -401,17 +401,17 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
messages: &mut Vec<Message>,
|
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: Option<&dyn Clipboard>,
|
clipboard: &mut dyn Clipboard,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
self.element.widget.on_event(
|
self.element.widget.on_event(
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
messages,
|
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
|
messages,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
//! Handle events of a user interface.
|
//! Handle events of a user interface.
|
||||||
use crate::{keyboard, mouse, window};
|
use crate::keyboard;
|
||||||
|
use crate::mouse;
|
||||||
|
use crate::touch;
|
||||||
|
use crate::window;
|
||||||
|
|
||||||
/// A user interface event.
|
/// A user interface event.
|
||||||
///
|
///
|
||||||
|
|
@ -17,6 +20,30 @@ pub enum Event {
|
||||||
|
|
||||||
/// A window event
|
/// A window event
|
||||||
Window(window::Event),
|
Window(window::Event),
|
||||||
|
|
||||||
|
/// A touch event
|
||||||
|
Touch(touch::Event),
|
||||||
|
|
||||||
|
/// A platform specific event
|
||||||
|
PlatformSpecific(PlatformSpecific),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A platform specific event
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum PlatformSpecific {
|
||||||
|
/// A MacOS specific event
|
||||||
|
MacOS(MacOS),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes an event specific to MacOS
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum MacOS {
|
||||||
|
/// Triggered when the app receives an URL from the system
|
||||||
|
///
|
||||||
|
/// _**Note:** For this event to be triggered, the executable needs to be properly [bundled]!_
|
||||||
|
///
|
||||||
|
/// [bundled]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW19
|
||||||
|
ReceivedUrl(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The status of an [`Event`] after being processed.
|
/// The status of an [`Event`] after being processed.
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,14 @@ pub struct Layout<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Layout<'a> {
|
impl<'a> Layout<'a> {
|
||||||
pub(crate) fn new(node: &'a Node) -> Self {
|
/// Creates a new [`Layout`] for the given [`Node`] at the origin.
|
||||||
|
pub fn new(node: &'a Node) -> Self {
|
||||||
Self::with_offset(Vector::new(0.0, 0.0), node)
|
Self::with_offset(Vector::new(0.0, 0.0), node)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_offset(offset: Vector, node: &'a Node) -> Self {
|
/// Creates a new [`Layout`] for the given [`Node`] with the provided offset
|
||||||
|
/// from the origin.
|
||||||
|
pub fn with_offset(offset: Vector, node: &'a Node) -> Self {
|
||||||
let bounds = node.bounds();
|
let bounds = node.bounds();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,10 @@
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::{Limits, Node},
|
layout::{Limits, Node},
|
||||||
Align, Element, Point, Size,
|
Align, Element, Padding, Point, Size,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The main axis of a flex layout.
|
/// The main axis of a flex layout.
|
||||||
|
|
@ -62,7 +63,7 @@ pub fn resolve<Message, Renderer>(
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &Limits,
|
limits: &Limits,
|
||||||
padding: f32,
|
padding: Padding,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
align_items: Align,
|
align_items: Align,
|
||||||
items: &[Element<'_, Message, Renderer>],
|
items: &[Element<'_, Message, Renderer>],
|
||||||
|
|
@ -141,14 +142,15 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut main = padding;
|
let pad = axis.pack(padding.left as f32, padding.top as f32);
|
||||||
|
let mut main = pad.0;
|
||||||
|
|
||||||
for (i, node) in nodes.iter_mut().enumerate() {
|
for (i, node) in nodes.iter_mut().enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
main += spacing;
|
main += spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (x, y) = axis.pack(main, padding);
|
let (x, y) = axis.pack(main, pad.1);
|
||||||
|
|
||||||
node.move_to(Point::new(x, y));
|
node.move_to(Point::new(x, y));
|
||||||
|
|
||||||
|
|
@ -166,7 +168,7 @@ where
|
||||||
main += axis.main(size);
|
main += axis.main(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (width, height) = axis.pack(main - padding, cross);
|
let (width, height) = axis.pack(main - pad.0, cross);
|
||||||
let size = limits.resolve(Size::new(width, height));
|
let size = limits.resolve(Size::new(width, height));
|
||||||
|
|
||||||
Node::with_children(size.pad(padding), nodes)
|
Node::with_children(size.pad(padding), nodes)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Length, Size};
|
use crate::{Length, Padding, Size};
|
||||||
|
|
||||||
/// A set of size constraints for layouting.
|
/// A set of size constraints for layouting.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -117,8 +117,11 @@ impl Limits {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shrinks the current [`Limits`] to account for the given padding.
|
/// Shrinks the current [`Limits`] to account for the given padding.
|
||||||
pub fn pad(&self, padding: f32) -> Limits {
|
pub fn pad(&self, padding: Padding) -> Limits {
|
||||||
self.shrink(Size::new(padding * 2.0, padding * 2.0))
|
self.shrink(Size::new(
|
||||||
|
padding.horizontal() as f32,
|
||||||
|
padding.vertical() as f32,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shrinks the current [`Limits`] by the given [`Size`].
|
/// Shrinks the current [`Limits`] by the given [`Size`].
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
#![deny(unused_results)]
|
#![deny(unused_results)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![forbid(rust_2018_idioms)]
|
#![forbid(rust_2018_idioms)]
|
||||||
|
pub mod clipboard;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
|
@ -41,10 +42,10 @@ pub mod overlay;
|
||||||
pub mod program;
|
pub mod program;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
|
pub mod touch;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
mod clipboard;
|
|
||||||
mod element;
|
mod element;
|
||||||
mod hasher;
|
mod hasher;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
|
|
@ -60,8 +61,8 @@ mod debug;
|
||||||
mod debug;
|
mod debug;
|
||||||
|
|
||||||
pub use iced_core::{
|
pub use iced_core::{
|
||||||
Align, Background, Color, Font, HorizontalAlignment, Length, Point,
|
menu, Align, Background, Color, Font, HorizontalAlignment, Length, Menu,
|
||||||
Rectangle, Size, Vector, VerticalAlignment,
|
Padding, Point, Rectangle, Size, Vector, VerticalAlignment,
|
||||||
};
|
};
|
||||||
pub use iced_futures::{executor, futures, Command};
|
pub use iced_futures::{executor, futures, Command};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,9 @@ where
|
||||||
_event: Event,
|
_event: Event,
|
||||||
_layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_messages: &mut Vec<Message>,
|
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
_clipboard: Option<&dyn Clipboard>,
|
_clipboard: &mut dyn Clipboard,
|
||||||
|
_messages: &mut Vec<Message>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
event::Status::Ignored
|
event::Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,17 +53,17 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
messages: &mut Vec<Message>,
|
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: Option<&dyn Clipboard>,
|
clipboard: &mut dyn Clipboard,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
self.overlay.on_event(
|
self.overlay.on_event(
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
messages,
|
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
|
messages,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,9 +117,9 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
messages: &mut Vec<B>,
|
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: Option<&dyn Clipboard>,
|
clipboard: &mut dyn Clipboard,
|
||||||
|
messages: &mut Vec<B>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
let mut original_messages = Vec::new();
|
let mut original_messages = Vec::new();
|
||||||
|
|
||||||
|
|
@ -127,9 +127,9 @@ where
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
&mut original_messages,
|
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
|
&mut original_messages,
|
||||||
);
|
);
|
||||||
|
|
||||||
original_messages
|
original_messages
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::scrollable;
|
use crate::scrollable;
|
||||||
use crate::text;
|
use crate::text;
|
||||||
|
use crate::touch;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Container, Element, Hasher, Layout, Length, Point, Rectangle,
|
Clipboard, Container, Element, Hasher, Layout, Length, Padding, Point,
|
||||||
Scrollable, Size, Vector, Widget,
|
Rectangle, Scrollable, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A list of selectable options.
|
/// A list of selectable options.
|
||||||
|
|
@ -19,7 +20,7 @@ pub struct Menu<'a, T, Renderer: self::Renderer> {
|
||||||
hovered_option: &'a mut Option<usize>,
|
hovered_option: &'a mut Option<usize>,
|
||||||
last_selection: &'a mut Option<T>,
|
last_selection: &'a mut Option<T>,
|
||||||
width: u16,
|
width: u16,
|
||||||
padding: u16,
|
padding: Padding,
|
||||||
text_size: Option<u16>,
|
text_size: Option<u16>,
|
||||||
font: Renderer::Font,
|
font: Renderer::Font,
|
||||||
style: <Renderer as self::Renderer>::Style,
|
style: <Renderer as self::Renderer>::Style,
|
||||||
|
|
@ -44,7 +45,7 @@ where
|
||||||
hovered_option,
|
hovered_option,
|
||||||
last_selection,
|
last_selection,
|
||||||
width: 0,
|
width: 0,
|
||||||
padding: 0,
|
padding: Padding::ZERO,
|
||||||
text_size: None,
|
text_size: None,
|
||||||
font: Default::default(),
|
font: Default::default(),
|
||||||
style: Default::default(),
|
style: Default::default(),
|
||||||
|
|
@ -57,9 +58,9 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the padding of the [`Menu`].
|
/// Sets the [`Padding`] of the [`Menu`].
|
||||||
pub fn padding(mut self, padding: u16) -> Self {
|
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
|
||||||
self.padding = padding;
|
self.padding = padding.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,17 +219,17 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
messages: &mut Vec<Message>,
|
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: Option<&dyn Clipboard>,
|
clipboard: &mut dyn Clipboard,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
self.container.on_event(
|
self.container.on_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
messages,
|
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
|
messages,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,7 +261,7 @@ struct List<'a, T, Renderer: self::Renderer> {
|
||||||
options: &'a [T],
|
options: &'a [T],
|
||||||
hovered_option: &'a mut Option<usize>,
|
hovered_option: &'a mut Option<usize>,
|
||||||
last_selection: &'a mut Option<T>,
|
last_selection: &'a mut Option<T>,
|
||||||
padding: u16,
|
padding: Padding,
|
||||||
text_size: Option<u16>,
|
text_size: Option<u16>,
|
||||||
font: Renderer::Font,
|
font: Renderer::Font,
|
||||||
style: <Renderer as self::Renderer>::Style,
|
style: <Renderer as self::Renderer>::Style,
|
||||||
|
|
@ -293,7 +294,7 @@ where
|
||||||
let size = {
|
let size = {
|
||||||
let intrinsic = Size::new(
|
let intrinsic = Size::new(
|
||||||
0.0,
|
0.0,
|
||||||
f32::from(text_size + self.padding * 2)
|
f32::from(text_size + self.padding.vertical())
|
||||||
* self.options.len() as f32,
|
* self.options.len() as f32,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -319,9 +320,9 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_messages: &mut Vec<Message>,
|
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
_clipboard: Option<&dyn Clipboard>,
|
_clipboard: &mut dyn Clipboard,
|
||||||
|
_messages: &mut Vec<Message>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||||
|
|
@ -337,17 +338,38 @@ where
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
|
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let text_size =
|
|
||||||
self.text_size.unwrap_or(renderer.default_size());
|
|
||||||
|
|
||||||
if bounds.contains(cursor_position) {
|
if bounds.contains(cursor_position) {
|
||||||
|
let text_size =
|
||||||
|
self.text_size.unwrap_or(renderer.default_size());
|
||||||
|
|
||||||
*self.hovered_option = Some(
|
*self.hovered_option = Some(
|
||||||
((cursor_position.y - bounds.y)
|
((cursor_position.y - bounds.y)
|
||||||
/ f32::from(text_size + self.padding * 2))
|
/ f32::from(text_size + self.padding.vertical()))
|
||||||
as usize,
|
as usize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
|
if bounds.contains(cursor_position) {
|
||||||
|
let text_size =
|
||||||
|
self.text_size.unwrap_or(renderer.default_size());
|
||||||
|
|
||||||
|
*self.hovered_option = Some(
|
||||||
|
((cursor_position.y - bounds.y)
|
||||||
|
/ f32::from(text_size + self.padding.vertical()))
|
||||||
|
as usize,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(index) = *self.hovered_option {
|
||||||
|
if let Some(option) = self.options.get(index) {
|
||||||
|
*self.last_selection = Some(option.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -408,7 +430,7 @@ pub trait Renderer:
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
options: &[T],
|
options: &[T],
|
||||||
hovered_option: Option<usize>,
|
hovered_option: Option<usize>,
|
||||||
padding: u16,
|
padding: Padding,
|
||||||
text_size: u16,
|
text_size: u16,
|
||||||
font: Self::Font,
|
font: Self::Font,
|
||||||
style: &<Self as Renderer>::Style,
|
style: &<Self as Renderer>::Style,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//! Build interactive programs using The Elm Architecture.
|
//! Build interactive programs using The Elm Architecture.
|
||||||
use crate::{Command, Element, Renderer};
|
use crate::{Clipboard, Command, Element, Renderer};
|
||||||
|
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
|
|
@ -11,7 +11,10 @@ pub trait Program: Sized {
|
||||||
type Renderer: Renderer;
|
type Renderer: Renderer;
|
||||||
|
|
||||||
/// The type of __messages__ your [`Program`] will produce.
|
/// The type of __messages__ your [`Program`] will produce.
|
||||||
type Message: std::fmt::Debug + Send;
|
type Message: std::fmt::Debug + Clone + Send;
|
||||||
|
|
||||||
|
/// The type of [`Clipboard`] your [`Program`] will use.
|
||||||
|
type Clipboard: Clipboard;
|
||||||
|
|
||||||
/// Handles a __message__ and updates the state of the [`Program`].
|
/// Handles a __message__ and updates the state of the [`Program`].
|
||||||
///
|
///
|
||||||
|
|
@ -21,7 +24,11 @@ pub trait Program: Sized {
|
||||||
///
|
///
|
||||||
/// Any [`Command`] returned will be executed immediately in the
|
/// Any [`Command`] returned will be executed immediately in the
|
||||||
/// background by shells.
|
/// background by shells.
|
||||||
fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Self::Message,
|
||||||
|
clipboard: &mut Self::Clipboard,
|
||||||
|
) -> Command<Self::Message>;
|
||||||
|
|
||||||
/// Returns the widgets to display in the [`Program`].
|
/// Returns the widgets to display in the [`Program`].
|
||||||
///
|
///
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue