Merge branch 'master' into feat/slider-orientation
This commit is contained in:
commit
2e6d90f141
174 changed files with 7170 additions and 1989 deletions
68
CHANGELOG.md
68
CHANGELOG.md
|
|
@ -6,6 +6,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.6.0] - 2022-12-07
|
||||||
|
### Added
|
||||||
|
- Support for non-uniform border radius for `Primitive::Quad`. [#1506](https://github.com/iced-rs/iced/pull/1506)
|
||||||
|
- Operation to query the current focused widget. [#1526](https://github.com/iced-rs/iced/pull/1526)
|
||||||
|
- Additional operations for `TextInput`. [#1529](https://github.com/iced-rs/iced/pull/1529)
|
||||||
|
- Styling support for `Svg`. [#1578](https://github.com/iced-rs/iced/pull/1578)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Triangle geometry using a solid color is now drawn in a single draw call. [#1538](https://github.com/iced-rs/iced/pull/1538)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Gradients for WebAssembly target. [#1524](https://github.com/iced-rs/iced/pull/1524)
|
||||||
|
- `Overlay` layout cache not being invalidated. [#1528](https://github.com/iced-rs/iced/pull/1528)
|
||||||
|
- Operations not working for `PaneGrid`. [#1533](https://github.com/iced-rs/iced/pull/1533)
|
||||||
|
- Mapped `widget::Operation` always returning `Outcome::None`. [#1536](https://github.com/iced-rs/iced/pull/1536)
|
||||||
|
- Padding of `TextInput` with `Length::Units` width. [#1539](https://github.com/iced-rs/iced/pull/1539)
|
||||||
|
- Clipping of `Image` and `Svg` widgets in `iced_glow`. [#1557](https://github.com/iced-rs/iced/pull/1557)
|
||||||
|
- Invalid links in documentation. [#1560](https://github.com/iced-rs/iced/pull/1560)
|
||||||
|
- `Custom` style of `PickList` widget. [#1570](https://github.com/iced-rs/iced/pull/1570)
|
||||||
|
- Scroller in `Scrollable` always being drawn. [#1574](https://github.com/iced-rs/iced/pull/1574)
|
||||||
|
|
||||||
|
Many thanks to...
|
||||||
|
|
||||||
|
- @bungoboingo
|
||||||
|
- @l1Dan
|
||||||
|
- @mmstick
|
||||||
|
- @mtkennerly
|
||||||
|
- @PolyMeilex
|
||||||
|
- @rksm
|
||||||
|
- @rs017991
|
||||||
|
- @tarkah
|
||||||
|
- @wash2
|
||||||
|
|
||||||
|
## [0.5.0] - 2022-11-10
|
||||||
|
### Added
|
||||||
|
- __[Stabilization of stateless widgets][stateless]__ (#1393)
|
||||||
|
The old widget API has been completely replaced by stateless widgets (introduced in #1284). Alongside the new API, there are a bunch of new helper functions and macros for easily describing view logic (like `row!` and `column!`).
|
||||||
|
|
||||||
|
- __[First-class theming][theming]__ (#1362)
|
||||||
|
A complete overhaul of our styling primitives, introducing a `Theme` as a first-class concept of the library.
|
||||||
|
|
||||||
|
- __[Widget operations][operations]__ (#1399)
|
||||||
|
An abstraction that can be used to traverse (and operate on) the widget tree of an application in order to query or update some widget state.
|
||||||
|
|
||||||
|
- __[`Lazy` widget][lazy]__ (#1400)
|
||||||
|
A widget that can call some view logic lazily only when some data has changed. Thanks to @nicksenger!
|
||||||
|
|
||||||
|
- __[Linear gradient support for `Canvas`][gradient]__ (#1448)
|
||||||
|
The `Canvas` widget can draw linear gradients now. Thanks to @bungoboingo!
|
||||||
|
|
||||||
|
- __[Touch support for `Canvas`][touch]__ (#1305)
|
||||||
|
The `Canvas` widget now supports touch events. Thanks to @artursapek!
|
||||||
|
|
||||||
|
- __[`Image` and `Svg` support for `iced_glow`][image]__ (#1485)
|
||||||
|
Our OpenGL renderer now is capable of rendering both the `Image` and `Svg` widgets. Thanks to @ids1024!
|
||||||
|
|
||||||
|
[stateless]: https://github.com/iced-rs/iced/pull/1393
|
||||||
|
[theming]: https://github.com/iced-rs/iced/pull/1362
|
||||||
|
[operations]: https://github.com/iced-rs/iced/pull/1399
|
||||||
|
[lazy]: https://github.com/iced-rs/iced/pull/1400
|
||||||
|
[gradient]: https://github.com/iced-rs/iced/pull/1448
|
||||||
|
[touch]: https://github.com/iced-rs/iced/pull/1305
|
||||||
|
[image]: https://github.com/iced-rs/iced/pull/1485
|
||||||
|
|
||||||
## [0.4.2] - 2022-05-03
|
## [0.4.2] - 2022-05-03
|
||||||
### Fixed
|
### Fixed
|
||||||
- `Padding` type not exposed in `iced`.
|
- `Padding` type not exposed in `iced`.
|
||||||
|
|
@ -257,7 +321,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/iced-rs/iced/compare/0.4.2...HEAD
|
[Unreleased]: https://github.com/iced-rs/iced/compare/0.6.0...HEAD
|
||||||
|
[0.6.0]: https://github.com/iced-rs/iced/compare/0.5.0...0.6.0
|
||||||
|
[0.5.0]: https://github.com/iced-rs/iced/compare/0.4.2...0.5.0
|
||||||
[0.4.2]: https://github.com/iced-rs/iced/compare/0.4.1...0.4.2
|
[0.4.2]: https://github.com/iced-rs/iced/compare/0.4.1...0.4.2
|
||||||
[0.4.1]: https://github.com/iced-rs/iced/compare/0.4.0...0.4.1
|
[0.4.1]: https://github.com/iced-rs/iced/compare/0.4.0...0.4.1
|
||||||
[0.4.0]: https://github.com/iced-rs/iced/compare/0.3.0...0.4.0
|
[0.4.0]: https://github.com/iced-rs/iced/compare/0.3.0...0.4.0
|
||||||
|
|
|
||||||
65
Cargo.toml
65
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced"
|
name = "iced"
|
||||||
version = "0.4.2"
|
version = "0.6.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A cross-platform GUI library inspired by Elm"
|
description = "A cross-platform GUI library inspired by Elm"
|
||||||
|
|
@ -10,14 +10,13 @@ documentation = "https://docs.rs/iced"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
||||||
categories = ["gui"]
|
categories = ["gui"]
|
||||||
resolver = "2"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wgpu"]
|
default = ["wgpu"]
|
||||||
# Enables the `Image` widget
|
# Enables the `Image` widget
|
||||||
image = ["iced_wgpu/image", "image_rs"]
|
image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"]
|
||||||
# Enables the `Svg` widget
|
# Enables the `Svg` widget
|
||||||
svg = ["iced_wgpu/svg"]
|
svg = ["iced_wgpu?/svg", "iced_glow?/svg"]
|
||||||
# Enables the `Canvas` widget
|
# Enables the `Canvas` widget
|
||||||
canvas = ["iced_graphics/canvas"]
|
canvas = ["iced_graphics/canvas"]
|
||||||
# Enables the `QRCode` widget
|
# Enables the `QRCode` widget
|
||||||
|
|
@ -25,11 +24,9 @@ qr_code = ["iced_graphics/qr_code"]
|
||||||
# Enables the `iced_wgpu` renderer
|
# Enables the `iced_wgpu` renderer
|
||||||
wgpu = ["iced_wgpu"]
|
wgpu = ["iced_wgpu"]
|
||||||
# Enables using system fonts
|
# Enables using system fonts
|
||||||
default_system_font = ["iced_wgpu/default_system_font"]
|
default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"]
|
||||||
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
|
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
|
||||||
glow = ["iced_glow", "iced_glutin"]
|
glow = ["iced_glow", "iced_glutin"]
|
||||||
# Enables using system fonts for `iced_glow`
|
|
||||||
glow_default_system_font = ["iced_glow/default_system_font"]
|
|
||||||
# Enables a debug view in native platforms (press F12)
|
# Enables a debug view in native platforms (press F12)
|
||||||
debug = ["iced_winit/debug"]
|
debug = ["iced_winit/debug"]
|
||||||
# Enables `tokio` as the `executor::Default` on native platforms
|
# Enables `tokio` as the `executor::Default` on native platforms
|
||||||
|
|
@ -58,61 +55,29 @@ members = [
|
||||||
"style",
|
"style",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
"examples/arc",
|
"examples/*",
|
||||||
"examples/bezier_tool",
|
|
||||||
"examples/clock",
|
|
||||||
"examples/color_palette",
|
|
||||||
"examples/component",
|
|
||||||
"examples/counter",
|
|
||||||
"examples/custom_widget",
|
|
||||||
"examples/download_progress",
|
|
||||||
"examples/events",
|
|
||||||
"examples/exit",
|
|
||||||
"examples/game_of_life",
|
|
||||||
"examples/geometry",
|
|
||||||
"examples/integration_opengl",
|
|
||||||
"examples/integration_wgpu",
|
|
||||||
"examples/multitouch",
|
|
||||||
"examples/pane_grid",
|
|
||||||
"examples/pick_list",
|
|
||||||
"examples/pokedex",
|
|
||||||
"examples/progress_bar",
|
|
||||||
"examples/qr_code",
|
|
||||||
"examples/scrollable",
|
|
||||||
"examples/sierpinski_triangle",
|
|
||||||
"examples/solar_system",
|
|
||||||
"examples/stopwatch",
|
|
||||||
"examples/styling",
|
|
||||||
"examples/svg",
|
|
||||||
"examples/system_information",
|
|
||||||
"examples/todos",
|
|
||||||
"examples/tooltip",
|
|
||||||
"examples/tour",
|
|
||||||
"examples/url_handler",
|
|
||||||
"examples/websocket",
|
|
||||||
"examples/slider",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_core = { version = "0.5", path = "core" }
|
iced_core = { version = "0.6", path = "core" }
|
||||||
iced_futures = { version = "0.4", path = "futures" }
|
iced_futures = { version = "0.5", path = "futures" }
|
||||||
iced_native = { version = "0.5", path = "native" }
|
iced_native = { version = "0.7", path = "native" }
|
||||||
iced_graphics = { version = "0.3", path = "graphics" }
|
iced_graphics = { version = "0.5", path = "graphics" }
|
||||||
iced_winit = { version = "0.4", path = "winit", features = ["application"] }
|
iced_winit = { version = "0.6", path = "winit", features = ["application"] }
|
||||||
iced_glutin = { version = "0.3", path = "glutin", optional = true }
|
iced_glutin = { version = "0.5", path = "glutin", optional = true }
|
||||||
iced_glow = { version = "0.3", path = "glow", optional = true }
|
iced_glow = { version = "0.5", path = "glow", optional = true }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
||||||
[dependencies.image_rs]
|
[dependencies.image_rs]
|
||||||
version = "0.23"
|
version = "0.24"
|
||||||
package = "image"
|
package = "image"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
iced_wgpu = { version = "0.5", path = "wgpu", optional = true }
|
iced_wgpu = { version = "0.7", path = "wgpu", optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
iced_wgpu = { version = "0.5", path = "wgpu", features = ["webgl"], optional = true }
|
iced_wgpu = { version = "0.7", path = "wgpu", features = ["webgl"], optional = true }
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,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.4"
|
iced = "0.6"
|
||||||
```
|
```
|
||||||
|
|
||||||
If your project is using a Rust edition older than 2021, then you will need to
|
If your project is using a Rust edition older than 2021, then you will need to
|
||||||
|
|
@ -215,7 +215,7 @@ cargo run --features iced/glow --package game_of_life
|
||||||
and then use it in your project with
|
and then use it in your project with
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
iced = { version = "0.4", default-features = false, features = ["glow"] }
|
iced = { version = "0.6", default-features = false, features = ["glow"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
|
__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_core"
|
name = "iced_core"
|
||||||
version = "0.5.0"
|
version = "0.6.2"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "The essential concepts of Iced"
|
description = "The essential concepts of Iced"
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,17 @@ impl Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the [`Color`] into its RGBA8 equivalent.
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_rgba8(self) -> [u8; 4] {
|
||||||
|
[
|
||||||
|
(self.r * 255.0).round() as u8,
|
||||||
|
(self.g * 255.0).round() as u8,
|
||||||
|
(self.b * 255.0).round() as u8,
|
||||||
|
(self.a * 255.0).round() as u8,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts the [`Color`] into its linear values.
|
/// Converts the [`Color`] into its linear values.
|
||||||
pub fn into_linear(self) -> [f32; 4] {
|
pub fn into_linear(self) -> [f32; 4] {
|
||||||
// As described in:
|
// As described in:
|
||||||
|
|
@ -148,24 +159,26 @@ impl From<[f32; 4]> for Color {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! color {
|
macro_rules! color {
|
||||||
($r:expr, $g:expr, $b:expr) => {
|
($r:expr, $g:expr, $b:expr) => {
|
||||||
Color::from_rgb8($r, $g, $b)
|
$crate::Color::from_rgb8($r, $g, $b)
|
||||||
};
|
};
|
||||||
($r:expr, $g:expr, $b:expr, $a:expr) => {
|
($r:expr, $g:expr, $b:expr, $a:expr) => {
|
||||||
Color::from_rgba8($r, $g, $b, $a)
|
$crate::Color::from_rgba8($r, $g, $b, $a)
|
||||||
};
|
};
|
||||||
($hex:expr) => {{
|
($hex:expr) => {{
|
||||||
let hex = $hex as u32;
|
let hex = $hex as u32;
|
||||||
let r = (hex & 0xff0000) >> 16;
|
let r = (hex & 0xff0000) >> 16;
|
||||||
let g = (hex & 0xff00) >> 8;
|
let g = (hex & 0xff00) >> 8;
|
||||||
let b = (hex & 0xff);
|
let b = (hex & 0xff);
|
||||||
Color::from_rgb8(r as u8, g as u8, b as u8)
|
|
||||||
|
$crate::Color::from_rgb8(r as u8, g as u8, b as u8)
|
||||||
}};
|
}};
|
||||||
($hex:expr, $a:expr) => {{
|
($hex:expr, $a:expr) => {{
|
||||||
let hex = $hex as u32;
|
let hex = $hex as u32;
|
||||||
let r = (hex & 0xff0000) >> 16;
|
let r = (hex & 0xff0000) >> 16;
|
||||||
let g = (hex & 0xff00) >> 8;
|
let g = (hex & 0xff00) >> 8;
|
||||||
let b = (hex & 0xff);
|
let b = (hex & 0xff);
|
||||||
Color::from_rgba8(r as u8, g as u8, b as u8, $a)
|
|
||||||
|
$crate::Color::from_rgba8(r as u8, g as u8, b as u8, $a)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
//! 
|
//! 
|
||||||
//!
|
//!
|
||||||
//! [Iced]: https://github.com/iced-rs/iced
|
//! [Iced]: https://github.com/iced-rs/iced
|
||||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native
|
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native
|
||||||
//! [`iced_web`]: https://github.com/iced-rs/iced_web
|
//! [`iced_web`]: https://github.com/iced-rs/iced_web
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::Size;
|
||||||
|
|
||||||
/// An amount of space to pad for each side of a box
|
/// An amount of space to pad for each side of a box
|
||||||
///
|
///
|
||||||
/// You can leverage the `From` trait to build [`Padding`] conveniently:
|
/// You can leverage the `From` trait to build [`Padding`] conveniently:
|
||||||
|
|
@ -71,9 +73,21 @@ impl Padding {
|
||||||
pub fn horizontal(self) -> u16 {
|
pub fn horizontal(self) -> u16 {
|
||||||
self.left + self.right
|
self.left + self.right
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fits the [`Padding`] between the provided `inner` and `outer` [`Size`].
|
||||||
|
pub fn fit(self, inner: Size, outer: Size) -> Self {
|
||||||
|
let available = (outer - inner).max(Size::ZERO);
|
||||||
|
|
||||||
|
Padding {
|
||||||
|
top: self.top.min((available.height as u16) / 2),
|
||||||
|
right: self.right.min((available.width as u16) / 2),
|
||||||
|
bottom: self.bottom.min((available.height as u16) / 2),
|
||||||
|
left: self.left.min((available.width as u16) / 2),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<u16> for Padding {
|
impl From<u16> for Padding {
|
||||||
fn from(p: u16) -> Self {
|
fn from(p: u16) -> Self {
|
||||||
Padding {
|
Padding {
|
||||||
top: p,
|
top: p,
|
||||||
|
|
@ -84,7 +98,7 @@ impl std::convert::From<u16> for Padding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<[u16; 2]> for Padding {
|
impl From<[u16; 2]> for Padding {
|
||||||
fn from(p: [u16; 2]) -> Self {
|
fn from(p: [u16; 2]) -> Self {
|
||||||
Padding {
|
Padding {
|
||||||
top: p[0],
|
top: p[0],
|
||||||
|
|
@ -95,7 +109,7 @@ impl std::convert::From<[u16; 2]> for Padding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<[u16; 4]> for Padding {
|
impl From<[u16; 4]> for Padding {
|
||||||
fn from(p: [u16; 4]) -> Self {
|
fn from(p: [u16; 4]) -> Self {
|
||||||
Padding {
|
Padding {
|
||||||
top: p[0],
|
top: p[0],
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,22 @@ impl Size {
|
||||||
height: self.height + padding.vertical() as f32,
|
height: self.height + padding.vertical() as f32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum of each component of this size and another
|
||||||
|
pub fn min(self, other: Self) -> Self {
|
||||||
|
Size {
|
||||||
|
width: self.width.min(other.width),
|
||||||
|
height: self.height.min(other.height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the maximum of each component of this size and another
|
||||||
|
pub fn max(self, other: Self) -> Self {
|
||||||
|
Size {
|
||||||
|
width: self.width.max(other.width),
|
||||||
|
height: self.height.max(other.height),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[f32; 2]> for Size {
|
impl From<[f32; 2]> for Size {
|
||||||
|
|
@ -68,3 +84,14 @@ impl From<Size> for Vector<f32> {
|
||||||
Vector::new(size.width, size.height)
|
Vector::new(size.width, size.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub for Size {
|
||||||
|
type Output = Size;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
Size {
|
||||||
|
width: self.width - rhs.width,
|
||||||
|
height: self.height - rhs.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::{f32::consts::PI, time::Instant};
|
||||||
|
|
||||||
use iced::executor;
|
use iced::executor;
|
||||||
use iced::widget::canvas::{
|
use iced::widget::canvas::{
|
||||||
self, Cache, Canvas, Cursor, Geometry, Path, Stroke,
|
self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
|
||||||
};
|
};
|
||||||
use iced::{
|
use iced::{
|
||||||
Application, Command, Element, Length, Point, Rectangle, Settings,
|
Application, Command, Element, Length, Point, Rectangle, Settings,
|
||||||
|
|
@ -52,11 +52,6 @@ impl Application for Arc {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
|
||||||
iced::time::every(std::time::Duration::from_millis(10))
|
|
||||||
.map(|_| Message::Tick)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
Canvas::new(self)
|
Canvas::new(self)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -67,6 +62,11 @@ impl Application for Arc {
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
Theme::Dark
|
Theme::Dark
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
iced::time::every(std::time::Duration::from_millis(10))
|
||||||
|
.map(|_| Message::Tick)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> canvas::Program<Message> for Arc {
|
impl<Message> canvas::Program<Message> for Arc {
|
||||||
|
|
@ -114,7 +114,7 @@ impl<Message> canvas::Program<Message> for Arc {
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&path,
|
&path,
|
||||||
Stroke {
|
Stroke {
|
||||||
color: palette.text,
|
style: stroke::Style::Solid(palette.text),
|
||||||
width: 10.0,
|
width: 10.0,
|
||||||
..Stroke::default()
|
..Stroke::default()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
10
examples/cached/Cargo.toml
Normal file
10
examples/cached/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "cached"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nick Senger <dev@nsenger.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../..", features = ["debug"] }
|
||||||
|
iced_lazy = { path = "../../lazy" }
|
||||||
139
examples/cached/src/main.rs
Normal file
139
examples/cached/src/main.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
use iced::theme;
|
||||||
|
use iced::widget::{
|
||||||
|
button, column, horizontal_space, row, scrollable, text, text_input,
|
||||||
|
};
|
||||||
|
use iced::{Element, Length, Sandbox, Settings};
|
||||||
|
use iced_lazy::lazy;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
App::run(Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
options: HashSet<String>,
|
||||||
|
input: String,
|
||||||
|
order: Order,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for App {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"]
|
||||||
|
.into_iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect(),
|
||||||
|
input: Default::default(),
|
||||||
|
order: Order::Ascending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
InputChanged(String),
|
||||||
|
ToggleOrder,
|
||||||
|
DeleteOption(String),
|
||||||
|
AddOption(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sandbox for App {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Cached - Iced")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) {
|
||||||
|
match message {
|
||||||
|
Message::InputChanged(input) => {
|
||||||
|
self.input = input;
|
||||||
|
}
|
||||||
|
Message::ToggleOrder => {
|
||||||
|
self.order = match self.order {
|
||||||
|
Order::Ascending => Order::Descending,
|
||||||
|
Order::Descending => Order::Ascending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::AddOption(option) => {
|
||||||
|
self.options.insert(option);
|
||||||
|
self.input.clear();
|
||||||
|
}
|
||||||
|
Message::DeleteOption(option) => {
|
||||||
|
self.options.remove(&option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
let options = lazy((&self.order, self.options.len()), || {
|
||||||
|
let mut options: Vec<_> = self.options.iter().collect();
|
||||||
|
|
||||||
|
options.sort_by(|a, b| match self.order {
|
||||||
|
Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()),
|
||||||
|
Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()),
|
||||||
|
});
|
||||||
|
|
||||||
|
column(
|
||||||
|
options
|
||||||
|
.into_iter()
|
||||||
|
.map(|option| {
|
||||||
|
row![
|
||||||
|
text(option),
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
button("Delete")
|
||||||
|
.on_press(Message::DeleteOption(
|
||||||
|
option.to_string(),
|
||||||
|
),)
|
||||||
|
.style(theme::Button::Destructive)
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.spacing(10)
|
||||||
|
});
|
||||||
|
|
||||||
|
column![
|
||||||
|
scrollable(options).height(Length::Fill),
|
||||||
|
row![
|
||||||
|
text_input(
|
||||||
|
"Add a new option",
|
||||||
|
&self.input,
|
||||||
|
Message::InputChanged,
|
||||||
|
)
|
||||||
|
.on_submit(Message::AddOption(self.input.clone())),
|
||||||
|
button(text(format!("Toggle Order ({})", self.order)))
|
||||||
|
.on_press(Message::ToggleOrder)
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
]
|
||||||
|
.spacing(20)
|
||||||
|
.padding(20)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
enum Order {
|
||||||
|
Ascending,
|
||||||
|
Descending,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Order {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::Ascending => "Ascending",
|
||||||
|
Self::Descending => "Descending",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use iced::executor;
|
use iced::executor;
|
||||||
use iced::widget::canvas::{Cache, Cursor, Geometry, LineCap, Path, Stroke};
|
use iced::widget::canvas::{
|
||||||
|
stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke,
|
||||||
|
};
|
||||||
use iced::widget::{canvas, container};
|
use iced::widget::{canvas, container};
|
||||||
use iced::{
|
use iced::{
|
||||||
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
|
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
|
||||||
|
|
@ -24,9 +26,9 @@ enum Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Clock {
|
impl Application for Clock {
|
||||||
|
type Executor = executor::Default;
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
type Theme = Theme;
|
type Theme = Theme;
|
||||||
type Executor = executor::Default;
|
|
||||||
type Flags = ();
|
type Flags = ();
|
||||||
|
|
||||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||||
|
|
@ -59,15 +61,6 @@ impl Application for Clock {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
|
||||||
iced::time::every(std::time::Duration::from_millis(500)).map(|_| {
|
|
||||||
Message::Tick(
|
|
||||||
time::OffsetDateTime::now_local()
|
|
||||||
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
let canvas = canvas(self as &Self)
|
let canvas = canvas(self as &Self)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -79,6 +72,15 @@ impl Application for Clock {
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
iced::time::every(std::time::Duration::from_millis(500)).map(|_| {
|
||||||
|
Message::Tick(
|
||||||
|
time::OffsetDateTime::now_local()
|
||||||
|
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> canvas::Program<Message> for Clock {
|
impl<Message> canvas::Program<Message> for Clock {
|
||||||
|
|
@ -104,33 +106,41 @@ impl<Message> canvas::Program<Message> for Clock {
|
||||||
let long_hand =
|
let long_hand =
|
||||||
Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius));
|
Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius));
|
||||||
|
|
||||||
let thin_stroke = Stroke {
|
let width = radius / 100.0;
|
||||||
width: radius / 100.0,
|
|
||||||
color: Color::WHITE,
|
let thin_stroke = || -> Stroke {
|
||||||
|
Stroke {
|
||||||
|
width,
|
||||||
|
style: stroke::Style::Solid(Color::WHITE),
|
||||||
line_cap: LineCap::Round,
|
line_cap: LineCap::Round,
|
||||||
..Stroke::default()
|
..Stroke::default()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let wide_stroke = Stroke {
|
let wide_stroke = || -> Stroke {
|
||||||
width: thin_stroke.width * 3.0,
|
Stroke {
|
||||||
..thin_stroke
|
width: width * 3.0,
|
||||||
|
style: stroke::Style::Solid(Color::WHITE),
|
||||||
|
line_cap: LineCap::Round,
|
||||||
|
..Stroke::default()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
frame.translate(Vector::new(center.x, center.y));
|
frame.translate(Vector::new(center.x, center.y));
|
||||||
|
|
||||||
frame.with_save(|frame| {
|
frame.with_save(|frame| {
|
||||||
frame.rotate(hand_rotation(self.now.hour(), 12));
|
frame.rotate(hand_rotation(self.now.hour(), 12));
|
||||||
frame.stroke(&short_hand, wide_stroke);
|
frame.stroke(&short_hand, wide_stroke());
|
||||||
});
|
});
|
||||||
|
|
||||||
frame.with_save(|frame| {
|
frame.with_save(|frame| {
|
||||||
frame.rotate(hand_rotation(self.now.minute(), 60));
|
frame.rotate(hand_rotation(self.now.minute(), 60));
|
||||||
frame.stroke(&long_hand, wide_stroke);
|
frame.stroke(&long_hand, wide_stroke());
|
||||||
});
|
});
|
||||||
|
|
||||||
frame.with_save(|frame| {
|
frame.with_save(|frame| {
|
||||||
frame.rotate(hand_rotation(self.now.second(), 60));
|
frame.rotate(hand_rotation(self.now.second(), 60));
|
||||||
frame.stroke(&long_hand, thin_stroke);
|
frame.stroke(&long_hand, thin_stroke());
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
10
examples/custom_quad/Cargo.toml
Normal file
10
examples/custom_quad/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "custom_quad"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Robert Krahn"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../.." }
|
||||||
|
iced_native = { path = "../../native" }
|
||||||
160
examples/custom_quad/src/main.rs
Normal file
160
examples/custom_quad/src/main.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
//! This example showcases a drawing a quad.
|
||||||
|
mod quad {
|
||||||
|
use iced_native::layout::{self, Layout};
|
||||||
|
use iced_native::renderer;
|
||||||
|
use iced_native::widget::{self, Widget};
|
||||||
|
use iced_native::{Color, Element, Length, Point, Rectangle, Size};
|
||||||
|
|
||||||
|
pub struct CustomQuad {
|
||||||
|
size: f32,
|
||||||
|
radius: [f32; 4],
|
||||||
|
border_width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomQuad {
|
||||||
|
pub fn new(size: f32, radius: [f32; 4], border_width: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
size,
|
||||||
|
radius,
|
||||||
|
border_width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message, Renderer> Widget<Message, Renderer> for CustomQuad
|
||||||
|
where
|
||||||
|
Renderer: renderer::Renderer,
|
||||||
|
{
|
||||||
|
fn width(&self) -> Length {
|
||||||
|
Length::Shrink
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> Length {
|
||||||
|
Length::Shrink
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
_limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
layout::Node::new(Size::new(self.size, self.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_state: &widget::Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
_theme: &Renderer::Theme,
|
||||||
|
_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
_cursor_position: Point,
|
||||||
|
_viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: layout.bounds(),
|
||||||
|
border_radius: self.radius.into(),
|
||||||
|
border_width: self.border_width,
|
||||||
|
border_color: Color::from_rgb(1.0, 0.0, 0.0),
|
||||||
|
},
|
||||||
|
Color::BLACK,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> From<CustomQuad> for Element<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: renderer::Renderer,
|
||||||
|
{
|
||||||
|
fn from(circle: CustomQuad) -> Self {
|
||||||
|
Self::new(circle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use iced::widget::{column, container, slider, text};
|
||||||
|
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
Example::run(Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Example {
|
||||||
|
radius: [f32; 4],
|
||||||
|
border_width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
enum Message {
|
||||||
|
RadiusTopLeftChanged(f32),
|
||||||
|
RadiusTopRightChanged(f32),
|
||||||
|
RadiusBottomRightChanged(f32),
|
||||||
|
RadiusBottomLeftChanged(f32),
|
||||||
|
BorderWidthChanged(f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sandbox for Example {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
radius: [50.0; 4],
|
||||||
|
border_width: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Custom widget - Iced")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) {
|
||||||
|
let [tl, tr, br, bl] = self.radius;
|
||||||
|
match message {
|
||||||
|
Message::RadiusTopLeftChanged(radius) => {
|
||||||
|
self.radius = [radius, tr, br, bl];
|
||||||
|
}
|
||||||
|
Message::RadiusTopRightChanged(radius) => {
|
||||||
|
self.radius = [tl, radius, br, bl];
|
||||||
|
}
|
||||||
|
Message::RadiusBottomRightChanged(radius) => {
|
||||||
|
self.radius = [tl, tr, radius, bl];
|
||||||
|
}
|
||||||
|
Message::RadiusBottomLeftChanged(radius) => {
|
||||||
|
self.radius = [tl, tr, br, radius];
|
||||||
|
}
|
||||||
|
Message::BorderWidthChanged(width) => {
|
||||||
|
self.border_width = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
let [tl, tr, br, bl] = self.radius;
|
||||||
|
|
||||||
|
let content = column![
|
||||||
|
quad::CustomQuad::new(200.0, self.radius, self.border_width),
|
||||||
|
text(format!("Radius: {tl:.2}/{tr:.2}/{br:.2}/{bl:.2}")),
|
||||||
|
slider(1.0..=100.0, tl, Message::RadiusTopLeftChanged).step(0.01),
|
||||||
|
slider(1.0..=100.0, tr, Message::RadiusTopRightChanged).step(0.01),
|
||||||
|
slider(1.0..=100.0, br, Message::RadiusBottomRightChanged)
|
||||||
|
.step(0.01),
|
||||||
|
slider(1.0..=100.0, bl, Message::RadiusBottomLeftChanged)
|
||||||
|
.step(0.01),
|
||||||
|
slider(1.0..=10.0, self.border_width, Message::BorderWidthChanged)
|
||||||
|
.step(0.01),
|
||||||
|
]
|
||||||
|
.padding(20)
|
||||||
|
.spacing(20)
|
||||||
|
.max_width(500)
|
||||||
|
.align_items(Alignment::Center);
|
||||||
|
|
||||||
|
container(content)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -61,7 +61,7 @@ mod circle {
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: layout.bounds(),
|
bounds: layout.bounds(),
|
||||||
border_radius: self.radius,
|
border_radius: self.radius.into(),
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -11,22 +11,18 @@ mod rainbow {
|
||||||
// if you wish to, by creating your own `Renderer` trait, which could be
|
// if you wish to, by creating your own `Renderer` trait, which could be
|
||||||
// implemented by `iced_wgpu` and other renderers.
|
// implemented by `iced_wgpu` and other renderers.
|
||||||
use iced_graphics::renderer::{self, Renderer};
|
use iced_graphics::renderer::{self, Renderer};
|
||||||
|
use iced_graphics::triangle::ColoredVertex2D;
|
||||||
use iced_graphics::{Backend, Primitive};
|
use iced_graphics::{Backend, Primitive};
|
||||||
|
|
||||||
|
use iced_native::layout;
|
||||||
use iced_native::widget::{self, Widget};
|
use iced_native::widget::{self, Widget};
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
layout, Element, Layout, Length, Point, Rectangle, Size, Vector,
|
Element, Layout, Length, Point, Rectangle, Size, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct Rainbow;
|
pub struct Rainbow;
|
||||||
|
|
||||||
impl Rainbow {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rainbow() -> Rainbow {
|
pub fn rainbow() -> Rainbow {
|
||||||
Rainbow
|
Rainbow
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +59,7 @@ mod rainbow {
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
use iced_graphics::triangle::{Mesh2D, Vertex2D};
|
use iced_graphics::triangle::Mesh2D;
|
||||||
use iced_native::Renderer as _;
|
use iced_native::Renderer as _;
|
||||||
|
|
||||||
let b = layout.bounds();
|
let b = layout.bounds();
|
||||||
|
|
@ -95,43 +91,43 @@ mod rainbow {
|
||||||
let posn_bl = [0.0, b.height];
|
let posn_bl = [0.0, b.height];
|
||||||
let posn_l = [0.0, b.height / 2.0];
|
let posn_l = [0.0, b.height / 2.0];
|
||||||
|
|
||||||
let mesh = Primitive::Mesh2D {
|
let mesh = Primitive::SolidMesh {
|
||||||
size: b.size(),
|
size: b.size(),
|
||||||
buffers: Mesh2D {
|
buffers: Mesh2D {
|
||||||
vertices: vec![
|
vertices: vec![
|
||||||
Vertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_center,
|
position: posn_center,
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
},
|
},
|
||||||
Vertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_tl,
|
position: posn_tl,
|
||||||
color: color_r,
|
color: color_r,
|
||||||
},
|
},
|
||||||
Vertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_t,
|
position: posn_t,
|
||||||
color: color_o,
|
color: color_o,
|
||||||
},
|
},
|
||||||
Vertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_tr,
|
position: posn_tr,
|
||||||
color: color_y,
|
color: color_y,
|
||||||
},
|
},
|
||||||
Vertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_r,
|
position: posn_r,
|
||||||
color: color_g,
|
color: color_g,
|
||||||
},
|
},
|
||||||
Vertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_br,
|
position: posn_br,
|
||||||
color: color_gb,
|
color: color_gb,
|
||||||
},
|
},
|
||||||
Vertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_b,
|
position: posn_b,
|
||||||
color: color_b,
|
color: color_b,
|
||||||
},
|
},
|
||||||
Vertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_bl,
|
position: posn_bl,
|
||||||
color: color_i,
|
color: color_i,
|
||||||
},
|
},
|
||||||
Vertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_l,
|
position: posn_l,
|
||||||
color: color_v,
|
color: color_v,
|
||||||
},
|
},
|
||||||
|
|
@ -166,7 +162,7 @@ mod rainbow {
|
||||||
}
|
}
|
||||||
|
|
||||||
use iced::widget::{column, container, scrollable};
|
use iced::widget::{column, container, scrollable};
|
||||||
use iced::{Alignment, Element, Length, Sandbox, Settings};
|
use iced::{Element, Length, Sandbox, Settings};
|
||||||
use rainbow::rainbow;
|
use rainbow::rainbow;
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
|
|
@ -179,7 +175,7 @@ impl Sandbox for Example {
|
||||||
type Message = ();
|
type Message = ();
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Example
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
|
|
@ -202,8 +198,7 @@ impl Sandbox for Example {
|
||||||
]
|
]
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.max_width(500)
|
.max_width(500);
|
||||||
.align_items(Alignment::Start);
|
|
||||||
|
|
||||||
let scrollable =
|
let scrollable =
|
||||||
scrollable(container(content).width(Length::Fill).center_x());
|
scrollable(container(content).width(Length::Fill).center_x());
|
||||||
|
|
|
||||||
10
examples/lazy/Cargo.toml
Normal file
10
examples/lazy/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "lazy"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nick Senger <dev@nsenger.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../..", features = ["debug"] }
|
||||||
|
iced_lazy = { path = "../../lazy" }
|
||||||
139
examples/lazy/src/main.rs
Normal file
139
examples/lazy/src/main.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
use iced::theme;
|
||||||
|
use iced::widget::{
|
||||||
|
button, column, horizontal_space, row, scrollable, text, text_input,
|
||||||
|
};
|
||||||
|
use iced::{Element, Length, Sandbox, Settings};
|
||||||
|
use iced_lazy::lazy;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
App::run(Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
options: HashSet<String>,
|
||||||
|
input: String,
|
||||||
|
order: Order,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for App {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"]
|
||||||
|
.into_iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect(),
|
||||||
|
input: Default::default(),
|
||||||
|
order: Order::Ascending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
InputChanged(String),
|
||||||
|
ToggleOrder,
|
||||||
|
DeleteOption(String),
|
||||||
|
AddOption(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sandbox for App {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Cached - Iced")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) {
|
||||||
|
match message {
|
||||||
|
Message::InputChanged(input) => {
|
||||||
|
self.input = input;
|
||||||
|
}
|
||||||
|
Message::ToggleOrder => {
|
||||||
|
self.order = match self.order {
|
||||||
|
Order::Ascending => Order::Descending,
|
||||||
|
Order::Descending => Order::Ascending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::AddOption(option) => {
|
||||||
|
self.options.insert(option);
|
||||||
|
self.input.clear();
|
||||||
|
}
|
||||||
|
Message::DeleteOption(option) => {
|
||||||
|
self.options.remove(&option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
let options = lazy((&self.order, self.options.len()), || {
|
||||||
|
let mut options: Vec<_> = self.options.iter().collect();
|
||||||
|
|
||||||
|
options.sort_by(|a, b| match self.order {
|
||||||
|
Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()),
|
||||||
|
Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()),
|
||||||
|
});
|
||||||
|
|
||||||
|
column(
|
||||||
|
options
|
||||||
|
.into_iter()
|
||||||
|
.map(|option| {
|
||||||
|
row![
|
||||||
|
text(option),
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
button("Delete")
|
||||||
|
.on_press(Message::DeleteOption(
|
||||||
|
option.to_string(),
|
||||||
|
),)
|
||||||
|
.style(theme::Button::Destructive)
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.spacing(10)
|
||||||
|
});
|
||||||
|
|
||||||
|
column![
|
||||||
|
scrollable(options).height(Length::Fill),
|
||||||
|
row![
|
||||||
|
text_input(
|
||||||
|
"Add a new option",
|
||||||
|
&self.input,
|
||||||
|
Message::InputChanged,
|
||||||
|
)
|
||||||
|
.on_submit(Message::AddOption(self.input.clone())),
|
||||||
|
button(text(format!("Toggle Order ({})", self.order)))
|
||||||
|
.on_press(Message::ToggleOrder)
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
]
|
||||||
|
.spacing(20)
|
||||||
|
.padding(20)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
enum Order {
|
||||||
|
Ascending,
|
||||||
|
Descending,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Order {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::Ascending => "Ascending",
|
||||||
|
Self::Descending => "Descending",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
10
examples/modal/Cargo.toml
Normal file
10
examples/modal/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "modal"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["tarkah <admin@tarkah.dev>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../..", features = [] }
|
||||||
|
iced_native = { path = "../../native" }
|
||||||
475
examples/modal/src/main.rs
Normal file
475
examples/modal/src/main.rs
Normal file
|
|
@ -0,0 +1,475 @@
|
||||||
|
use iced::widget::{
|
||||||
|
self, button, column, container, horizontal_space, row, text, text_input,
|
||||||
|
};
|
||||||
|
use iced::{
|
||||||
|
executor, keyboard, subscription, theme, Alignment, Application, Command,
|
||||||
|
Element, Event, Length, Settings, Subscription,
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::modal::Modal;
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
App::run(Settings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct App {
|
||||||
|
show_modal: bool,
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {
|
||||||
|
ShowModal,
|
||||||
|
HideModal,
|
||||||
|
Email(String),
|
||||||
|
Password(String),
|
||||||
|
Submit,
|
||||||
|
Event(Event),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for App {
|
||||||
|
type Executor = executor::Default;
|
||||||
|
type Message = Message;
|
||||||
|
type Theme = iced::Theme;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||||
|
(App::default(), Command::none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Modal - Iced")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
|
subscription::events().map(Message::Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
|
match message {
|
||||||
|
Message::ShowModal => {
|
||||||
|
self.show_modal = true;
|
||||||
|
widget::focus_next()
|
||||||
|
}
|
||||||
|
Message::HideModal => {
|
||||||
|
self.hide_modal();
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Email(email) => {
|
||||||
|
self.email = email;
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Password(password) => {
|
||||||
|
self.password = password;
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Submit => {
|
||||||
|
if !self.email.is_empty() && !self.password.is_empty() {
|
||||||
|
self.hide_modal();
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
Message::Event(event) => match event {
|
||||||
|
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
|
key_code: keyboard::KeyCode::Tab,
|
||||||
|
modifiers,
|
||||||
|
}) => {
|
||||||
|
if modifiers.shift() {
|
||||||
|
widget::focus_previous()
|
||||||
|
} else {
|
||||||
|
widget::focus_next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
|
key_code: keyboard::KeyCode::Escape,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
self.hide_modal();
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
_ => Command::none(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
let content = container(
|
||||||
|
column![
|
||||||
|
row![
|
||||||
|
text("Top Left"),
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
text("Top Right")
|
||||||
|
]
|
||||||
|
.align_items(Alignment::Start)
|
||||||
|
.height(Length::Fill),
|
||||||
|
container(
|
||||||
|
button(text("Show Modal")).on_press(Message::ShowModal)
|
||||||
|
)
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill),
|
||||||
|
row![
|
||||||
|
text("Bottom Left"),
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
text("Bottom Right")
|
||||||
|
]
|
||||||
|
.align_items(Alignment::End)
|
||||||
|
.height(Length::Fill),
|
||||||
|
]
|
||||||
|
.height(Length::Fill),
|
||||||
|
)
|
||||||
|
.padding(10)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill);
|
||||||
|
|
||||||
|
if self.show_modal {
|
||||||
|
let modal = container(
|
||||||
|
column![
|
||||||
|
text("Sign Up").size(24),
|
||||||
|
column![
|
||||||
|
column![
|
||||||
|
text("Email").size(12),
|
||||||
|
text_input(
|
||||||
|
"abc@123.com",
|
||||||
|
&self.email,
|
||||||
|
Message::Email
|
||||||
|
)
|
||||||
|
.on_submit(Message::Submit)
|
||||||
|
.padding(5),
|
||||||
|
]
|
||||||
|
.spacing(5),
|
||||||
|
column![
|
||||||
|
text("Password").size(12),
|
||||||
|
text_input("", &self.password, Message::Password)
|
||||||
|
.on_submit(Message::Submit)
|
||||||
|
.password()
|
||||||
|
.padding(5),
|
||||||
|
]
|
||||||
|
.spacing(5),
|
||||||
|
button(text("Submit")).on_press(Message::HideModal),
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
]
|
||||||
|
.spacing(20),
|
||||||
|
)
|
||||||
|
.width(Length::Units(300))
|
||||||
|
.padding(10)
|
||||||
|
.style(theme::Container::Box);
|
||||||
|
|
||||||
|
Modal::new(content, modal)
|
||||||
|
.on_blur(Message::HideModal)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
content.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn hide_modal(&mut self) {
|
||||||
|
self.show_modal = false;
|
||||||
|
self.email.clear();
|
||||||
|
self.password.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod modal {
|
||||||
|
use iced_native::alignment::Alignment;
|
||||||
|
use iced_native::widget::{self, Tree};
|
||||||
|
use iced_native::{
|
||||||
|
event, layout, mouse, overlay, renderer, Clipboard, Color, Element,
|
||||||
|
Event, Layout, Length, Point, Rectangle, Shell, Size, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A widget that centers a modal element over some base element
|
||||||
|
pub struct Modal<'a, Message, Renderer> {
|
||||||
|
base: Element<'a, Message, Renderer>,
|
||||||
|
modal: Element<'a, Message, Renderer>,
|
||||||
|
on_blur: Option<Message>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
|
||||||
|
/// Returns a new [`Modal`]
|
||||||
|
pub fn new(
|
||||||
|
base: impl Into<Element<'a, Message, Renderer>>,
|
||||||
|
modal: impl Into<Element<'a, Message, Renderer>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
base: base.into(),
|
||||||
|
modal: modal.into(),
|
||||||
|
on_blur: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the message that will be produces when the background
|
||||||
|
/// of the [`Modal`] is pressed
|
||||||
|
pub fn on_blur(self, on_blur: Message) -> Self {
|
||||||
|
Self {
|
||||||
|
on_blur: Some(on_blur),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
|
for Modal<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: iced_native::Renderer,
|
||||||
|
Message: Clone,
|
||||||
|
{
|
||||||
|
fn children(&self) -> Vec<Tree> {
|
||||||
|
vec![Tree::new(&self.base), Tree::new(&self.modal)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&self, tree: &mut Tree) {
|
||||||
|
tree.diff_children(&[&self.base, &self.modal]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> Length {
|
||||||
|
self.base.as_widget().width()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> Length {
|
||||||
|
self.base.as_widget().height()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
renderer: &Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
self.base.as_widget().layout(renderer, limits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
state: &mut Tree,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
renderer: &Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) -> event::Status {
|
||||||
|
self.base.as_widget_mut().on_event(
|
||||||
|
&mut state.children[0],
|
||||||
|
event,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
state: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &<Renderer as iced_native::Renderer>::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
self.base.as_widget().draw(
|
||||||
|
&state.children[0],
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlay<'b>(
|
||||||
|
&'b mut self,
|
||||||
|
state: &'b mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
_renderer: &Renderer,
|
||||||
|
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||||
|
Some(overlay::Element::new(
|
||||||
|
layout.position(),
|
||||||
|
Box::new(Overlay {
|
||||||
|
content: &mut self.modal,
|
||||||
|
tree: &mut state.children[1],
|
||||||
|
size: layout.bounds().size(),
|
||||||
|
on_blur: self.on_blur.clone(),
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
state: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.base.as_widget().mouse_interaction(
|
||||||
|
&state.children[0],
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&self,
|
||||||
|
state: &mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
operation: &mut dyn widget::Operation<Message>,
|
||||||
|
) {
|
||||||
|
self.base.as_widget().operate(
|
||||||
|
&mut state.children[0],
|
||||||
|
layout,
|
||||||
|
operation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Overlay<'a, 'b, Message, Renderer> {
|
||||||
|
content: &'b mut Element<'a, Message, Renderer>,
|
||||||
|
tree: &'b mut Tree,
|
||||||
|
size: Size,
|
||||||
|
on_blur: Option<Message>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
|
||||||
|
for Overlay<'a, 'b, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: iced_native::Renderer,
|
||||||
|
Message: Clone,
|
||||||
|
{
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
renderer: &Renderer,
|
||||||
|
_bounds: Size,
|
||||||
|
position: Point,
|
||||||
|
) -> layout::Node {
|
||||||
|
let limits = layout::Limits::new(Size::ZERO, self.size)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill);
|
||||||
|
|
||||||
|
let mut child = self.content.as_widget().layout(renderer, &limits);
|
||||||
|
child.align(Alignment::Center, Alignment::Center, limits.max());
|
||||||
|
|
||||||
|
let mut node = layout::Node::with_children(self.size, vec![child]);
|
||||||
|
node.move_to(position);
|
||||||
|
|
||||||
|
node
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
renderer: &Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) -> event::Status {
|
||||||
|
let content_bounds = layout.children().next().unwrap().bounds();
|
||||||
|
|
||||||
|
if let Some(message) = self.on_blur.as_ref() {
|
||||||
|
if let Event::Mouse(mouse::Event::ButtonPressed(
|
||||||
|
mouse::Button::Left,
|
||||||
|
)) = &event
|
||||||
|
{
|
||||||
|
if !content_bounds.contains(cursor_position) {
|
||||||
|
shell.publish(message.clone());
|
||||||
|
return event::Status::Captured;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content.as_widget_mut().on_event(
|
||||||
|
self.tree,
|
||||||
|
event,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Renderer::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
) {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: layout.bounds(),
|
||||||
|
border_radius: renderer::BorderRadius::from(0.0),
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
Color {
|
||||||
|
a: 0.80,
|
||||||
|
..Color::BLACK
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.content.as_widget().draw(
|
||||||
|
self.tree,
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor_position,
|
||||||
|
&layout.bounds(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&mut self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
operation: &mut dyn widget::Operation<Message>,
|
||||||
|
) {
|
||||||
|
self.content.as_widget().operate(
|
||||||
|
self.tree,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
operation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.content.as_widget().mouse_interaction(
|
||||||
|
self.tree,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
|
||||||
|
for Element<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: 'a + iced_native::Renderer,
|
||||||
|
Message: 'a + Clone,
|
||||||
|
{
|
||||||
|
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
|
||||||
|
Element::new(modal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
examples/modern_art/Cargo.toml
Normal file
11
examples/modern_art/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "modern_art"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Bingus <shankern@protonmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||||
|
rand = "0.8.5"
|
||||||
|
env_logger = "0.9"
|
||||||
142
examples/modern_art/src/main.rs
Normal file
142
examples/modern_art/src/main.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
use iced::widget::canvas::{
|
||||||
|
self, gradient::Location, gradient::Position, Cache, Canvas, Cursor, Frame,
|
||||||
|
Geometry, Gradient,
|
||||||
|
};
|
||||||
|
use iced::{
|
||||||
|
executor, Application, Color, Command, Element, Length, Point, Rectangle,
|
||||||
|
Renderer, Settings, Size, Theme,
|
||||||
|
};
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
fn main() -> iced::Result {
|
||||||
|
env_logger::builder().format_timestamp(None).init();
|
||||||
|
|
||||||
|
ModernArt::run(Settings {
|
||||||
|
antialiasing: true,
|
||||||
|
..Settings::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Message {}
|
||||||
|
|
||||||
|
struct ModernArt {
|
||||||
|
cache: Cache,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for ModernArt {
|
||||||
|
type Executor = executor::Default;
|
||||||
|
type Message = Message;
|
||||||
|
type Theme = Theme;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||||
|
(
|
||||||
|
ModernArt {
|
||||||
|
cache: Default::default(),
|
||||||
|
},
|
||||||
|
Command::none(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> String {
|
||||||
|
String::from("Modern Art")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _message: Message) -> Command<Message> {
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
|
||||||
|
Canvas::new(self)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> canvas::Program<Message> for ModernArt {
|
||||||
|
type State = ();
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_state: &Self::State,
|
||||||
|
_theme: &Theme,
|
||||||
|
bounds: Rectangle,
|
||||||
|
_cursor: Cursor,
|
||||||
|
) -> Vec<Geometry> {
|
||||||
|
let geometry = self.cache.draw(bounds.size(), |frame| {
|
||||||
|
let num_squares = thread_rng().gen_range(0..1200);
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i <= num_squares {
|
||||||
|
generate_box(frame, bounds.size());
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
vec![geometry]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_direction() -> Location {
|
||||||
|
match thread_rng().gen_range(0..8) {
|
||||||
|
0 => Location::TopLeft,
|
||||||
|
1 => Location::Top,
|
||||||
|
2 => Location::TopRight,
|
||||||
|
3 => Location::Right,
|
||||||
|
4 => Location::BottomRight,
|
||||||
|
5 => Location::Bottom,
|
||||||
|
6 => Location::BottomLeft,
|
||||||
|
7 => Location::Left,
|
||||||
|
_ => Location::TopLeft,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
|
||||||
|
let solid = rand::random::<bool>();
|
||||||
|
|
||||||
|
let random_color = || -> Color {
|
||||||
|
Color::from_rgb(
|
||||||
|
thread_rng().gen_range(0.0..1.0),
|
||||||
|
thread_rng().gen_range(0.0..1.0),
|
||||||
|
thread_rng().gen_range(0.0..1.0),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let gradient = |top_left: Point, size: Size| -> Gradient {
|
||||||
|
let mut builder = Gradient::linear(Position::Relative {
|
||||||
|
top_left,
|
||||||
|
size,
|
||||||
|
start: random_direction(),
|
||||||
|
end: random_direction(),
|
||||||
|
});
|
||||||
|
let stops = thread_rng().gen_range(1..15u32);
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i <= stops {
|
||||||
|
builder = builder.add_stop(i as f32 / stops as f32, random_color());
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.build().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let top_left = Point::new(
|
||||||
|
thread_rng().gen_range(0.0..bounds.width),
|
||||||
|
thread_rng().gen_range(0.0..bounds.height),
|
||||||
|
);
|
||||||
|
|
||||||
|
let size = Size::new(
|
||||||
|
thread_rng().gen_range(50.0..200.0),
|
||||||
|
thread_rng().gen_range(50.0..200.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
if solid {
|
||||||
|
frame.fill_rectangle(top_left, size, random_color());
|
||||||
|
} else {
|
||||||
|
frame.fill_rectangle(top_left, size, gradient(top_left, size));
|
||||||
|
};
|
||||||
|
|
||||||
|
solid
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
//! a circle around each fingertip. This only works on touch-enabled
|
//! a circle around each fingertip. This only works on touch-enabled
|
||||||
//! computers like Microsoft Surface.
|
//! computers like Microsoft Surface.
|
||||||
use iced::widget::canvas::event;
|
use iced::widget::canvas::event;
|
||||||
use iced::widget::canvas::{self, Canvas, Cursor, Geometry, Stroke};
|
use iced::widget::canvas::stroke::{self, Stroke};
|
||||||
|
use iced::widget::canvas::{self, Canvas, Cursor, Geometry};
|
||||||
use iced::{
|
use iced::{
|
||||||
executor, touch, window, Application, Color, Command, Element, Length,
|
executor, touch, window, Application, Color, Command, Element, Length,
|
||||||
Point, Rectangle, Settings, Subscription, Theme,
|
Point, Rectangle, Settings, Subscription, Theme,
|
||||||
|
|
@ -186,7 +187,7 @@ impl canvas::Program<Message> for State {
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&path,
|
&path,
|
||||||
Stroke {
|
Stroke {
|
||||||
color: Color::BLACK,
|
style: stroke::Style::Solid(Color::BLACK),
|
||||||
width: 3.0,
|
width: 3.0,
|
||||||
..Stroke::default()
|
..Stroke::default()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ enum Message {
|
||||||
Dragged(pane_grid::DragEvent),
|
Dragged(pane_grid::DragEvent),
|
||||||
Resized(pane_grid::ResizeEvent),
|
Resized(pane_grid::ResizeEvent),
|
||||||
TogglePin(pane_grid::Pane),
|
TogglePin(pane_grid::Pane),
|
||||||
|
Maximize(pane_grid::Pane),
|
||||||
|
Restore,
|
||||||
Close(pane_grid::Pane),
|
Close(pane_grid::Pane),
|
||||||
CloseFocused,
|
CloseFocused,
|
||||||
}
|
}
|
||||||
|
|
@ -114,6 +116,10 @@ impl Application for Example {
|
||||||
*is_pinned = !*is_pinned;
|
*is_pinned = !*is_pinned;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::Maximize(pane) => self.panes.maximize(&pane),
|
||||||
|
Message::Restore => {
|
||||||
|
self.panes.restore();
|
||||||
|
}
|
||||||
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);
|
||||||
|
|
@ -157,7 +163,7 @@ 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(&self.panes, |id, pane| {
|
let pane_grid = PaneGrid::new(&self.panes, |id, pane, is_maximized| {
|
||||||
let is_focused = focus == Some(id);
|
let is_focused = focus == Some(id);
|
||||||
|
|
||||||
let pin_button = button(
|
let pin_button = button(
|
||||||
|
|
@ -178,7 +184,12 @@ impl Application for Example {
|
||||||
.spacing(5);
|
.spacing(5);
|
||||||
|
|
||||||
let title_bar = pane_grid::TitleBar::new(title)
|
let title_bar = pane_grid::TitleBar::new(title)
|
||||||
.controls(view_controls(id, total_panes, pane.is_pinned))
|
.controls(view_controls(
|
||||||
|
id,
|
||||||
|
total_panes,
|
||||||
|
pane.is_pinned,
|
||||||
|
is_maximized,
|
||||||
|
))
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.style(if is_focused {
|
.style(if is_focused {
|
||||||
style::title_bar_focused
|
style::title_bar_focused
|
||||||
|
|
@ -314,16 +325,35 @@ fn view_controls<'a>(
|
||||||
pane: pane_grid::Pane,
|
pane: pane_grid::Pane,
|
||||||
total_panes: usize,
|
total_panes: usize,
|
||||||
is_pinned: bool,
|
is_pinned: bool,
|
||||||
|
is_maximized: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let mut button = button(text("Close").size(14))
|
let mut row = row![].spacing(5);
|
||||||
|
|
||||||
|
if total_panes > 1 {
|
||||||
|
let toggle = {
|
||||||
|
let (content, message) = if is_maximized {
|
||||||
|
("Restore", Message::Restore)
|
||||||
|
} else {
|
||||||
|
("Maximize", Message::Maximize(pane))
|
||||||
|
};
|
||||||
|
button(text(content).size(14))
|
||||||
|
.style(theme::Button::Secondary)
|
||||||
|
.padding(3)
|
||||||
|
.on_press(message)
|
||||||
|
};
|
||||||
|
|
||||||
|
row = row.push(toggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut close = button(text("Close").size(14))
|
||||||
.style(theme::Button::Destructive)
|
.style(theme::Button::Destructive)
|
||||||
.padding(3);
|
.padding(3);
|
||||||
|
|
||||||
if total_panes > 1 && !is_pinned {
|
if total_panes > 1 && !is_pinned {
|
||||||
button = button.on_press(Message::Close(pane));
|
close = close.on_press(Message::Close(pane));
|
||||||
}
|
}
|
||||||
|
|
||||||
button.into()
|
row.push(close).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
mod style {
|
mod style {
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,15 @@ struct ScrollableDemo {
|
||||||
variants: Vec<Variant>,
|
variants: Vec<Variant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
enum ThemeType {
|
||||||
|
Light,
|
||||||
|
Dark,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
ThemeChanged(Theme),
|
ThemeChanged(ThemeType),
|
||||||
ScrollToTop(usize),
|
ScrollToTop(usize),
|
||||||
ScrollToBottom(usize),
|
ScrollToBottom(usize),
|
||||||
Scrolled(usize, f32),
|
Scrolled(usize, f32),
|
||||||
|
|
@ -45,7 +51,10 @@ impl Application for ScrollableDemo {
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::ThemeChanged(theme) => {
|
Message::ThemeChanged(theme) => {
|
||||||
self.theme = theme;
|
self.theme = match theme {
|
||||||
|
ThemeType::Light => Theme::Light,
|
||||||
|
ThemeType::Dark => Theme::Dark,
|
||||||
|
};
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
@ -78,17 +87,15 @@ impl Application for ScrollableDemo {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
let ScrollableDemo {
|
let ScrollableDemo { variants, .. } = self;
|
||||||
theme, variants, ..
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
|
let choose_theme = [ThemeType::Light, ThemeType::Dark].iter().fold(
|
||||||
column!["Choose a theme:"].spacing(10),
|
column!["Choose a theme:"].spacing(10),
|
||||||
|column, option| {
|
|column, option| {
|
||||||
column.push(radio(
|
column.push(radio(
|
||||||
format!("{:?}", option),
|
format!("{:?}", option),
|
||||||
*option,
|
*option,
|
||||||
Some(*theme),
|
Some(*option),
|
||||||
Message::ThemeChanged,
|
Message::ThemeChanged,
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
|
|
@ -198,7 +205,7 @@ impl Application for ScrollableDemo {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ use iced::executor;
|
||||||
use iced::theme::{self, Theme};
|
use iced::theme::{self, Theme};
|
||||||
use iced::time;
|
use iced::time;
|
||||||
use iced::widget::canvas;
|
use iced::widget::canvas;
|
||||||
use iced::widget::canvas::{Cursor, Path, Stroke};
|
use iced::widget::canvas::gradient::{self, Gradient};
|
||||||
|
use iced::widget::canvas::stroke::{self, Stroke};
|
||||||
|
use iced::widget::canvas::{Cursor, Path};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
|
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
|
||||||
|
|
@ -37,9 +39,9 @@ enum Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for SolarSystem {
|
impl Application for SolarSystem {
|
||||||
|
type Executor = executor::Default;
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
type Theme = Theme;
|
type Theme = Theme;
|
||||||
type Executor = executor::Default;
|
|
||||||
type Flags = ();
|
type Flags = ();
|
||||||
|
|
||||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||||
|
|
@ -65,10 +67,6 @@ impl Application for SolarSystem {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
|
||||||
time::every(std::time::Duration::from_millis(10)).map(Message::Tick)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
canvas(&self.state)
|
canvas(&self.state)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
@ -81,10 +79,18 @@ impl Application for SolarSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> theme::Application {
|
fn style(&self) -> theme::Application {
|
||||||
theme::Application::Custom(|_theme| application::Appearance {
|
fn dark_background(_theme: &Theme) -> application::Appearance {
|
||||||
|
application::Appearance {
|
||||||
background_color: Color::BLACK,
|
background_color: Color::BLACK,
|
||||||
text_color: Color::WHITE,
|
text_color: Color::WHITE,
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
theme::Application::from(dark_background as fn(&Theme) -> _)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
|
time::every(time::Duration::from_millis(10)).map(Message::Tick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,8 +184,10 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
frame.stroke(
|
frame.stroke(
|
||||||
&orbit,
|
&orbit,
|
||||||
Stroke {
|
Stroke {
|
||||||
|
style: stroke::Style::Solid(Color::from_rgba8(
|
||||||
|
0, 153, 255, 0.1,
|
||||||
|
)),
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
color: Color::from_rgba8(0, 153, 255, 0.1),
|
|
||||||
line_dash: canvas::LineDash {
|
line_dash: canvas::LineDash {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
segments: &[3.0, 6.0],
|
segments: &[3.0, 6.0],
|
||||||
|
|
@ -198,15 +206,18 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
|
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
|
||||||
|
|
||||||
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
|
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
|
||||||
let shadow = Path::rectangle(
|
|
||||||
Point::new(0.0, -Self::EARTH_RADIUS),
|
|
||||||
Size::new(
|
|
||||||
Self::EARTH_RADIUS * 4.0,
|
|
||||||
Self::EARTH_RADIUS * 2.0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6));
|
let earth_fill =
|
||||||
|
Gradient::linear(gradient::Position::Absolute {
|
||||||
|
start: Point::new(-Self::EARTH_RADIUS, 0.0),
|
||||||
|
end: Point::new(Self::EARTH_RADIUS, 0.0),
|
||||||
|
})
|
||||||
|
.add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
|
||||||
|
.add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47))
|
||||||
|
.build()
|
||||||
|
.expect("Build Earth fill gradient");
|
||||||
|
|
||||||
|
frame.fill(&earth, earth_fill);
|
||||||
|
|
||||||
frame.with_save(|frame| {
|
frame.with_save(|frame| {
|
||||||
frame.rotate(rotation * 10.0);
|
frame.rotate(rotation * 10.0);
|
||||||
|
|
@ -215,14 +226,6 @@ impl<Message> canvas::Program<Message> for State {
|
||||||
let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
|
let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
|
||||||
frame.fill(&moon, Color::WHITE);
|
frame.fill(&moon, Color::WHITE);
|
||||||
});
|
});
|
||||||
|
|
||||||
frame.fill(
|
|
||||||
&shadow,
|
|
||||||
Color {
|
|
||||||
a: 0.7,
|
|
||||||
..Color::BLACK
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
|
use iced::theme::{self, Theme};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, checkbox, column, container, horizontal_rule, progress_bar, radio,
|
button, checkbox, column, container, horizontal_rule, progress_bar, radio,
|
||||||
row, scrollable, slider, text, text_input, toggler, vertical_rule,
|
row, scrollable, slider, text, text_input, toggler, vertical_rule,
|
||||||
vertical_space,
|
vertical_space,
|
||||||
};
|
};
|
||||||
use iced::{Alignment, Element, Length, Sandbox, Settings, Theme};
|
use iced::{Alignment, Color, Element, Length, Sandbox, Settings};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
Styling::run(Settings::default())
|
Styling::run(Settings::default())
|
||||||
|
|
@ -18,9 +19,16 @@ struct Styling {
|
||||||
toggler_value: bool,
|
toggler_value: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
enum ThemeType {
|
||||||
|
Light,
|
||||||
|
Dark,
|
||||||
|
Custom,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
ThemeChanged(Theme),
|
ThemeChanged(ThemeType),
|
||||||
InputChanged(String),
|
InputChanged(String),
|
||||||
ButtonPressed,
|
ButtonPressed,
|
||||||
SliderChanged(f32),
|
SliderChanged(f32),
|
||||||
|
|
@ -41,7 +49,19 @@ impl Sandbox for Styling {
|
||||||
|
|
||||||
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 = match theme {
|
||||||
|
ThemeType::Light => Theme::Light,
|
||||||
|
ThemeType::Dark => Theme::Dark,
|
||||||
|
ThemeType::Custom => Theme::custom(theme::Palette {
|
||||||
|
background: Color::from_rgb(1.0, 0.9, 1.0),
|
||||||
|
text: Color::BLACK,
|
||||||
|
primary: Color::from_rgb(0.5, 0.5, 0.0),
|
||||||
|
success: Color::from_rgb(0.0, 1.0, 0.0),
|
||||||
|
danger: Color::from_rgb(1.0, 0.0, 0.0),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
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,
|
||||||
|
|
@ -51,13 +71,20 @@ impl Sandbox for Styling {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
|
let choose_theme =
|
||||||
|
[ThemeType::Light, ThemeType::Dark, ThemeType::Custom]
|
||||||
|
.iter()
|
||||||
|
.fold(
|
||||||
column![text("Choose a theme:")].spacing(10),
|
column![text("Choose a theme:")].spacing(10),
|
||||||
|column, theme| {
|
|column, theme| {
|
||||||
column.push(radio(
|
column.push(radio(
|
||||||
format!("{:?}", theme),
|
format!("{:?}", theme),
|
||||||
*theme,
|
*theme,
|
||||||
Some(self.theme),
|
Some(match self.theme {
|
||||||
|
Theme::Light => ThemeType::Light,
|
||||||
|
Theme::Dark => ThemeType::Dark,
|
||||||
|
Theme::Custom { .. } => ThemeType::Custom,
|
||||||
|
}),
|
||||||
Message::ThemeChanged,
|
Message::ThemeChanged,
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
|
|
@ -132,6 +159,6 @@ impl Sandbox for Styling {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,71 @@
|
||||||
use iced::widget::{container, svg};
|
use iced::theme;
|
||||||
use iced::{Element, Length, Sandbox, Settings};
|
use iced::widget::{checkbox, column, container, svg};
|
||||||
|
use iced::{color, Element, Length, Sandbox, Settings};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
Tiger::run(Settings::default())
|
Tiger::run(Settings::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Tiger;
|
#[derive(Debug, Default)]
|
||||||
|
struct Tiger {
|
||||||
|
apply_color_filter: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Message {
|
||||||
|
ToggleColorFilter(bool),
|
||||||
|
}
|
||||||
|
|
||||||
impl Sandbox for Tiger {
|
impl Sandbox for Tiger {
|
||||||
type Message = ();
|
type Message = Message;
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Tiger
|
Tiger::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
String::from("SVG - Iced")
|
String::from("SVG - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, _message: ()) {}
|
fn update(&mut self, message: Self::Message) {
|
||||||
|
match message {
|
||||||
|
Message::ToggleColorFilter(apply_color_filter) => {
|
||||||
|
self.apply_color_filter = apply_color_filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<()> {
|
fn view(&self) -> Element<Self::Message> {
|
||||||
let svg = svg(svg::Handle::from_path(format!(
|
let handle = svg::Handle::from_path(format!(
|
||||||
"{}/resources/tiger.svg",
|
"{}/resources/tiger.svg",
|
||||||
env!("CARGO_MANIFEST_DIR")
|
env!("CARGO_MANIFEST_DIR")
|
||||||
)))
|
));
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Fill);
|
|
||||||
|
|
||||||
container(svg)
|
let svg = svg(handle).width(Length::Fill).height(Length::Fill).style(
|
||||||
|
if self.apply_color_filter {
|
||||||
|
theme::Svg::custom_fn(|_theme| svg::Appearance {
|
||||||
|
color: Some(color!(0x0000ff)),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
theme::Svg::Default
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let apply_color_filter = checkbox(
|
||||||
|
"Apply a color filter",
|
||||||
|
self.apply_color_filter,
|
||||||
|
Message::ToggleColorFilter,
|
||||||
|
);
|
||||||
|
|
||||||
|
container(
|
||||||
|
column![
|
||||||
|
svg,
|
||||||
|
container(apply_color_filter).width(Length::Fill).center_x()
|
||||||
|
]
|
||||||
|
.spacing(20)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill),
|
||||||
|
)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.padding(20)
|
.padding(20)
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,11 @@ impl Application for Todos {
|
||||||
task.update(task_message);
|
task.update(task_message);
|
||||||
|
|
||||||
if should_focus {
|
if should_focus {
|
||||||
text_input::focus(Task::text_input_id(i))
|
let id = Task::text_input_id(i);
|
||||||
|
Command::batch(vec![
|
||||||
|
text_input::focus(id.clone()),
|
||||||
|
text_input::select_all(id),
|
||||||
|
])
|
||||||
} else {
|
} else {
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_futures"
|
name = "iced_futures"
|
||||||
version = "0.4.1"
|
version = "0.5.1"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Commands, subscriptions, and runtimes for Iced"
|
description = "Commands, subscriptions, and runtimes for Iced"
|
||||||
|
|
|
||||||
|
|
@ -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/iced-rs/iced/tree/0.4/examples
|
/// [examples]: https://github.com/iced-rs/iced/tree/0.6/examples
|
||||||
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.4/examples/download_progress
|
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.6/examples/download_progress
|
||||||
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.4/examples/stopwatch
|
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.6/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`].
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_glow"
|
name = "iced_glow"
|
||||||
version = "0.3.0"
|
version = "0.5.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A glow renderer for iced"
|
description = "A glow renderer for iced"
|
||||||
|
|
@ -8,12 +8,22 @@ license = "MIT AND OFL-1.1"
|
||||||
repository = "https://github.com/iced-rs/iced"
|
repository = "https://github.com/iced-rs/iced"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
svg = ["iced_graphics/svg"]
|
||||||
|
image = ["iced_graphics/image"]
|
||||||
|
png = ["iced_graphics/png"]
|
||||||
|
jpeg = ["iced_graphics/jpeg"]
|
||||||
|
jpeg_rayon = ["iced_graphics/jpeg_rayon"]
|
||||||
|
gif = ["iced_graphics/gif"]
|
||||||
|
webp = ["iced_graphics/webp"]
|
||||||
|
pnm = ["iced_graphics/pnm"]
|
||||||
|
ico = ["iced_graphics/ico"]
|
||||||
|
bmp = ["iced_graphics/bmp"]
|
||||||
|
hdr = ["iced_graphics/hdr"]
|
||||||
|
dds = ["iced_graphics/dds"]
|
||||||
|
farbfeld = ["iced_graphics/farbfeld"]
|
||||||
canvas = ["iced_graphics/canvas"]
|
canvas = ["iced_graphics/canvas"]
|
||||||
qr_code = ["iced_graphics/qr_code"]
|
qr_code = ["iced_graphics/qr_code"]
|
||||||
default_system_font = ["iced_graphics/font-source"]
|
default_system_font = ["iced_graphics/font-source"]
|
||||||
# Not supported yet!
|
|
||||||
image = []
|
|
||||||
svg = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glow = "0.11.1"
|
glow = "0.11.1"
|
||||||
|
|
@ -24,11 +34,11 @@ bytemuck = "1.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
[dependencies.iced_native]
|
[dependencies.iced_native]
|
||||||
version = "0.5"
|
version = "0.7"
|
||||||
path = "../native"
|
path = "../native"
|
||||||
|
|
||||||
[dependencies.iced_graphics]
|
[dependencies.iced_graphics]
|
||||||
version = "0.3"
|
version = "0.5"
|
||||||
path = "../graphics"
|
path = "../graphics"
|
||||||
features = ["font-fallback", "font-icons", "opengl"]
|
features = ["font-fallback", "font-icons", "opengl"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::program;
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
use crate::image;
|
||||||
use crate::quad;
|
use crate::quad;
|
||||||
use crate::text;
|
use crate::text;
|
||||||
use crate::triangle;
|
use crate::{program, triangle};
|
||||||
use crate::{Settings, Transformation, Viewport};
|
use crate::{Settings, Transformation, Viewport};
|
||||||
|
|
||||||
use iced_graphics::backend;
|
use iced_graphics::backend;
|
||||||
|
|
@ -16,6 +17,8 @@ use iced_native::{Font, Size};
|
||||||
/// [`iced`]: https://github.com/iced-rs/iced
|
/// [`iced`]: https://github.com/iced-rs/iced
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
image_pipeline: image::Pipeline,
|
||||||
quad_pipeline: quad::Pipeline,
|
quad_pipeline: quad::Pipeline,
|
||||||
text_pipeline: text::Pipeline,
|
text_pipeline: text::Pipeline,
|
||||||
triangle_pipeline: triangle::Pipeline,
|
triangle_pipeline: triangle::Pipeline,
|
||||||
|
|
@ -33,10 +36,14 @@ impl Backend {
|
||||||
|
|
||||||
let shader_version = program::Version::new(gl);
|
let shader_version = program::Version::new(gl);
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
let image_pipeline = image::Pipeline::new(gl, &shader_version);
|
||||||
let quad_pipeline = quad::Pipeline::new(gl, &shader_version);
|
let quad_pipeline = quad::Pipeline::new(gl, &shader_version);
|
||||||
let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version);
|
let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
image_pipeline,
|
||||||
quad_pipeline,
|
quad_pipeline,
|
||||||
text_pipeline,
|
text_pipeline,
|
||||||
triangle_pipeline,
|
triangle_pipeline,
|
||||||
|
|
@ -71,6 +78,9 @@ impl Backend {
|
||||||
viewport_size.height,
|
viewport_size.height,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
self.image_pipeline.trim_cache(gl);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(
|
fn flush(
|
||||||
|
|
@ -105,11 +115,26 @@ impl Backend {
|
||||||
* Transformation::scale(scale_factor, scale_factor);
|
* Transformation::scale(scale_factor, scale_factor);
|
||||||
|
|
||||||
self.triangle_pipeline.draw(
|
self.triangle_pipeline.draw(
|
||||||
|
&layer.meshes,
|
||||||
gl,
|
gl,
|
||||||
target_height,
|
target_height,
|
||||||
scaled,
|
scaled,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
&layer.meshes,
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
if !layer.images.is_empty() {
|
||||||
|
let scaled = transformation
|
||||||
|
* Transformation::scale(scale_factor, scale_factor);
|
||||||
|
|
||||||
|
self.image_pipeline.draw(
|
||||||
|
gl,
|
||||||
|
target_height,
|
||||||
|
scaled,
|
||||||
|
scale_factor,
|
||||||
|
&layer.images,
|
||||||
|
bounds,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,8 +264,8 @@ impl backend::Text for Backend {
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
impl backend::Image for Backend {
|
impl backend::Image for Backend {
|
||||||
fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) {
|
fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
|
||||||
(50, 50)
|
self.image_pipeline.dimensions(handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,8 +273,8 @@ impl backend::Image for Backend {
|
||||||
impl backend::Svg for Backend {
|
impl backend::Svg for Backend {
|
||||||
fn viewport_dimensions(
|
fn viewport_dimensions(
|
||||||
&self,
|
&self,
|
||||||
_handle: &iced_native::svg::Handle,
|
handle: &iced_native::svg::Handle,
|
||||||
) -> (u32, u32) {
|
) -> Size<u32> {
|
||||||
(50, 50)
|
self.image_pipeline.viewport_dimensions(handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
248
glow/src/image.rs
Normal file
248
glow/src/image.rs
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
mod storage;
|
||||||
|
|
||||||
|
use storage::Storage;
|
||||||
|
|
||||||
|
pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
|
||||||
|
|
||||||
|
use crate::program::{self, Shader};
|
||||||
|
use crate::Transformation;
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
use iced_graphics::image::raster;
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
use iced_graphics::image::vector;
|
||||||
|
|
||||||
|
use iced_graphics::layer;
|
||||||
|
use iced_graphics::Rectangle;
|
||||||
|
use iced_graphics::Size;
|
||||||
|
|
||||||
|
use glow::HasContext;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Pipeline {
|
||||||
|
program: <glow::Context as HasContext>::Program,
|
||||||
|
vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||||
|
vertex_buffer: <glow::Context as HasContext>::Buffer,
|
||||||
|
transform_location: <glow::Context as HasContext>::UniformLocation,
|
||||||
|
storage: Storage,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
raster_cache: RefCell<raster::Cache<Storage>>,
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
vector_cache: RefCell<vector::Cache<Storage>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pipeline {
|
||||||
|
pub fn new(
|
||||||
|
gl: &glow::Context,
|
||||||
|
shader_version: &program::Version,
|
||||||
|
) -> Pipeline {
|
||||||
|
let program = unsafe {
|
||||||
|
let vertex_shader = Shader::vertex(
|
||||||
|
gl,
|
||||||
|
shader_version,
|
||||||
|
include_str!("shader/common/image.vert"),
|
||||||
|
);
|
||||||
|
let fragment_shader = Shader::fragment(
|
||||||
|
gl,
|
||||||
|
shader_version,
|
||||||
|
include_str!("shader/common/image.frag"),
|
||||||
|
);
|
||||||
|
|
||||||
|
program::create(
|
||||||
|
gl,
|
||||||
|
&[vertex_shader, fragment_shader],
|
||||||
|
&[(0, "i_Position")],
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let transform_location =
|
||||||
|
unsafe { gl.get_uniform_location(program, "u_Transform") }
|
||||||
|
.expect("Get transform location");
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl.use_program(Some(program));
|
||||||
|
|
||||||
|
let transform: [f32; 16] = Transformation::identity().into();
|
||||||
|
gl.uniform_matrix_4_f32_slice(
|
||||||
|
Some(&transform_location),
|
||||||
|
false,
|
||||||
|
&transform,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.use_program(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertex_buffer =
|
||||||
|
unsafe { gl.create_buffer().expect("Create vertex buffer") };
|
||||||
|
let vertex_array =
|
||||||
|
unsafe { gl.create_vertex_array().expect("Create vertex array") };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl.bind_vertex_array(Some(vertex_array));
|
||||||
|
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
|
||||||
|
|
||||||
|
let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1];
|
||||||
|
gl.buffer_data_size(
|
||||||
|
glow::ARRAY_BUFFER,
|
||||||
|
vertices.len() as i32,
|
||||||
|
glow::STATIC_DRAW,
|
||||||
|
);
|
||||||
|
gl.buffer_sub_data_u8_slice(
|
||||||
|
glow::ARRAY_BUFFER,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(vertices),
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.enable_vertex_attrib_array(0);
|
||||||
|
gl.vertex_attrib_pointer_f32(
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
glow::UNSIGNED_BYTE,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
||||||
|
gl.bind_vertex_array(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipeline {
|
||||||
|
program,
|
||||||
|
vertex_array,
|
||||||
|
vertex_buffer,
|
||||||
|
transform_location,
|
||||||
|
storage: Storage::default(),
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
raster_cache: RefCell::new(raster::Cache::default()),
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
vector_cache: RefCell::new(vector::Cache::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
|
||||||
|
self.raster_cache.borrow_mut().load(handle).dimensions()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
pub fn viewport_dimensions(
|
||||||
|
&self,
|
||||||
|
handle: &iced_native::svg::Handle,
|
||||||
|
) -> Size<u32> {
|
||||||
|
let mut cache = self.vector_cache.borrow_mut();
|
||||||
|
let svg = cache.load(handle);
|
||||||
|
|
||||||
|
svg.viewport_dimensions()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(
|
||||||
|
&mut self,
|
||||||
|
mut gl: &glow::Context,
|
||||||
|
target_height: u32,
|
||||||
|
transformation: Transformation,
|
||||||
|
_scale_factor: f32,
|
||||||
|
images: &[layer::Image],
|
||||||
|
layer_bounds: Rectangle<u32>,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
gl.use_program(Some(self.program));
|
||||||
|
gl.bind_vertex_array(Some(self.vertex_array));
|
||||||
|
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
|
||||||
|
gl.enable(glow::SCISSOR_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
let mut raster_cache = self.raster_cache.borrow_mut();
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
let mut vector_cache = self.vector_cache.borrow_mut();
|
||||||
|
|
||||||
|
for image in images {
|
||||||
|
let (entry, bounds) = match &image {
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
layer::Image::Raster { handle, bounds } => (
|
||||||
|
raster_cache.upload(handle, &mut gl, &mut self.storage),
|
||||||
|
bounds,
|
||||||
|
),
|
||||||
|
#[cfg(not(feature = "image"))]
|
||||||
|
layer::Image::Raster { handle: _, bounds } => (None, bounds),
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
layer::Image::Vector {
|
||||||
|
handle,
|
||||||
|
color,
|
||||||
|
bounds,
|
||||||
|
} => {
|
||||||
|
let size = [bounds.width, bounds.height];
|
||||||
|
(
|
||||||
|
vector_cache.upload(
|
||||||
|
handle,
|
||||||
|
*color,
|
||||||
|
size,
|
||||||
|
_scale_factor,
|
||||||
|
&mut gl,
|
||||||
|
&mut self.storage,
|
||||||
|
),
|
||||||
|
bounds,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "svg"))]
|
||||||
|
layer::Image::Vector { bounds, .. } => (None, bounds),
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl.scissor(
|
||||||
|
layer_bounds.x as i32,
|
||||||
|
(target_height - (layer_bounds.y + layer_bounds.height))
|
||||||
|
as i32,
|
||||||
|
layer_bounds.width as i32,
|
||||||
|
layer_bounds.height as i32,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(storage::Entry { texture, .. }) = entry {
|
||||||
|
gl.bind_texture(glow::TEXTURE_2D, Some(*texture))
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let translate = Transformation::translate(bounds.x, bounds.y);
|
||||||
|
let scale = Transformation::scale(bounds.width, bounds.height);
|
||||||
|
let transformation = transformation * translate * scale;
|
||||||
|
let matrix: [f32; 16] = transformation.into();
|
||||||
|
gl.uniform_matrix_4_f32_slice(
|
||||||
|
Some(&self.transform_location),
|
||||||
|
false,
|
||||||
|
&matrix,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
|
||||||
|
|
||||||
|
gl.bind_texture(glow::TEXTURE_2D, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
||||||
|
gl.bind_vertex_array(None);
|
||||||
|
gl.use_program(None);
|
||||||
|
gl.disable(glow::SCISSOR_TEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim_cache(&mut self, mut gl: &glow::Context) {
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
self.raster_cache
|
||||||
|
.borrow_mut()
|
||||||
|
.trim(&mut self.storage, &mut gl);
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
self.vector_cache
|
||||||
|
.borrow_mut()
|
||||||
|
.trim(&mut self.storage, &mut gl);
|
||||||
|
}
|
||||||
|
}
|
||||||
78
glow/src/image/storage.rs
Normal file
78
glow/src/image/storage.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
use iced_graphics::image;
|
||||||
|
use iced_graphics::Size;
|
||||||
|
|
||||||
|
use glow::HasContext;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Storage;
|
||||||
|
|
||||||
|
impl image::Storage for Storage {
|
||||||
|
type Entry = Entry;
|
||||||
|
type State<'a> = &'a glow::Context;
|
||||||
|
|
||||||
|
fn upload(
|
||||||
|
&mut self,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
data: &[u8],
|
||||||
|
gl: &mut &glow::Context,
|
||||||
|
) -> Option<Self::Entry> {
|
||||||
|
unsafe {
|
||||||
|
let texture = gl.create_texture().expect("create texture");
|
||||||
|
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
|
||||||
|
gl.tex_image_2d(
|
||||||
|
glow::TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
glow::SRGB8_ALPHA8 as i32,
|
||||||
|
width as i32,
|
||||||
|
height as i32,
|
||||||
|
0,
|
||||||
|
glow::RGBA,
|
||||||
|
glow::UNSIGNED_BYTE,
|
||||||
|
Some(data),
|
||||||
|
);
|
||||||
|
gl.tex_parameter_i32(
|
||||||
|
glow::TEXTURE_2D,
|
||||||
|
glow::TEXTURE_WRAP_S,
|
||||||
|
glow::CLAMP_TO_EDGE as _,
|
||||||
|
);
|
||||||
|
gl.tex_parameter_i32(
|
||||||
|
glow::TEXTURE_2D,
|
||||||
|
glow::TEXTURE_WRAP_T,
|
||||||
|
glow::CLAMP_TO_EDGE as _,
|
||||||
|
);
|
||||||
|
gl.tex_parameter_i32(
|
||||||
|
glow::TEXTURE_2D,
|
||||||
|
glow::TEXTURE_MIN_FILTER,
|
||||||
|
glow::LINEAR as _,
|
||||||
|
);
|
||||||
|
gl.tex_parameter_i32(
|
||||||
|
glow::TEXTURE_2D,
|
||||||
|
glow::TEXTURE_MAG_FILTER,
|
||||||
|
glow::LINEAR as _,
|
||||||
|
);
|
||||||
|
gl.bind_texture(glow::TEXTURE_2D, None);
|
||||||
|
|
||||||
|
Some(Entry {
|
||||||
|
size: Size::new(width, height),
|
||||||
|
texture,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) {
|
||||||
|
unsafe { gl.delete_texture(entry.texture) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Entry {
|
||||||
|
size: Size<u32>,
|
||||||
|
pub(super) texture: glow::NativeTexture,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl image::storage::Entry for Entry {
|
||||||
|
fn size(&self) -> Size<u32> {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
//! 
|
//! 
|
||||||
//!
|
//!
|
||||||
//! [`glow`]: https://github.com/grovesNL/glow
|
//! [`glow`]: https://github.com/grovesNL/glow
|
||||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native
|
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||||
)]
|
)]
|
||||||
|
|
@ -24,6 +24,8 @@
|
||||||
pub use glow;
|
pub use glow;
|
||||||
|
|
||||||
mod backend;
|
mod backend;
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
mod image;
|
||||||
mod program;
|
mod program;
|
||||||
mod quad;
|
mod quad;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
|
||||||
|
|
@ -70,11 +70,10 @@ impl Pipeline {
|
||||||
unsafe {
|
unsafe {
|
||||||
gl.use_program(Some(program));
|
gl.use_program(Some(program));
|
||||||
|
|
||||||
let matrix: [f32; 16] = Transformation::identity().into();
|
|
||||||
gl.uniform_matrix_4_f32_slice(
|
gl.uniform_matrix_4_f32_slice(
|
||||||
Some(&transform_location),
|
Some(&transform_location),
|
||||||
false,
|
false,
|
||||||
&matrix,
|
Transformation::identity().as_ref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
gl.uniform_1_f32(Some(&scale_location), 1.0);
|
gl.uniform_1_f32(Some(&scale_location), 1.0);
|
||||||
|
|
@ -139,11 +138,10 @@ impl Pipeline {
|
||||||
|
|
||||||
if transformation != self.current_transform {
|
if transformation != self.current_transform {
|
||||||
unsafe {
|
unsafe {
|
||||||
let matrix: [f32; 16] = transformation.into();
|
|
||||||
gl.uniform_matrix_4_f32_slice(
|
gl.uniform_matrix_4_f32_slice(
|
||||||
Some(&self.transform_location),
|
Some(&self.transform_location),
|
||||||
false,
|
false,
|
||||||
&matrix,
|
transformation.as_ref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.current_transform = transformation;
|
self.current_transform = transformation;
|
||||||
|
|
@ -256,7 +254,7 @@ unsafe fn create_buffers(
|
||||||
gl.enable_vertex_attrib_array(4);
|
gl.enable_vertex_attrib_array(4);
|
||||||
gl.vertex_attrib_pointer_f32(
|
gl.vertex_attrib_pointer_f32(
|
||||||
4,
|
4,
|
||||||
1,
|
4,
|
||||||
glow::FLOAT,
|
glow::FLOAT,
|
||||||
false,
|
false,
|
||||||
stride,
|
stride,
|
||||||
|
|
@ -270,7 +268,7 @@ unsafe fn create_buffers(
|
||||||
glow::FLOAT,
|
glow::FLOAT,
|
||||||
false,
|
false,
|
||||||
stride,
|
stride,
|
||||||
4 * (2 + 2 + 4 + 4 + 1),
|
4 * (2 + 2 + 4 + 4 + 4),
|
||||||
);
|
);
|
||||||
|
|
||||||
gl.enable_vertex_attrib_array(6);
|
gl.enable_vertex_attrib_array(6);
|
||||||
|
|
@ -280,7 +278,7 @@ unsafe fn create_buffers(
|
||||||
glow::FLOAT,
|
glow::FLOAT,
|
||||||
false,
|
false,
|
||||||
stride,
|
stride,
|
||||||
4 * (2 + 2 + 4 + 4 + 1 + 1),
|
4 * (2 + 2 + 4 + 4 + 4 + 1),
|
||||||
);
|
);
|
||||||
|
|
||||||
gl.bind_vertex_array(None);
|
gl.bind_vertex_array(None);
|
||||||
|
|
@ -309,7 +307,7 @@ pub struct Vertex {
|
||||||
pub border_color: [f32; 4],
|
pub border_color: [f32; 4],
|
||||||
|
|
||||||
/// The border radius of the [`Vertex`].
|
/// The border radius of the [`Vertex`].
|
||||||
pub border_radius: f32,
|
pub border_radius: [f32; 4],
|
||||||
|
|
||||||
/// The border width of the [`Vertex`].
|
/// The border width of the [`Vertex`].
|
||||||
pub border_width: f32,
|
pub border_width: f32,
|
||||||
|
|
|
||||||
|
|
@ -65,11 +65,10 @@ impl Pipeline {
|
||||||
unsafe {
|
unsafe {
|
||||||
gl.use_program(Some(program));
|
gl.use_program(Some(program));
|
||||||
|
|
||||||
let matrix: [f32; 16] = Transformation::identity().into();
|
|
||||||
gl.uniform_matrix_4_f32_slice(
|
gl.uniform_matrix_4_f32_slice(
|
||||||
Some(&transform_location),
|
Some(&transform_location),
|
||||||
false,
|
false,
|
||||||
&matrix,
|
Transformation::identity().as_ref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
gl.uniform_1_f32(Some(&scale_location), 1.0);
|
gl.uniform_1_f32(Some(&scale_location), 1.0);
|
||||||
|
|
@ -119,11 +118,10 @@ impl Pipeline {
|
||||||
|
|
||||||
if transformation != self.current_transform {
|
if transformation != self.current_transform {
|
||||||
unsafe {
|
unsafe {
|
||||||
let matrix: [f32; 16] = transformation.into();
|
|
||||||
gl.uniform_matrix_4_f32_slice(
|
gl.uniform_matrix_4_f32_slice(
|
||||||
Some(&self.transform_location),
|
Some(&self.transform_location),
|
||||||
false,
|
false,
|
||||||
&matrix,
|
transformation.as_ref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.current_transform = transformation;
|
self.current_transform = transformation;
|
||||||
|
|
@ -220,7 +218,7 @@ unsafe fn create_instance_buffer(
|
||||||
gl.enable_vertex_attrib_array(4);
|
gl.enable_vertex_attrib_array(4);
|
||||||
gl.vertex_attrib_pointer_f32(
|
gl.vertex_attrib_pointer_f32(
|
||||||
4,
|
4,
|
||||||
1,
|
4,
|
||||||
glow::FLOAT,
|
glow::FLOAT,
|
||||||
false,
|
false,
|
||||||
stride,
|
stride,
|
||||||
|
|
@ -235,7 +233,7 @@ unsafe fn create_instance_buffer(
|
||||||
glow::FLOAT,
|
glow::FLOAT,
|
||||||
false,
|
false,
|
||||||
stride,
|
stride,
|
||||||
4 * (2 + 2 + 4 + 4 + 1),
|
4 * (2 + 2 + 4 + 4 + 4),
|
||||||
);
|
);
|
||||||
gl.vertex_attrib_divisor(5, 1);
|
gl.vertex_attrib_divisor(5, 1);
|
||||||
|
|
||||||
|
|
|
||||||
59
glow/src/shader/common/gradient.frag
Normal file
59
glow/src/shader/common/gradient.frag
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#ifdef GL_ES
|
||||||
|
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||||
|
precision highp float;
|
||||||
|
#else
|
||||||
|
precision mediump float;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HIGHER_THAN_300
|
||||||
|
layout (location = 0) out vec4 fragColor;
|
||||||
|
#define gl_FragColor fragColor
|
||||||
|
#endif
|
||||||
|
|
||||||
|
in vec2 raw_position;
|
||||||
|
|
||||||
|
uniform vec4 gradient_direction;
|
||||||
|
uniform int color_stops_size;
|
||||||
|
// GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops
|
||||||
|
//stored as color(vec4) -> offset(vec4) sequentially;
|
||||||
|
uniform vec4 color_stops[32];
|
||||||
|
|
||||||
|
//TODO: rewrite without branching to make ALUs happy
|
||||||
|
void main() {
|
||||||
|
vec2 start = gradient_direction.xy;
|
||||||
|
vec2 end = gradient_direction.zw;
|
||||||
|
vec2 gradient_vec = vec2(end - start);
|
||||||
|
vec2 current_vec = vec2(raw_position.xy - start);
|
||||||
|
vec2 unit = normalize(gradient_vec);
|
||||||
|
float coord_offset = dot(unit, current_vec) / length(gradient_vec);
|
||||||
|
//if a gradient has a start/end stop that is identical, the mesh will have a transparent fill
|
||||||
|
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
float min_offset = color_stops[1].x;
|
||||||
|
float max_offset = color_stops[color_stops_size - 1].x;
|
||||||
|
|
||||||
|
for (int i = 0; i < color_stops_size - 2; i += 2) {
|
||||||
|
float curr_offset = color_stops[i+1].x;
|
||||||
|
float next_offset = color_stops[i+3].x;
|
||||||
|
|
||||||
|
if (coord_offset <= min_offset) {
|
||||||
|
//current coordinate is before the first defined offset, set it to the start color
|
||||||
|
gl_FragColor = color_stops[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
|
||||||
|
//current fragment is between the current offset processing & the next one, interpolate colors
|
||||||
|
gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep(
|
||||||
|
curr_offset,
|
||||||
|
next_offset,
|
||||||
|
coord_offset
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coord_offset >= max_offset) {
|
||||||
|
//current coordinate is before the last defined offset, set it to the last color
|
||||||
|
gl_FragColor = color_stops[color_stops_size - 2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
glow/src/shader/common/gradient.vert
Normal file
9
glow/src/shader/common/gradient.vert
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
uniform mat4 u_Transform;
|
||||||
|
|
||||||
|
in vec2 i_Position;
|
||||||
|
out vec2 raw_position;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
|
||||||
|
raw_position = i_Position;
|
||||||
|
}
|
||||||
22
glow/src/shader/common/image.frag
Normal file
22
glow/src/shader/common/image.frag
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#ifdef GL_ES
|
||||||
|
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||||
|
precision highp float;
|
||||||
|
#else
|
||||||
|
precision mediump float;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uniform sampler2D tex;
|
||||||
|
in vec2 tex_pos;
|
||||||
|
|
||||||
|
#ifdef HIGHER_THAN_300
|
||||||
|
out vec4 fragColor;
|
||||||
|
#define gl_FragColor fragColor
|
||||||
|
#endif
|
||||||
|
#ifdef GL_ES
|
||||||
|
#define texture texture2D
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = texture(tex, tex_pos);
|
||||||
|
}
|
||||||
9
glow/src/shader/common/image.vert
Normal file
9
glow/src/shader/common/image.vert
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
uniform mat4 u_Transform;
|
||||||
|
|
||||||
|
in vec2 i_Position;
|
||||||
|
out vec2 tex_pos;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
|
||||||
|
tex_pos = i_Position;
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ varying vec4 v_Color;
|
||||||
varying vec4 v_BorderColor;
|
varying vec4 v_BorderColor;
|
||||||
varying vec2 v_Pos;
|
varying vec2 v_Pos;
|
||||||
varying vec2 v_Scale;
|
varying vec2 v_Scale;
|
||||||
varying float v_BorderRadius;
|
varying vec4 v_BorderRadius;
|
||||||
varying float v_BorderWidth;
|
varying float v_BorderWidth;
|
||||||
|
|
||||||
float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius)
|
float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius)
|
||||||
|
|
@ -33,10 +33,26 @@ float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius)
|
||||||
return sqrt(distance.x * distance.x + distance.y * distance.y);
|
return sqrt(distance.x * distance.x + distance.y * distance.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float selectBorderRadius(vec4 radi, vec2 position, vec2 center)
|
||||||
|
{
|
||||||
|
float rx = radi.x;
|
||||||
|
float ry = radi.y;
|
||||||
|
rx = position.x > center.x ? radi.y : radi.x;
|
||||||
|
ry = position.x > center.x ? radi.z : radi.w;
|
||||||
|
rx = position.y > center.y ? ry : rx;
|
||||||
|
return rx;
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
|
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
|
||||||
|
|
||||||
float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0);
|
float border_radius = selectBorderRadius(
|
||||||
|
v_BorderRadius,
|
||||||
|
fragCoord,
|
||||||
|
(v_Pos + v_Scale * 0.5).xy
|
||||||
|
);
|
||||||
|
|
||||||
|
float internal_border = max(border_radius - v_BorderWidth, 0.0);
|
||||||
|
|
||||||
float internal_distance = _distance(
|
float internal_distance = _distance(
|
||||||
fragCoord,
|
fragCoord,
|
||||||
|
|
@ -57,11 +73,11 @@ void main() {
|
||||||
fragCoord,
|
fragCoord,
|
||||||
v_Pos,
|
v_Pos,
|
||||||
v_Scale,
|
v_Scale,
|
||||||
v_BorderRadius
|
border_radius
|
||||||
);
|
);
|
||||||
|
|
||||||
float radius_alpha =
|
float radius_alpha =
|
||||||
1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d);
|
1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d);
|
||||||
|
|
||||||
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
|
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ attribute vec2 i_Pos;
|
||||||
attribute vec2 i_Scale;
|
attribute vec2 i_Scale;
|
||||||
attribute vec4 i_Color;
|
attribute vec4 i_Color;
|
||||||
attribute vec4 i_BorderColor;
|
attribute vec4 i_BorderColor;
|
||||||
attribute float i_BorderRadius;
|
attribute vec4 i_BorderRadius;
|
||||||
attribute float i_BorderWidth;
|
attribute float i_BorderWidth;
|
||||||
attribute vec2 q_Pos;
|
attribute vec2 q_Pos;
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ varying vec4 v_Color;
|
||||||
varying vec4 v_BorderColor;
|
varying vec4 v_BorderColor;
|
||||||
varying vec2 v_Pos;
|
varying vec2 v_Pos;
|
||||||
varying vec2 v_Scale;
|
varying vec2 v_Scale;
|
||||||
varying float v_BorderRadius;
|
varying vec4 v_BorderRadius;
|
||||||
varying float v_BorderWidth;
|
varying float v_BorderWidth;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,9 +21,11 @@ void main() {
|
||||||
vec2 p_Pos = i_Pos * u_Scale;
|
vec2 p_Pos = i_Pos * u_Scale;
|
||||||
vec2 p_Scale = i_Scale * u_Scale;
|
vec2 p_Scale = i_Scale * u_Scale;
|
||||||
|
|
||||||
float i_BorderRadius = min(
|
vec4 i_BorderRadius = vec4(
|
||||||
i_BorderRadius,
|
min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||||
min(i_Scale.x, i_Scale.y) / 2.0
|
min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||||
|
min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||||
|
min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
mat4 i_Transform = mat4(
|
mat4 i_Transform = mat4(
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ in vec4 v_Color;
|
||||||
in vec4 v_BorderColor;
|
in vec4 v_BorderColor;
|
||||||
in vec2 v_Pos;
|
in vec2 v_Pos;
|
||||||
in vec2 v_Scale;
|
in vec2 v_Scale;
|
||||||
in float v_BorderRadius;
|
in vec4 v_BorderRadius;
|
||||||
in float v_BorderWidth;
|
in float v_BorderWidth;
|
||||||
|
|
||||||
float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius)
|
float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius)
|
||||||
|
|
@ -38,14 +38,30 @@ float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius)
|
||||||
return sqrt(distance.x * distance.x + distance.y * distance.y);
|
return sqrt(distance.x * distance.x + distance.y * distance.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float selectBorderRadius(vec4 radi, vec2 position, vec2 center)
|
||||||
|
{
|
||||||
|
float rx = radi.x;
|
||||||
|
float ry = radi.y;
|
||||||
|
rx = position.x > center.x ? radi.y : radi.x;
|
||||||
|
ry = position.x > center.x ? radi.z : radi.w;
|
||||||
|
rx = position.y > center.y ? ry : rx;
|
||||||
|
return rx;
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 mixed_color;
|
vec4 mixed_color;
|
||||||
|
|
||||||
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
|
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
|
||||||
|
|
||||||
|
float border_radius = selectBorderRadius(
|
||||||
|
v_BorderRadius,
|
||||||
|
fragCoord,
|
||||||
|
(v_Pos + v_Scale * 0.5).xy
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: Remove branching (?)
|
// TODO: Remove branching (?)
|
||||||
if(v_BorderWidth > 0.0) {
|
if(v_BorderWidth > 0.0) {
|
||||||
float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0);
|
float internal_border = max(border_radius - v_BorderWidth, 0.0);
|
||||||
|
|
||||||
float internal_distance = fDistance(
|
float internal_distance = fDistance(
|
||||||
fragCoord,
|
fragCoord,
|
||||||
|
|
@ -69,11 +85,11 @@ void main() {
|
||||||
fragCoord,
|
fragCoord,
|
||||||
v_Pos,
|
v_Pos,
|
||||||
v_Scale,
|
v_Scale,
|
||||||
v_BorderRadius
|
border_radius
|
||||||
);
|
);
|
||||||
|
|
||||||
float radius_alpha =
|
float radius_alpha =
|
||||||
1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d);
|
1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d);
|
||||||
|
|
||||||
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
|
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ in vec2 i_Pos;
|
||||||
in vec2 i_Scale;
|
in vec2 i_Scale;
|
||||||
in vec4 i_Color;
|
in vec4 i_Color;
|
||||||
in vec4 i_BorderColor;
|
in vec4 i_BorderColor;
|
||||||
in float i_BorderRadius;
|
in vec4 i_BorderRadius;
|
||||||
in float i_BorderWidth;
|
in float i_BorderWidth;
|
||||||
|
|
||||||
out vec4 v_Color;
|
out vec4 v_Color;
|
||||||
out vec4 v_BorderColor;
|
out vec4 v_BorderColor;
|
||||||
out vec2 v_Pos;
|
out vec2 v_Pos;
|
||||||
out vec2 v_Scale;
|
out vec2 v_Scale;
|
||||||
out float v_BorderRadius;
|
out vec4 v_BorderRadius;
|
||||||
out float v_BorderWidth;
|
out float v_BorderWidth;
|
||||||
|
|
||||||
vec2 positions[4] = vec2[](
|
vec2 positions[4] = vec2[](
|
||||||
|
|
@ -27,9 +27,11 @@ void main() {
|
||||||
vec2 p_Pos = i_Pos * u_Scale;
|
vec2 p_Pos = i_Pos * u_Scale;
|
||||||
vec2 p_Scale = i_Scale * u_Scale;
|
vec2 p_Scale = i_Scale * u_Scale;
|
||||||
|
|
||||||
float i_BorderRadius = min(
|
vec4 i_BorderRadius = vec4(
|
||||||
i_BorderRadius,
|
min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||||
min(i_Scale.x, i_Scale.y) / 2.0
|
min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||||
|
min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||||
|
min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
mat4 i_Transform = mat4(
|
mat4 i_Transform = mat4(
|
||||||
|
|
|
||||||
|
|
@ -1,204 +1,145 @@
|
||||||
//! Draw meshes of triangles.
|
//! Draw meshes of triangles.
|
||||||
use crate::program::{self, Shader};
|
use crate::program;
|
||||||
use crate::Transformation;
|
use crate::Transformation;
|
||||||
|
|
||||||
|
use iced_graphics::gradient::Gradient;
|
||||||
|
use iced_graphics::layer::mesh::{self, Mesh};
|
||||||
|
use iced_graphics::triangle::{ColoredVertex2D, Vertex2D};
|
||||||
|
|
||||||
use glow::HasContext;
|
use glow::HasContext;
|
||||||
use iced_graphics::layer;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
|
const DEFAULT_VERTICES: usize = 1_000;
|
||||||
|
const DEFAULT_INDICES: usize = 1_000;
|
||||||
const VERTEX_BUFFER_SIZE: usize = 10_000;
|
|
||||||
const INDEX_BUFFER_SIZE: usize = 10_000;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Pipeline {
|
pub(crate) struct Pipeline {
|
||||||
program: <glow::Context as HasContext>::Program,
|
|
||||||
vertex_array: <glow::Context as HasContext>::VertexArray,
|
|
||||||
vertices: Buffer<Vertex2D>,
|
|
||||||
indices: Buffer<u32>,
|
indices: Buffer<u32>,
|
||||||
transform_location: <glow::Context as HasContext>::UniformLocation,
|
solid: solid::Program,
|
||||||
current_transform: Transformation,
|
gradient: gradient::Program,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pipeline {
|
impl Pipeline {
|
||||||
pub fn new(
|
pub fn new(gl: &glow::Context, shader_version: &program::Version) -> Self {
|
||||||
gl: &glow::Context,
|
let mut indices = unsafe {
|
||||||
shader_version: &program::Version,
|
|
||||||
) -> Pipeline {
|
|
||||||
let program = unsafe {
|
|
||||||
let vertex_shader = Shader::vertex(
|
|
||||||
gl,
|
|
||||||
shader_version,
|
|
||||||
include_str!("shader/common/triangle.vert"),
|
|
||||||
);
|
|
||||||
let fragment_shader = Shader::fragment(
|
|
||||||
gl,
|
|
||||||
shader_version,
|
|
||||||
include_str!("shader/common/triangle.frag"),
|
|
||||||
);
|
|
||||||
|
|
||||||
program::create(
|
|
||||||
gl,
|
|
||||||
&[vertex_shader, fragment_shader],
|
|
||||||
&[(0, "i_Position"), (1, "i_Color")],
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let transform_location =
|
|
||||||
unsafe { gl.get_uniform_location(program, "u_Transform") }
|
|
||||||
.expect("Get transform location");
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
gl.use_program(Some(program));
|
|
||||||
|
|
||||||
let transform: [f32; 16] = Transformation::identity().into();
|
|
||||||
gl.uniform_matrix_4_f32_slice(
|
|
||||||
Some(&transform_location),
|
|
||||||
false,
|
|
||||||
&transform,
|
|
||||||
);
|
|
||||||
|
|
||||||
gl.use_program(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let vertex_array =
|
|
||||||
unsafe { gl.create_vertex_array().expect("Create vertex array") };
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
gl.bind_vertex_array(Some(vertex_array));
|
|
||||||
}
|
|
||||||
|
|
||||||
let vertices = unsafe {
|
|
||||||
Buffer::new(
|
|
||||||
gl,
|
|
||||||
glow::ARRAY_BUFFER,
|
|
||||||
glow::DYNAMIC_DRAW,
|
|
||||||
VERTEX_BUFFER_SIZE,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let indices = unsafe {
|
|
||||||
Buffer::new(
|
Buffer::new(
|
||||||
gl,
|
gl,
|
||||||
glow::ELEMENT_ARRAY_BUFFER,
|
glow::ELEMENT_ARRAY_BUFFER,
|
||||||
glow::DYNAMIC_DRAW,
|
glow::DYNAMIC_DRAW,
|
||||||
INDEX_BUFFER_SIZE,
|
DEFAULT_INDICES,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let solid = solid::Program::new(gl, shader_version);
|
||||||
|
let gradient = gradient::Program::new(gl, shader_version);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let stride = std::mem::size_of::<Vertex2D>() as i32;
|
gl.bind_vertex_array(Some(solid.vertex_array));
|
||||||
|
indices.bind(gl, 0);
|
||||||
|
|
||||||
gl.enable_vertex_attrib_array(0);
|
gl.bind_vertex_array(Some(gradient.vertex_array));
|
||||||
gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0);
|
indices.bind(gl, 0);
|
||||||
|
|
||||||
gl.enable_vertex_attrib_array(1);
|
|
||||||
gl.vertex_attrib_pointer_f32(
|
|
||||||
1,
|
|
||||||
4,
|
|
||||||
glow::FLOAT,
|
|
||||||
false,
|
|
||||||
stride,
|
|
||||||
4 * 2,
|
|
||||||
);
|
|
||||||
|
|
||||||
gl.bind_vertex_array(None);
|
gl.bind_vertex_array(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Pipeline {
|
Self {
|
||||||
program,
|
|
||||||
vertex_array,
|
|
||||||
vertices,
|
|
||||||
indices,
|
indices,
|
||||||
transform_location,
|
solid,
|
||||||
current_transform: Transformation::identity(),
|
gradient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
meshes: &[Mesh<'_>],
|
||||||
gl: &glow::Context,
|
gl: &glow::Context,
|
||||||
target_height: u32,
|
target_height: u32,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
meshes: &[layer::Mesh<'_>],
|
|
||||||
) {
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
gl.enable(glow::MULTISAMPLE);
|
gl.enable(glow::MULTISAMPLE);
|
||||||
gl.enable(glow::SCISSOR_TEST);
|
gl.enable(glow::SCISSOR_TEST);
|
||||||
gl.use_program(Some(self.program));
|
|
||||||
gl.bind_vertex_array(Some(self.vertex_array));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This looks a bit crazy, but we are just counting how many vertices
|
// Count the total amount of vertices & indices we need to handle
|
||||||
// and indices we will need to handle.
|
let count = mesh::attribute_count_of(meshes);
|
||||||
// TODO: Improve readability
|
|
||||||
let (total_vertices, total_indices) = meshes
|
|
||||||
.iter()
|
|
||||||
.map(|layer::Mesh { buffers, .. }| {
|
|
||||||
(buffers.vertices.len(), buffers.indices.len())
|
|
||||||
})
|
|
||||||
.fold((0, 0), |(total_v, total_i), (v, i)| {
|
|
||||||
(total_v + v, total_i + i)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then we ensure the current buffers are big enough, resizing if
|
// Then we ensure the current attribute buffers are big enough, resizing if necessary
|
||||||
// necessary
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.vertices.bind(gl, total_vertices);
|
self.indices.bind(gl, count.indices);
|
||||||
self.indices.bind(gl, total_indices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We upload all the vertices and indices upfront
|
// We upload all the vertices and indices upfront
|
||||||
let mut last_vertex = 0;
|
let mut solid_vertex_offset = 0;
|
||||||
let mut last_index = 0;
|
let mut gradient_vertex_offset = 0;
|
||||||
|
let mut index_offset = 0;
|
||||||
|
|
||||||
|
for mesh in meshes {
|
||||||
|
let indices = mesh.indices();
|
||||||
|
|
||||||
for layer::Mesh { buffers, .. } in meshes {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
gl.buffer_sub_data_u8_slice(
|
gl.buffer_sub_data_u8_slice(
|
||||||
glow::ARRAY_BUFFER,
|
glow::ELEMENT_ARRAY_BUFFER,
|
||||||
(last_vertex * std::mem::size_of::<Vertex2D>()) as i32,
|
(index_offset * std::mem::size_of::<u32>()) as i32,
|
||||||
bytemuck::cast_slice(&buffers.vertices),
|
bytemuck::cast_slice(indices),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
index_offset += indices.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
match mesh {
|
||||||
|
Mesh::Solid { buffers, .. } => {
|
||||||
|
unsafe {
|
||||||
|
self.solid.vertices.bind(gl, count.solid_vertices);
|
||||||
|
|
||||||
gl.buffer_sub_data_u8_slice(
|
gl.buffer_sub_data_u8_slice(
|
||||||
glow::ELEMENT_ARRAY_BUFFER,
|
glow::ARRAY_BUFFER,
|
||||||
(last_index * std::mem::size_of::<u32>()) as i32,
|
(solid_vertex_offset
|
||||||
bytemuck::cast_slice(&buffers.indices),
|
* std::mem::size_of::<ColoredVertex2D>())
|
||||||
|
as i32,
|
||||||
|
bytemuck::cast_slice(&buffers.vertices),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
last_vertex += buffers.vertices.len();
|
solid_vertex_offset += buffers.vertices.len();
|
||||||
last_index += buffers.indices.len();
|
}
|
||||||
|
Mesh::Gradient { buffers, .. } => {
|
||||||
|
unsafe {
|
||||||
|
self.gradient
|
||||||
|
.vertices
|
||||||
|
.bind(gl, count.gradient_vertices);
|
||||||
|
|
||||||
|
gl.buffer_sub_data_u8_slice(
|
||||||
|
glow::ARRAY_BUFFER,
|
||||||
|
(gradient_vertex_offset
|
||||||
|
* std::mem::size_of::<Vertex2D>())
|
||||||
|
as i32,
|
||||||
|
bytemuck::cast_slice(&buffers.vertices),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gradient_vertex_offset += buffers.vertices.len();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then we draw each mesh using offsets
|
// Then we draw each mesh using offsets
|
||||||
let mut last_vertex = 0;
|
let mut last_solid_vertex = 0;
|
||||||
|
let mut last_gradient_vertex = 0;
|
||||||
let mut last_index = 0;
|
let mut last_index = 0;
|
||||||
|
|
||||||
for layer::Mesh {
|
for mesh in meshes {
|
||||||
buffers,
|
let indices = mesh.indices();
|
||||||
origin,
|
let origin = mesh.origin();
|
||||||
clip_bounds,
|
|
||||||
} in meshes
|
|
||||||
{
|
|
||||||
let transform =
|
let transform =
|
||||||
transformation * Transformation::translate(origin.x, origin.y);
|
transformation * Transformation::translate(origin.x, origin.y);
|
||||||
|
|
||||||
let clip_bounds = (*clip_bounds * scale_factor).snap();
|
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
if self.current_transform != transform {
|
|
||||||
let matrix: [f32; 16] = transform.into();
|
|
||||||
gl.uniform_matrix_4_f32_slice(
|
|
||||||
Some(&self.transform_location),
|
|
||||||
false,
|
|
||||||
&matrix,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.current_transform = transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.scissor(
|
gl.scissor(
|
||||||
clip_bounds.x as i32,
|
clip_bounds.x as i32,
|
||||||
(target_height - (clip_bounds.y + clip_bounds.height))
|
(target_height - (clip_bounds.y + clip_bounds.height))
|
||||||
|
|
@ -206,56 +147,138 @@ impl Pipeline {
|
||||||
clip_bounds.width as i32,
|
clip_bounds.width as i32,
|
||||||
clip_bounds.height as i32,
|
clip_bounds.height as i32,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
match mesh {
|
||||||
|
Mesh::Solid { buffers, .. } => unsafe {
|
||||||
|
gl.use_program(Some(self.solid.program));
|
||||||
|
gl.bind_vertex_array(Some(self.solid.vertex_array));
|
||||||
|
|
||||||
|
if transform != self.solid.uniforms.transform {
|
||||||
|
gl.uniform_matrix_4_f32_slice(
|
||||||
|
Some(&self.solid.uniforms.transform_location),
|
||||||
|
false,
|
||||||
|
transform.as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.solid.uniforms.transform = transform;
|
||||||
|
}
|
||||||
|
|
||||||
gl.draw_elements_base_vertex(
|
gl.draw_elements_base_vertex(
|
||||||
glow::TRIANGLES,
|
glow::TRIANGLES,
|
||||||
buffers.indices.len() as i32,
|
indices.len() as i32,
|
||||||
glow::UNSIGNED_INT,
|
glow::UNSIGNED_INT,
|
||||||
(last_index * std::mem::size_of::<u32>()) as i32,
|
(last_index * std::mem::size_of::<u32>()) as i32,
|
||||||
last_vertex as i32,
|
last_solid_vertex as i32,
|
||||||
);
|
);
|
||||||
|
|
||||||
last_vertex += buffers.vertices.len();
|
last_solid_vertex += buffers.vertices.len();
|
||||||
last_index += buffers.indices.len();
|
},
|
||||||
|
Mesh::Gradient {
|
||||||
|
buffers, gradient, ..
|
||||||
|
} => unsafe {
|
||||||
|
gl.use_program(Some(self.gradient.program));
|
||||||
|
gl.bind_vertex_array(Some(self.gradient.vertex_array));
|
||||||
|
|
||||||
|
if transform != self.gradient.uniforms.transform {
|
||||||
|
gl.uniform_matrix_4_f32_slice(
|
||||||
|
Some(&self.gradient.uniforms.locations.transform),
|
||||||
|
false,
|
||||||
|
transform.as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.gradient.uniforms.transform = transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if &self.gradient.uniforms.gradient != *gradient {
|
||||||
|
match gradient {
|
||||||
|
Gradient::Linear(linear) => {
|
||||||
|
gl.uniform_4_f32(
|
||||||
|
Some(
|
||||||
|
&self
|
||||||
|
.gradient
|
||||||
|
.uniforms
|
||||||
|
.locations
|
||||||
|
.gradient_direction,
|
||||||
|
),
|
||||||
|
linear.start.x,
|
||||||
|
linear.start.y,
|
||||||
|
linear.end.x,
|
||||||
|
linear.end.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.uniform_1_i32(
|
||||||
|
Some(
|
||||||
|
&self
|
||||||
|
.gradient
|
||||||
|
.uniforms
|
||||||
|
.locations
|
||||||
|
.color_stops_size,
|
||||||
|
),
|
||||||
|
(linear.color_stops.len() * 2) as i32,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut stops = [0.0; 128];
|
||||||
|
|
||||||
|
for (index, stop) in linear
|
||||||
|
.color_stops
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.take(16)
|
||||||
|
{
|
||||||
|
let [r, g, b, a] = stop.color.into_linear();
|
||||||
|
|
||||||
|
stops[index * 8] = r;
|
||||||
|
stops[(index * 8) + 1] = g;
|
||||||
|
stops[(index * 8) + 2] = b;
|
||||||
|
stops[(index * 8) + 3] = a;
|
||||||
|
stops[(index * 8) + 4] = stop.offset;
|
||||||
|
stops[(index * 8) + 5] = 0.;
|
||||||
|
stops[(index * 8) + 6] = 0.;
|
||||||
|
stops[(index * 8) + 7] = 0.;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.uniform_4_f32_slice(
|
||||||
|
Some(
|
||||||
|
&self
|
||||||
|
.gradient
|
||||||
|
.uniforms
|
||||||
|
.locations
|
||||||
|
.color_stops,
|
||||||
|
),
|
||||||
|
&stops,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gradient.uniforms.gradient = (*gradient).clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.draw_elements_base_vertex(
|
||||||
|
glow::TRIANGLES,
|
||||||
|
indices.len() as i32,
|
||||||
|
glow::UNSIGNED_INT,
|
||||||
|
(last_index * std::mem::size_of::<u32>()) as i32,
|
||||||
|
last_gradient_vertex as i32,
|
||||||
|
);
|
||||||
|
|
||||||
|
last_gradient_vertex += buffers.vertices.len();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
last_index += indices.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
gl.bind_vertex_array(None);
|
gl.bind_vertex_array(None);
|
||||||
gl.use_program(None);
|
|
||||||
gl.disable(glow::SCISSOR_TEST);
|
gl.disable(glow::SCISSOR_TEST);
|
||||||
gl.disable(glow::MULTISAMPLE);
|
gl.disable(glow::MULTISAMPLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
struct Uniforms {
|
|
||||||
transform: [f32; 16],
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl bytemuck::Zeroable for Uniforms {}
|
|
||||||
unsafe impl bytemuck::Pod for Uniforms {}
|
|
||||||
|
|
||||||
impl Default for Uniforms {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
transform: *Transformation::identity().as_ref(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Transformation> for Uniforms {
|
|
||||||
fn from(transformation: Transformation) -> Uniforms {
|
|
||||||
Self {
|
|
||||||
transform: transformation.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Buffer<T> {
|
pub struct Buffer<T> {
|
||||||
raw: <glow::Context as HasContext>::Buffer,
|
raw: <glow::Context as HasContext>::Buffer,
|
||||||
target: u32,
|
target: u32,
|
||||||
usage: u32,
|
usage: u32,
|
||||||
|
|
@ -299,3 +322,268 @@ impl<T> Buffer<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod solid {
|
||||||
|
use crate::program;
|
||||||
|
use crate::triangle;
|
||||||
|
use glow::{Context, HasContext, NativeProgram};
|
||||||
|
use iced_graphics::triangle::ColoredVertex2D;
|
||||||
|
use iced_graphics::Transformation;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Program {
|
||||||
|
pub program: <Context as HasContext>::Program,
|
||||||
|
pub vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||||
|
pub vertices: triangle::Buffer<ColoredVertex2D>,
|
||||||
|
pub uniforms: Uniforms,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Program {
|
||||||
|
pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
|
||||||
|
let program = unsafe {
|
||||||
|
let vertex_shader = program::Shader::vertex(
|
||||||
|
gl,
|
||||||
|
shader_version,
|
||||||
|
include_str!("shader/common/solid.vert"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let fragment_shader = program::Shader::fragment(
|
||||||
|
gl,
|
||||||
|
shader_version,
|
||||||
|
include_str!("shader/common/solid.frag"),
|
||||||
|
);
|
||||||
|
|
||||||
|
program::create(
|
||||||
|
gl,
|
||||||
|
&[vertex_shader, fragment_shader],
|
||||||
|
&[(0, "i_Position"), (1, "i_Color")],
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let vertex_array = unsafe {
|
||||||
|
gl.create_vertex_array().expect("Create vertex array")
|
||||||
|
};
|
||||||
|
|
||||||
|
let vertices = unsafe {
|
||||||
|
triangle::Buffer::new(
|
||||||
|
gl,
|
||||||
|
glow::ARRAY_BUFFER,
|
||||||
|
glow::DYNAMIC_DRAW,
|
||||||
|
super::DEFAULT_VERTICES,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl.bind_vertex_array(Some(vertex_array));
|
||||||
|
|
||||||
|
let stride = std::mem::size_of::<ColoredVertex2D>() as i32;
|
||||||
|
|
||||||
|
gl.enable_vertex_attrib_array(0);
|
||||||
|
gl.vertex_attrib_pointer_f32(
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
glow::FLOAT,
|
||||||
|
false,
|
||||||
|
stride,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.enable_vertex_attrib_array(1);
|
||||||
|
gl.vertex_attrib_pointer_f32(
|
||||||
|
1,
|
||||||
|
4,
|
||||||
|
glow::FLOAT,
|
||||||
|
false,
|
||||||
|
stride,
|
||||||
|
4 * 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.bind_vertex_array(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
program,
|
||||||
|
vertex_array,
|
||||||
|
vertices,
|
||||||
|
uniforms: Uniforms::new(gl, program),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Uniforms {
|
||||||
|
pub transform: Transformation,
|
||||||
|
pub transform_location: <Context as HasContext>::UniformLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Uniforms {
|
||||||
|
fn new(gl: &Context, program: NativeProgram) -> Self {
|
||||||
|
let transform = Transformation::identity();
|
||||||
|
let transform_location =
|
||||||
|
unsafe { gl.get_uniform_location(program, "u_Transform") }
|
||||||
|
.expect("Solid - Get u_Transform.");
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl.use_program(Some(program));
|
||||||
|
|
||||||
|
gl.uniform_matrix_4_f32_slice(
|
||||||
|
Some(&transform_location),
|
||||||
|
false,
|
||||||
|
transform.as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.use_program(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
transform,
|
||||||
|
transform_location,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod gradient {
|
||||||
|
use crate::program;
|
||||||
|
use crate::triangle;
|
||||||
|
use glow::{Context, HasContext, NativeProgram};
|
||||||
|
use iced_graphics::gradient::{self, Gradient};
|
||||||
|
use iced_graphics::triangle::Vertex2D;
|
||||||
|
use iced_graphics::Transformation;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Program {
|
||||||
|
pub program: <Context as HasContext>::Program,
|
||||||
|
pub vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||||
|
pub vertices: triangle::Buffer<Vertex2D>,
|
||||||
|
pub uniforms: Uniforms,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Program {
|
||||||
|
pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
|
||||||
|
let program = unsafe {
|
||||||
|
let vertex_shader = program::Shader::vertex(
|
||||||
|
gl,
|
||||||
|
shader_version,
|
||||||
|
include_str!("shader/common/gradient.vert"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let fragment_shader = program::Shader::fragment(
|
||||||
|
gl,
|
||||||
|
shader_version,
|
||||||
|
include_str!("shader/common/gradient.frag"),
|
||||||
|
);
|
||||||
|
|
||||||
|
program::create(
|
||||||
|
gl,
|
||||||
|
&[vertex_shader, fragment_shader],
|
||||||
|
&[(0, "i_Position")],
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let vertex_array = unsafe {
|
||||||
|
gl.create_vertex_array().expect("Create vertex array")
|
||||||
|
};
|
||||||
|
|
||||||
|
let vertices = unsafe {
|
||||||
|
triangle::Buffer::new(
|
||||||
|
gl,
|
||||||
|
glow::ARRAY_BUFFER,
|
||||||
|
glow::DYNAMIC_DRAW,
|
||||||
|
super::DEFAULT_VERTICES,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl.bind_vertex_array(Some(vertex_array));
|
||||||
|
|
||||||
|
let stride = std::mem::size_of::<Vertex2D>() as i32;
|
||||||
|
|
||||||
|
gl.enable_vertex_attrib_array(0);
|
||||||
|
gl.vertex_attrib_pointer_f32(
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
glow::FLOAT,
|
||||||
|
false,
|
||||||
|
stride,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.bind_vertex_array(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
program,
|
||||||
|
vertex_array,
|
||||||
|
vertices,
|
||||||
|
uniforms: Uniforms::new(gl, program),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Uniforms {
|
||||||
|
pub gradient: Gradient,
|
||||||
|
pub transform: Transformation,
|
||||||
|
pub locations: Locations,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Locations {
|
||||||
|
pub gradient_direction: <Context as HasContext>::UniformLocation,
|
||||||
|
pub color_stops_size: <Context as HasContext>::UniformLocation,
|
||||||
|
//currently the maximum number of stops is 16 due to lack of SSBO in GL2.1
|
||||||
|
pub color_stops: <Context as HasContext>::UniformLocation,
|
||||||
|
pub transform: <Context as HasContext>::UniformLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Uniforms {
|
||||||
|
fn new(gl: &Context, program: NativeProgram) -> Self {
|
||||||
|
let gradient_direction = unsafe {
|
||||||
|
gl.get_uniform_location(program, "gradient_direction")
|
||||||
|
}
|
||||||
|
.expect("Gradient - Get gradient_direction.");
|
||||||
|
|
||||||
|
let color_stops_size =
|
||||||
|
unsafe { gl.get_uniform_location(program, "color_stops_size") }
|
||||||
|
.expect("Gradient - Get color_stops_size.");
|
||||||
|
|
||||||
|
let color_stops = unsafe {
|
||||||
|
gl.get_uniform_location(program, "color_stops")
|
||||||
|
.expect("Gradient - Get color_stops.")
|
||||||
|
};
|
||||||
|
|
||||||
|
let transform = Transformation::identity();
|
||||||
|
let transform_location =
|
||||||
|
unsafe { gl.get_uniform_location(program, "u_Transform") }
|
||||||
|
.expect("Solid - Get u_Transform.");
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl.use_program(Some(program));
|
||||||
|
|
||||||
|
gl.uniform_matrix_4_f32_slice(
|
||||||
|
Some(&transform_location),
|
||||||
|
false,
|
||||||
|
transform.as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.use_program(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
gradient: Gradient::Linear(gradient::Linear {
|
||||||
|
start: Default::default(),
|
||||||
|
end: Default::default(),
|
||||||
|
color_stops: vec![],
|
||||||
|
}),
|
||||||
|
transform: Transformation::identity(),
|
||||||
|
locations: Locations {
|
||||||
|
gradient_direction,
|
||||||
|
color_stops_size,
|
||||||
|
color_stops,
|
||||||
|
transform: transform_location,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,11 @@ impl<Theme> iced_graphics::window::GLCompositor for Compositor<Theme> {
|
||||||
log::info!("{:#?}", settings);
|
log::info!("{:#?}", settings);
|
||||||
|
|
||||||
let version = gl.version();
|
let version = gl.version();
|
||||||
log::info!("Version: {:?}", version);
|
log::info!(
|
||||||
log::info!("Embedded: {}", version.is_embedded);
|
"OpenGL version: {:?} (Embedded: {})",
|
||||||
|
version,
|
||||||
|
version.is_embedded
|
||||||
|
);
|
||||||
|
|
||||||
let renderer = gl.get_parameter_string(glow::RENDERER);
|
let renderer = gl.get_parameter_string(glow::RENDERER);
|
||||||
log::info!("Renderer: {}", renderer);
|
log::info!("Renderer: {}", renderer);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_glutin"
|
name = "iced_glutin"
|
||||||
version = "0.3.0"
|
version = "0.5.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A glutin runtime for Iced"
|
description = "A glutin runtime for Iced"
|
||||||
|
|
@ -23,14 +23,15 @@ git = "https://github.com/iced-rs/glutin"
|
||||||
rev = "da8d291486b4c9bec12487a46c119c4b1d386abf"
|
rev = "da8d291486b4c9bec12487a46c119c4b1d386abf"
|
||||||
|
|
||||||
[dependencies.iced_native]
|
[dependencies.iced_native]
|
||||||
version = "0.5"
|
version = "0.7"
|
||||||
path = "../native"
|
path = "../native"
|
||||||
|
|
||||||
[dependencies.iced_winit]
|
[dependencies.iced_winit]
|
||||||
version = "0.4"
|
version = "0.6"
|
||||||
path = "../winit"
|
path = "../winit"
|
||||||
|
features = ["application"]
|
||||||
|
|
||||||
[dependencies.iced_graphics]
|
[dependencies.iced_graphics]
|
||||||
version = "0.3"
|
version = "0.5"
|
||||||
path = "../graphics"
|
path = "../graphics"
|
||||||
features = ["opengl"]
|
features = ["opengl"]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_graphics"
|
name = "iced_graphics"
|
||||||
version = "0.3.1"
|
version = "0.5.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
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"
|
||||||
|
|
@ -11,28 +11,44 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
||||||
categories = ["gui"]
|
categories = ["gui"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
svg = ["resvg", "usvg", "tiny-skia"]
|
||||||
|
image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"]
|
||||||
|
png = ["image_rs/png"]
|
||||||
|
jpeg = ["image_rs/jpeg"]
|
||||||
|
jpeg_rayon = ["image_rs/jpeg_rayon"]
|
||||||
|
gif = ["image_rs/gif"]
|
||||||
|
webp = ["image_rs/webp"]
|
||||||
|
pnm = ["image_rs/pnm"]
|
||||||
|
ico = ["image_rs/ico"]
|
||||||
|
bmp = ["image_rs/bmp"]
|
||||||
|
hdr = ["image_rs/hdr"]
|
||||||
|
dds = ["image_rs/dds"]
|
||||||
|
farbfeld = ["image_rs/farbfeld"]
|
||||||
canvas = ["lyon"]
|
canvas = ["lyon"]
|
||||||
qr_code = ["qrcode", "canvas"]
|
qr_code = ["qrcode", "canvas"]
|
||||||
font-source = ["font-kit"]
|
font-source = ["font-kit"]
|
||||||
font-fallback = []
|
font-fallback = []
|
||||||
font-icons = []
|
font-icons = []
|
||||||
opengl = []
|
opengl = []
|
||||||
|
image_rs = ["kamadak-exif"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam = "0.10"
|
glam = "0.21.3"
|
||||||
|
log = "0.4"
|
||||||
raw-window-handle = "0.5"
|
raw-window-handle = "0.5"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
bitflags = "1.2"
|
||||||
|
|
||||||
[dependencies.bytemuck]
|
[dependencies.bytemuck]
|
||||||
version = "1.4"
|
version = "1.4"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
|
|
||||||
[dependencies.iced_native]
|
[dependencies.iced_native]
|
||||||
version = "0.5"
|
version = "0.7"
|
||||||
path = "../native"
|
path = "../native"
|
||||||
|
|
||||||
[dependencies.iced_style]
|
[dependencies.iced_style]
|
||||||
version = "0.4"
|
version = "0.5"
|
||||||
path = "../style"
|
path = "../style"
|
||||||
|
|
||||||
[dependencies.lyon]
|
[dependencies.lyon]
|
||||||
|
|
@ -48,6 +64,28 @@ default-features = false
|
||||||
version = "0.10"
|
version = "0.10"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.image_rs]
|
||||||
|
version = "0.24"
|
||||||
|
package = "image"
|
||||||
|
default-features = false
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.resvg]
|
||||||
|
version = "0.18"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.usvg]
|
||||||
|
version = "0.18"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.tiny-skia]
|
||||||
|
version = "0.6"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.kamadak-exif]
|
||||||
|
version = "0.5"
|
||||||
|
optional = true
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
|
||||||
|
|
@ -66,11 +66,11 @@ pub trait Text {
|
||||||
/// A graphics backend that supports image rendering.
|
/// A graphics backend that supports image rendering.
|
||||||
pub trait Image {
|
pub trait Image {
|
||||||
/// Returns the dimensions of the provided image.
|
/// Returns the dimensions of the provided image.
|
||||||
fn dimensions(&self, handle: &image::Handle) -> (u32, u32);
|
fn dimensions(&self, handle: &image::Handle) -> Size<u32>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A graphics backend that supports SVG rendering.
|
/// A graphics backend that supports SVG rendering.
|
||||||
pub trait Svg {
|
pub trait Svg {
|
||||||
/// Returns the viewport dimensions of the provided SVG.
|
/// Returns the viewport dimensions of the provided SVG.
|
||||||
fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32);
|
fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
117
graphics/src/gradient.rs
Normal file
117
graphics/src/gradient.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
//! For creating a Gradient.
|
||||||
|
pub mod linear;
|
||||||
|
|
||||||
|
pub use linear::Linear;
|
||||||
|
|
||||||
|
use crate::{Color, Point, Size};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
|
||||||
|
/// or conically (TBD).
|
||||||
|
pub enum Gradient {
|
||||||
|
/// A linear gradient interpolates colors along a direction from its `start` to its `end`
|
||||||
|
/// point.
|
||||||
|
Linear(Linear),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gradient {
|
||||||
|
/// Creates a new linear [`linear::Builder`].
|
||||||
|
pub fn linear(position: impl Into<Position>) -> linear::Builder {
|
||||||
|
linear::Builder::new(position.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
/// A point along the gradient vector where the specified [`color`] is unmixed.
|
||||||
|
///
|
||||||
|
/// [`color`]: Self::color
|
||||||
|
pub struct ColorStop {
|
||||||
|
/// Offset along the gradient vector.
|
||||||
|
pub offset: f32,
|
||||||
|
|
||||||
|
/// The color of the gradient at the specified [`offset`].
|
||||||
|
///
|
||||||
|
/// [`offset`]: Self::offset
|
||||||
|
pub color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// The position of the gradient within its bounds.
|
||||||
|
pub enum Position {
|
||||||
|
/// The gradient will be positioned with respect to two points.
|
||||||
|
Absolute {
|
||||||
|
/// The starting point of the gradient.
|
||||||
|
start: Point,
|
||||||
|
/// The ending point of the gradient.
|
||||||
|
end: Point,
|
||||||
|
},
|
||||||
|
/// The gradient will be positioned relative to the provided bounds.
|
||||||
|
Relative {
|
||||||
|
/// The top left position of the bounds.
|
||||||
|
top_left: Point,
|
||||||
|
/// The width & height of the bounds.
|
||||||
|
size: Size,
|
||||||
|
/// The start [Location] of the gradient.
|
||||||
|
start: Location,
|
||||||
|
/// The end [Location] of the gradient.
|
||||||
|
end: Location,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(Point, Point)> for Position {
|
||||||
|
fn from((start, end): (Point, Point)) -> Self {
|
||||||
|
Self::Absolute { start, end }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// The location of a relatively-positioned gradient.
|
||||||
|
pub enum Location {
|
||||||
|
/// Top left.
|
||||||
|
TopLeft,
|
||||||
|
/// Top.
|
||||||
|
Top,
|
||||||
|
/// Top right.
|
||||||
|
TopRight,
|
||||||
|
/// Right.
|
||||||
|
Right,
|
||||||
|
/// Bottom right.
|
||||||
|
BottomRight,
|
||||||
|
/// Bottom.
|
||||||
|
Bottom,
|
||||||
|
/// Bottom left.
|
||||||
|
BottomLeft,
|
||||||
|
/// Left.
|
||||||
|
Left,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location {
|
||||||
|
fn to_absolute(&self, top_left: Point, size: Size) -> Point {
|
||||||
|
match self {
|
||||||
|
Location::TopLeft => top_left,
|
||||||
|
Location::Top => {
|
||||||
|
Point::new(top_left.x + size.width / 2.0, top_left.y)
|
||||||
|
}
|
||||||
|
Location::TopRight => {
|
||||||
|
Point::new(top_left.x + size.width, top_left.y)
|
||||||
|
}
|
||||||
|
Location::Right => Point::new(
|
||||||
|
top_left.x + size.width,
|
||||||
|
top_left.y + size.height / 2.0,
|
||||||
|
),
|
||||||
|
Location::BottomRight => {
|
||||||
|
Point::new(top_left.x + size.width, top_left.y + size.height)
|
||||||
|
}
|
||||||
|
Location::Bottom => Point::new(
|
||||||
|
top_left.x + size.width / 2.0,
|
||||||
|
top_left.y + size.height,
|
||||||
|
),
|
||||||
|
Location::BottomLeft => {
|
||||||
|
Point::new(top_left.x, top_left.y + size.height)
|
||||||
|
}
|
||||||
|
Location::Left => {
|
||||||
|
Point::new(top_left.x, top_left.y + size.height / 2.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
graphics/src/gradient/linear.rs
Normal file
112
graphics/src/gradient/linear.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
//! Linear gradient builder & definition.
|
||||||
|
use crate::gradient::{ColorStop, Gradient, Position};
|
||||||
|
use crate::{Color, Point};
|
||||||
|
|
||||||
|
/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`].
|
||||||
|
///
|
||||||
|
/// [`Fill`]: crate::widget::canvas::Fill
|
||||||
|
/// [`Stroke`]: crate::widget::canvas::Stroke
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Linear {
|
||||||
|
/// The point where the linear gradient begins.
|
||||||
|
pub start: Point,
|
||||||
|
/// The point where the linear gradient ends.
|
||||||
|
pub end: Point,
|
||||||
|
/// [`ColorStop`]s along the linear gradient path.
|
||||||
|
pub color_stops: Vec<ColorStop>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`Linear`] builder.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Builder {
|
||||||
|
start: Point,
|
||||||
|
end: Point,
|
||||||
|
stops: Vec<ColorStop>,
|
||||||
|
error: Option<BuilderError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
/// Creates a new [`Builder`].
|
||||||
|
pub fn new(position: Position) -> Self {
|
||||||
|
let (start, end) = match position {
|
||||||
|
Position::Absolute { start, end } => (start, end),
|
||||||
|
Position::Relative {
|
||||||
|
top_left,
|
||||||
|
size,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
} => (
|
||||||
|
start.to_absolute(top_left, size),
|
||||||
|
end.to_absolute(top_left, size),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
stops: vec![],
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new stop, defined by an offset and a color, to the gradient.
|
||||||
|
///
|
||||||
|
/// `offset` must be between `0.0` and `1.0` or the gradient cannot be built.
|
||||||
|
///
|
||||||
|
/// Note: when using the [`glow`] backend, any color stop added after the 16th
|
||||||
|
/// will not be displayed.
|
||||||
|
///
|
||||||
|
/// On the [`wgpu`] backend this limitation does not exist (technical limit is 524,288 stops).
|
||||||
|
///
|
||||||
|
/// [`glow`]: https://docs.rs/iced_glow
|
||||||
|
/// [`wgpu`]: https://docs.rs/iced_wgpu
|
||||||
|
pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
|
||||||
|
if offset.is_finite() && (0.0..=1.0).contains(&offset) {
|
||||||
|
match self.stops.binary_search_by(|stop| {
|
||||||
|
stop.offset.partial_cmp(&offset).unwrap()
|
||||||
|
}) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.error = Some(BuilderError::DuplicateOffset(offset))
|
||||||
|
}
|
||||||
|
Err(index) => {
|
||||||
|
self.stops.insert(index, ColorStop { offset, color });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.error = Some(BuilderError::InvalidOffset(offset))
|
||||||
|
};
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the linear [`Gradient`] of this [`Builder`].
|
||||||
|
///
|
||||||
|
/// Returns `BuilderError` if gradient in invalid.
|
||||||
|
pub fn build(self) -> Result<Gradient, BuilderError> {
|
||||||
|
if self.stops.is_empty() {
|
||||||
|
Err(BuilderError::MissingColorStop)
|
||||||
|
} else if let Some(error) = self.error {
|
||||||
|
Err(error)
|
||||||
|
} else {
|
||||||
|
Ok(Gradient::Linear(Linear {
|
||||||
|
start: self.start,
|
||||||
|
end: self.end,
|
||||||
|
color_stops: self.stops,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error that happened when building a [`Linear`] gradient.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum BuilderError {
|
||||||
|
#[error("Gradients must contain at least one color stop.")]
|
||||||
|
/// Gradients must contain at least one color stop.
|
||||||
|
MissingColorStop,
|
||||||
|
#[error("Offset {0} must be a unique, finite number.")]
|
||||||
|
/// Offsets in a gradient must all be unique & finite.
|
||||||
|
DuplicateOffset(f32),
|
||||||
|
#[error("Offset {0} must be between 0.0..=1.0.")]
|
||||||
|
/// Offsets in a gradient must be between 0.0..=1.0.
|
||||||
|
InvalidOffset(f32),
|
||||||
|
}
|
||||||
10
graphics/src/image.rs
Normal file
10
graphics/src/image.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
//! Render images.
|
||||||
|
#[cfg(feature = "image_rs")]
|
||||||
|
pub mod raster;
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
pub mod vector;
|
||||||
|
|
||||||
|
pub mod storage;
|
||||||
|
|
||||||
|
pub use storage::Storage;
|
||||||
|
|
@ -1,43 +1,53 @@
|
||||||
use crate::image::atlas::{self, Atlas};
|
//! Raster image loading and caching.
|
||||||
|
use crate::image::Storage;
|
||||||
|
use crate::Size;
|
||||||
|
|
||||||
use iced_native::image;
|
use iced_native::image;
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
/// Entry in cache corresponding to an image handle
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Memory {
|
pub enum Memory<T: Storage> {
|
||||||
Host(::image_rs::ImageBuffer<::image_rs::Bgra<u8>, Vec<u8>>),
|
/// Image data on host
|
||||||
Device(atlas::Entry),
|
Host(::image_rs::ImageBuffer<::image_rs::Rgba<u8>, Vec<u8>>),
|
||||||
|
/// Storage entry
|
||||||
|
Device(T::Entry),
|
||||||
|
/// Image not found
|
||||||
NotFound,
|
NotFound,
|
||||||
|
/// Invalid image data
|
||||||
Invalid,
|
Invalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Memory {
|
impl<T: Storage> Memory<T> {
|
||||||
pub fn dimensions(&self) -> (u32, u32) {
|
/// Width and height of image
|
||||||
|
pub fn dimensions(&self) -> Size<u32> {
|
||||||
|
use crate::image::storage::Entry;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Memory::Host(image) => image.dimensions(),
|
Memory::Host(image) => {
|
||||||
|
let (width, height) = image.dimensions();
|
||||||
|
|
||||||
|
Size::new(width, height)
|
||||||
|
}
|
||||||
Memory::Device(entry) => entry.size(),
|
Memory::Device(entry) => entry.size(),
|
||||||
Memory::NotFound => (1, 1),
|
Memory::NotFound => Size::new(1, 1),
|
||||||
Memory::Invalid => (1, 1),
|
Memory::Invalid => Size::new(1, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Caches image raster data
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Cache {
|
pub struct Cache<T: Storage> {
|
||||||
map: HashMap<u64, Memory>,
|
map: HashMap<u64, Memory<T>>,
|
||||||
hits: HashSet<u64>,
|
hits: HashSet<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cache {
|
impl<T: Storage> Cache<T> {
|
||||||
pub fn new() -> Self {
|
/// Load image
|
||||||
Self {
|
pub fn load(&mut self, handle: &image::Handle) -> &mut Memory<T> {
|
||||||
map: HashMap::new(),
|
|
||||||
hits: HashSet::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load(&mut self, handle: &image::Handle) -> &mut Memory {
|
|
||||||
if self.contains(handle) {
|
if self.contains(handle) {
|
||||||
return self.get(handle).unwrap();
|
return self.get(handle).unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +63,7 @@ impl Cache {
|
||||||
})
|
})
|
||||||
.unwrap_or_else(Operation::empty);
|
.unwrap_or_else(Operation::empty);
|
||||||
|
|
||||||
Memory::Host(operation.perform(image.to_bgra8()))
|
Memory::Host(operation.perform(image.to_rgba8()))
|
||||||
} else {
|
} else {
|
||||||
Memory::NotFound
|
Memory::NotFound
|
||||||
}
|
}
|
||||||
|
|
@ -65,12 +75,12 @@ impl Cache {
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap_or_else(Operation::empty);
|
.unwrap_or_else(Operation::empty);
|
||||||
|
|
||||||
Memory::Host(operation.perform(image.to_bgra8()))
|
Memory::Host(operation.perform(image.to_rgba8()))
|
||||||
} else {
|
} else {
|
||||||
Memory::Invalid
|
Memory::Invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
image::Data::Pixels {
|
image::Data::Rgba {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
pixels,
|
pixels,
|
||||||
|
|
@ -91,19 +101,19 @@ impl Cache {
|
||||||
self.get(handle).unwrap()
|
self.get(handle).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load image and upload raster data
|
||||||
pub fn upload(
|
pub fn upload(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &image::Handle,
|
handle: &image::Handle,
|
||||||
device: &wgpu::Device,
|
state: &mut T::State<'_>,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
storage: &mut T,
|
||||||
atlas: &mut Atlas,
|
) -> Option<&T::Entry> {
|
||||||
) -> Option<&atlas::Entry> {
|
|
||||||
let memory = self.load(handle);
|
let memory = self.load(handle);
|
||||||
|
|
||||||
if let Memory::Host(image) = memory {
|
if let Memory::Host(image) = memory {
|
||||||
let (width, height) = image.dimensions();
|
let (width, height) = image.dimensions();
|
||||||
|
|
||||||
let entry = atlas.upload(width, height, image, device, encoder)?;
|
let entry = storage.upload(width, height, image, state)?;
|
||||||
|
|
||||||
*memory = Memory::Device(entry);
|
*memory = Memory::Device(entry);
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +125,8 @@ impl Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trim(&mut self, atlas: &mut Atlas) {
|
/// Trim cache misses from cache
|
||||||
|
pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) {
|
||||||
let hits = &self.hits;
|
let hits = &self.hits;
|
||||||
|
|
||||||
self.map.retain(|k, memory| {
|
self.map.retain(|k, memory| {
|
||||||
|
|
@ -123,7 +134,7 @@ impl Cache {
|
||||||
|
|
||||||
if !retain {
|
if !retain {
|
||||||
if let Memory::Device(entry) = memory {
|
if let Memory::Device(entry) = memory {
|
||||||
atlas.remove(entry);
|
storage.remove(entry, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,13 +144,13 @@ impl Cache {
|
||||||
self.hits.clear();
|
self.hits.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
|
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory<T>> {
|
||||||
let _ = self.hits.insert(handle.id());
|
let _ = self.hits.insert(handle.id());
|
||||||
|
|
||||||
self.map.get_mut(&handle.id())
|
self.map.get_mut(&handle.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, handle: &image::Handle, memory: Memory) {
|
fn insert(&mut self, handle: &image::Handle, memory: Memory<T>) {
|
||||||
let _ = self.map.insert(handle.id(), memory);
|
let _ = self.map.insert(handle.id(), memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,6 +159,15 @@ impl Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Storage> Default for Cache<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
map: HashMap::new(),
|
||||||
|
hits: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct Operation: u8 {
|
struct Operation: u8 {
|
||||||
const FLIP_HORIZONTALLY = 0b001;
|
const FLIP_HORIZONTALLY = 0b001;
|
||||||
31
graphics/src/image/storage.rs
Normal file
31
graphics/src/image/storage.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
//! Store images.
|
||||||
|
use crate::Size;
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// Stores cached image data for use in rendering
|
||||||
|
pub trait Storage {
|
||||||
|
/// The type of an [`Entry`] in the [`Storage`].
|
||||||
|
type Entry: Entry;
|
||||||
|
|
||||||
|
/// State provided to upload or remove a [`Self::Entry`].
|
||||||
|
type State<'a>;
|
||||||
|
|
||||||
|
/// Upload the image data of a [`Self::Entry`].
|
||||||
|
fn upload(
|
||||||
|
&mut self,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
data: &[u8],
|
||||||
|
state: &mut Self::State<'_>,
|
||||||
|
) -> Option<Self::Entry>;
|
||||||
|
|
||||||
|
/// Romve a [`Self::Entry`] from the [`Storage`].
|
||||||
|
fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An entry in some [`Storage`],
|
||||||
|
pub trait Entry: Debug {
|
||||||
|
/// The [`Size`] of the [`Entry`].
|
||||||
|
fn size(&self) -> Size<u32>;
|
||||||
|
}
|
||||||
|
|
@ -1,46 +1,48 @@
|
||||||
use crate::image::atlas::{self, Atlas};
|
//! Vector image loading and caching
|
||||||
|
use crate::image::Storage;
|
||||||
|
use crate::Color;
|
||||||
|
|
||||||
use iced_native::svg;
|
use iced_native::svg;
|
||||||
|
use iced_native::Size;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
/// Entry in cache corresponding to an svg handle
|
||||||
pub enum Svg {
|
pub enum Svg {
|
||||||
|
/// Parsed svg
|
||||||
Loaded(usvg::Tree),
|
Loaded(usvg::Tree),
|
||||||
|
/// Svg not found or failed to parse
|
||||||
NotFound,
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Svg {
|
impl Svg {
|
||||||
pub fn viewport_dimensions(&self) -> (u32, u32) {
|
/// Viewport width and height
|
||||||
|
pub fn viewport_dimensions(&self) -> Size<u32> {
|
||||||
match self {
|
match self {
|
||||||
Svg::Loaded(tree) => {
|
Svg::Loaded(tree) => {
|
||||||
let size = tree.svg_node().size;
|
let size = tree.svg_node().size;
|
||||||
|
|
||||||
(size.width() as u32, size.height() as u32)
|
Size::new(size.width() as u32, size.height() as u32)
|
||||||
}
|
}
|
||||||
Svg::NotFound => (1, 1),
|
Svg::NotFound => Size::new(1, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Caches svg vector and raster data
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Cache {
|
pub struct Cache<T: Storage> {
|
||||||
svgs: HashMap<u64, Svg>,
|
svgs: HashMap<u64, Svg>,
|
||||||
rasterized: HashMap<(u64, u32, u32), atlas::Entry>,
|
rasterized: HashMap<(u64, u32, u32, ColorFilter), T::Entry>,
|
||||||
svg_hits: HashSet<u64>,
|
svg_hits: HashSet<u64>,
|
||||||
rasterized_hits: HashSet<(u64, u32, u32)>,
|
rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cache {
|
type ColorFilter = Option<[u8; 4]>;
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
svgs: HashMap::new(),
|
|
||||||
rasterized: HashMap::new(),
|
|
||||||
svg_hits: HashSet::new(),
|
|
||||||
rasterized_hits: HashSet::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl<T: Storage> Cache<T> {
|
||||||
|
/// Load svg
|
||||||
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
|
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
|
||||||
if self.svgs.contains_key(&handle.id()) {
|
if self.svgs.contains_key(&handle.id()) {
|
||||||
return self.svgs.get(&handle.id()).unwrap();
|
return self.svgs.get(&handle.id()).unwrap();
|
||||||
|
|
@ -73,15 +75,16 @@ impl Cache {
|
||||||
self.svgs.get(&handle.id()).unwrap()
|
self.svgs.get(&handle.id()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load svg and upload raster data
|
||||||
pub fn upload(
|
pub fn upload(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &svg::Handle,
|
handle: &svg::Handle,
|
||||||
|
color: Option<Color>,
|
||||||
[width, height]: [f32; 2],
|
[width, height]: [f32; 2],
|
||||||
scale: f32,
|
scale: f32,
|
||||||
device: &wgpu::Device,
|
state: &mut T::State<'_>,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
storage: &mut T,
|
||||||
texture_atlas: &mut Atlas,
|
) -> Option<&T::Entry> {
|
||||||
) -> Option<&atlas::Entry> {
|
|
||||||
let id = handle.id();
|
let id = handle.id();
|
||||||
|
|
||||||
let (width, height) = (
|
let (width, height) = (
|
||||||
|
|
@ -89,15 +92,18 @@ impl Cache {
|
||||||
(scale * height).ceil() as u32,
|
(scale * height).ceil() as u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let color = color.map(Color::into_rgba8);
|
||||||
|
let key = (id, width, height, color);
|
||||||
|
|
||||||
// TODO: Optimize!
|
// TODO: Optimize!
|
||||||
// We currently rerasterize the SVG when its size changes. This is slow
|
// We currently rerasterize the SVG when its size changes. This is slow
|
||||||
// as heck. A GPU rasterizer like `pathfinder` may perform better.
|
// as heck. A GPU rasterizer like `pathfinder` may perform better.
|
||||||
// It would be cool to be able to smooth resize the `svg` example.
|
// It would be cool to be able to smooth resize the `svg` example.
|
||||||
if self.rasterized.contains_key(&(id, width, height)) {
|
if self.rasterized.contains_key(&key) {
|
||||||
let _ = self.svg_hits.insert(id);
|
let _ = self.svg_hits.insert(id);
|
||||||
let _ = self.rasterized_hits.insert((id, width, height));
|
let _ = self.rasterized_hits.insert(key);
|
||||||
|
|
||||||
return self.rasterized.get(&(id, width, height));
|
return self.rasterized.get(&key);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.load(handle) {
|
match self.load(handle) {
|
||||||
|
|
@ -123,28 +129,32 @@ impl Cache {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut rgba = img.take();
|
let mut rgba = img.take();
|
||||||
rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2));
|
|
||||||
|
|
||||||
let allocation = texture_atlas.upload(
|
if let Some(color) = color {
|
||||||
width,
|
rgba.chunks_exact_mut(4).for_each(|rgba| {
|
||||||
height,
|
if rgba[3] > 0 {
|
||||||
bytemuck::cast_slice(rgba.as_slice()),
|
rgba[0] = color[0];
|
||||||
device,
|
rgba[1] = color[1];
|
||||||
encoder,
|
rgba[2] = color[2];
|
||||||
)?;
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let allocation = storage.upload(width, height, &rgba, state)?;
|
||||||
log::debug!("allocating {} {}x{}", id, width, height);
|
log::debug!("allocating {} {}x{}", id, width, height);
|
||||||
|
|
||||||
let _ = self.svg_hits.insert(id);
|
let _ = self.svg_hits.insert(id);
|
||||||
let _ = self.rasterized_hits.insert((id, width, height));
|
let _ = self.rasterized_hits.insert(key);
|
||||||
let _ = self.rasterized.insert((id, width, height), allocation);
|
let _ = self.rasterized.insert(key, allocation);
|
||||||
|
|
||||||
self.rasterized.get(&(id, width, height))
|
self.rasterized.get(&key)
|
||||||
}
|
}
|
||||||
Svg::NotFound => None,
|
Svg::NotFound => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trim(&mut self, atlas: &mut Atlas) {
|
/// Load svg and upload raster data
|
||||||
|
pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) {
|
||||||
let svg_hits = &self.svg_hits;
|
let svg_hits = &self.svg_hits;
|
||||||
let rasterized_hits = &self.rasterized_hits;
|
let rasterized_hits = &self.rasterized_hits;
|
||||||
|
|
||||||
|
|
@ -153,7 +163,7 @@ impl Cache {
|
||||||
let retain = rasterized_hits.contains(k);
|
let retain = rasterized_hits.contains(k);
|
||||||
|
|
||||||
if !retain {
|
if !retain {
|
||||||
atlas.remove(entry);
|
storage.remove(entry, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
retain
|
retain
|
||||||
|
|
@ -163,6 +173,17 @@ impl Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Storage> Default for Cache<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
svgs: HashMap::new(),
|
||||||
|
rasterized: HashMap::new(),
|
||||||
|
svg_hits: HashSet::new(),
|
||||||
|
rasterized_hits: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Svg {
|
impl std::fmt::Debug for Svg {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
//! Organize rendering primitives into a flattened list of layers.
|
//! Organize rendering primitives into a flattened list of layers.
|
||||||
|
mod image;
|
||||||
|
mod quad;
|
||||||
|
mod text;
|
||||||
|
|
||||||
|
pub mod mesh;
|
||||||
|
|
||||||
|
pub use image::Image;
|
||||||
|
pub use mesh::Mesh;
|
||||||
|
pub use quad::Quad;
|
||||||
|
pub use text::Text;
|
||||||
|
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::triangle;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport,
|
Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport,
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced_native::image;
|
|
||||||
use iced_native::svg;
|
|
||||||
|
|
||||||
/// A group of primitives that should be clipped together.
|
/// A group of primitives that should be clipped together.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct Layer<'a> {
|
pub struct Layer<'a> {
|
||||||
/// The clipping bounds of the [`Layer`].
|
/// The clipping bounds of the [`Layer`].
|
||||||
pub bounds: Rectangle,
|
pub bounds: Rectangle,
|
||||||
|
|
@ -159,7 +166,7 @@ impl<'a> Layer<'a> {
|
||||||
border_color: border_color.into_linear(),
|
border_color: border_color.into_linear(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Primitive::Mesh2D { buffers, size } => {
|
Primitive::SolidMesh { buffers, size } => {
|
||||||
let layer = &mut layers[current_layer];
|
let layer = &mut layers[current_layer];
|
||||||
|
|
||||||
let bounds = Rectangle::new(
|
let bounds = Rectangle::new(
|
||||||
|
|
@ -169,13 +176,35 @@ impl<'a> Layer<'a> {
|
||||||
|
|
||||||
// Only draw visible content
|
// Only draw visible content
|
||||||
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
|
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
|
||||||
layer.meshes.push(Mesh {
|
layer.meshes.push(Mesh::Solid {
|
||||||
origin: Point::new(translation.x, translation.y),
|
origin: Point::new(translation.x, translation.y),
|
||||||
buffers,
|
buffers,
|
||||||
clip_bounds,
|
clip_bounds,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Primitive::GradientMesh {
|
||||||
|
buffers,
|
||||||
|
size,
|
||||||
|
gradient,
|
||||||
|
} => {
|
||||||
|
let layer = &mut layers[current_layer];
|
||||||
|
|
||||||
|
let bounds = Rectangle::new(
|
||||||
|
Point::new(translation.x, translation.y),
|
||||||
|
*size,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only draw visible content
|
||||||
|
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
|
||||||
|
layer.meshes.push(Mesh::Gradient {
|
||||||
|
origin: Point::new(translation.x, translation.y),
|
||||||
|
buffers,
|
||||||
|
clip_bounds,
|
||||||
|
gradient,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Primitive::Clip { bounds, content } => {
|
Primitive::Clip { bounds, content } => {
|
||||||
let layer = &mut layers[current_layer];
|
let layer = &mut layers[current_layer];
|
||||||
let translated_bounds = *bounds + translation;
|
let translated_bounds = *bounds + translation;
|
||||||
|
|
@ -222,104 +251,19 @@ impl<'a> Layer<'a> {
|
||||||
bounds: *bounds + translation,
|
bounds: *bounds + translation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Primitive::Svg { handle, bounds } => {
|
Primitive::Svg {
|
||||||
|
handle,
|
||||||
|
color,
|
||||||
|
bounds,
|
||||||
|
} => {
|
||||||
let layer = &mut layers[current_layer];
|
let layer = &mut layers[current_layer];
|
||||||
|
|
||||||
layer.images.push(Image::Vector {
|
layer.images.push(Image::Vector {
|
||||||
handle: handle.clone(),
|
handle: handle.clone(),
|
||||||
|
color: *color,
|
||||||
bounds: *bounds + translation,
|
bounds: *bounds + translation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A colored rectangle with a border.
|
|
||||||
///
|
|
||||||
/// This type can be directly uploaded to GPU memory.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Quad {
|
|
||||||
/// The position of the [`Quad`].
|
|
||||||
pub position: [f32; 2],
|
|
||||||
|
|
||||||
/// The size of the [`Quad`].
|
|
||||||
pub size: [f32; 2],
|
|
||||||
|
|
||||||
/// The color of the [`Quad`], in __linear RGB__.
|
|
||||||
pub color: [f32; 4],
|
|
||||||
|
|
||||||
/// The border color of the [`Quad`], in __linear RGB__.
|
|
||||||
pub border_color: [f32; 4],
|
|
||||||
|
|
||||||
/// The border radius of the [`Quad`].
|
|
||||||
pub border_radius: f32,
|
|
||||||
|
|
||||||
/// The border width of the [`Quad`].
|
|
||||||
pub border_width: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A mesh of triangles.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct Mesh<'a> {
|
|
||||||
/// The origin of the vertices of the [`Mesh`].
|
|
||||||
pub origin: Point,
|
|
||||||
|
|
||||||
/// The vertex and index buffers of the [`Mesh`].
|
|
||||||
pub buffers: &'a triangle::Mesh2D,
|
|
||||||
|
|
||||||
/// The clipping bounds of the [`Mesh`].
|
|
||||||
pub clip_bounds: Rectangle<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A paragraph of text.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct Text<'a> {
|
|
||||||
/// The content of the [`Text`].
|
|
||||||
pub content: &'a str,
|
|
||||||
|
|
||||||
/// The layout bounds of the [`Text`].
|
|
||||||
pub bounds: Rectangle,
|
|
||||||
|
|
||||||
/// The color of the [`Text`], in __linear RGB_.
|
|
||||||
pub color: [f32; 4],
|
|
||||||
|
|
||||||
/// The size of the [`Text`].
|
|
||||||
pub size: f32,
|
|
||||||
|
|
||||||
/// The font of the [`Text`].
|
|
||||||
pub font: Font,
|
|
||||||
|
|
||||||
/// The horizontal alignment of the [`Text`].
|
|
||||||
pub horizontal_alignment: alignment::Horizontal,
|
|
||||||
|
|
||||||
/// The vertical alignment of the [`Text`].
|
|
||||||
pub vertical_alignment: alignment::Vertical,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A raster or vector image.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Image {
|
|
||||||
/// A raster image.
|
|
||||||
Raster {
|
|
||||||
/// The handle of a raster image.
|
|
||||||
handle: image::Handle,
|
|
||||||
|
|
||||||
/// The bounds of the image.
|
|
||||||
bounds: Rectangle,
|
|
||||||
},
|
|
||||||
/// A vector image.
|
|
||||||
Vector {
|
|
||||||
/// The handle of a vector image.
|
|
||||||
handle: svg::Handle,
|
|
||||||
|
|
||||||
/// The bounds of the image.
|
|
||||||
bounds: Rectangle,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
unsafe impl bytemuck::Zeroable for Quad {}
|
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
unsafe impl bytemuck::Pod for Quad {}
|
|
||||||
|
|
|
||||||
27
graphics/src/layer/image.rs
Normal file
27
graphics/src/layer/image.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::{Color, Rectangle};
|
||||||
|
|
||||||
|
use iced_native::{image, svg};
|
||||||
|
|
||||||
|
/// A raster or vector image.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Image {
|
||||||
|
/// A raster image.
|
||||||
|
Raster {
|
||||||
|
/// The handle of a raster image.
|
||||||
|
handle: image::Handle,
|
||||||
|
|
||||||
|
/// The bounds of the image.
|
||||||
|
bounds: Rectangle,
|
||||||
|
},
|
||||||
|
/// A vector image.
|
||||||
|
Vector {
|
||||||
|
/// The handle of a vector image.
|
||||||
|
handle: svg::Handle,
|
||||||
|
|
||||||
|
/// The [`Color`] filter
|
||||||
|
color: Option<Color>,
|
||||||
|
|
||||||
|
/// The bounds of the image.
|
||||||
|
bounds: Rectangle,
|
||||||
|
},
|
||||||
|
}
|
||||||
93
graphics/src/layer/mesh.rs
Normal file
93
graphics/src/layer/mesh.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
//! A collection of triangle primitives.
|
||||||
|
use crate::triangle;
|
||||||
|
use crate::{Gradient, Point, Rectangle};
|
||||||
|
|
||||||
|
/// A mesh of triangles.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Mesh<'a> {
|
||||||
|
/// A mesh of triangles with a solid color.
|
||||||
|
Solid {
|
||||||
|
/// The origin of the vertices of the [`Mesh`].
|
||||||
|
origin: Point,
|
||||||
|
|
||||||
|
/// The vertex and index buffers of the [`Mesh`].
|
||||||
|
buffers: &'a triangle::Mesh2D<triangle::ColoredVertex2D>,
|
||||||
|
|
||||||
|
/// The clipping bounds of the [`Mesh`].
|
||||||
|
clip_bounds: Rectangle<f32>,
|
||||||
|
},
|
||||||
|
/// A mesh of triangles with a gradient color.
|
||||||
|
Gradient {
|
||||||
|
/// The origin of the vertices of the [`Mesh`].
|
||||||
|
origin: Point,
|
||||||
|
|
||||||
|
/// The vertex and index buffers of the [`Mesh`].
|
||||||
|
buffers: &'a triangle::Mesh2D<triangle::Vertex2D>,
|
||||||
|
|
||||||
|
/// The clipping bounds of the [`Mesh`].
|
||||||
|
clip_bounds: Rectangle<f32>,
|
||||||
|
|
||||||
|
/// The gradient to apply to the [`Mesh`].
|
||||||
|
gradient: &'a Gradient,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mesh<'_> {
|
||||||
|
/// Returns the origin of the [`Mesh`].
|
||||||
|
pub fn origin(&self) -> Point {
|
||||||
|
match self {
|
||||||
|
Self::Solid { origin, .. } | Self::Gradient { origin, .. } => {
|
||||||
|
*origin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the indices of the [`Mesh`].
|
||||||
|
pub fn indices(&self) -> &[u32] {
|
||||||
|
match self {
|
||||||
|
Self::Solid { buffers, .. } => &buffers.indices,
|
||||||
|
Self::Gradient { buffers, .. } => &buffers.indices,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the clip bounds of the [`Mesh`].
|
||||||
|
pub fn clip_bounds(&self) -> Rectangle<f32> {
|
||||||
|
match self {
|
||||||
|
Self::Solid { clip_bounds, .. }
|
||||||
|
| Self::Gradient { clip_bounds, .. } => *clip_bounds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result of counting the attributes of a set of meshes.
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct AttributeCount {
|
||||||
|
/// The total amount of solid vertices.
|
||||||
|
pub solid_vertices: usize,
|
||||||
|
|
||||||
|
/// The total amount of gradient vertices.
|
||||||
|
pub gradient_vertices: usize,
|
||||||
|
|
||||||
|
/// The total amount of indices.
|
||||||
|
pub indices: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
|
||||||
|
pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
|
||||||
|
meshes
|
||||||
|
.iter()
|
||||||
|
.fold(AttributeCount::default(), |mut count, mesh| {
|
||||||
|
match mesh {
|
||||||
|
Mesh::Solid { buffers, .. } => {
|
||||||
|
count.solid_vertices += buffers.vertices.len();
|
||||||
|
count.indices += buffers.indices.len();
|
||||||
|
}
|
||||||
|
Mesh::Gradient { buffers, .. } => {
|
||||||
|
count.gradient_vertices += buffers.vertices.len();
|
||||||
|
count.indices += buffers.indices.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count
|
||||||
|
})
|
||||||
|
}
|
||||||
30
graphics/src/layer/quad.rs
Normal file
30
graphics/src/layer/quad.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/// A colored rectangle with a border.
|
||||||
|
///
|
||||||
|
/// This type can be directly uploaded to GPU memory.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Quad {
|
||||||
|
/// The position of the [`Quad`].
|
||||||
|
pub position: [f32; 2],
|
||||||
|
|
||||||
|
/// The size of the [`Quad`].
|
||||||
|
pub size: [f32; 2],
|
||||||
|
|
||||||
|
/// The color of the [`Quad`], in __linear RGB__.
|
||||||
|
pub color: [f32; 4],
|
||||||
|
|
||||||
|
/// The border color of the [`Quad`], in __linear RGB__.
|
||||||
|
pub border_color: [f32; 4],
|
||||||
|
|
||||||
|
/// The border radius of the [`Quad`].
|
||||||
|
pub border_radius: [f32; 4],
|
||||||
|
|
||||||
|
/// The border width of the [`Quad`].
|
||||||
|
pub border_width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
unsafe impl bytemuck::Zeroable for Quad {}
|
||||||
|
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
unsafe impl bytemuck::Pod for Quad {}
|
||||||
26
graphics/src/layer/text.rs
Normal file
26
graphics/src/layer/text.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use crate::{alignment, Font, Rectangle};
|
||||||
|
|
||||||
|
/// A paragraph of text.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Text<'a> {
|
||||||
|
/// The content of the [`Text`].
|
||||||
|
pub content: &'a str,
|
||||||
|
|
||||||
|
/// The layout bounds of the [`Text`].
|
||||||
|
pub bounds: Rectangle,
|
||||||
|
|
||||||
|
/// The color of the [`Text`], in __linear RGB_.
|
||||||
|
pub color: [f32; 4],
|
||||||
|
|
||||||
|
/// The size of the [`Text`].
|
||||||
|
pub size: f32,
|
||||||
|
|
||||||
|
/// The font of the [`Text`].
|
||||||
|
pub font: Font,
|
||||||
|
|
||||||
|
/// The horizontal alignment of the [`Text`].
|
||||||
|
pub horizontal_alignment: alignment::Horizontal,
|
||||||
|
|
||||||
|
/// The vertical alignment of the [`Text`].
|
||||||
|
pub vertical_alignment: alignment::Vertical,
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,8 @@ mod viewport;
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
|
pub mod gradient;
|
||||||
|
pub mod image;
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
pub mod overlay;
|
pub mod overlay;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
|
|
@ -39,6 +41,7 @@ pub mod window;
|
||||||
pub use antialiasing::Antialiasing;
|
pub use antialiasing::Antialiasing;
|
||||||
pub use backend::Backend;
|
pub use backend::Backend;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
pub use gradient::Gradient;
|
||||||
pub use layer::Layer;
|
pub use layer::Layer;
|
||||||
pub use primitive::Primitive;
|
pub use primitive::Primitive;
|
||||||
pub use renderer::Renderer;
|
pub use renderer::Renderer;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use iced_native::svg;
|
||||||
use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
|
use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
|
||||||
|
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
|
use crate::gradient::Gradient;
|
||||||
use crate::triangle;
|
use crate::triangle;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -41,7 +42,7 @@ pub enum Primitive {
|
||||||
/// The background of the quad
|
/// The background of the quad
|
||||||
background: Background,
|
background: Background,
|
||||||
/// The border radius of the quad
|
/// The border radius of the quad
|
||||||
border_radius: f32,
|
border_radius: [f32; 4],
|
||||||
/// The border width of the quad
|
/// The border width of the quad
|
||||||
border_width: f32,
|
border_width: f32,
|
||||||
/// The border color of the quad
|
/// The border color of the quad
|
||||||
|
|
@ -59,6 +60,9 @@ pub enum Primitive {
|
||||||
/// The path of the SVG file
|
/// The path of the SVG file
|
||||||
handle: svg::Handle,
|
handle: svg::Handle,
|
||||||
|
|
||||||
|
/// The [`Color`] filter
|
||||||
|
color: Option<Color>,
|
||||||
|
|
||||||
/// The bounds of the viewport
|
/// The bounds of the viewport
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
},
|
},
|
||||||
|
|
@ -77,18 +81,33 @@ pub enum Primitive {
|
||||||
/// The primitive to translate
|
/// The primitive to translate
|
||||||
content: Box<Primitive>,
|
content: Box<Primitive>,
|
||||||
},
|
},
|
||||||
/// A low-level primitive to render a mesh of triangles.
|
/// A low-level primitive to render a mesh of triangles with a solid color.
|
||||||
///
|
///
|
||||||
/// It can be used to render many kinds of geometry freely.
|
/// It can be used to render many kinds of geometry freely.
|
||||||
Mesh2D {
|
SolidMesh {
|
||||||
/// The vertex and index buffers of the mesh
|
/// The vertices and indices of the mesh.
|
||||||
buffers: triangle::Mesh2D,
|
buffers: triangle::Mesh2D<triangle::ColoredVertex2D>,
|
||||||
|
|
||||||
/// The size of the drawable region of the mesh.
|
/// The size of the drawable region of the mesh.
|
||||||
///
|
///
|
||||||
/// Any geometry that falls out of this region will be clipped.
|
/// Any geometry that falls out of this region will be clipped.
|
||||||
size: Size,
|
size: Size,
|
||||||
},
|
},
|
||||||
|
/// A low-level primitive to render a mesh of triangles with a gradient.
|
||||||
|
///
|
||||||
|
/// It can be used to render many kinds of geometry freely.
|
||||||
|
GradientMesh {
|
||||||
|
/// The vertices and indices of the mesh.
|
||||||
|
buffers: triangle::Mesh2D<triangle::Vertex2D>,
|
||||||
|
|
||||||
|
/// The size of the drawable region of the mesh.
|
||||||
|
///
|
||||||
|
/// Any geometry that falls out of this region will be clipped.
|
||||||
|
size: Size,
|
||||||
|
|
||||||
|
/// The [`Gradient`] to apply to the mesh.
|
||||||
|
gradient: Gradient,
|
||||||
|
},
|
||||||
/// A cached primitive.
|
/// A cached primitive.
|
||||||
///
|
///
|
||||||
/// This can be useful if you are implementing a widget where primitive
|
/// This can be useful if you are implementing a widget where primitive
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use iced_native::layout;
|
||||||
use iced_native::renderer;
|
use iced_native::renderer;
|
||||||
use iced_native::svg;
|
use iced_native::svg;
|
||||||
use iced_native::text::{self, Text};
|
use iced_native::text::{self, Text};
|
||||||
use iced_native::{Background, Element, Font, Point, Rectangle, Size};
|
use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size};
|
||||||
|
|
||||||
pub use iced_native::renderer::Style;
|
pub use iced_native::renderer::Style;
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ where
|
||||||
self.primitives.push(Primitive::Quad {
|
self.primitives.push(Primitive::Quad {
|
||||||
bounds: quad.bounds,
|
bounds: quad.bounds,
|
||||||
background: background.into(),
|
background: background.into(),
|
||||||
border_radius: quad.border_radius,
|
border_radius: quad.border_radius.into(),
|
||||||
border_width: quad.border_width,
|
border_width: quad.border_width,
|
||||||
border_color: quad.border_color,
|
border_color: quad.border_color,
|
||||||
});
|
});
|
||||||
|
|
@ -183,7 +183,7 @@ where
|
||||||
{
|
{
|
||||||
type Handle = image::Handle;
|
type Handle = image::Handle;
|
||||||
|
|
||||||
fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
|
fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
|
||||||
self.backend().dimensions(handle)
|
self.backend().dimensions(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,11 +196,20 @@ impl<B, T> svg::Renderer for Renderer<B, T>
|
||||||
where
|
where
|
||||||
B: Backend + backend::Svg,
|
B: Backend + backend::Svg,
|
||||||
{
|
{
|
||||||
fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) {
|
fn dimensions(&self, handle: &svg::Handle) -> Size<u32> {
|
||||||
self.backend().viewport_dimensions(handle)
|
self.backend().viewport_dimensions(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) {
|
fn draw(
|
||||||
self.draw_primitive(Primitive::Svg { handle, bounds })
|
&mut self,
|
||||||
|
handle: svg::Handle,
|
||||||
|
color: Option<Color>,
|
||||||
|
bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
self.draw_primitive(Primitive::Svg {
|
||||||
|
handle,
|
||||||
|
color,
|
||||||
|
bounds,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ pub struct Transformation(Mat4);
|
||||||
impl Transformation {
|
impl Transformation {
|
||||||
/// Get the identity transformation.
|
/// Get the identity transformation.
|
||||||
pub fn identity() -> Transformation {
|
pub fn identity() -> Transformation {
|
||||||
Transformation(Mat4::identity())
|
Transformation(Mat4::IDENTITY)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an orthographic projection.
|
/// Creates an orthographic projection.
|
||||||
|
|
@ -51,3 +51,9 @@ impl From<Transformation> for [f32; 16] {
|
||||||
*t.as_ref()
|
*t.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Transformation> for Mat4 {
|
||||||
|
fn from(transformation: Transformation) -> Self {
|
||||||
|
transformation.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,31 @@ use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
/// A set of [`Vertex2D`] and indices representing a list of triangles.
|
/// A set of [`Vertex2D`] and indices representing a list of triangles.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Mesh2D {
|
pub struct Mesh2D<T> {
|
||||||
/// The vertices of the mesh
|
/// The vertices of the mesh
|
||||||
pub vertices: Vec<Vertex2D>,
|
pub vertices: Vec<T>,
|
||||||
|
|
||||||
/// The list of vertex indices that defines the triangles of the mesh.
|
/// The list of vertex indices that defines the triangles of the mesh.
|
||||||
///
|
///
|
||||||
/// Therefore, this list should always have a length that is a multiple of
|
/// Therefore, this list should always have a length that is a multiple of 3.
|
||||||
/// 3.
|
|
||||||
pub indices: Vec<u32>,
|
pub indices: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A two-dimensional vertex with some color in __linear__ RGBA.
|
/// A two-dimensional vertex.
|
||||||
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Vertex2D {
|
pub struct Vertex2D {
|
||||||
/// The vertex position
|
/// The vertex position in 2D space.
|
||||||
pub position: [f32; 2],
|
pub position: [f32; 2],
|
||||||
/// The vertex color in __linear__ RGBA.
|
}
|
||||||
|
|
||||||
|
/// A two-dimensional vertex with a color.
|
||||||
|
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ColoredVertex2D {
|
||||||
|
/// The vertex position in 2D space.
|
||||||
|
pub position: [f32; 2],
|
||||||
|
|
||||||
|
/// The color of the vertex in __linear__ RGBA.
|
||||||
pub color: [f32; 4],
|
pub color: [f32; 4],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,20 @@
|
||||||
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
|
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
|
||||||
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
|
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
|
||||||
//! and more!
|
//! and more!
|
||||||
|
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod fill;
|
||||||
pub mod path;
|
pub mod path;
|
||||||
|
pub mod stroke;
|
||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
mod fill;
|
|
||||||
mod frame;
|
mod frame;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
mod program;
|
mod program;
|
||||||
mod stroke;
|
mod style;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
|
pub use crate::gradient::{self, Gradient};
|
||||||
pub use cache::Cache;
|
pub use cache::Cache;
|
||||||
pub use cursor::Cursor;
|
pub use cursor::Cursor;
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
|
|
@ -25,6 +26,7 @@ pub use geometry::Geometry;
|
||||||
pub use path::Path;
|
pub use path::Path;
|
||||||
pub use program::Program;
|
pub use program::Program;
|
||||||
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
|
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
|
||||||
|
pub use style::Style;
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
|
|
||||||
use crate::{Backend, Primitive, Renderer};
|
use crate::{Backend, Primitive, Renderer};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
use iced_native::Color;
|
//! Fill [crate::widget::canvas::Geometry] with a certain style.
|
||||||
|
use crate::{Color, Gradient};
|
||||||
|
|
||||||
|
pub use crate::widget::canvas::Style;
|
||||||
|
|
||||||
/// The style used to fill geometry.
|
/// The style used to fill geometry.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Fill {
|
pub struct Fill {
|
||||||
/// The color used to fill geometry.
|
/// The color or gradient of the fill.
|
||||||
///
|
///
|
||||||
/// By default, it is set to `BLACK`.
|
/// By default, it is set to [`Style::Solid`] with [`Color::BLACK`].
|
||||||
pub color: Color,
|
pub style: Style,
|
||||||
|
|
||||||
/// The fill rule defines how to determine what is inside and what is
|
/// The fill rule defines how to determine what is inside and what is
|
||||||
/// outside of a shape.
|
/// outside of a shape.
|
||||||
|
|
@ -20,9 +23,9 @@ pub struct Fill {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Fill {
|
impl Default for Fill {
|
||||||
fn default() -> Fill {
|
fn default() -> Self {
|
||||||
Fill {
|
Self {
|
||||||
color: Color::BLACK,
|
style: Style::Solid(Color::BLACK),
|
||||||
rule: FillRule::NonZero,
|
rule: FillRule::NonZero,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -31,12 +34,21 @@ impl Default for Fill {
|
||||||
impl From<Color> for Fill {
|
impl From<Color> for Fill {
|
||||||
fn from(color: Color) -> Fill {
|
fn from(color: Color) -> Fill {
|
||||||
Fill {
|
Fill {
|
||||||
color,
|
style: Style::Solid(color),
|
||||||
..Fill::default()
|
..Fill::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Gradient> for Fill {
|
||||||
|
fn from(gradient: Gradient) -> Self {
|
||||||
|
Fill {
|
||||||
|
style: Style::Gradient(gradient),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The fill rule defines how to determine what is inside and what is outside of
|
/// The fill rule defines how to determine what is inside and what is outside of
|
||||||
/// a shape.
|
/// a shape.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use std::borrow::Cow;
|
use crate::gradient::Gradient;
|
||||||
|
use crate::triangle;
|
||||||
|
use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text};
|
||||||
|
use crate::Primitive;
|
||||||
|
|
||||||
use iced_native::{Point, Rectangle, Size, Vector};
|
use iced_native::{Point, Rectangle, Size, Vector};
|
||||||
|
|
||||||
use crate::triangle;
|
use lyon::geom::euclid;
|
||||||
use crate::widget::canvas::path;
|
|
||||||
use crate::widget::canvas::{Fill, Geometry, Path, Stroke, Text};
|
|
||||||
use crate::Primitive;
|
|
||||||
|
|
||||||
use lyon::tessellation;
|
use lyon::tessellation;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// The frame of a [`Canvas`].
|
/// The frame of a [`Canvas`].
|
||||||
///
|
///
|
||||||
|
|
@ -15,13 +15,91 @@ use lyon::tessellation;
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
size: Size,
|
size: Size,
|
||||||
buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>,
|
buffers: BufferStack,
|
||||||
primitives: Vec<Primitive>,
|
primitives: Vec<Primitive>,
|
||||||
transforms: Transforms,
|
transforms: Transforms,
|
||||||
fill_tessellator: tessellation::FillTessellator,
|
fill_tessellator: tessellation::FillTessellator,
|
||||||
stroke_tessellator: tessellation::StrokeTessellator,
|
stroke_tessellator: tessellation::StrokeTessellator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Buffer {
|
||||||
|
Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>),
|
||||||
|
Gradient(
|
||||||
|
tessellation::VertexBuffers<triangle::Vertex2D, u32>,
|
||||||
|
Gradient,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BufferStack {
|
||||||
|
stack: Vec<Buffer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferStack {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { stack: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mut(&mut self, style: &Style) -> &mut Buffer {
|
||||||
|
match style {
|
||||||
|
Style::Solid(_) => match self.stack.last() {
|
||||||
|
Some(Buffer::Solid(_)) => {}
|
||||||
|
_ => {
|
||||||
|
self.stack.push(Buffer::Solid(
|
||||||
|
tessellation::VertexBuffers::new(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Style::Gradient(gradient) => match self.stack.last() {
|
||||||
|
Some(Buffer::Gradient(_, last)) if gradient == last => {}
|
||||||
|
_ => {
|
||||||
|
self.stack.push(Buffer::Gradient(
|
||||||
|
tessellation::VertexBuffers::new(),
|
||||||
|
gradient.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stack.last_mut().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fill<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
style: &Style,
|
||||||
|
) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
|
||||||
|
match (style, self.get_mut(style)) {
|
||||||
|
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
||||||
|
Box::new(tessellation::BuffersBuilder::new(
|
||||||
|
buffer,
|
||||||
|
TriangleVertex2DBuilder(color.into_linear()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
(Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
|
||||||
|
tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
|
||||||
|
),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_stroke<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
style: &Style,
|
||||||
|
) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
|
||||||
|
match (style, self.get_mut(style)) {
|
||||||
|
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
||||||
|
Box::new(tessellation::BuffersBuilder::new(
|
||||||
|
buffer,
|
||||||
|
TriangleVertex2DBuilder(color.into_linear()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
(Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
|
||||||
|
tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
|
||||||
|
),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Transforms {
|
struct Transforms {
|
||||||
previous: Vec<Transform>,
|
previous: Vec<Transform>,
|
||||||
|
|
@ -34,6 +112,35 @@ struct Transform {
|
||||||
is_identity: bool,
|
is_identity: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Transform {
|
||||||
|
/// Transforms the given [Point] by the transformation matrix.
|
||||||
|
fn transform_point(&self, point: &mut Point) {
|
||||||
|
let transformed = self
|
||||||
|
.raw
|
||||||
|
.transform_point(euclid::Point2D::new(point.x, point.y));
|
||||||
|
point.x = transformed.x;
|
||||||
|
point.y = transformed.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_style(&self, style: Style) -> Style {
|
||||||
|
match style {
|
||||||
|
Style::Solid(color) => Style::Solid(color),
|
||||||
|
Style::Gradient(gradient) => {
|
||||||
|
Style::Gradient(self.transform_gradient(gradient))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
|
||||||
|
let (start, end) = match &mut gradient {
|
||||||
|
Gradient::Linear(linear) => (&mut linear.start, &mut linear.end),
|
||||||
|
};
|
||||||
|
self.transform_point(start);
|
||||||
|
self.transform_point(end);
|
||||||
|
gradient
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
/// Creates a new empty [`Frame`] with the given dimensions.
|
/// Creates a new empty [`Frame`] with the given dimensions.
|
||||||
///
|
///
|
||||||
|
|
@ -42,7 +149,7 @@ impl Frame {
|
||||||
pub fn new(size: Size) -> Frame {
|
pub fn new(size: Size) -> Frame {
|
||||||
Frame {
|
Frame {
|
||||||
size,
|
size,
|
||||||
buffers: lyon::tessellation::VertexBuffers::new(),
|
buffers: BufferStack::new(),
|
||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
transforms: Transforms {
|
transforms: Transforms {
|
||||||
previous: Vec::new(),
|
previous: Vec::new(),
|
||||||
|
|
@ -83,21 +190,20 @@ impl Frame {
|
||||||
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
|
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
|
||||||
/// provided style.
|
/// provided style.
|
||||||
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||||
let Fill { color, rule } = fill.into();
|
let Fill { style, rule } = fill.into();
|
||||||
|
|
||||||
let mut buffers = tessellation::BuffersBuilder::new(
|
let mut buffer = self
|
||||||
&mut self.buffers,
|
.buffers
|
||||||
FillVertex(color.into_linear()),
|
.get_fill(&self.transforms.current.transform_style(style));
|
||||||
);
|
|
||||||
|
|
||||||
let options =
|
let options =
|
||||||
tessellation::FillOptions::default().with_fill_rule(rule.into());
|
tessellation::FillOptions::default().with_fill_rule(rule.into());
|
||||||
|
|
||||||
let result = if self.transforms.current.is_identity {
|
if self.transforms.current.is_identity {
|
||||||
self.fill_tessellator.tessellate_path(
|
self.fill_tessellator.tessellate_path(
|
||||||
path.raw(),
|
path.raw(),
|
||||||
&options,
|
&options,
|
||||||
&mut buffers,
|
buffer.as_mut(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let path = path.transformed(&self.transforms.current.raw);
|
let path = path.transformed(&self.transforms.current.raw);
|
||||||
|
|
@ -105,11 +211,10 @@ impl Frame {
|
||||||
self.fill_tessellator.tessellate_path(
|
self.fill_tessellator.tessellate_path(
|
||||||
path.raw(),
|
path.raw(),
|
||||||
&options,
|
&options,
|
||||||
&mut buffers,
|
buffer.as_mut(),
|
||||||
)
|
)
|
||||||
};
|
}
|
||||||
|
.expect("Tessellate path.");
|
||||||
result.expect("Tessellate path");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
|
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
|
||||||
|
|
@ -120,12 +225,11 @@ impl Frame {
|
||||||
size: Size,
|
size: Size,
|
||||||
fill: impl Into<Fill>,
|
fill: impl Into<Fill>,
|
||||||
) {
|
) {
|
||||||
let Fill { color, rule } = fill.into();
|
let Fill { style, rule } = fill.into();
|
||||||
|
|
||||||
let mut buffers = tessellation::BuffersBuilder::new(
|
let mut buffer = self
|
||||||
&mut self.buffers,
|
.buffers
|
||||||
FillVertex(color.into_linear()),
|
.get_fill(&self.transforms.current.transform_style(style));
|
||||||
);
|
|
||||||
|
|
||||||
let top_left =
|
let top_left =
|
||||||
self.transforms.current.raw.transform_point(
|
self.transforms.current.raw.transform_point(
|
||||||
|
|
@ -144,7 +248,7 @@ impl Frame {
|
||||||
.tessellate_rectangle(
|
.tessellate_rectangle(
|
||||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||||
&options,
|
&options,
|
||||||
&mut buffers,
|
buffer.as_mut(),
|
||||||
)
|
)
|
||||||
.expect("Fill rectangle");
|
.expect("Fill rectangle");
|
||||||
}
|
}
|
||||||
|
|
@ -154,10 +258,9 @@ impl Frame {
|
||||||
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||||
let stroke = stroke.into();
|
let stroke = stroke.into();
|
||||||
|
|
||||||
let mut buffers = tessellation::BuffersBuilder::new(
|
let mut buffer = self
|
||||||
&mut self.buffers,
|
.buffers
|
||||||
StrokeVertex(stroke.color.into_linear()),
|
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||||
);
|
|
||||||
|
|
||||||
let mut options = tessellation::StrokeOptions::default();
|
let mut options = tessellation::StrokeOptions::default();
|
||||||
options.line_width = stroke.width;
|
options.line_width = stroke.width;
|
||||||
|
|
@ -171,11 +274,11 @@ impl Frame {
|
||||||
Cow::Owned(path::dashed(path, stroke.line_dash))
|
Cow::Owned(path::dashed(path, stroke.line_dash))
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = if self.transforms.current.is_identity {
|
if self.transforms.current.is_identity {
|
||||||
self.stroke_tessellator.tessellate_path(
|
self.stroke_tessellator.tessellate_path(
|
||||||
path.raw(),
|
path.raw(),
|
||||||
&options,
|
&options,
|
||||||
&mut buffers,
|
buffer.as_mut(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let path = path.transformed(&self.transforms.current.raw);
|
let path = path.transformed(&self.transforms.current.raw);
|
||||||
|
|
@ -183,11 +286,10 @@ impl Frame {
|
||||||
self.stroke_tessellator.tessellate_path(
|
self.stroke_tessellator.tessellate_path(
|
||||||
path.raw(),
|
path.raw(),
|
||||||
&options,
|
&options,
|
||||||
&mut buffers,
|
buffer.as_mut(),
|
||||||
)
|
)
|
||||||
};
|
}
|
||||||
|
.expect("Stroke path");
|
||||||
result.expect("Stroke path");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
|
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
|
||||||
|
|
@ -206,8 +308,6 @@ impl Frame {
|
||||||
///
|
///
|
||||||
/// [`Canvas`]: crate::widget::Canvas
|
/// [`Canvas`]: crate::widget::Canvas
|
||||||
pub fn fill_text(&mut self, text: impl Into<Text>) {
|
pub fn fill_text(&mut self, text: impl Into<Text>) {
|
||||||
use std::f32;
|
|
||||||
|
|
||||||
let text = text.into();
|
let text = text.into();
|
||||||
|
|
||||||
let position = if self.transforms.current.is_identity {
|
let position = if self.transforms.current.is_identity {
|
||||||
|
|
@ -304,7 +404,7 @@ impl Frame {
|
||||||
self.transforms.current.is_identity = false;
|
self.transforms.current.is_identity = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies a rotation to the current transform of the [`Frame`].
|
/// Applies a rotation in radians to the current transform of the [`Frame`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn rotate(&mut self, angle: f32) {
|
pub fn rotate(&mut self, angle: f32) {
|
||||||
self.transforms.current.raw = self
|
self.transforms.current.raw = self
|
||||||
|
|
@ -331,50 +431,98 @@ impl Frame {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_primitives(mut self) -> Vec<Primitive> {
|
fn into_primitives(mut self) -> Vec<Primitive> {
|
||||||
if !self.buffers.indices.is_empty() {
|
for buffer in self.buffers.stack {
|
||||||
self.primitives.push(Primitive::Mesh2D {
|
match buffer {
|
||||||
|
Buffer::Solid(buffer) => {
|
||||||
|
if !buffer.indices.is_empty() {
|
||||||
|
self.primitives.push(Primitive::SolidMesh {
|
||||||
buffers: triangle::Mesh2D {
|
buffers: triangle::Mesh2D {
|
||||||
vertices: self.buffers.vertices,
|
vertices: buffer.vertices,
|
||||||
indices: self.buffers.indices,
|
indices: buffer.indices,
|
||||||
},
|
},
|
||||||
size: self.size,
|
size: self.size,
|
||||||
});
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Buffer::Gradient(buffer, gradient) => {
|
||||||
|
if !buffer.indices.is_empty() {
|
||||||
|
self.primitives.push(Primitive::GradientMesh {
|
||||||
|
buffers: triangle::Mesh2D {
|
||||||
|
vertices: buffer.vertices,
|
||||||
|
indices: buffer.indices,
|
||||||
|
},
|
||||||
|
size: self.size,
|
||||||
|
gradient,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.primitives
|
self.primitives
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FillVertex([f32; 4]);
|
struct Vertex2DBuilder;
|
||||||
|
|
||||||
impl lyon::tessellation::FillVertexConstructor<triangle::Vertex2D>
|
impl tessellation::FillVertexConstructor<triangle::Vertex2D>
|
||||||
for FillVertex
|
for Vertex2DBuilder
|
||||||
{
|
{
|
||||||
fn new_vertex(
|
fn new_vertex(
|
||||||
&mut self,
|
&mut self,
|
||||||
vertex: lyon::tessellation::FillVertex<'_>,
|
vertex: tessellation::FillVertex<'_>,
|
||||||
) -> triangle::Vertex2D {
|
) -> triangle::Vertex2D {
|
||||||
let position = vertex.position();
|
let position = vertex.position();
|
||||||
|
|
||||||
triangle::Vertex2D {
|
triangle::Vertex2D {
|
||||||
position: [position.x, position.y],
|
position: [position.x, position.y],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tessellation::StrokeVertexConstructor<triangle::Vertex2D>
|
||||||
|
for Vertex2DBuilder
|
||||||
|
{
|
||||||
|
fn new_vertex(
|
||||||
|
&mut self,
|
||||||
|
vertex: tessellation::StrokeVertex<'_, '_>,
|
||||||
|
) -> triangle::Vertex2D {
|
||||||
|
let position = vertex.position();
|
||||||
|
|
||||||
|
triangle::Vertex2D {
|
||||||
|
position: [position.x, position.y],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TriangleVertex2DBuilder([f32; 4]);
|
||||||
|
|
||||||
|
impl tessellation::FillVertexConstructor<triangle::ColoredVertex2D>
|
||||||
|
for TriangleVertex2DBuilder
|
||||||
|
{
|
||||||
|
fn new_vertex(
|
||||||
|
&mut self,
|
||||||
|
vertex: tessellation::FillVertex<'_>,
|
||||||
|
) -> triangle::ColoredVertex2D {
|
||||||
|
let position = vertex.position();
|
||||||
|
|
||||||
|
triangle::ColoredVertex2D {
|
||||||
|
position: [position.x, position.y],
|
||||||
color: self.0,
|
color: self.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StrokeVertex([f32; 4]);
|
impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D>
|
||||||
|
for TriangleVertex2DBuilder
|
||||||
impl lyon::tessellation::StrokeVertexConstructor<triangle::Vertex2D>
|
|
||||||
for StrokeVertex
|
|
||||||
{
|
{
|
||||||
fn new_vertex(
|
fn new_vertex(
|
||||||
&mut self,
|
&mut self,
|
||||||
vertex: lyon::tessellation::StrokeVertex<'_, '_>,
|
vertex: tessellation::StrokeVertex<'_, '_>,
|
||||||
) -> triangle::Vertex2D {
|
) -> triangle::ColoredVertex2D {
|
||||||
let position = vertex.position();
|
let position = vertex.position();
|
||||||
|
|
||||||
triangle::Vertex2D {
|
triangle::ColoredVertex2D {
|
||||||
position: [position.x, position.y],
|
position: [position.x, position.y],
|
||||||
color: self.0,
|
color: self.0,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
|
//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles.
|
||||||
|
pub use crate::widget::canvas::Style;
|
||||||
|
|
||||||
use iced_native::Color;
|
use iced_native::Color;
|
||||||
|
|
||||||
/// The style of a stroke.
|
/// The style of a stroke.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Stroke<'a> {
|
pub struct Stroke<'a> {
|
||||||
/// The color of the stroke.
|
/// The color or gradient of the stroke.
|
||||||
pub color: Color,
|
///
|
||||||
|
/// By default, it is set to a [`Style::Solid`] with [`Color::BLACK`].
|
||||||
|
pub style: Style,
|
||||||
/// The distance between the two edges of the stroke.
|
/// The distance between the two edges of the stroke.
|
||||||
pub width: f32,
|
pub width: f32,
|
||||||
/// The shape to be used at the end of open subpaths when they are stroked.
|
/// The shape to be used at the end of open subpaths when they are stroked.
|
||||||
|
|
@ -19,7 +24,10 @@ pub struct Stroke<'a> {
|
||||||
impl<'a> Stroke<'a> {
|
impl<'a> Stroke<'a> {
|
||||||
/// Sets the color of the [`Stroke`].
|
/// Sets the color of the [`Stroke`].
|
||||||
pub fn with_color(self, color: Color) -> Self {
|
pub fn with_color(self, color: Color) -> Self {
|
||||||
Stroke { color, ..self }
|
Stroke {
|
||||||
|
style: Style::Solid(color),
|
||||||
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the width of the [`Stroke`].
|
/// Sets the width of the [`Stroke`].
|
||||||
|
|
@ -41,7 +49,7 @@ impl<'a> Stroke<'a> {
|
||||||
impl<'a> Default for Stroke<'a> {
|
impl<'a> Default for Stroke<'a> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Stroke {
|
Stroke {
|
||||||
color: Color::BLACK,
|
style: Style::Solid(Color::BLACK),
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
line_cap: LineCap::default(),
|
line_cap: LineCap::default(),
|
||||||
line_join: LineJoin::default(),
|
line_join: LineJoin::default(),
|
||||||
|
|
|
||||||
23
graphics/src/widget/canvas/style.rs
Normal file
23
graphics/src/widget/canvas/style.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::{Color, Gradient};
|
||||||
|
|
||||||
|
/// The coloring style of some drawing.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Style {
|
||||||
|
/// A solid [`Color`].
|
||||||
|
Solid(Color),
|
||||||
|
|
||||||
|
/// A [`Gradient`] color.
|
||||||
|
Gradient(Gradient),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for Style {
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
Self::Solid(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Gradient> for Style {
|
||||||
|
fn from(gradient: Gradient) -> Self {
|
||||||
|
Self::Gradient(gradient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -40,7 +40,7 @@ pub trait Compositor: Sized {
|
||||||
height: u32,
|
height: u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Returns [`GraphicsInformation`] used by this [`Compositor`].
|
/// Returns [`Information`] used by this [`Compositor`].
|
||||||
fn fetch_information(&self) -> Information;
|
fn fetch_information(&self) -> Information;
|
||||||
|
|
||||||
/// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
|
/// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ pub trait GLCompositor: Sized {
|
||||||
/// Resizes the viewport of the [`GLCompositor`].
|
/// Resizes the viewport of the [`GLCompositor`].
|
||||||
fn resize_viewport(&mut self, physical_size: Size<u32>);
|
fn resize_viewport(&mut self, physical_size: Size<u32>);
|
||||||
|
|
||||||
/// Returns [`GraphicsInformation`] used by this [`Compositor`].
|
/// Returns [`Information`] used by this [`GLCompositor`].
|
||||||
fn fetch_information(&self) -> Information;
|
fn fetch_information(&self) -> Information;
|
||||||
|
|
||||||
/// Presents the primitives of the [`Renderer`] to the next frame of the
|
/// Presents the primitives of the [`Renderer`] to the next frame of the
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_lazy"
|
name = "iced_lazy"
|
||||||
version = "0.1.1"
|
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 = "2021"
|
edition = "2021"
|
||||||
description = "Lazy widgets for Iced"
|
description = "Lazy widgets for Iced"
|
||||||
|
|
@ -14,5 +14,5 @@ categories = ["gui"]
|
||||||
ouroboros = "0.13"
|
ouroboros = "0.13"
|
||||||
|
|
||||||
[dependencies.iced_native]
|
[dependencies.iced_native]
|
||||||
version = "0.5"
|
version = "0.7"
|
||||||
path = "../native"
|
path = "../native"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use iced_native::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use ouroboros::self_referencing;
|
use ouroboros::self_referencing;
|
||||||
use std::cell::{Ref, RefCell};
|
use std::cell::RefCell;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
/// A reusable, custom widget that uses The Elm Architecture.
|
/// A reusable, custom widget that uses The Elm Architecture.
|
||||||
|
|
@ -260,6 +260,14 @@ where
|
||||||
) {
|
) {
|
||||||
self.operation.focusable(state, id);
|
self.operation.focusable(state, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn text_input(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn widget::operation::TextInput,
|
||||||
|
id: Option<&widget::Id>,
|
||||||
|
) {
|
||||||
|
self.operation.text_input(state, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.with_element(|element| {
|
self.with_element(|element| {
|
||||||
|
|
@ -314,25 +322,25 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
&'b self,
|
&'b mut self,
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||||
let overlay = OverlayBuilder {
|
let overlay = OverlayBuilder {
|
||||||
instance: self,
|
instance: self,
|
||||||
instance_ref_builder: |instance| instance.state.borrow(),
|
|
||||||
tree,
|
tree,
|
||||||
types: PhantomData,
|
types: PhantomData,
|
||||||
overlay_builder: |instance, tree| {
|
overlay_builder: |instance, tree| {
|
||||||
instance
|
instance.state.get_mut().as_mut().unwrap().with_element_mut(
|
||||||
.as_ref()
|
move |element| {
|
||||||
.unwrap()
|
element.as_mut().unwrap().as_widget_mut().overlay(
|
||||||
.borrow_element()
|
&mut tree.children[0],
|
||||||
.as_ref()
|
layout,
|
||||||
.unwrap()
|
renderer,
|
||||||
.as_widget()
|
)
|
||||||
.overlay(&mut tree.children[0], layout, renderer)
|
},
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
.build();
|
.build();
|
||||||
|
|
@ -354,15 +362,11 @@ where
|
||||||
|
|
||||||
#[self_referencing]
|
#[self_referencing]
|
||||||
struct Overlay<'a, 'b, Message, Renderer, Event, S> {
|
struct Overlay<'a, 'b, Message, Renderer, Event, S> {
|
||||||
instance: &'a Instance<'b, Message, Renderer, Event, S>,
|
instance: &'a mut Instance<'b, Message, Renderer, Event, S>,
|
||||||
tree: &'a mut Tree,
|
tree: &'a mut Tree,
|
||||||
types: PhantomData<(Message, Event, S)>,
|
types: PhantomData<(Message, Event, S)>,
|
||||||
|
|
||||||
#[borrows(instance)]
|
#[borrows(mut instance, mut tree)]
|
||||||
#[covariant]
|
|
||||||
instance_ref: Ref<'this, Option<State<'a, Message, Renderer, Event, S>>>,
|
|
||||||
|
|
||||||
#[borrows(instance_ref, mut tree)]
|
|
||||||
#[covariant]
|
#[covariant]
|
||||||
overlay: Option<overlay::Element<'this, Event, Renderer>>,
|
overlay: Option<overlay::Element<'this, Event, Renderer>>,
|
||||||
}
|
}
|
||||||
|
|
@ -506,7 +510,6 @@ where
|
||||||
self.overlay = Some(
|
self.overlay = Some(
|
||||||
OverlayBuilder {
|
OverlayBuilder {
|
||||||
instance: overlay.instance,
|
instance: overlay.instance,
|
||||||
instance_ref_builder: |instance| instance.state.borrow(),
|
|
||||||
tree: overlay.tree,
|
tree: overlay.tree,
|
||||||
types: PhantomData,
|
types: PhantomData,
|
||||||
overlay_builder: |_, _| None,
|
overlay_builder: |_, _| None,
|
||||||
|
|
|
||||||
353
lazy/src/lazy.rs
Normal file
353
lazy/src/lazy.rs
Normal file
|
|
@ -0,0 +1,353 @@
|
||||||
|
use iced_native::event;
|
||||||
|
use iced_native::layout::{self, Layout};
|
||||||
|
use iced_native::mouse;
|
||||||
|
use iced_native::overlay;
|
||||||
|
use iced_native::renderer;
|
||||||
|
use iced_native::widget::tree::{self, Tree};
|
||||||
|
use iced_native::widget::{self, Widget};
|
||||||
|
use iced_native::Element;
|
||||||
|
use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size};
|
||||||
|
|
||||||
|
use ouroboros::self_referencing;
|
||||||
|
use std::cell::{Ref, RefCell, RefMut};
|
||||||
|
use std::hash::{Hash, Hasher as H};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Lazy<'a, Message, Renderer, Dependency, View> {
|
||||||
|
dependency: Dependency,
|
||||||
|
view: Box<dyn Fn() -> View + 'a>,
|
||||||
|
element: RefCell<Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer, Dependency, View>
|
||||||
|
Lazy<'a, Message, Renderer, Dependency, View>
|
||||||
|
where
|
||||||
|
Dependency: Hash + 'a,
|
||||||
|
View: Into<Element<'static, Message, Renderer>>,
|
||||||
|
{
|
||||||
|
pub fn new(dependency: Dependency, view: impl Fn() -> View + 'a) -> Self {
|
||||||
|
Self {
|
||||||
|
dependency,
|
||||||
|
view: Box::new(view),
|
||||||
|
element: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_element<T>(
|
||||||
|
&self,
|
||||||
|
f: impl FnOnce(Ref<Element<Message, Renderer>>) -> T,
|
||||||
|
) -> T {
|
||||||
|
f(self.element.borrow().as_ref().unwrap().borrow())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_element_mut<T>(
|
||||||
|
&self,
|
||||||
|
f: impl FnOnce(RefMut<Element<Message, Renderer>>) -> T,
|
||||||
|
) -> T {
|
||||||
|
f(self.element.borrow().as_ref().unwrap().borrow_mut())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Internal<Message, Renderer> {
|
||||||
|
element: Rc<RefCell<Element<'static, Message, Renderer>>>,
|
||||||
|
hash: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer, Dependency, View> Widget<Message, Renderer>
|
||||||
|
for Lazy<'a, Message, Renderer, Dependency, View>
|
||||||
|
where
|
||||||
|
View: Into<Element<'static, Message, Renderer>> + 'static,
|
||||||
|
Dependency: Hash + 'a,
|
||||||
|
Message: 'static,
|
||||||
|
Renderer: iced_native::Renderer + 'static,
|
||||||
|
{
|
||||||
|
fn tag(&self) -> tree::Tag {
|
||||||
|
struct Tag<T>(T);
|
||||||
|
tree::Tag::of::<Tag<View>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> tree::State {
|
||||||
|
let mut hasher = Hasher::default();
|
||||||
|
self.dependency.hash(&mut hasher);
|
||||||
|
let hash = hasher.finish();
|
||||||
|
|
||||||
|
let element = Rc::new(RefCell::new((self.view)().into()));
|
||||||
|
|
||||||
|
(*self.element.borrow_mut()) = Some(element.clone());
|
||||||
|
|
||||||
|
tree::State::new(Internal { element, hash })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> Vec<Tree> {
|
||||||
|
vec![Tree::new(
|
||||||
|
self.element.borrow().as_ref().unwrap().borrow().as_widget(),
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&self, tree: &mut Tree) {
|
||||||
|
let current = tree.state.downcast_mut::<Internal<Message, Renderer>>();
|
||||||
|
|
||||||
|
let mut hasher = Hasher::default();
|
||||||
|
self.dependency.hash(&mut hasher);
|
||||||
|
let new_hash = hasher.finish();
|
||||||
|
|
||||||
|
if current.hash != new_hash {
|
||||||
|
current.hash = new_hash;
|
||||||
|
|
||||||
|
let element = (self.view)().into();
|
||||||
|
current.element = Rc::new(RefCell::new(element));
|
||||||
|
|
||||||
|
(*self.element.borrow_mut()) = Some(current.element.clone());
|
||||||
|
tree.diff_children(std::slice::from_ref(
|
||||||
|
&self.element.borrow().as_ref().unwrap().borrow().as_widget(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
(*self.element.borrow_mut()) = Some(current.element.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> Length {
|
||||||
|
self.with_element(|element| element.as_widget().width())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> Length {
|
||||||
|
self.with_element(|element| element.as_widget().height())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
renderer: &Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
self.with_element(|element| {
|
||||||
|
element.as_widget().layout(renderer, limits)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
operation: &mut dyn widget::Operation<Message>,
|
||||||
|
) {
|
||||||
|
self.with_element(|element| {
|
||||||
|
element.as_widget().operate(
|
||||||
|
&mut tree.children[0],
|
||||||
|
layout,
|
||||||
|
operation,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
event: iced_native::Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
renderer: &Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) -> event::Status {
|
||||||
|
self.with_element_mut(|mut element| {
|
||||||
|
element.as_widget_mut().on_event(
|
||||||
|
&mut tree.children[0],
|
||||||
|
event,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.with_element(|element| {
|
||||||
|
element.as_widget().mouse_interaction(
|
||||||
|
&tree.children[0],
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Renderer::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
self.with_element(|element| {
|
||||||
|
element.as_widget().draw(
|
||||||
|
&tree.children[0],
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlay<'b>(
|
||||||
|
&'b mut self,
|
||||||
|
tree: &'b mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||||
|
let overlay = OverlayBuilder {
|
||||||
|
cached: self,
|
||||||
|
tree: &mut tree.children[0],
|
||||||
|
types: PhantomData,
|
||||||
|
overlay_builder: |cached, tree| {
|
||||||
|
Rc::get_mut(cached.element.get_mut().as_mut().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.get_mut()
|
||||||
|
.as_widget_mut()
|
||||||
|
.overlay(tree, layout, renderer)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let has_overlay = overlay.with_overlay(|overlay| {
|
||||||
|
overlay.as_ref().map(overlay::Element::position)
|
||||||
|
});
|
||||||
|
|
||||||
|
has_overlay
|
||||||
|
.map(|position| overlay::Element::new(position, Box::new(overlay)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[self_referencing]
|
||||||
|
struct Overlay<'a, 'b, Message, Renderer, Dependency, View> {
|
||||||
|
cached: &'a mut Lazy<'b, Message, Renderer, Dependency, View>,
|
||||||
|
tree: &'a mut Tree,
|
||||||
|
types: PhantomData<(Message, Dependency, View)>,
|
||||||
|
|
||||||
|
#[borrows(mut cached, mut tree)]
|
||||||
|
#[covariant]
|
||||||
|
overlay: Option<overlay::Element<'this, Message, Renderer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, Message, Renderer, Dependency, View>
|
||||||
|
Overlay<'a, 'b, Message, Renderer, Dependency, View>
|
||||||
|
{
|
||||||
|
fn with_overlay_maybe<T>(
|
||||||
|
&self,
|
||||||
|
f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T,
|
||||||
|
) -> Option<T> {
|
||||||
|
self.borrow_overlay().as_ref().map(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_overlay_mut_maybe<T>(
|
||||||
|
&mut self,
|
||||||
|
f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T,
|
||||||
|
) -> Option<T> {
|
||||||
|
self.with_overlay_mut(|overlay| overlay.as_mut().map(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, Message, Renderer, Dependency, View>
|
||||||
|
overlay::Overlay<Message, Renderer>
|
||||||
|
for Overlay<'a, 'b, Message, Renderer, Dependency, View>
|
||||||
|
where
|
||||||
|
Renderer: iced_native::Renderer,
|
||||||
|
{
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
renderer: &Renderer,
|
||||||
|
bounds: Size,
|
||||||
|
position: Point,
|
||||||
|
) -> layout::Node {
|
||||||
|
self.with_overlay_maybe(|overlay| {
|
||||||
|
let vector = position - overlay.position();
|
||||||
|
|
||||||
|
overlay.layout(renderer, bounds).translate(vector)
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Renderer::Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
) {
|
||||||
|
let _ = self.with_overlay_maybe(|overlay| {
|
||||||
|
overlay.draw(renderer, theme, style, layout, cursor_position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
self.with_overlay_maybe(|overlay| {
|
||||||
|
overlay.mouse_interaction(
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
event: iced_native::Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
renderer: &Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) -> event::Status {
|
||||||
|
self.with_overlay_mut_maybe(|overlay| {
|
||||||
|
overlay.on_event(
|
||||||
|
event,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or(iced_native::event::Status::Ignored)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer, Dependency, View>
|
||||||
|
From<Lazy<'a, Message, Renderer, Dependency, View>>
|
||||||
|
for Element<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
View: Into<Element<'static, Message, Renderer>> + 'static,
|
||||||
|
Renderer: iced_native::Renderer + 'static,
|
||||||
|
Message: 'static,
|
||||||
|
Dependency: Hash + 'a,
|
||||||
|
{
|
||||||
|
fn from(lazy: Lazy<'a, Message, Renderer, Dependency, View>) -> Self {
|
||||||
|
Self::new(lazy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,15 +17,30 @@
|
||||||
clippy::type_complexity
|
clippy::type_complexity
|
||||||
)]
|
)]
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
mod lazy;
|
||||||
|
|
||||||
pub mod component;
|
pub mod component;
|
||||||
pub mod responsive;
|
pub mod responsive;
|
||||||
|
|
||||||
pub use component::Component;
|
pub use component::Component;
|
||||||
|
pub use lazy::Lazy;
|
||||||
pub use responsive::Responsive;
|
pub use responsive::Responsive;
|
||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
|
|
||||||
use iced_native::{Element, Size};
|
use iced_native::{Element, Size};
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
pub fn lazy<'a, Message, Renderer, Dependency, View>(
|
||||||
|
dependency: Dependency,
|
||||||
|
view: impl Fn() -> View + 'a,
|
||||||
|
) -> Lazy<'a, Message, Renderer, Dependency, View>
|
||||||
|
where
|
||||||
|
Dependency: Hash + 'a,
|
||||||
|
View: Into<Element<'static, Message, Renderer>>,
|
||||||
|
{
|
||||||
|
Lazy::new(dependency, view)
|
||||||
|
}
|
||||||
|
|
||||||
/// Turns an implementor of [`Component`] into an [`Element`] that can be
|
/// Turns an implementor of [`Component`] into an [`Element`] that can be
|
||||||
/// embedded in any application.
|
/// embedded in any application.
|
||||||
|
|
|
||||||
|
|
@ -235,18 +235,20 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
&'b self,
|
&'b mut self,
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
let overlay = OverlayBuilder {
|
let overlay = OverlayBuilder {
|
||||||
content: self.content.borrow_mut(),
|
content: self.content.borrow_mut(),
|
||||||
tree: state.tree.borrow_mut(),
|
tree: state.tree.borrow_mut(),
|
||||||
types: PhantomData,
|
types: PhantomData,
|
||||||
overlay_builder: |content, tree| {
|
overlay_builder: |content: &mut RefMut<Content<_, _>>, tree| {
|
||||||
content.update(
|
content.update(
|
||||||
tree,
|
tree,
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -254,16 +256,18 @@ where
|
||||||
&self.view,
|
&self.view,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let Content {
|
||||||
|
element, layout, ..
|
||||||
|
} = content.deref_mut();
|
||||||
|
|
||||||
let content_layout = Layout::with_offset(
|
let content_layout = Layout::with_offset(
|
||||||
layout.position() - Point::ORIGIN,
|
layout.bounds().position() - Point::ORIGIN,
|
||||||
&content.layout,
|
layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
content.element.as_widget().overlay(
|
element
|
||||||
tree,
|
.as_widget_mut()
|
||||||
content_layout,
|
.overlay(tree, content_layout, renderer)
|
||||||
renderer,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
.build();
|
.build();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced_native"
|
name = "iced_native"
|
||||||
version = "0.5.1"
|
version = "0.7.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A renderer-agnostic library for native GUIs"
|
description = "A renderer-agnostic library for native GUIs"
|
||||||
|
|
@ -16,14 +16,14 @@ unicode-segmentation = "1.6"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
|
||||||
[dependencies.iced_core]
|
[dependencies.iced_core]
|
||||||
version = "0.5"
|
version = "0.6"
|
||||||
path = "../core"
|
path = "../core"
|
||||||
|
|
||||||
[dependencies.iced_futures]
|
[dependencies.iced_futures]
|
||||||
version = "0.4"
|
version = "0.5"
|
||||||
path = "../futures"
|
path = "../futures"
|
||||||
features = ["thread-pool"]
|
features = ["thread-pool"]
|
||||||
|
|
||||||
[dependencies.iced_style]
|
[dependencies.iced_style]
|
||||||
version = "0.4"
|
version = "0.5.1"
|
||||||
path = "../style"
|
path = "../style"
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,22 @@ where
|
||||||
) {
|
) {
|
||||||
self.operation.focusable(state, id);
|
self.operation.focusable(state, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scrollable(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn widget::operation::Scrollable,
|
||||||
|
id: Option<&widget::Id>,
|
||||||
|
) {
|
||||||
|
self.operation.scrollable(state, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_input(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn widget::operation::TextInput,
|
||||||
|
id: Option<&widget::Id>,
|
||||||
|
) {
|
||||||
|
self.operation.text_input(state, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.widget
|
self.widget
|
||||||
|
|
@ -389,7 +405,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
&'b self,
|
&'b mut self,
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
|
|
@ -503,7 +519,7 @@ where
|
||||||
bounds: layout.bounds(),
|
bounds: layout.bounds(),
|
||||||
border_color: color,
|
border_color: color,
|
||||||
border_width: 1.0,
|
border_width: 1.0,
|
||||||
border_radius: 0.0,
|
border_radius: 0.0.into(),
|
||||||
},
|
},
|
||||||
Color::TRANSPARENT,
|
Color::TRANSPARENT,
|
||||||
);
|
);
|
||||||
|
|
@ -544,7 +560,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
&'b self,
|
&'b mut self,
|
||||||
state: &'b mut Tree,
|
state: &'b mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! Load and draw raster graphics.
|
//! Load and draw raster graphics.
|
||||||
use crate::{Hasher, Rectangle};
|
use crate::{Hasher, Rectangle, Size};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::hash::{Hash, Hasher as _};
|
use std::hash::{Hash, Hasher as _};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -21,15 +22,19 @@ impl Handle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an image [`Handle`] containing the image pixels directly. This
|
/// Creates an image [`Handle`] containing the image pixels directly. This
|
||||||
/// function expects the input data to be provided as a `Vec<u8>` of BGRA
|
/// function expects the input data to be provided as a `Vec<u8>` of RGBA
|
||||||
/// pixels.
|
/// pixels.
|
||||||
///
|
///
|
||||||
/// This is useful if you have already decoded your image.
|
/// This is useful if you have already decoded your image.
|
||||||
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
|
pub fn from_pixels(
|
||||||
Self::from_data(Data::Pixels {
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
pixels: impl Into<Cow<'static, [u8]>>,
|
||||||
|
) -> Handle {
|
||||||
|
Self::from_data(Data::Rgba {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
pixels,
|
pixels: pixels.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,8 +44,8 @@ impl Handle {
|
||||||
///
|
///
|
||||||
/// This is useful if you already have your image loaded in-memory, maybe
|
/// This is useful if you already have your image loaded in-memory, maybe
|
||||||
/// because you downloaded or generated it procedurally.
|
/// because you downloaded or generated it procedurally.
|
||||||
pub fn from_memory(bytes: Vec<u8>) -> Handle {
|
pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
|
||||||
Self::from_data(Data::Bytes(bytes))
|
Self::from_data(Data::Bytes(bytes.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_data(data: Data) -> Handle {
|
fn from_data(data: Data) -> Handle {
|
||||||
|
|
@ -86,16 +91,16 @@ pub enum Data {
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
|
|
||||||
/// In-memory data
|
/// In-memory data
|
||||||
Bytes(Vec<u8>),
|
Bytes(Cow<'static, [u8]>),
|
||||||
|
|
||||||
/// Decoded image pixels in BGRA format.
|
/// Decoded image pixels in RGBA format.
|
||||||
Pixels {
|
Rgba {
|
||||||
/// The width of the image.
|
/// The width of the image.
|
||||||
width: u32,
|
width: u32,
|
||||||
/// The height of the image.
|
/// The height of the image.
|
||||||
height: u32,
|
height: u32,
|
||||||
/// The pixels.
|
/// The pixels.
|
||||||
pixels: Vec<u8>,
|
pixels: Cow<'static, [u8]>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +109,7 @@ impl std::fmt::Debug for Data {
|
||||||
match self {
|
match self {
|
||||||
Data::Path(path) => write!(f, "Path({:?})", path),
|
Data::Path(path) => write!(f, "Path({:?})", path),
|
||||||
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
||||||
Data::Pixels { width, height, .. } => {
|
Data::Rgba { width, height, .. } => {
|
||||||
write!(f, "Pixels({} * {})", width, height)
|
write!(f, "Pixels({} * {})", width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +126,7 @@ pub trait Renderer: crate::Renderer {
|
||||||
type Handle: Clone + Hash;
|
type Handle: Clone + Hash;
|
||||||
|
|
||||||
/// Returns the dimensions of an image for the given [`Handle`].
|
/// Returns the dimensions of an image for the given [`Handle`].
|
||||||
fn dimensions(&self, handle: &Self::Handle) -> (u32, u32);
|
fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;
|
||||||
|
|
||||||
/// Draws an image with the given [`Handle`] and inside the provided
|
/// Draws an image with the given [`Handle`] and inside the provided
|
||||||
/// `bounds`.
|
/// `bounds`.
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@
|
||||||
//! - Build a new renderer, see the [renderer] module.
|
//! - Build a new renderer, see the [renderer] module.
|
||||||
//! - Build a custom widget, start at the [`Widget`] trait.
|
//! - Build a custom widget, start at the [`Widget`] trait.
|
||||||
//!
|
//!
|
||||||
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.4/core
|
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.6/core
|
||||||
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.4/winit
|
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.6/winit
|
||||||
//! [`druid`]: https://github.com/xi-editor/druid
|
//! [`druid`]: https://github.com/xi-editor/druid
|
||||||
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
|
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
|
||||||
//! [renderer]: crate::renderer
|
//! [renderer]: crate::renderer
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::widget::tree::{self, Tree};
|
use crate::widget::Tree;
|
||||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
|
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
|
||||||
|
|
||||||
/// An interactive component that can be displayed on top of other widgets.
|
/// An interactive component that can be displayed on top of other widgets.
|
||||||
|
|
@ -42,31 +42,9 @@ where
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Returns the [`Tag`] of the [`Widget`].
|
/// Applies a [`widget::Operation`] to the [`Overlay`].
|
||||||
///
|
|
||||||
/// [`Tag`]: tree::Tag
|
|
||||||
fn tag(&self) -> tree::Tag {
|
|
||||||
tree::Tag::stateless()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the [`State`] of the [`Widget`].
|
|
||||||
///
|
|
||||||
/// [`State`]: tree::State
|
|
||||||
fn state(&self) -> tree::State {
|
|
||||||
tree::State::None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the state [`Tree`] of the children of the [`Widget`].
|
|
||||||
fn children(&self) -> Vec<Tree> {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reconciliates the [`Widget`] with the provided [`Tree`].
|
|
||||||
fn diff(&self, _tree: &mut Tree) {}
|
|
||||||
|
|
||||||
/// Applies an [`Operation`] to the [`Widget`].
|
|
||||||
fn operate(
|
fn operate(
|
||||||
&self,
|
&mut self,
|
||||||
_layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
_operation: &mut dyn widget::Operation<Message>,
|
_operation: &mut dyn widget::Operation<Message>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -115,7 +93,7 @@ where
|
||||||
/// This method will generally only be used by advanced users that are
|
/// This method will generally only be used by advanced users that are
|
||||||
/// implementing the [`Widget`](crate::Widget) trait.
|
/// implementing the [`Widget`](crate::Widget) trait.
|
||||||
pub fn from_children<'a, Message, Renderer>(
|
pub fn from_children<'a, Message, Renderer>(
|
||||||
children: &'a [crate::Element<'_, Message, Renderer>],
|
children: &'a mut [crate::Element<'_, Message, Renderer>],
|
||||||
tree: &'a mut Tree,
|
tree: &'a mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
|
|
@ -124,11 +102,11 @@ where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
children
|
children
|
||||||
.iter()
|
.iter_mut()
|
||||||
.zip(&mut tree.children)
|
.zip(&mut tree.children)
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.filter_map(|((child, state), layout)| {
|
.filter_map(|((child, state), layout)| {
|
||||||
child.as_widget().overlay(state, layout, renderer)
|
child.as_widget_mut().overlay(state, layout, renderer)
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,9 +104,9 @@ where
|
||||||
.draw(renderer, theme, style, layout, cursor_position)
|
.draw(renderer, theme, style, layout, cursor_position)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies an [`Operation`] to the [`Element`].
|
/// Applies a [`widget::Operation`] to the [`Element`].
|
||||||
pub fn operate(
|
pub fn operate(
|
||||||
&self,
|
&mut self,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
operation: &mut dyn widget::Operation<Message>,
|
operation: &mut dyn widget::Operation<Message>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -141,6 +141,57 @@ where
|
||||||
self.content.layout(renderer, bounds, position)
|
self.content.layout(renderer, bounds, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&mut self,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
operation: &mut dyn widget::Operation<B>,
|
||||||
|
) {
|
||||||
|
struct MapOperation<'a, B> {
|
||||||
|
operation: &'a mut dyn widget::Operation<B>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> {
|
||||||
|
fn container(
|
||||||
|
&mut self,
|
||||||
|
id: Option<&widget::Id>,
|
||||||
|
operate_on_children: &mut dyn FnMut(
|
||||||
|
&mut dyn widget::Operation<T>,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
self.operation.container(id, &mut |operation| {
|
||||||
|
operate_on_children(&mut MapOperation { operation });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn focusable(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn widget::operation::Focusable,
|
||||||
|
id: Option<&widget::Id>,
|
||||||
|
) {
|
||||||
|
self.operation.focusable(state, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scrollable(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn widget::operation::Scrollable,
|
||||||
|
id: Option<&widget::Id>,
|
||||||
|
) {
|
||||||
|
self.operation.scrollable(state, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_input(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn widget::operation::TextInput,
|
||||||
|
id: Option<&widget::Id>,
|
||||||
|
) {
|
||||||
|
self.operation.text_input(state, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content
|
||||||
|
.operate(layout, &mut MapOperation { operation });
|
||||||
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn on_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::text::{self, Text};
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
use crate::widget::container::{self, Container};
|
use crate::widget::container::{self, Container};
|
||||||
use crate::widget::scrollable::{self, Scrollable};
|
use crate::widget::scrollable::{self, Scrollable};
|
||||||
use crate::widget::tree::{self, Tree};
|
use crate::widget::Tree;
|
||||||
use crate::{
|
use crate::{
|
||||||
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
|
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
|
||||||
Shell, Size, Vector, Widget,
|
Shell, Size, Vector, Widget,
|
||||||
|
|
@ -178,7 +178,7 @@ where
|
||||||
font,
|
font,
|
||||||
text_size,
|
text_size,
|
||||||
padding,
|
padding,
|
||||||
style,
|
style: style.clone(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
state.tree.diff(&container as &dyn Widget<_, _>);
|
state.tree.diff(&container as &dyn Widget<_, _>);
|
||||||
|
|
@ -199,18 +199,6 @@ where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
Renderer::Theme: StyleSheet + container::StyleSheet,
|
Renderer::Theme: StyleSheet + container::StyleSheet,
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
|
||||||
self.container.tag()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn state(&self) -> tree::State {
|
|
||||||
self.container.state()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn children(&self) -> Vec<Tree> {
|
|
||||||
self.container.children()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
|
|
@ -288,7 +276,7 @@ where
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) {
|
) {
|
||||||
let appearance = theme.appearance(self.style);
|
let appearance = theme.appearance(&self.style);
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
|
|
@ -299,7 +287,7 @@ where
|
||||||
},
|
},
|
||||||
border_color: appearance.border_color,
|
border_color: appearance.border_color,
|
||||||
border_width: appearance.border_width,
|
border_width: appearance.border_width,
|
||||||
border_radius: appearance.border_radius,
|
border_radius: appearance.border_radius.into(),
|
||||||
},
|
},
|
||||||
appearance.background,
|
appearance.background,
|
||||||
);
|
);
|
||||||
|
|
@ -460,7 +448,7 @@ where
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let appearance = theme.appearance(self.style);
|
let appearance = theme.appearance(&self.style);
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let text_size =
|
let text_size =
|
||||||
|
|
@ -491,7 +479,7 @@ where
|
||||||
bounds,
|
bounds,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_radius: appearance.border_radius,
|
border_radius: appearance.border_radius.into(),
|
||||||
},
|
},
|
||||||
appearance.selected_background,
|
appearance.selected_background,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ pub struct Quad {
|
||||||
pub bounds: Rectangle,
|
pub bounds: Rectangle,
|
||||||
|
|
||||||
/// The border radius of the [`Quad`].
|
/// The border radius of the [`Quad`].
|
||||||
pub border_radius: f32,
|
pub border_radius: BorderRadius,
|
||||||
|
|
||||||
/// The border width of the [`Quad`].
|
/// The border width of the [`Quad`].
|
||||||
pub border_width: f32,
|
pub border_width: f32,
|
||||||
|
|
@ -59,6 +59,29 @@ pub struct Quad {
|
||||||
pub border_color: Color,
|
pub border_color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The border radi for the corners of a graphics primitive in the order:
|
||||||
|
/// top-left, top-right, bottom-right, bottom-left.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
|
pub struct BorderRadius([f32; 4]);
|
||||||
|
|
||||||
|
impl From<f32> for BorderRadius {
|
||||||
|
fn from(w: f32) -> Self {
|
||||||
|
Self([w; 4])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[f32; 4]> for BorderRadius {
|
||||||
|
fn from(radi: [f32; 4]) -> Self {
|
||||||
|
Self(radi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BorderRadius> for [f32; 4] {
|
||||||
|
fn from(radi: BorderRadius) -> Self {
|
||||||
|
radi.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The styling attributes of a [`Renderer`].
|
/// The styling attributes of a [`Renderer`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ where
|
||||||
/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket
|
/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket
|
||||||
/// connection open.
|
/// connection open.
|
||||||
///
|
///
|
||||||
/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.4/examples/websocket
|
/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.6/examples/websocket
|
||||||
pub fn unfold<I, T, Fut, Message>(
|
pub fn unfold<I, T, Fut, Message>(
|
||||||
id: I,
|
id: I,
|
||||||
initial: T,
|
initial: T,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! Load and draw vector graphics.
|
//! Load and draw vector graphics.
|
||||||
use crate::{Hasher, Rectangle};
|
use crate::{Color, Hasher, Rectangle, Size};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::hash::{Hash, Hasher as _};
|
use std::hash::{Hash, Hasher as _};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -24,7 +25,7 @@ impl Handle {
|
||||||
///
|
///
|
||||||
/// This is useful if you already have your SVG data in-memory, maybe
|
/// This is useful if you already have your SVG data in-memory, maybe
|
||||||
/// because you downloaded or generated it procedurally.
|
/// because you downloaded or generated it procedurally.
|
||||||
pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
|
pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
|
||||||
Self::from_data(Data::Bytes(bytes.into()))
|
Self::from_data(Data::Bytes(bytes.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +65,7 @@ pub enum Data {
|
||||||
/// In-memory data
|
/// In-memory data
|
||||||
///
|
///
|
||||||
/// Can contain an SVG string or a gzip compressed data.
|
/// Can contain an SVG string or a gzip compressed data.
|
||||||
Bytes(Vec<u8>),
|
Bytes(Cow<'static, [u8]>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Data {
|
impl std::fmt::Debug for Data {
|
||||||
|
|
@ -81,8 +82,8 @@ impl std::fmt::Debug for Data {
|
||||||
/// [renderer]: crate::renderer
|
/// [renderer]: crate::renderer
|
||||||
pub trait Renderer: crate::Renderer {
|
pub trait Renderer: crate::Renderer {
|
||||||
/// Returns the default dimensions of an SVG for the given [`Handle`].
|
/// Returns the default dimensions of an SVG for the given [`Handle`].
|
||||||
fn dimensions(&self, handle: &Handle) -> (u32, u32);
|
fn dimensions(&self, handle: &Handle) -> Size<u32>;
|
||||||
|
|
||||||
/// Draws an SVG with the given [`Handle`] and inside the provided `bounds`.
|
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
|
||||||
fn draw(&mut self, handle: Handle, bounds: Rectangle);
|
fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
||||||
/// The [`integration_opengl`] & [`integration_wgpu`] examples use a
|
/// The [`integration_opengl`] & [`integration_wgpu`] examples use a
|
||||||
/// [`UserInterface`] to integrate Iced in an existing graphical application.
|
/// [`UserInterface`] to integrate Iced in an existing graphical application.
|
||||||
///
|
///
|
||||||
/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_opengl
|
/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.6/examples/integration_opengl
|
||||||
/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_wgpu
|
/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/examples/integration_wgpu
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct UserInterface<'a, Message, Renderer> {
|
pub struct UserInterface<'a, Message, Renderer> {
|
||||||
root: Element<'a, Message, Renderer>,
|
root: Element<'a, Message, Renderer>,
|
||||||
|
|
@ -190,7 +190,7 @@ where
|
||||||
|
|
||||||
let mut state = State::Updated;
|
let mut state = State::Updated;
|
||||||
let mut manual_overlay =
|
let mut manual_overlay =
|
||||||
ManuallyDrop::new(self.root.as_widget().overlay(
|
ManuallyDrop::new(self.root.as_widget_mut().overlay(
|
||||||
&mut self.state,
|
&mut self.state,
|
||||||
Layout::new(&self.base),
|
Layout::new(&self.base),
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -226,7 +226,7 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
manual_overlay =
|
manual_overlay =
|
||||||
ManuallyDrop::new(self.root.as_widget().overlay(
|
ManuallyDrop::new(self.root.as_widget_mut().overlay(
|
||||||
&mut self.state,
|
&mut self.state,
|
||||||
Layout::new(&self.base),
|
Layout::new(&self.base),
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -285,6 +285,10 @@ where
|
||||||
&mut shell,
|
&mut shell,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if matches!(event_status, event::Status::Captured) {
|
||||||
|
self.overlay = None;
|
||||||
|
}
|
||||||
|
|
||||||
shell.revalidate_layout(|| {
|
shell.revalidate_layout(|| {
|
||||||
self.base = renderer.layout(
|
self.base = renderer.layout(
|
||||||
&self.root,
|
&self.root,
|
||||||
|
|
@ -391,11 +395,11 @@ where
|
||||||
|
|
||||||
let viewport = Rectangle::with_size(self.bounds);
|
let viewport = Rectangle::with_size(self.bounds);
|
||||||
|
|
||||||
let base_cursor = if let Some(overlay) = self.root.as_widget().overlay(
|
let base_cursor = if let Some(overlay) = self
|
||||||
&mut self.state,
|
.root
|
||||||
Layout::new(&self.base),
|
.as_widget_mut()
|
||||||
renderer,
|
.overlay(&mut self.state, Layout::new(&self.base), renderer)
|
||||||
) {
|
{
|
||||||
let overlay_layout = self
|
let overlay_layout = self
|
||||||
.overlay
|
.overlay
|
||||||
.take()
|
.take()
|
||||||
|
|
@ -448,7 +452,7 @@ where
|
||||||
overlay
|
overlay
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|layout| {
|
.and_then(|layout| {
|
||||||
root.as_widget()
|
root.as_widget_mut()
|
||||||
.overlay(&mut self.state, Layout::new(base), renderer)
|
.overlay(&mut self.state, Layout::new(base), renderer)
|
||||||
.map(|overlay| {
|
.map(|overlay| {
|
||||||
let overlay_interaction = overlay.mouse_interaction(
|
let overlay_interaction = overlay.mouse_interaction(
|
||||||
|
|
@ -492,14 +496,19 @@ where
|
||||||
operation,
|
operation,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(layout) = self.overlay.as_ref() {
|
if let Some(mut overlay) = self.root.as_widget_mut().overlay(
|
||||||
if let Some(overlay) = self.root.as_widget().overlay(
|
|
||||||
&mut self.state,
|
&mut self.state,
|
||||||
Layout::new(&self.base),
|
Layout::new(&self.base),
|
||||||
renderer,
|
renderer,
|
||||||
) {
|
) {
|
||||||
overlay.operate(Layout::new(layout), operation);
|
if self.overlay.is_none() {
|
||||||
|
self.overlay = Some(overlay.layout(renderer, self.bounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
overlay.operate(
|
||||||
|
Layout::new(self.overlay.as_ref().unwrap()),
|
||||||
|
operation,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,12 +107,12 @@ use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell};
|
||||||
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
|
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
|
||||||
/// `Mesh2D` primitive in [`iced_wgpu`].
|
/// `Mesh2D` primitive in [`iced_wgpu`].
|
||||||
///
|
///
|
||||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.4/examples
|
/// [examples]: https://github.com/iced-rs/iced/tree/0.6/examples
|
||||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.4/examples/bezier_tool
|
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.6/examples/bezier_tool
|
||||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.4/examples/custom_widget
|
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.6/examples/custom_widget
|
||||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.4/examples/geometry
|
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.6/examples/geometry
|
||||||
/// [`lyon`]: https://github.com/nical/lyon
|
/// [`lyon`]: https://github.com/nical/lyon
|
||||||
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/wgpu
|
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/wgpu
|
||||||
pub trait Widget<Message, Renderer>
|
pub trait Widget<Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
|
|
@ -208,7 +208,7 @@ where
|
||||||
|
|
||||||
/// Returns the overlay of the [`Widget`], if there is any.
|
/// Returns the overlay of the [`Widget`], if there is any.
|
||||||
fn overlay<'a>(
|
fn overlay<'a>(
|
||||||
&'a self,
|
&'a mut self,
|
||||||
_state: &'a mut Tree,
|
_state: &'a mut Tree,
|
||||||
_layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::widget::operation::{self, Operation};
|
use crate::widget::operation::{self, Focusable, Operation, Scrollable};
|
||||||
use crate::widget::Id;
|
use crate::widget::Id;
|
||||||
|
|
||||||
use iced_futures::MaybeSend;
|
use iced_futures::MaybeSend;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
/// An operation to be performed on the widget tree.
|
/// An operation to be performed on the widget tree.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Action<T>(Box<dyn Operation<T>>);
|
pub struct Action<T>(Box<dyn Operation<T>>);
|
||||||
|
|
@ -24,7 +26,7 @@ impl<T> Action<T> {
|
||||||
{
|
{
|
||||||
Action(Box::new(Map {
|
Action(Box::new(Map {
|
||||||
operation: self.0,
|
operation: self.0,
|
||||||
f: Box::new(f),
|
f: Rc::new(f),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +39,7 @@ impl<T> Action<T> {
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
struct Map<A, B> {
|
struct Map<A, B> {
|
||||||
operation: Box<dyn Operation<A>>,
|
operation: Box<dyn Operation<A>>,
|
||||||
f: Box<dyn Fn(A) -> B>,
|
f: Rc<dyn Fn(A) -> B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B> Operation<B> for Map<A, B>
|
impl<A, B> Operation<B> for Map<A, B>
|
||||||
|
|
@ -50,30 +52,44 @@ where
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
||||||
) {
|
) {
|
||||||
struct MapRef<'a, A, B> {
|
struct MapRef<'a, A> {
|
||||||
operation: &'a mut dyn Operation<A>,
|
operation: &'a mut dyn Operation<A>,
|
||||||
f: &'a dyn Fn(A) -> B,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, A, B> Operation<B> for MapRef<'a, A, B> {
|
impl<'a, A, B> Operation<B> for MapRef<'a, A> {
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&Id>,
|
id: Option<&Id>,
|
||||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
||||||
) {
|
) {
|
||||||
let Self { operation, f } = self;
|
let Self { operation, .. } = self;
|
||||||
|
|
||||||
operation.container(id, &mut |operation| {
|
operation.container(id, &mut |operation| {
|
||||||
operate_on_children(&mut MapRef { operation, f });
|
operate_on_children(&mut MapRef { operation });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scrollable(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn Scrollable,
|
||||||
|
id: Option<&Id>,
|
||||||
|
) {
|
||||||
|
self.operation.scrollable(state, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn focusable(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn Focusable,
|
||||||
|
id: Option<&Id>,
|
||||||
|
) {
|
||||||
|
self.operation.focusable(state, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Self { operation, f } = self;
|
let Self { operation, .. } = self;
|
||||||
|
|
||||||
MapRef {
|
MapRef {
|
||||||
operation: operation.as_mut(),
|
operation: operation.as_mut(),
|
||||||
f,
|
|
||||||
}
|
}
|
||||||
.container(id, operate_on_children);
|
.container(id, operate_on_children);
|
||||||
}
|
}
|
||||||
|
|
@ -85,4 +101,35 @@ where
|
||||||
) {
|
) {
|
||||||
self.operation.focusable(state, id);
|
self.operation.focusable(state, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scrollable(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn operation::Scrollable,
|
||||||
|
id: Option<&Id>,
|
||||||
|
) {
|
||||||
|
self.operation.scrollable(state, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_input(
|
||||||
|
&mut self,
|
||||||
|
state: &mut dyn operation::TextInput,
|
||||||
|
id: Option<&Id>,
|
||||||
|
) {
|
||||||
|
self.operation.text_input(state, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(&self) -> operation::Outcome<B> {
|
||||||
|
match self.operation.finish() {
|
||||||
|
operation::Outcome::None => operation::Outcome::None,
|
||||||
|
operation::Outcome::Some(output) => {
|
||||||
|
operation::Outcome::Some((self.f)(output))
|
||||||
|
}
|
||||||
|
operation::Outcome::Chain(next) => {
|
||||||
|
operation::Outcome::Chain(Box::new(Map {
|
||||||
|
operation: next,
|
||||||
|
f: self.f.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ where
|
||||||
cursor_position,
|
cursor_position,
|
||||||
self.on_press.is_some(),
|
self.on_press.is_some(),
|
||||||
theme,
|
theme,
|
||||||
self.style,
|
&self.style,
|
||||||
|| tree.state.downcast_ref::<State>(),
|
|| tree.state.downcast_ref::<State>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -260,12 +260,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
&'b self,
|
&'b mut self,
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||||
self.content.as_widget().overlay(
|
self.content.as_widget_mut().overlay(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
layout.children().next().unwrap(),
|
layout.children().next().unwrap(),
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -361,7 +361,7 @@ pub fn draw<'a, Renderer: crate::Renderer>(
|
||||||
style_sheet: &dyn StyleSheet<
|
style_sheet: &dyn StyleSheet<
|
||||||
Style = <Renderer::Theme as StyleSheet>::Style,
|
Style = <Renderer::Theme as StyleSheet>::Style,
|
||||||
>,
|
>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||||
state: impl FnOnce() -> &'a State,
|
state: impl FnOnce() -> &'a State,
|
||||||
) -> Appearance
|
) -> Appearance
|
||||||
where
|
where
|
||||||
|
|
@ -393,7 +393,7 @@ where
|
||||||
y: bounds.y + styling.shadow_offset.y,
|
y: bounds.y + styling.shadow_offset.y,
|
||||||
..bounds
|
..bounds
|
||||||
},
|
},
|
||||||
border_radius: styling.border_radius,
|
border_radius: styling.border_radius.into(),
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
|
|
@ -404,7 +404,7 @@ where
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds,
|
bounds,
|
||||||
border_radius: styling.border_radius,
|
border_radius: styling.border_radius.into(),
|
||||||
border_width: styling.border_width,
|
border_width: styling.border_width,
|
||||||
border_color: styling.border_color,
|
border_color: styling.border_color,
|
||||||
},
|
},
|
||||||
|
|
@ -426,13 +426,14 @@ pub fn layout<Renderer>(
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
|
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let limits = limits.width(width).height(height).pad(padding);
|
let limits = limits.width(width).height(height);
|
||||||
|
|
||||||
|
let mut content = layout_content(renderer, &limits.pad(padding));
|
||||||
|
let padding = padding.fit(content.size(), limits.max());
|
||||||
|
let size = limits.pad(padding).resolve(content.size()).pad(padding);
|
||||||
|
|
||||||
let mut content = layout_content(renderer, &limits);
|
|
||||||
content.move_to(Point::new(padding.left.into(), padding.top.into()));
|
content.move_to(Point::new(padding.left.into(), padding.top.into()));
|
||||||
|
|
||||||
let size = limits.resolve(content.size()).pad(padding);
|
|
||||||
|
|
||||||
layout::Node::with_children(size, vec![content])
|
layout::Node::with_children(size, vec![content])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -224,9 +224,9 @@ where
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
|
|
||||||
let custom_style = if is_mouse_over {
|
let custom_style = if is_mouse_over {
|
||||||
theme.hovered(self.style, self.is_checked)
|
theme.hovered(&self.style, self.is_checked)
|
||||||
} else {
|
} else {
|
||||||
theme.active(self.style, self.is_checked)
|
theme.active(&self.style, self.is_checked)
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -236,7 +236,7 @@ where
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds,
|
bounds,
|
||||||
border_radius: custom_style.border_radius,
|
border_radius: custom_style.border_radius.into(),
|
||||||
border_width: custom_style.border_width,
|
border_width: custom_style.border_width,
|
||||||
border_color: custom_style.border_color,
|
border_color: custom_style.border_color,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -242,12 +242,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
&'b self,
|
&'b mut self,
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||||
overlay::from_children(&self.children, tree, layout, renderer)
|
overlay::from_children(&mut self.children, tree, layout, renderer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,7 @@ where
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let style = theme.appearance(self.style);
|
let style = theme.appearance(&self.style);
|
||||||
|
|
||||||
draw_background(renderer, &style, layout.bounds());
|
draw_background(renderer, &style, layout.bounds());
|
||||||
|
|
||||||
|
|
@ -248,12 +248,12 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
&'b self,
|
&'b mut self,
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||||
self.content.as_widget().overlay(
|
self.content.as_widget_mut().overlay(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
layout.children().next().unwrap(),
|
layout.children().next().unwrap(),
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -293,11 +293,11 @@ pub fn layout<Renderer>(
|
||||||
.max_width(max_width)
|
.max_width(max_width)
|
||||||
.max_height(max_height)
|
.max_height(max_height)
|
||||||
.width(width)
|
.width(width)
|
||||||
.height(height)
|
.height(height);
|
||||||
.pad(padding);
|
|
||||||
|
|
||||||
let mut content = layout_content(renderer, &limits.loose());
|
let mut content = layout_content(renderer, &limits.pad(padding).loose());
|
||||||
let size = limits.resolve(content.size());
|
let padding = padding.fit(content.size(), limits.max());
|
||||||
|
let size = limits.pad(padding).resolve(content.size());
|
||||||
|
|
||||||
content.move_to(Point::new(padding.left.into(), padding.top.into()));
|
content.move_to(Point::new(padding.left.into(), padding.top.into()));
|
||||||
content.align(
|
content.align(
|
||||||
|
|
@ -309,7 +309,7 @@ pub fn layout<Renderer>(
|
||||||
layout::Node::with_children(size.pad(padding), vec![content])
|
layout::Node::with_children(size.pad(padding), vec![content])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
|
/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`.
|
||||||
pub fn draw_background<Renderer>(
|
pub fn draw_background<Renderer>(
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
|
|
@ -321,7 +321,7 @@ pub fn draw_background<Renderer>(
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds,
|
bounds,
|
||||||
border_radius: appearance.border_radius,
|
border_radius: appearance.border_radius.into(),
|
||||||
border_width: appearance.border_width,
|
border_width: appearance.border_width,
|
||||||
border_color: appearance.border_color,
|
border_color: appearance.border_color,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
//! Helper functions to create pure widgets.
|
//! Helper functions to create pure widgets.
|
||||||
|
use crate::overlay;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::{Element, Length};
|
use crate::{Element, Length};
|
||||||
|
|
||||||
|
|
@ -18,7 +19,7 @@ macro_rules! column {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [Row`] with the given children.
|
/// Creates a [`Row`] with the given children.
|
||||||
///
|
///
|
||||||
/// [`Row`]: widget::Row
|
/// [`Row`]: widget::Row
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
@ -84,6 +85,7 @@ pub fn button<'a, Message, Renderer>(
|
||||||
where
|
where
|
||||||
Renderer: crate::Renderer,
|
Renderer: crate::Renderer,
|
||||||
Renderer::Theme: widget::button::StyleSheet,
|
Renderer::Theme: widget::button::StyleSheet,
|
||||||
|
<Renderer::Theme as widget::button::StyleSheet>::Style: Default,
|
||||||
{
|
{
|
||||||
widget::Button::new(content)
|
widget::Button::new(content)
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +210,12 @@ where
|
||||||
T: ToString + Eq + 'static,
|
T: ToString + Eq + 'static,
|
||||||
[T]: ToOwned<Owned = Vec<T>>,
|
[T]: ToOwned<Owned = Vec<T>>,
|
||||||
Renderer: crate::text::Renderer,
|
Renderer: crate::text::Renderer,
|
||||||
Renderer::Theme: widget::pick_list::StyleSheet,
|
Renderer::Theme: widget::pick_list::StyleSheet
|
||||||
|
+ widget::scrollable::StyleSheet
|
||||||
|
+ overlay::menu::StyleSheet
|
||||||
|
+ widget::container::StyleSheet,
|
||||||
|
<Renderer::Theme as overlay::menu::StyleSheet>::Style:
|
||||||
|
From<<Renderer::Theme as widget::pick_list::StyleSheet>::Style>,
|
||||||
{
|
{
|
||||||
widget::PickList::new(options, selected, on_selected)
|
widget::PickList::new(options, selected, on_selected)
|
||||||
}
|
}
|
||||||
|
|
@ -278,6 +285,12 @@ where
|
||||||
///
|
///
|
||||||
/// [`Svg`]: widget::Svg
|
/// [`Svg`]: widget::Svg
|
||||||
/// [`Handle`]: widget::svg::Handle
|
/// [`Handle`]: widget::svg::Handle
|
||||||
pub fn svg(handle: impl Into<widget::svg::Handle>) -> widget::Svg {
|
pub fn svg<Renderer>(
|
||||||
|
handle: impl Into<widget::svg::Handle>,
|
||||||
|
) -> widget::Svg<Renderer>
|
||||||
|
where
|
||||||
|
Renderer: crate::svg::Renderer,
|
||||||
|
Renderer::Theme: widget::svg::StyleSheet,
|
||||||
|
{
|
||||||
widget::Svg::new(handle)
|
widget::Svg::new(handle)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ where
|
||||||
{
|
{
|
||||||
// The raw w/h of the underlying image
|
// The raw w/h of the underlying image
|
||||||
let image_size = {
|
let image_size = {
|
||||||
let (width, height) = renderer.dimensions(handle);
|
let Size { width, height } = renderer.dimensions(handle);
|
||||||
|
|
||||||
Size::new(width as f32, height as f32)
|
Size::new(width as f32, height as f32)
|
||||||
};
|
};
|
||||||
|
|
@ -149,7 +149,7 @@ where
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let (width, height) = renderer.dimensions(&self.handle);
|
let Size { width, height } = renderer.dimensions(&self.handle);
|
||||||
let image_size = Size::new(width as f32, height as f32);
|
let image_size = Size::new(width as f32, height as f32);
|
||||||
|
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
|
||||||
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