Merge branch 'master' into non-uniform-border-radius-for-quads
This commit is contained in:
commit
4029a1cdaa
147 changed files with 4828 additions and 2184 deletions
34
CHANGELOG.md
34
CHANGELOG.md
|
|
@ -6,6 +6,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [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
|
||||
### Fixed
|
||||
- `Padding` type not exposed in `iced`.
|
||||
|
|
@ -257,7 +288,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
- First release! :tada:
|
||||
|
||||
[Unreleased]: https://github.com/iced-rs/iced/compare/0.4.2...HEAD
|
||||
[Unreleased]: https://github.com/iced-rs/iced/compare/0.5.0...HEAD
|
||||
[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.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
|
||||
|
|
|
|||
65
Cargo.toml
65
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced"
|
||||
version = "0.4.2"
|
||||
version = "0.5.2"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A cross-platform GUI library inspired by Elm"
|
||||
|
|
@ -10,14 +10,13 @@ documentation = "https://docs.rs/iced"
|
|||
readme = "README.md"
|
||||
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
||||
categories = ["gui"]
|
||||
resolver = "2"
|
||||
|
||||
[features]
|
||||
default = ["wgpu"]
|
||||
# Enables the `Image` widget
|
||||
image = ["iced_wgpu/image", "image_rs"]
|
||||
image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"]
|
||||
# Enables the `Svg` widget
|
||||
svg = ["iced_wgpu/svg"]
|
||||
svg = ["iced_wgpu?/svg", "iced_glow?/svg"]
|
||||
# Enables the `Canvas` widget
|
||||
canvas = ["iced_graphics/canvas"]
|
||||
# Enables the `QRCode` widget
|
||||
|
|
@ -25,11 +24,9 @@ qr_code = ["iced_graphics/qr_code"]
|
|||
# Enables the `iced_wgpu` renderer
|
||||
wgpu = ["iced_wgpu"]
|
||||
# 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`
|
||||
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)
|
||||
debug = ["iced_winit/debug"]
|
||||
# Enables `tokio` as the `executor::Default` on native platforms
|
||||
|
|
@ -58,61 +55,29 @@ members = [
|
|||
"style",
|
||||
"wgpu",
|
||||
"winit",
|
||||
"examples/arc",
|
||||
"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/integration_opengl",
|
||||
"examples/integration_wgpu",
|
||||
"examples/lazy",
|
||||
"examples/modern_art",
|
||||
"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/*",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
iced_core = { version = "0.5", path = "core" }
|
||||
iced_futures = { version = "0.4", path = "futures" }
|
||||
iced_native = { version = "0.5", path = "native" }
|
||||
iced_graphics = { version = "0.3", path = "graphics" }
|
||||
iced_winit = { version = "0.4", path = "winit", features = ["application"] }
|
||||
iced_glutin = { version = "0.3", path = "glutin", optional = true }
|
||||
iced_glow = { version = "0.3", path = "glow", optional = true }
|
||||
iced_core = { version = "0.6", path = "core" }
|
||||
iced_futures = { version = "0.5", path = "futures" }
|
||||
iced_native = { version = "0.6", path = "native" }
|
||||
iced_graphics = { version = "0.4", path = "graphics" }
|
||||
iced_winit = { version = "0.5", path = "winit", features = ["application"] }
|
||||
iced_glutin = { version = "0.4", path = "glutin", optional = true }
|
||||
iced_glow = { version = "0.4", path = "glow", optional = true }
|
||||
thiserror = "1.0"
|
||||
|
||||
[dependencies.image_rs]
|
||||
version = "0.23"
|
||||
version = "0.24"
|
||||
package = "image"
|
||||
optional = true
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
iced_wgpu = { version = "0.5", path = "wgpu", optional = true }
|
||||
iced_wgpu = { version = "0.6", path = "wgpu", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
iced_wgpu = { version = "0.5", path = "wgpu", features = ["webgl"], optional = true }
|
||||
iced_wgpu = { version = "0.6", path = "wgpu", features = ["webgl"], optional = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
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`:
|
||||
|
||||
```toml
|
||||
iced = "0.4"
|
||||
iced = "0.5"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```toml
|
||||
iced = { version = "0.4", default-features = false, features = ["glow"] }
|
||||
iced = { version = "0.5", default-features = false, features = ["glow"] }
|
||||
```
|
||||
|
||||
__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_core"
|
||||
version = "0.5.0"
|
||||
version = "0.6.1"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "The essential concepts of Iced"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//! 
|
||||
//!
|
||||
//! [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.5/native
|
||||
//! [`iced_web`]: https://github.com/iced-rs/iced_web
|
||||
#![doc(
|
||||
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
|
||||
///
|
||||
/// You can leverage the `From` trait to build [`Padding`] conveniently:
|
||||
|
|
@ -71,9 +73,21 @@ impl Padding {
|
|||
pub fn horizontal(self) -> u16 {
|
||||
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 {
|
||||
Padding {
|
||||
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 {
|
||||
Padding {
|
||||
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 {
|
||||
Padding {
|
||||
top: p[0],
|
||||
|
|
|
|||
|
|
@ -34,6 +34,22 @@ impl Size {
|
|||
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 {
|
||||
|
|
@ -68,3 +84,14 @@ impl From<Size> for Vector<f32> {
|
|||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
examples/geometry/Cargo.toml
Normal file
11
examples/geometry/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "geometry"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced_native = { path = "../../native" }
|
||||
iced_graphics = { path = "../../graphics" }
|
||||
18
examples/geometry/README.md
Normal file
18
examples/geometry/README.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
## Geometry
|
||||
|
||||
A custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../../wgpu).
|
||||
|
||||
The __[`main`]__ file contains all the code of the example.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://gfycat.com/activeunfitkangaroo">
|
||||
<img src="https://thumbs.gfycat.com/ActiveUnfitKangaroo-small.gif">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
You can run it with `cargo run`:
|
||||
```
|
||||
cargo run --package geometry
|
||||
```
|
||||
|
||||
[`main`]: src/main.rs
|
||||
212
examples/geometry/src/main.rs
Normal file
212
examples/geometry/src/main.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
//! This example showcases a simple native custom widget that renders using
|
||||
//! arbitrary low-level geometry.
|
||||
mod rainbow {
|
||||
// For now, to implement a custom native widget you will need to add
|
||||
// `iced_native` and `iced_wgpu` to your dependencies.
|
||||
//
|
||||
// Then, you simply need to define your widget type and implement the
|
||||
// `iced_native::Widget` trait with the `iced_wgpu::Renderer`.
|
||||
//
|
||||
// Of course, you can choose to make the implementation renderer-agnostic,
|
||||
// if you wish to, by creating your own `Renderer` trait, which could be
|
||||
// implemented by `iced_wgpu` and other renderers.
|
||||
use iced_graphics::renderer::{self, Renderer};
|
||||
use iced_graphics::triangle::ColoredVertex2D;
|
||||
use iced_graphics::{Backend, Primitive};
|
||||
|
||||
use iced_native::layout;
|
||||
use iced_native::widget::{self, Widget};
|
||||
use iced_native::{
|
||||
Element, Layout, Length, Point, Rectangle, Size, Vector,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Rainbow;
|
||||
|
||||
pub fn rainbow() -> Rainbow {
|
||||
Rainbow
|
||||
}
|
||||
|
||||
impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Length::Fill
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer<B, T>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let size = limits.width(Length::Fill).resolve(Size::ZERO);
|
||||
|
||||
layout::Node::new(Size::new(size.width, size.width))
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_tree: &widget::Tree,
|
||||
renderer: &mut Renderer<B, T>,
|
||||
_theme: &T,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
use iced_graphics::triangle::Mesh2D;
|
||||
use iced_native::Renderer as _;
|
||||
|
||||
let b = layout.bounds();
|
||||
|
||||
// R O Y G B I V
|
||||
let color_r = [1.0, 0.0, 0.0, 1.0];
|
||||
let color_o = [1.0, 0.5, 0.0, 1.0];
|
||||
let color_y = [1.0, 1.0, 0.0, 1.0];
|
||||
let color_g = [0.0, 1.0, 0.0, 1.0];
|
||||
let color_gb = [0.0, 1.0, 0.5, 1.0];
|
||||
let color_b = [0.0, 0.2, 1.0, 1.0];
|
||||
let color_i = [0.5, 0.0, 1.0, 1.0];
|
||||
let color_v = [0.75, 0.0, 0.5, 1.0];
|
||||
|
||||
let posn_center = {
|
||||
if b.contains(cursor_position) {
|
||||
[cursor_position.x - b.x, cursor_position.y - b.y]
|
||||
} else {
|
||||
[b.width / 2.0, b.height / 2.0]
|
||||
}
|
||||
};
|
||||
|
||||
let posn_tl = [0.0, 0.0];
|
||||
let posn_t = [b.width / 2.0, 0.0];
|
||||
let posn_tr = [b.width, 0.0];
|
||||
let posn_r = [b.width, b.height / 2.0];
|
||||
let posn_br = [b.width, b.height];
|
||||
let posn_b = [(b.width / 2.0), b.height];
|
||||
let posn_bl = [0.0, b.height];
|
||||
let posn_l = [0.0, b.height / 2.0];
|
||||
|
||||
let mesh = Primitive::SolidMesh {
|
||||
size: b.size(),
|
||||
buffers: Mesh2D {
|
||||
vertices: vec![
|
||||
ColoredVertex2D {
|
||||
position: posn_center,
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
},
|
||||
ColoredVertex2D {
|
||||
position: posn_tl,
|
||||
color: color_r,
|
||||
},
|
||||
ColoredVertex2D {
|
||||
position: posn_t,
|
||||
color: color_o,
|
||||
},
|
||||
ColoredVertex2D {
|
||||
position: posn_tr,
|
||||
color: color_y,
|
||||
},
|
||||
ColoredVertex2D {
|
||||
position: posn_r,
|
||||
color: color_g,
|
||||
},
|
||||
ColoredVertex2D {
|
||||
position: posn_br,
|
||||
color: color_gb,
|
||||
},
|
||||
ColoredVertex2D {
|
||||
position: posn_b,
|
||||
color: color_b,
|
||||
},
|
||||
ColoredVertex2D {
|
||||
position: posn_bl,
|
||||
color: color_i,
|
||||
},
|
||||
ColoredVertex2D {
|
||||
position: posn_l,
|
||||
color: color_v,
|
||||
},
|
||||
],
|
||||
indices: vec![
|
||||
0, 1, 2, // TL
|
||||
0, 2, 3, // T
|
||||
0, 3, 4, // TR
|
||||
0, 4, 5, // R
|
||||
0, 5, 6, // BR
|
||||
0, 6, 7, // B
|
||||
0, 7, 8, // BL
|
||||
0, 8, 1, // L
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
|
||||
renderer.draw_primitive(mesh);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>>
|
||||
where
|
||||
B: Backend,
|
||||
{
|
||||
fn from(rainbow: Rainbow) -> Self {
|
||||
Self::new(rainbow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use iced::widget::{column, container, scrollable};
|
||||
use iced::{Element, Length, Sandbox, Settings};
|
||||
use rainbow::rainbow;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
}
|
||||
|
||||
struct Example;
|
||||
|
||||
impl Sandbox for Example {
|
||||
type Message = ();
|
||||
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Custom 2D geometry - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, _: ()) {}
|
||||
|
||||
fn view(&self) -> Element<()> {
|
||||
let content = column![
|
||||
rainbow(),
|
||||
"In this example we draw a custom widget Rainbow, using \
|
||||
the Mesh2D primitive. This primitive supplies a list of \
|
||||
triangles, expressed as vertices and indices.",
|
||||
"Move your cursor over it, and see the center vertex \
|
||||
follow you!",
|
||||
"Every Vertex2D defines its own color. You could use the \
|
||||
Mesh2D primitive to render virtually any two-dimensional \
|
||||
geometry for your widget.",
|
||||
]
|
||||
.padding(20)
|
||||
.spacing(20)
|
||||
.max_width(500);
|
||||
|
||||
let scrollable =
|
||||
scrollable(container(content).width(Length::Fill).center_x());
|
||||
|
||||
container(scrollable)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
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: 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,3 +8,4 @@ publish = false
|
|||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||
rand = "0.8.5"
|
||||
env_logger = "0.9"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ use iced::{
|
|||
use rand::{thread_rng, Rng};
|
||||
|
||||
fn main() -> iced::Result {
|
||||
env_logger::builder().format_timestamp(None).init();
|
||||
|
||||
ModernArt::run(Settings {
|
||||
antialiasing: true,
|
||||
..Settings::default()
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ enum Message {
|
|||
Dragged(pane_grid::DragEvent),
|
||||
Resized(pane_grid::ResizeEvent),
|
||||
TogglePin(pane_grid::Pane),
|
||||
Maximize(pane_grid::Pane),
|
||||
Restore,
|
||||
Close(pane_grid::Pane),
|
||||
CloseFocused,
|
||||
}
|
||||
|
|
@ -114,6 +116,10 @@ impl Application for Example {
|
|||
*is_pinned = !*is_pinned;
|
||||
}
|
||||
}
|
||||
Message::Maximize(pane) => self.panes.maximize(&pane),
|
||||
Message::Restore => {
|
||||
self.panes.restore();
|
||||
}
|
||||
Message::Close(pane) => {
|
||||
if let Some((_, sibling)) = self.panes.close(&pane) {
|
||||
self.focus = Some(sibling);
|
||||
|
|
@ -157,7 +163,7 @@ impl Application for Example {
|
|||
let focus = self.focus;
|
||||
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 pin_button = button(
|
||||
|
|
@ -178,7 +184,12 @@ impl Application for Example {
|
|||
.spacing(5);
|
||||
|
||||
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)
|
||||
.style(if is_focused {
|
||||
style::title_bar_focused
|
||||
|
|
@ -314,16 +325,35 @@ fn view_controls<'a>(
|
|||
pane: pane_grid::Pane,
|
||||
total_panes: usize,
|
||||
is_pinned: bool,
|
||||
is_maximized: bool,
|
||||
) -> 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)
|
||||
.padding(3);
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -79,10 +79,14 @@ impl Application for SolarSystem {
|
|||
}
|
||||
|
||||
fn style(&self) -> theme::Application {
|
||||
theme::Application::Custom(|_theme| application::Appearance {
|
||||
fn dark_background(_theme: &Theme) -> application::Appearance {
|
||||
application::Appearance {
|
||||
background_color: Color::BLACK,
|
||||
text_color: Color::WHITE,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
theme::Application::from(dark_background as fn(&Theme) -> _)
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
|
|
|
|||
|
|
@ -131,7 +131,11 @@ impl Application for Todos {
|
|||
task.update(task_message);
|
||||
|
||||
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 {
|
||||
Command::none()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_futures"
|
||||
version = "0.4.1"
|
||||
version = "0.5.1"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
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
|
||||
/// to listen to time.
|
||||
///
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.4/examples
|
||||
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.4/examples/download_progress
|
||||
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.4/examples/stopwatch
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.5/examples
|
||||
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.5/examples/download_progress
|
||||
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.5/examples/stopwatch
|
||||
pub trait Recipe<Hasher: std::hash::Hasher, Event> {
|
||||
/// The events that will be produced by a [`Subscription`] with this
|
||||
/// [`Recipe`].
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_glow"
|
||||
version = "0.3.0"
|
||||
version = "0.4.1"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A glow renderer for iced"
|
||||
|
|
@ -8,12 +8,22 @@ license = "MIT AND OFL-1.1"
|
|||
repository = "https://github.com/iced-rs/iced"
|
||||
|
||||
[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"]
|
||||
qr_code = ["iced_graphics/qr_code"]
|
||||
default_system_font = ["iced_graphics/font-source"]
|
||||
# Not supported yet!
|
||||
image = []
|
||||
svg = []
|
||||
|
||||
[dependencies]
|
||||
glow = "0.11.1"
|
||||
|
|
@ -24,11 +34,11 @@ bytemuck = "1.4"
|
|||
log = "0.4"
|
||||
|
||||
[dependencies.iced_native]
|
||||
version = "0.5"
|
||||
version = "0.6"
|
||||
path = "../native"
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.3"
|
||||
version = "0.4"
|
||||
path = "../graphics"
|
||||
features = ["font-fallback", "font-icons", "opengl"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
use crate::image;
|
||||
use crate::quad;
|
||||
use crate::text;
|
||||
use crate::{program, triangle};
|
||||
|
|
@ -15,6 +17,8 @@ use iced_native::{Font, Size};
|
|||
/// [`iced`]: https://github.com/iced-rs/iced
|
||||
#[derive(Debug)]
|
||||
pub struct Backend {
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline: image::Pipeline,
|
||||
quad_pipeline: quad::Pipeline,
|
||||
text_pipeline: text::Pipeline,
|
||||
triangle_pipeline: triangle::Pipeline,
|
||||
|
|
@ -32,10 +36,14 @@ impl Backend {
|
|||
|
||||
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 triangle_pipeline = triangle::Pipeline::new(gl, &shader_version);
|
||||
|
||||
Self {
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline,
|
||||
quad_pipeline,
|
||||
text_pipeline,
|
||||
triangle_pipeline,
|
||||
|
|
@ -70,6 +78,9 @@ impl Backend {
|
|||
viewport_size.height,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
self.image_pipeline.trim_cache(gl);
|
||||
}
|
||||
|
||||
fn flush(
|
||||
|
|
@ -112,6 +123,21 @@ impl Backend {
|
|||
);
|
||||
}
|
||||
|
||||
#[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,
|
||||
);
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
for text in layer.text.iter() {
|
||||
// Target physical coordinates directly to avoid blurry text
|
||||
|
|
@ -238,8 +264,8 @@ impl backend::Text for Backend {
|
|||
|
||||
#[cfg(feature = "image")]
|
||||
impl backend::Image for Backend {
|
||||
fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) {
|
||||
(50, 50)
|
||||
fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
|
||||
self.image_pipeline.dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -247,8 +273,8 @@ impl backend::Image for Backend {
|
|||
impl backend::Svg for Backend {
|
||||
fn viewport_dimensions(
|
||||
&self,
|
||||
_handle: &iced_native::svg::Handle,
|
||||
) -> (u32, u32) {
|
||||
(50, 50)
|
||||
handle: &iced_native::svg::Handle,
|
||||
) -> Size<u32> {
|
||||
self.image_pipeline.viewport_dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
243
glow/src/image.rs
Normal file
243
glow/src/image.rs
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
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, bounds } => {
|
||||
let size = [bounds.width, bounds.height];
|
||||
(
|
||||
vector_cache.upload(
|
||||
handle,
|
||||
size,
|
||||
_scale_factor,
|
||||
&mut gl,
|
||||
&mut self.storage,
|
||||
),
|
||||
bounds,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "svg"))]
|
||||
layer::Image::Vector { handle: _, 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
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||
)]
|
||||
|
|
@ -24,6 +24,8 @@
|
|||
pub use glow;
|
||||
|
||||
mod backend;
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
mod image;
|
||||
mod program;
|
||||
mod quad;
|
||||
mod text;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
#ifdef GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
#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
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
#define gl_FragColor fragColor
|
||||
#endif
|
||||
|
||||
in vec2 raw_position;
|
||||
|
||||
uniform vec4 gradient_direction;
|
||||
uniform uint color_stops_size;
|
||||
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];
|
||||
|
|
@ -28,23 +28,23 @@ void main() {
|
|||
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
|
||||
fragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
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 - 1u].x;
|
||||
float max_offset = color_stops[color_stops_size - 1].x;
|
||||
|
||||
for (uint i = 0u; i < color_stops_size - 2u; i += 2u) {
|
||||
float curr_offset = color_stops[i+1u].x;
|
||||
float next_offset = color_stops[i+3u].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
|
||||
fragColor = color_stops[0];
|
||||
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
|
||||
fragColor = mix(color_stops[i], color_stops[i+2u], smoothstep(
|
||||
gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep(
|
||||
curr_offset,
|
||||
next_offset,
|
||||
coord_offset
|
||||
|
|
@ -53,7 +53,7 @@ void main() {
|
|||
|
||||
if (coord_offset >= max_offset) {
|
||||
//current coordinate is before the last defined offset, set it to the last color
|
||||
fragColor = color_stops[color_stops_size - 2u];
|
||||
gl_FragColor = color_stops[color_stops_size - 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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;
|
||||
}
|
||||
|
|
@ -11,8 +11,8 @@ out vec4 fragColor;
|
|||
#define gl_FragColor fragColor
|
||||
#endif
|
||||
|
||||
uniform vec4 color;
|
||||
in vec4 v_Color;
|
||||
|
||||
void main() {
|
||||
fragColor = color;
|
||||
gl_FragColor = v_Color;
|
||||
}
|
||||
11
glow/src/shader/common/solid.vert
Normal file
11
glow/src/shader/common/solid.vert
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
uniform mat4 u_Transform;
|
||||
|
||||
in vec2 i_Position;
|
||||
in vec4 i_Color;
|
||||
|
||||
out vec4 v_Color;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
|
||||
v_Color = i_Color;
|
||||
}
|
||||
|
|
@ -1,74 +1,52 @@
|
|||
//! Draw meshes of triangles.
|
||||
mod gradient;
|
||||
mod solid;
|
||||
|
||||
use crate::program;
|
||||
use crate::Transformation;
|
||||
|
||||
use iced_graphics::gradient::Gradient;
|
||||
use iced_graphics::layer::mesh::{self, Mesh};
|
||||
use iced_graphics::triangle::{self, Vertex2D};
|
||||
use iced_graphics::triangle::{ColoredVertex2D, Vertex2D};
|
||||
|
||||
use glow::HasContext;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Pipeline {
|
||||
vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||
vertices: Buffer<Vertex2D>,
|
||||
indices: Buffer<u32>,
|
||||
programs: ProgramList,
|
||||
}
|
||||
const DEFAULT_VERTICES: usize = 1_000;
|
||||
const DEFAULT_INDICES: usize = 1_000;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ProgramList {
|
||||
pub(crate) struct Pipeline {
|
||||
indices: Buffer<u32>,
|
||||
solid: solid::Program,
|
||||
gradient: gradient::Program,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(gl: &glow::Context, shader_version: &program::Version) -> Self {
|
||||
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,
|
||||
std::mem::size_of::<Vertex2D>() as usize,
|
||||
)
|
||||
};
|
||||
|
||||
let indices = unsafe {
|
||||
let mut indices = unsafe {
|
||||
Buffer::new(
|
||||
gl,
|
||||
glow::ELEMENT_ARRAY_BUFFER,
|
||||
glow::DYNAMIC_DRAW,
|
||||
std::mem::size_of::<u32>() as usize,
|
||||
DEFAULT_INDICES,
|
||||
)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let stride = std::mem::size_of::<Vertex2D>() as i32;
|
||||
let solid = solid::Program::new(gl, shader_version);
|
||||
let gradient = gradient::Program::new(gl, shader_version);
|
||||
|
||||
gl.enable_vertex_attrib_array(0);
|
||||
gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0);
|
||||
unsafe {
|
||||
gl.bind_vertex_array(Some(solid.vertex_array));
|
||||
indices.bind(gl, 0);
|
||||
|
||||
gl.bind_vertex_array(Some(gradient.vertex_array));
|
||||
indices.bind(gl, 0);
|
||||
|
||||
gl.bind_vertex_array(None);
|
||||
};
|
||||
}
|
||||
|
||||
Self {
|
||||
vertex_array,
|
||||
vertices,
|
||||
indices,
|
||||
programs: ProgramList {
|
||||
solid: solid::Program::new(gl, shader_version),
|
||||
gradient: gradient::Program::new(gl, shader_version),
|
||||
},
|
||||
solid,
|
||||
gradient,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,50 +61,83 @@ impl Pipeline {
|
|||
unsafe {
|
||||
gl.enable(glow::MULTISAMPLE);
|
||||
gl.enable(glow::SCISSOR_TEST);
|
||||
gl.bind_vertex_array(Some(self.vertex_array))
|
||||
}
|
||||
|
||||
//count the total amount of vertices & indices we need to handle
|
||||
let (total_vertices, total_indices) = mesh::attribute_count_of(meshes);
|
||||
// Count the total amount of vertices & indices we need to handle
|
||||
let count = mesh::attribute_count_of(meshes);
|
||||
|
||||
// Then we ensure the current attribute buffers are big enough, resizing if necessary
|
||||
unsafe {
|
||||
self.vertices.bind(gl, total_vertices);
|
||||
self.indices.bind(gl, total_indices);
|
||||
self.indices.bind(gl, count.indices);
|
||||
}
|
||||
|
||||
// We upload all the vertices and indices upfront
|
||||
let mut vertex_offset = 0;
|
||||
let mut solid_vertex_offset = 0;
|
||||
let mut gradient_vertex_offset = 0;
|
||||
let mut index_offset = 0;
|
||||
|
||||
for mesh in meshes {
|
||||
unsafe {
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
(vertex_offset * std::mem::size_of::<Vertex2D>()) as i32,
|
||||
bytemuck::cast_slice(&mesh.buffers.vertices),
|
||||
);
|
||||
let indices = mesh.indices();
|
||||
|
||||
unsafe {
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ELEMENT_ARRAY_BUFFER,
|
||||
(index_offset * std::mem::size_of::<u32>()) as i32,
|
||||
bytemuck::cast_slice(&mesh.buffers.indices),
|
||||
bytemuck::cast_slice(indices),
|
||||
);
|
||||
|
||||
vertex_offset += mesh.buffers.vertices.len();
|
||||
index_offset += mesh.buffers.indices.len();
|
||||
index_offset += indices.len();
|
||||
}
|
||||
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
unsafe {
|
||||
self.solid.vertices.bind(gl, count.solid_vertices);
|
||||
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
(solid_vertex_offset
|
||||
* std::mem::size_of::<ColoredVertex2D>())
|
||||
as i32,
|
||||
bytemuck::cast_slice(&buffers.vertices),
|
||||
);
|
||||
}
|
||||
|
||||
solid_vertex_offset += buffers.vertices.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
|
||||
let mut last_vertex = 0;
|
||||
let mut last_solid_vertex = 0;
|
||||
let mut last_gradient_vertex = 0;
|
||||
let mut last_index = 0;
|
||||
|
||||
for mesh in meshes {
|
||||
let transform = transformation
|
||||
* Transformation::translate(mesh.origin.x, mesh.origin.y);
|
||||
let indices = mesh.indices();
|
||||
let origin = mesh.origin();
|
||||
|
||||
let clip_bounds = (mesh.clip_bounds * scale_factor).snap();
|
||||
let transform =
|
||||
transformation * Transformation::translate(origin.x, origin.y);
|
||||
|
||||
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
|
||||
|
||||
unsafe {
|
||||
gl.scissor(
|
||||
|
|
@ -136,29 +147,126 @@ impl Pipeline {
|
|||
clip_bounds.width as i32,
|
||||
clip_bounds.height as i32,
|
||||
);
|
||||
}
|
||||
|
||||
match mesh.style {
|
||||
triangle::Style::Solid(color) => {
|
||||
self.programs.solid.use_program(gl, color, &transform);
|
||||
}
|
||||
triangle::Style::Gradient(gradient) => {
|
||||
self.programs
|
||||
.gradient
|
||||
.use_program(gl, gradient, &transform);
|
||||
}
|
||||
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(
|
||||
glow::TRIANGLES,
|
||||
mesh.buffers.indices.len() as i32,
|
||||
indices.len() as i32,
|
||||
glow::UNSIGNED_INT,
|
||||
(last_index * std::mem::size_of::<u32>()) as i32,
|
||||
last_vertex as i32,
|
||||
last_solid_vertex as i32,
|
||||
);
|
||||
|
||||
last_vertex += mesh.buffers.vertices.len();
|
||||
last_index += mesh.buffers.indices.len();
|
||||
last_solid_vertex += buffers.vertices.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 {
|
||||
|
|
@ -169,47 +277,8 @@ impl Pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
/// A simple shader program. Uses [`triangle.vert`] for its vertex shader and only binds position
|
||||
/// attribute location.
|
||||
pub(super) fn program(
|
||||
gl: &glow::Context,
|
||||
shader_version: &program::Version,
|
||||
fragment_shader: &'static str,
|
||||
) -> <glow::Context as HasContext>::Program {
|
||||
unsafe {
|
||||
let vertex_shader = program::Shader::vertex(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("shader/common/triangle.vert"),
|
||||
);
|
||||
|
||||
let fragment_shader =
|
||||
program::Shader::fragment(gl, shader_version, fragment_shader);
|
||||
|
||||
program::create(
|
||||
gl,
|
||||
&[vertex_shader, fragment_shader],
|
||||
&[(0, "i_Position")],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_transform(
|
||||
gl: &glow::Context,
|
||||
location: <glow::Context as HasContext>::UniformLocation,
|
||||
transform: Transformation,
|
||||
) {
|
||||
unsafe {
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&location),
|
||||
false,
|
||||
transform.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Buffer<T> {
|
||||
pub struct Buffer<T> {
|
||||
raw: <glow::Context as HasContext>::Buffer,
|
||||
target: u32,
|
||||
usage: u32,
|
||||
|
|
@ -253,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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,162 +0,0 @@
|
|||
use crate::program::Version;
|
||||
use crate::triangle;
|
||||
use glow::{Context, HasContext, NativeProgram};
|
||||
use iced_graphics::gradient::Gradient;
|
||||
use iced_graphics::gradient::Linear;
|
||||
use iced_graphics::Transformation;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Program {
|
||||
pub program: <Context as HasContext>::Program,
|
||||
pub uniform_data: UniformData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UniformData {
|
||||
gradient: Gradient,
|
||||
transform: Transformation,
|
||||
uniform_locations: UniformLocations,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UniformLocations {
|
||||
gradient_direction_location: <Context as HasContext>::UniformLocation,
|
||||
color_stops_size_location: <Context as HasContext>::UniformLocation,
|
||||
//currently the maximum number of stops is 16 due to lack of SSBO in GL2.1
|
||||
color_stops_location: <Context as HasContext>::UniformLocation,
|
||||
transform_location: <Context as HasContext>::UniformLocation,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
pub fn new(gl: &Context, shader_version: &Version) -> Self {
|
||||
let program = triangle::program(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("../shader/common/gradient.frag"),
|
||||
);
|
||||
|
||||
Self {
|
||||
program,
|
||||
uniform_data: UniformData::new(gl, program),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_uniforms(
|
||||
&mut self,
|
||||
gl: &Context,
|
||||
gradient: &Gradient,
|
||||
transform: &Transformation,
|
||||
) {
|
||||
if transform != &self.uniform_data.transform {
|
||||
triangle::set_transform(
|
||||
gl,
|
||||
self.uniform_data.uniform_locations.transform_location,
|
||||
*transform,
|
||||
);
|
||||
}
|
||||
|
||||
if &self.uniform_data.gradient != gradient {
|
||||
match gradient {
|
||||
Gradient::Linear(linear) => unsafe {
|
||||
gl.uniform_4_f32(
|
||||
Some(
|
||||
&self
|
||||
.uniform_data
|
||||
.uniform_locations
|
||||
.gradient_direction_location,
|
||||
),
|
||||
linear.start.x,
|
||||
linear.start.y,
|
||||
linear.end.x,
|
||||
linear.end.y,
|
||||
);
|
||||
|
||||
gl.uniform_1_u32(
|
||||
Some(
|
||||
&self
|
||||
.uniform_data
|
||||
.uniform_locations
|
||||
.color_stops_size_location,
|
||||
),
|
||||
(linear.color_stops.len() * 2) as u32,
|
||||
);
|
||||
|
||||
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
|
||||
.uniform_data
|
||||
.uniform_locations
|
||||
.color_stops_location,
|
||||
),
|
||||
&stops,
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
self.uniform_data.gradient = gradient.clone();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_program(
|
||||
&mut self,
|
||||
gl: &Context,
|
||||
gradient: &Gradient,
|
||||
transform: &Transformation,
|
||||
) {
|
||||
unsafe { gl.use_program(Some(self.program)) }
|
||||
self.write_uniforms(gl, gradient, transform);
|
||||
}
|
||||
}
|
||||
|
||||
impl UniformData {
|
||||
fn new(gl: &Context, program: NativeProgram) -> Self {
|
||||
let gradient_direction_location =
|
||||
unsafe { gl.get_uniform_location(program, "gradient_direction") }
|
||||
.expect("Gradient - Get gradient_direction.");
|
||||
|
||||
let color_stops_size_location =
|
||||
unsafe { gl.get_uniform_location(program, "color_stops_size") }
|
||||
.expect("Gradient - Get color_stops_size.");
|
||||
|
||||
let color_stops_location = unsafe {
|
||||
gl.get_uniform_location(program, "color_stops")
|
||||
.expect("Gradient - Get color_stops.")
|
||||
};
|
||||
|
||||
let transform_location =
|
||||
unsafe { gl.get_uniform_location(program, "u_Transform") }
|
||||
.expect("Gradient - Get u_Transform.");
|
||||
|
||||
Self {
|
||||
gradient: Gradient::Linear(Linear {
|
||||
start: Default::default(),
|
||||
end: Default::default(),
|
||||
color_stops: vec![],
|
||||
}),
|
||||
transform: Transformation::identity(),
|
||||
uniform_locations: UniformLocations {
|
||||
gradient_direction_location,
|
||||
color_stops_size_location,
|
||||
color_stops_location,
|
||||
transform_location,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
use crate::program::Version;
|
||||
use crate::{triangle, Color};
|
||||
use glow::{Context, HasContext, NativeProgram};
|
||||
use iced_graphics::Transformation;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Program {
|
||||
program: <Context as HasContext>::Program,
|
||||
uniform_data: UniformData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UniformData {
|
||||
pub color: Color,
|
||||
pub color_location: <Context as HasContext>::UniformLocation,
|
||||
pub transform: Transformation,
|
||||
pub transform_location: <Context as HasContext>::UniformLocation,
|
||||
}
|
||||
|
||||
impl UniformData {
|
||||
fn new(gl: &Context, program: NativeProgram) -> Self {
|
||||
Self {
|
||||
color: Color::TRANSPARENT,
|
||||
color_location: unsafe {
|
||||
gl.get_uniform_location(program, "color")
|
||||
}
|
||||
.expect("Solid - Get color."),
|
||||
transform: Transformation::identity(),
|
||||
transform_location: unsafe {
|
||||
gl.get_uniform_location(program, "u_Transform")
|
||||
}
|
||||
.expect("Solid - Get u_Transform."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Program {
|
||||
pub fn new(gl: &Context, shader_version: &Version) -> Self {
|
||||
let program = triangle::program(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("../shader/common/triangle.frag"),
|
||||
);
|
||||
|
||||
Self {
|
||||
program,
|
||||
uniform_data: UniformData::new(gl, program),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_uniforms(
|
||||
&mut self,
|
||||
gl: &Context,
|
||||
color: &Color,
|
||||
transform: &Transformation,
|
||||
) {
|
||||
if transform != &self.uniform_data.transform {
|
||||
triangle::set_transform(
|
||||
gl,
|
||||
self.uniform_data.transform_location,
|
||||
*transform,
|
||||
)
|
||||
}
|
||||
|
||||
if color != &self.uniform_data.color {
|
||||
let [r, g, b, a] = color.into_linear();
|
||||
|
||||
unsafe {
|
||||
gl.uniform_4_f32(
|
||||
Some(&self.uniform_data.color_location),
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a,
|
||||
);
|
||||
}
|
||||
|
||||
self.uniform_data.color = *color;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_program(
|
||||
&mut self,
|
||||
gl: &Context,
|
||||
color: &Color,
|
||||
transform: &Transformation,
|
||||
) {
|
||||
unsafe { gl.use_program(Some(self.program)) }
|
||||
self.write_uniforms(gl, color, transform)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_glutin"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A glutin runtime for Iced"
|
||||
|
|
@ -23,15 +23,15 @@ git = "https://github.com/iced-rs/glutin"
|
|||
rev = "da8d291486b4c9bec12487a46c119c4b1d386abf"
|
||||
|
||||
[dependencies.iced_native]
|
||||
version = "0.5"
|
||||
version = "0.6"
|
||||
path = "../native"
|
||||
|
||||
[dependencies.iced_winit]
|
||||
version = "0.4"
|
||||
version = "0.5"
|
||||
path = "../winit"
|
||||
features = ["application"]
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.3"
|
||||
version = "0.4"
|
||||
path = "../graphics"
|
||||
features = ["opengl"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_graphics"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
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"]
|
||||
|
||||
[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"]
|
||||
qr_code = ["qrcode", "canvas"]
|
||||
font-source = ["font-kit"]
|
||||
font-fallback = []
|
||||
font-icons = []
|
||||
opengl = []
|
||||
image_rs = ["kamadak-exif"]
|
||||
|
||||
[dependencies]
|
||||
glam = "0.21.3"
|
||||
log = "0.4"
|
||||
raw-window-handle = "0.5"
|
||||
thiserror = "1.0"
|
||||
bitflags = "1.2"
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.4"
|
||||
features = ["derive"]
|
||||
|
||||
[dependencies.iced_native]
|
||||
version = "0.5"
|
||||
version = "0.6"
|
||||
path = "../native"
|
||||
|
||||
[dependencies.iced_style]
|
||||
version = "0.4"
|
||||
version = "0.5"
|
||||
path = "../style"
|
||||
|
||||
[dependencies.lyon]
|
||||
|
|
@ -48,6 +64,28 @@ default-features = false
|
|||
version = "0.10"
|
||||
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]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
all-features = true
|
||||
|
|
|
|||
|
|
@ -66,11 +66,11 @@ pub trait Text {
|
|||
/// A graphics backend that supports image rendering.
|
||||
pub trait 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.
|
||||
pub trait 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>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::{Color, Point, Size};
|
|||
/// 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`]
|
||||
/// A linear gradient interpolates colors along a direction from its `start` to its `end`
|
||||
/// point.
|
||||
Linear(Linear),
|
||||
}
|
||||
|
|
@ -23,10 +23,15 @@ impl Gradient {
|
|||
|
||||
#[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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
use crate::gradient::{ColorStop, Gradient, Position};
|
||||
use crate::{Color, Point};
|
||||
|
||||
/// A linear gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`].
|
||||
/// 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.
|
||||
|
|
|
|||
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 std::collections::{HashMap, HashSet};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
/// Entry in cache corresponding to an image handle
|
||||
#[derive(Debug)]
|
||||
pub enum Memory {
|
||||
Host(::image_rs::ImageBuffer<::image_rs::Bgra<u8>, Vec<u8>>),
|
||||
Device(atlas::Entry),
|
||||
pub enum Memory<T: Storage> {
|
||||
/// Image data on host
|
||||
Host(::image_rs::ImageBuffer<::image_rs::Rgba<u8>, Vec<u8>>),
|
||||
/// Storage entry
|
||||
Device(T::Entry),
|
||||
/// Image not found
|
||||
NotFound,
|
||||
/// Invalid image data
|
||||
Invalid,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
pub fn dimensions(&self) -> (u32, u32) {
|
||||
impl<T: Storage> Memory<T> {
|
||||
/// Width and height of image
|
||||
pub fn dimensions(&self) -> Size<u32> {
|
||||
use crate::image::storage::Entry;
|
||||
|
||||
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::NotFound => (1, 1),
|
||||
Memory::Invalid => (1, 1),
|
||||
Memory::NotFound => Size::new(1, 1),
|
||||
Memory::Invalid => Size::new(1, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches image raster data
|
||||
#[derive(Debug)]
|
||||
pub struct Cache {
|
||||
map: HashMap<u64, Memory>,
|
||||
pub struct Cache<T: Storage> {
|
||||
map: HashMap<u64, Memory<T>>,
|
||||
hits: HashSet<u64>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
hits: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&mut self, handle: &image::Handle) -> &mut Memory {
|
||||
impl<T: Storage> Cache<T> {
|
||||
/// Load image
|
||||
pub fn load(&mut self, handle: &image::Handle) -> &mut Memory<T> {
|
||||
if self.contains(handle) {
|
||||
return self.get(handle).unwrap();
|
||||
}
|
||||
|
|
@ -53,7 +63,7 @@ impl Cache {
|
|||
})
|
||||
.unwrap_or_else(Operation::empty);
|
||||
|
||||
Memory::Host(operation.perform(image.to_bgra8()))
|
||||
Memory::Host(operation.perform(image.to_rgba8()))
|
||||
} else {
|
||||
Memory::NotFound
|
||||
}
|
||||
|
|
@ -65,12 +75,12 @@ impl Cache {
|
|||
.ok()
|
||||
.unwrap_or_else(Operation::empty);
|
||||
|
||||
Memory::Host(operation.perform(image.to_bgra8()))
|
||||
Memory::Host(operation.perform(image.to_rgba8()))
|
||||
} else {
|
||||
Memory::Invalid
|
||||
}
|
||||
}
|
||||
image::Data::Pixels {
|
||||
image::Data::Rgba {
|
||||
width,
|
||||
height,
|
||||
pixels,
|
||||
|
|
@ -91,19 +101,19 @@ impl Cache {
|
|||
self.get(handle).unwrap()
|
||||
}
|
||||
|
||||
/// Load image and upload raster data
|
||||
pub fn upload(
|
||||
&mut self,
|
||||
handle: &image::Handle,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
atlas: &mut Atlas,
|
||||
) -> Option<&atlas::Entry> {
|
||||
state: &mut T::State<'_>,
|
||||
storage: &mut T,
|
||||
) -> Option<&T::Entry> {
|
||||
let memory = self.load(handle);
|
||||
|
||||
if let Memory::Host(image) = memory {
|
||||
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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
self.map.retain(|k, memory| {
|
||||
|
|
@ -123,7 +134,7 @@ impl Cache {
|
|||
|
||||
if !retain {
|
||||
if let Memory::Device(entry) = memory {
|
||||
atlas.remove(entry);
|
||||
storage.remove(entry, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,13 +144,13 @@ impl Cache {
|
|||
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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -148,6 +159,15 @@ impl Cache {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Storage> Default for Cache<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
hits: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct Operation: u8 {
|
||||
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,45 @@
|
|||
use crate::image::atlas::{self, Atlas};
|
||||
//! Vector image loading and caching
|
||||
use crate::image::Storage;
|
||||
|
||||
use iced_native::svg;
|
||||
use iced_native::Size;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
|
||||
/// Entry in cache corresponding to an svg handle
|
||||
pub enum Svg {
|
||||
/// Parsed svg
|
||||
Loaded(usvg::Tree),
|
||||
/// Svg not found or failed to parse
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl Svg {
|
||||
pub fn viewport_dimensions(&self) -> (u32, u32) {
|
||||
/// Viewport width and height
|
||||
pub fn viewport_dimensions(&self) -> Size<u32> {
|
||||
match self {
|
||||
Svg::Loaded(tree) => {
|
||||
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)]
|
||||
pub struct Cache {
|
||||
pub struct Cache<T: Storage> {
|
||||
svgs: HashMap<u64, Svg>,
|
||||
rasterized: HashMap<(u64, u32, u32), atlas::Entry>,
|
||||
rasterized: HashMap<(u64, u32, u32), T::Entry>,
|
||||
svg_hits: HashSet<u64>,
|
||||
rasterized_hits: HashSet<(u64, u32, u32)>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
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 {
|
||||
if self.svgs.contains_key(&handle.id()) {
|
||||
return self.svgs.get(&handle.id()).unwrap();
|
||||
|
|
@ -73,15 +72,15 @@ impl Cache {
|
|||
self.svgs.get(&handle.id()).unwrap()
|
||||
}
|
||||
|
||||
/// Load svg and upload raster data
|
||||
pub fn upload(
|
||||
&mut self,
|
||||
handle: &svg::Handle,
|
||||
[width, height]: [f32; 2],
|
||||
scale: f32,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
texture_atlas: &mut Atlas,
|
||||
) -> Option<&atlas::Entry> {
|
||||
state: &mut T::State<'_>,
|
||||
storage: &mut T,
|
||||
) -> Option<&T::Entry> {
|
||||
let id = handle.id();
|
||||
|
||||
let (width, height) = (
|
||||
|
|
@ -122,16 +121,8 @@ impl Cache {
|
|||
img.as_mut(),
|
||||
)?;
|
||||
|
||||
let mut rgba = img.take();
|
||||
rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2));
|
||||
|
||||
let allocation = texture_atlas.upload(
|
||||
width,
|
||||
height,
|
||||
bytemuck::cast_slice(rgba.as_slice()),
|
||||
device,
|
||||
encoder,
|
||||
)?;
|
||||
let allocation =
|
||||
storage.upload(width, height, img.data(), state)?;
|
||||
log::debug!("allocating {} {}x{}", id, width, height);
|
||||
|
||||
let _ = self.svg_hits.insert(id);
|
||||
|
|
@ -144,7 +135,8 @@ impl Cache {
|
|||
}
|
||||
}
|
||||
|
||||
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 rasterized_hits = &self.rasterized_hits;
|
||||
|
||||
|
|
@ -153,7 +145,7 @@ impl Cache {
|
|||
let retain = rasterized_hits.contains(k);
|
||||
|
||||
if !retain {
|
||||
atlas.remove(entry);
|
||||
storage.remove(entry, state);
|
||||
}
|
||||
|
||||
retain
|
||||
|
|
@ -163,6 +155,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 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
|
@ -166,10 +166,27 @@ impl<'a> Layer<'a> {
|
|||
border_color: border_color.into_linear(),
|
||||
});
|
||||
}
|
||||
Primitive::Mesh2D {
|
||||
Primitive::SolidMesh { buffers, size } => {
|
||||
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::Solid {
|
||||
origin: Point::new(translation.x, translation.y),
|
||||
buffers,
|
||||
clip_bounds,
|
||||
});
|
||||
}
|
||||
}
|
||||
Primitive::GradientMesh {
|
||||
buffers,
|
||||
size,
|
||||
style,
|
||||
gradient,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
|
|
@ -180,11 +197,11 @@ impl<'a> Layer<'a> {
|
|||
|
||||
// Only draw visible content
|
||||
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
|
||||
layer.meshes.push(Mesh {
|
||||
layer.meshes.push(Mesh::Gradient {
|
||||
origin: Point::new(translation.x, translation.y),
|
||||
buffers,
|
||||
clip_bounds,
|
||||
style,
|
||||
gradient,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,93 @@
|
|||
//! A collection of triangle primitives.
|
||||
use crate::triangle;
|
||||
use crate::{Point, Rectangle};
|
||||
use crate::{Gradient, Point, Rectangle};
|
||||
|
||||
/// A mesh of triangles.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Mesh<'a> {
|
||||
pub enum Mesh<'a> {
|
||||
/// A mesh of triangles with a solid color.
|
||||
Solid {
|
||||
/// The origin of the vertices of the [`Mesh`].
|
||||
pub origin: Point,
|
||||
origin: Point,
|
||||
|
||||
/// The vertex and index buffers of the [`Mesh`].
|
||||
pub buffers: &'a triangle::Mesh2D,
|
||||
buffers: &'a triangle::Mesh2D<triangle::ColoredVertex2D>,
|
||||
|
||||
/// The clipping bounds of the [`Mesh`].
|
||||
pub clip_bounds: Rectangle<f32>,
|
||||
clip_bounds: Rectangle<f32>,
|
||||
},
|
||||
/// A mesh of triangles with a gradient color.
|
||||
Gradient {
|
||||
/// The origin of the vertices of the [`Mesh`].
|
||||
origin: Point,
|
||||
|
||||
/// The shader of the [`Mesh`].
|
||||
pub style: &'a triangle::Style,
|
||||
/// 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>]) -> (usize, usize) {
|
||||
pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
|
||||
meshes
|
||||
.iter()
|
||||
.map(|Mesh { buffers, .. }| {
|
||||
(buffers.vertices.len(), buffers.indices.len())
|
||||
})
|
||||
.fold((0, 0), |(total_v, total_i), (v, i)| {
|
||||
(total_v + v, total_i + i)
|
||||
.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,6 +30,7 @@ mod viewport;
|
|||
pub mod backend;
|
||||
pub mod font;
|
||||
pub mod gradient;
|
||||
pub mod image;
|
||||
pub mod layer;
|
||||
pub mod overlay;
|
||||
pub mod renderer;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use iced_native::svg;
|
|||
use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
|
||||
|
||||
use crate::alignment;
|
||||
use crate::gradient::Gradient;
|
||||
use crate::triangle;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
|
@ -77,20 +78,32 @@ pub enum Primitive {
|
|||
/// The primitive to translate
|
||||
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.
|
||||
Mesh2D {
|
||||
/// The vertex and index buffers of the mesh
|
||||
buffers: triangle::Mesh2D,
|
||||
SolidMesh {
|
||||
/// The vertices and indices of the mesh.
|
||||
buffers: triangle::Mesh2D<triangle::ColoredVertex2D>,
|
||||
|
||||
/// The size of the drawable region of the mesh.
|
||||
///
|
||||
/// Any geometry that falls out of this region will be clipped.
|
||||
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 shader of the mesh
|
||||
style: triangle::Style,
|
||||
/// The [`Gradient`] to apply to the mesh.
|
||||
gradient: Gradient,
|
||||
},
|
||||
/// A cached primitive.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ where
|
|||
{
|
||||
type Handle = image::Handle;
|
||||
|
||||
fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
|
||||
fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
|
||||
self.backend().dimensions(handle)
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ impl<B, T> svg::Renderer for Renderer<B, T>
|
|||
where
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
//! Draw geometry using meshes of triangles.
|
||||
use crate::{Color, Gradient};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
/// A set of [`Vertex2D`] and indices representing a list of triangles.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Mesh2D {
|
||||
pub struct Mesh2D<T> {
|
||||
/// 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.
|
||||
///
|
||||
/// Therefore, this list should always have a length that is a multiple of 3.
|
||||
|
|
@ -22,23 +21,13 @@ pub struct Vertex2D {
|
|||
pub position: [f32; 2],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
/// Supported shaders for triangle primitives.
|
||||
pub enum Style {
|
||||
/// Fill a primitive with a solid color.
|
||||
Solid(Color),
|
||||
/// Fill a primitive with an interpolated color.
|
||||
Gradient(Gradient),
|
||||
}
|
||||
/// 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],
|
||||
|
||||
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)
|
||||
}
|
||||
/// The color of the vertex in __linear__ RGBA.
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ mod cursor;
|
|||
mod frame;
|
||||
mod geometry;
|
||||
mod program;
|
||||
mod style;
|
||||
mod text;
|
||||
|
||||
pub use crate::gradient::{self, Gradient};
|
||||
|
|
@ -25,6 +26,7 @@ pub use geometry::Geometry;
|
|||
pub use path::Path;
|
||||
pub use program::Program;
|
||||
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
|
||||
pub use style::Style;
|
||||
pub use text::Text;
|
||||
|
||||
use crate::{Backend, Primitive, Renderer};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
//! Fill [crate::widget::canvas::Geometry] with a certain style.
|
||||
use crate::{Color, Gradient};
|
||||
|
||||
pub use crate::triangle::Style;
|
||||
pub use crate::widget::canvas::Style;
|
||||
|
||||
/// The style used to fill geometry.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Fill {
|
||||
/// The color or gradient of the fill.
|
||||
///
|
||||
/// By default, it is set to [`FillStyle::Solid`] `BLACK`.
|
||||
/// By default, it is set to [`Style::Solid`] with [`Color::BLACK`].
|
||||
pub style: Style,
|
||||
|
||||
/// The fill rule defines how to determine what is inside and what is
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::gradient::Gradient;
|
||||
use crate::triangle;
|
||||
use crate::triangle::Vertex2D;
|
||||
use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Text};
|
||||
use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text};
|
||||
use crate::Primitive;
|
||||
|
||||
use iced_native::{Point, Rectangle, Size, Vector};
|
||||
|
|
@ -23,8 +22,16 @@ pub struct Frame {
|
|||
stroke_tessellator: tessellation::StrokeTessellator,
|
||||
}
|
||||
|
||||
enum Buffer {
|
||||
Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>),
|
||||
Gradient(
|
||||
tessellation::VertexBuffers<triangle::Vertex2D, u32>,
|
||||
Gradient,
|
||||
),
|
||||
}
|
||||
|
||||
struct BufferStack {
|
||||
stack: Vec<(tessellation::VertexBuffers<Vertex2D, u32>, triangle::Style)>,
|
||||
stack: Vec<Buffer>,
|
||||
}
|
||||
|
||||
impl BufferStack {
|
||||
|
|
@ -32,22 +39,64 @@ impl BufferStack {
|
|||
Self { stack: Vec::new() }
|
||||
}
|
||||
|
||||
fn get(
|
||||
&mut self,
|
||||
mesh_style: triangle::Style,
|
||||
) -> tessellation::BuffersBuilder<'_, Vertex2D, u32, Vertex2DBuilder> {
|
||||
match self.stack.last_mut() {
|
||||
Some((_, current_style)) if current_style == &mesh_style => {}
|
||||
fn get_mut(&mut self, style: &Style) -> &mut Buffer {
|
||||
match style {
|
||||
Style::Solid(_) => match self.stack.last() {
|
||||
Some(Buffer::Solid(_)) => {}
|
||||
_ => {
|
||||
self.stack
|
||||
.push((tessellation::VertexBuffers::new(), mesh_style));
|
||||
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(),
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
tessellation::BuffersBuilder::new(
|
||||
&mut self.stack.last_mut().unwrap().0,
|
||||
Vertex2DBuilder,
|
||||
)
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,11 +122,11 @@ impl Transform {
|
|||
point.y = transformed.y;
|
||||
}
|
||||
|
||||
fn transform_style(&self, style: triangle::Style) -> triangle::Style {
|
||||
fn transform_style(&self, style: Style) -> Style {
|
||||
match style {
|
||||
triangle::Style::Solid(color) => triangle::Style::Solid(color),
|
||||
triangle::Style::Gradient(gradient) => {
|
||||
triangle::Style::Gradient(self.transform_gradient(gradient))
|
||||
Style::Solid(color) => Style::Solid(color),
|
||||
Style::Gradient(gradient) => {
|
||||
Style::Gradient(self.transform_gradient(gradient))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +194,7 @@ impl Frame {
|
|||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get(self.transforms.current.transform_style(style));
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let options =
|
||||
tessellation::FillOptions::default().with_fill_rule(rule.into());
|
||||
|
|
@ -154,7 +203,7 @@ impl Frame {
|
|||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transformed(&self.transforms.current.raw);
|
||||
|
|
@ -162,7 +211,7 @@ impl Frame {
|
|||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Tessellate path.");
|
||||
|
|
@ -180,7 +229,7 @@ impl Frame {
|
|||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get(self.transforms.current.transform_style(style));
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let top_left =
|
||||
self.transforms.current.raw.transform_point(
|
||||
|
|
@ -199,7 +248,7 @@ impl Frame {
|
|||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.expect("Fill rectangle");
|
||||
}
|
||||
|
|
@ -211,7 +260,7 @@ impl Frame {
|
|||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get(self.transforms.current.transform_style(stroke.style));
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
|
|
@ -229,7 +278,7 @@ impl Frame {
|
|||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transformed(&self.transforms.current.raw);
|
||||
|
|
@ -237,7 +286,7 @@ impl Frame {
|
|||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
&mut buffer,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Stroke path");
|
||||
|
|
@ -382,18 +431,33 @@ impl Frame {
|
|||
}
|
||||
|
||||
fn into_primitives(mut self) -> Vec<Primitive> {
|
||||
for (buffer, style) in self.buffers.stack {
|
||||
for buffer in self.buffers.stack {
|
||||
match buffer {
|
||||
Buffer::Solid(buffer) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::Mesh2D {
|
||||
self.primitives.push(Primitive::SolidMesh {
|
||||
buffers: triangle::Mesh2D {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
size: self.size,
|
||||
style,
|
||||
})
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
@ -401,25 +465,66 @@ impl Frame {
|
|||
|
||||
struct Vertex2DBuilder;
|
||||
|
||||
impl tessellation::FillVertexConstructor<Vertex2D> for Vertex2DBuilder {
|
||||
fn new_vertex(&mut self, vertex: tessellation::FillVertex<'_>) -> Vertex2D {
|
||||
impl tessellation::FillVertexConstructor<triangle::Vertex2D>
|
||||
for Vertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::FillVertex<'_>,
|
||||
) -> triangle::Vertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
Vertex2D {
|
||||
triangle::Vertex2D {
|
||||
position: [position.x, position.y],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tessellation::StrokeVertexConstructor<Vertex2D> for Vertex2DBuilder {
|
||||
impl tessellation::StrokeVertexConstructor<triangle::Vertex2D>
|
||||
for Vertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::StrokeVertex<'_, '_>,
|
||||
) -> Vertex2D {
|
||||
) -> triangle::Vertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
Vertex2D {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D>
|
||||
for TriangleVertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::StrokeVertex<'_, '_>,
|
||||
) -> triangle::ColoredVertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
triangle::ColoredVertex2D {
|
||||
position: [position.x, position.y],
|
||||
color: self.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles.
|
||||
pub use crate::triangle::Style;
|
||||
pub use crate::widget::canvas::Style;
|
||||
|
||||
use iced_native::Color;
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ use iced_native::Color;
|
|||
pub struct Stroke<'a> {
|
||||
/// The color or gradient of the stroke.
|
||||
///
|
||||
/// By default, it is set to [`StrokeStyle::Solid`] `BLACK`.
|
||||
/// By default, it is set to a [`Style::Solid`] with [`Color::BLACK`].
|
||||
pub style: Style,
|
||||
/// The distance between the two edges of the stroke.
|
||||
pub width: f32,
|
||||
|
|
|
|||
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,
|
||||
);
|
||||
|
||||
/// Returns [`GraphicsInformation`] used by this [`Compositor`].
|
||||
/// Returns [`Information`] used by this [`Compositor`].
|
||||
fn fetch_information(&self) -> Information;
|
||||
|
||||
/// 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`].
|
||||
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;
|
||||
|
||||
/// Presents the primitives of the [`Renderer`] to the next frame of the
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_lazy"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "Lazy widgets for Iced"
|
||||
|
|
@ -14,5 +14,5 @@ categories = ["gui"]
|
|||
ouroboros = "0.13"
|
||||
|
||||
[dependencies.iced_native]
|
||||
version = "0.5"
|
||||
version = "0.6"
|
||||
path = "../native"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use iced_native::{
|
|||
};
|
||||
|
||||
use ouroboros::self_referencing;
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::cell::RefCell;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A reusable, custom widget that uses The Elm Architecture.
|
||||
|
|
@ -260,6 +260,14 @@ where
|
|||
) {
|
||||
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| {
|
||||
|
|
@ -314,25 +322,25 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let overlay = OverlayBuilder {
|
||||
instance: self,
|
||||
instance_ref_builder: |instance| instance.state.borrow(),
|
||||
tree,
|
||||
types: PhantomData,
|
||||
overlay_builder: |instance, tree| {
|
||||
instance
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.borrow_element()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.as_widget()
|
||||
.overlay(&mut tree.children[0], layout, renderer)
|
||||
instance.state.get_mut().as_mut().unwrap().with_element_mut(
|
||||
move |element| {
|
||||
element.as_mut().unwrap().as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
.build();
|
||||
|
|
@ -354,15 +362,11 @@ where
|
|||
|
||||
#[self_referencing]
|
||||
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,
|
||||
types: PhantomData<(Message, Event, S)>,
|
||||
|
||||
#[borrows(instance)]
|
||||
#[covariant]
|
||||
instance_ref: Ref<'this, Option<State<'a, Message, Renderer, Event, S>>>,
|
||||
|
||||
#[borrows(instance_ref, mut tree)]
|
||||
#[borrows(mut instance, mut tree)]
|
||||
#[covariant]
|
||||
overlay: Option<overlay::Element<'this, Event, Renderer>>,
|
||||
}
|
||||
|
|
@ -506,7 +510,6 @@ where
|
|||
self.overlay = Some(
|
||||
OverlayBuilder {
|
||||
instance: overlay.instance,
|
||||
instance_ref_builder: |instance| instance.state.borrow(),
|
||||
tree: overlay.tree,
|
||||
types: PhantomData,
|
||||
overlay_builder: |_, _| None,
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -216,12 +216,12 @@ where
|
|||
cached: self,
|
||||
tree: &mut tree.children[0],
|
||||
types: PhantomData,
|
||||
element_ref_builder: |cached| cached.element.borrow(),
|
||||
element_builder: |element_ref| {
|
||||
element_ref.as_ref().unwrap().borrow()
|
||||
},
|
||||
overlay_builder: |element, tree| {
|
||||
element.as_widget().overlay(tree, layout, renderer)
|
||||
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();
|
||||
|
|
@ -237,20 +237,11 @@ where
|
|||
|
||||
#[self_referencing]
|
||||
struct Overlay<'a, 'b, Message, Renderer, Dependency, View> {
|
||||
cached: &'a Lazy<'b, Message, Renderer, Dependency, View>,
|
||||
cached: &'a mut Lazy<'b, Message, Renderer, Dependency, View>,
|
||||
tree: &'a mut Tree,
|
||||
types: PhantomData<(Message, Dependency, View)>,
|
||||
|
||||
#[borrows(cached)]
|
||||
#[covariant]
|
||||
element_ref:
|
||||
Ref<'this, Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>,
|
||||
|
||||
#[borrows(element_ref)]
|
||||
#[covariant]
|
||||
element: Ref<'this, Element<'static, Message, Renderer>>,
|
||||
|
||||
#[borrows(element, mut tree)]
|
||||
#[borrows(mut cached, mut tree)]
|
||||
#[covariant]
|
||||
overlay: Option<overlay::Element<'this, Message, Renderer>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,18 +235,20 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
use std::ops::DerefMut;
|
||||
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
|
||||
let overlay = OverlayBuilder {
|
||||
content: self.content.borrow_mut(),
|
||||
tree: state.tree.borrow_mut(),
|
||||
types: PhantomData,
|
||||
overlay_builder: |content, tree| {
|
||||
overlay_builder: |content: &mut RefMut<Content<_, _>>, tree| {
|
||||
content.update(
|
||||
tree,
|
||||
renderer,
|
||||
|
|
@ -254,16 +256,18 @@ where
|
|||
&self.view,
|
||||
);
|
||||
|
||||
let Content {
|
||||
element, layout, ..
|
||||
} = content.deref_mut();
|
||||
|
||||
let content_layout = Layout::with_offset(
|
||||
layout.position() - Point::ORIGIN,
|
||||
&content.layout,
|
||||
layout.bounds().position() - Point::ORIGIN,
|
||||
layout,
|
||||
);
|
||||
|
||||
content.element.as_widget().overlay(
|
||||
tree,
|
||||
content_layout,
|
||||
renderer,
|
||||
)
|
||||
element
|
||||
.as_widget_mut()
|
||||
.overlay(tree, content_layout, renderer)
|
||||
},
|
||||
}
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "iced_native"
|
||||
version = "0.5.1"
|
||||
version = "0.6.1"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A renderer-agnostic library for native GUIs"
|
||||
|
|
@ -16,14 +16,14 @@ unicode-segmentation = "1.6"
|
|||
num-traits = "0.2"
|
||||
|
||||
[dependencies.iced_core]
|
||||
version = "0.5"
|
||||
version = "0.6"
|
||||
path = "../core"
|
||||
|
||||
[dependencies.iced_futures]
|
||||
version = "0.4"
|
||||
version = "0.5"
|
||||
path = "../futures"
|
||||
features = ["thread-pool"]
|
||||
|
||||
[dependencies.iced_style]
|
||||
version = "0.4"
|
||||
version = "0.5"
|
||||
path = "../style"
|
||||
|
|
|
|||
|
|
@ -316,6 +316,22 @@ where
|
|||
) {
|
||||
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
|
||||
|
|
@ -389,7 +405,7 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -544,7 +560,7 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! 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::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -21,15 +22,19 @@ impl Handle {
|
|||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This is useful if you have already decoded your image.
|
||||
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
|
||||
Self::from_data(Data::Pixels {
|
||||
pub fn from_pixels(
|
||||
width: u32,
|
||||
height: u32,
|
||||
pixels: impl Into<Cow<'static, [u8]>>,
|
||||
) -> Handle {
|
||||
Self::from_data(Data::Rgba {
|
||||
width,
|
||||
height,
|
||||
pixels,
|
||||
pixels: pixels.into(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -39,8 +44,8 @@ impl Handle {
|
|||
///
|
||||
/// This is useful if you already have your image loaded in-memory, maybe
|
||||
/// because you downloaded or generated it procedurally.
|
||||
pub fn from_memory(bytes: Vec<u8>) -> Handle {
|
||||
Self::from_data(Data::Bytes(bytes))
|
||||
pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
|
||||
Self::from_data(Data::Bytes(bytes.into()))
|
||||
}
|
||||
|
||||
fn from_data(data: Data) -> Handle {
|
||||
|
|
@ -86,16 +91,16 @@ pub enum Data {
|
|||
Path(PathBuf),
|
||||
|
||||
/// In-memory data
|
||||
Bytes(Vec<u8>),
|
||||
Bytes(Cow<'static, [u8]>),
|
||||
|
||||
/// Decoded image pixels in BGRA format.
|
||||
Pixels {
|
||||
/// Decoded image pixels in RGBA format.
|
||||
Rgba {
|
||||
/// The width of the image.
|
||||
width: u32,
|
||||
/// The height of the image.
|
||||
height: u32,
|
||||
/// The pixels.
|
||||
pixels: Vec<u8>,
|
||||
pixels: Cow<'static, [u8]>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +109,7 @@ impl std::fmt::Debug for Data {
|
|||
match self {
|
||||
Data::Path(path) => write!(f, "Path({:?})", path),
|
||||
Data::Bytes(_) => write!(f, "Bytes(...)"),
|
||||
Data::Pixels { width, height, .. } => {
|
||||
Data::Rgba { width, height, .. } => {
|
||||
write!(f, "Pixels({} * {})", width, height)
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +126,7 @@ pub trait Renderer: crate::Renderer {
|
|||
type Handle: Clone + Hash;
|
||||
|
||||
/// 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
|
||||
/// `bounds`.
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
//! - Build a new renderer, see the [renderer] module.
|
||||
//! - Build a custom widget, start at the [`Widget`] trait.
|
||||
//!
|
||||
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.4/core
|
||||
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.4/winit
|
||||
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.5/core
|
||||
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.5/winit
|
||||
//! [`druid`]: https://github.com/xi-editor/druid
|
||||
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
|
||||
//! [renderer]: crate::renderer
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use crate::layout;
|
|||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::widget::Tree;
|
||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
|
||||
|
||||
/// An interactive component that can be displayed on top of other widgets.
|
||||
|
|
@ -42,31 +42,9 @@ where
|
|||
cursor_position: Point,
|
||||
);
|
||||
|
||||
/// Returns the [`Tag`] of the [`Widget`].
|
||||
///
|
||||
/// [`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`].
|
||||
/// Applies a [`widget::Operation`] to the [`Overlay`].
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
_layout: Layout<'_>,
|
||||
_operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
|
|
@ -115,7 +93,7 @@ where
|
|||
/// This method will generally only be used by advanced users that are
|
||||
/// implementing the [`Widget`](crate::Widget) trait.
|
||||
pub fn from_children<'a, Message, Renderer>(
|
||||
children: &'a [crate::Element<'_, Message, Renderer>],
|
||||
children: &'a mut [crate::Element<'_, Message, Renderer>],
|
||||
tree: &'a mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -124,11 +102,11 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
{
|
||||
children
|
||||
.iter()
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.filter_map(|((child, state), layout)| {
|
||||
child.as_widget().overlay(state, layout, renderer)
|
||||
child.as_widget_mut().overlay(state, layout, renderer)
|
||||
})
|
||||
.next()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,9 +104,9 @@ where
|
|||
.draw(renderer, theme, style, layout, cursor_position)
|
||||
}
|
||||
|
||||
/// Applies an [`Operation`] to the [`Element`].
|
||||
/// Applies a [`widget::Operation`] to the [`Element`].
|
||||
pub fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
|
|
@ -141,6 +141,57 @@ where
|
|||
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(
|
||||
&mut self,
|
||||
event: Event,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::text::{self, Text};
|
|||
use crate::touch;
|
||||
use crate::widget::container::{self, Container};
|
||||
use crate::widget::scrollable::{self, Scrollable};
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::widget::Tree;
|
||||
use crate::{
|
||||
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
|
||||
Shell, Size, Vector, Widget,
|
||||
|
|
@ -178,7 +178,7 @@ where
|
|||
font,
|
||||
text_size,
|
||||
padding,
|
||||
style,
|
||||
style: style.clone(),
|
||||
}));
|
||||
|
||||
state.tree.diff(&container as &dyn Widget<_, _>);
|
||||
|
|
@ -199,18 +199,6 @@ where
|
|||
Renderer: text::Renderer,
|
||||
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(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -288,7 +276,7 @@ where
|
|||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) {
|
||||
let appearance = theme.appearance(self.style);
|
||||
let appearance = theme.appearance(&self.style);
|
||||
let bounds = layout.bounds();
|
||||
|
||||
renderer.fill_quad(
|
||||
|
|
@ -460,7 +448,7 @@ where
|
|||
_cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let appearance = theme.appearance(self.style);
|
||||
let appearance = theme.appearance(&self.style);
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let text_size =
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ where
|
|||
/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket
|
||||
/// connection open.
|
||||
///
|
||||
/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.4/examples/websocket
|
||||
/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.5/examples/websocket
|
||||
pub fn unfold<I, T, Fut, Message>(
|
||||
id: I,
|
||||
initial: T,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Load and draw vector graphics.
|
||||
use crate::{Hasher, Rectangle};
|
||||
use crate::{Hasher, Rectangle, Size};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher as _};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -24,7 +25,7 @@ impl Handle {
|
|||
///
|
||||
/// This is useful if you already have your SVG data in-memory, maybe
|
||||
/// 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()))
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +65,7 @@ pub enum Data {
|
|||
/// In-memory data
|
||||
///
|
||||
/// Can contain an SVG string or a gzip compressed data.
|
||||
Bytes(Vec<u8>),
|
||||
Bytes(Cow<'static, [u8]>),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Data {
|
||||
|
|
@ -81,7 +82,7 @@ impl std::fmt::Debug for Data {
|
|||
/// [renderer]: crate::renderer
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Returns the default dimensions of an SVG for the given [`Handle`].
|
||||
fn dimensions(&self, handle: &Handle) -> (u32, u32);
|
||||
fn dimensions(&self, handle: &Handle) -> Size<u32>;
|
||||
|
||||
/// Draws an SVG with the given [`Handle`] and inside the provided `bounds`.
|
||||
fn draw(&mut self, handle: Handle, bounds: Rectangle);
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
|||
/// The [`integration_opengl`] & [`integration_wgpu`] examples use a
|
||||
/// [`UserInterface`] to integrate Iced in an existing graphical application.
|
||||
///
|
||||
/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_opengl
|
||||
/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_wgpu
|
||||
/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.5/examples/integration_opengl
|
||||
/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/examples/integration_wgpu
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct UserInterface<'a, Message, Renderer> {
|
||||
root: Element<'a, Message, Renderer>,
|
||||
|
|
@ -190,7 +190,7 @@ where
|
|||
|
||||
let mut state = State::Updated;
|
||||
let mut manual_overlay =
|
||||
ManuallyDrop::new(self.root.as_widget().overlay(
|
||||
ManuallyDrop::new(self.root.as_widget_mut().overlay(
|
||||
&mut self.state,
|
||||
Layout::new(&self.base),
|
||||
renderer,
|
||||
|
|
@ -226,7 +226,7 @@ where
|
|||
);
|
||||
|
||||
manual_overlay =
|
||||
ManuallyDrop::new(self.root.as_widget().overlay(
|
||||
ManuallyDrop::new(self.root.as_widget_mut().overlay(
|
||||
&mut self.state,
|
||||
Layout::new(&self.base),
|
||||
renderer,
|
||||
|
|
@ -285,6 +285,10 @@ where
|
|||
&mut shell,
|
||||
);
|
||||
|
||||
if matches!(event_status, event::Status::Captured) {
|
||||
self.overlay = None;
|
||||
}
|
||||
|
||||
shell.revalidate_layout(|| {
|
||||
self.base = renderer.layout(
|
||||
&self.root,
|
||||
|
|
@ -391,11 +395,11 @@ where
|
|||
|
||||
let viewport = Rectangle::with_size(self.bounds);
|
||||
|
||||
let base_cursor = if let Some(overlay) = self.root.as_widget().overlay(
|
||||
&mut self.state,
|
||||
Layout::new(&self.base),
|
||||
renderer,
|
||||
) {
|
||||
let base_cursor = if let Some(overlay) = self
|
||||
.root
|
||||
.as_widget_mut()
|
||||
.overlay(&mut self.state, Layout::new(&self.base), renderer)
|
||||
{
|
||||
let overlay_layout = self
|
||||
.overlay
|
||||
.take()
|
||||
|
|
@ -448,7 +452,7 @@ where
|
|||
overlay
|
||||
.as_ref()
|
||||
.and_then(|layout| {
|
||||
root.as_widget()
|
||||
root.as_widget_mut()
|
||||
.overlay(&mut self.state, Layout::new(base), renderer)
|
||||
.map(|overlay| {
|
||||
let overlay_interaction = overlay.mouse_interaction(
|
||||
|
|
@ -492,14 +496,19 @@ where
|
|||
operation,
|
||||
);
|
||||
|
||||
if let Some(layout) = self.overlay.as_ref() {
|
||||
if let Some(overlay) = self.root.as_widget().overlay(
|
||||
if let Some(mut overlay) = self.root.as_widget_mut().overlay(
|
||||
&mut self.state,
|
||||
Layout::new(&self.base),
|
||||
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
|
||||
/// `Mesh2D` primitive in [`iced_wgpu`].
|
||||
///
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.4/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.4/examples/bezier_tool
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.4/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.4/examples/geometry
|
||||
/// [examples]: https://github.com/iced-rs/iced/tree/0.5/examples
|
||||
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.5/examples/bezier_tool
|
||||
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.5/examples/custom_widget
|
||||
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.5/examples/geometry
|
||||
/// [`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.5/wgpu
|
||||
pub trait Widget<Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
|
|
@ -208,7 +208,7 @@ where
|
|||
|
||||
/// Returns the overlay of the [`Widget`], if there is any.
|
||||
fn overlay<'a>(
|
||||
&'a self,
|
||||
&'a mut self,
|
||||
_state: &'a mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
_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 iced_futures::MaybeSend;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
/// An operation to be performed on the widget tree.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Action<T>(Box<dyn Operation<T>>);
|
||||
|
|
@ -24,7 +26,7 @@ impl<T> Action<T> {
|
|||
{
|
||||
Action(Box::new(Map {
|
||||
operation: self.0,
|
||||
f: Box::new(f),
|
||||
f: Rc::new(f),
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +39,7 @@ impl<T> Action<T> {
|
|||
#[allow(missing_debug_implementations)]
|
||||
struct Map<A, B> {
|
||||
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>
|
||||
|
|
@ -50,30 +52,44 @@ where
|
|||
id: Option<&Id>,
|
||||
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>,
|
||||
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(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
||||
) {
|
||||
let Self { operation, f } = self;
|
||||
let Self { operation, .. } = self;
|
||||
|
||||
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 {
|
||||
operation: operation.as_mut(),
|
||||
f,
|
||||
}
|
||||
.container(id, operate_on_children);
|
||||
}
|
||||
|
|
@ -85,4 +101,35 @@ where
|
|||
) {
|
||||
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,
|
||||
self.on_press.is_some(),
|
||||
theme,
|
||||
self.style,
|
||||
&self.style,
|
||||
|| tree.state.downcast_ref::<State>(),
|
||||
);
|
||||
|
||||
|
|
@ -260,12 +260,12 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget().overlay(
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
|
|
@ -361,7 +361,7 @@ pub fn draw<'a, Renderer: crate::Renderer>(
|
|||
style_sheet: &dyn StyleSheet<
|
||||
Style = <Renderer::Theme as StyleSheet>::Style,
|
||||
>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
state: impl FnOnce() -> &'a State,
|
||||
) -> Appearance
|
||||
where
|
||||
|
|
@ -426,13 +426,14 @@ pub fn layout<Renderer>(
|
|||
padding: Padding,
|
||||
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> 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()));
|
||||
|
||||
let size = limits.resolve(content.size()).pad(padding);
|
||||
|
||||
layout::Node::with_children(size, vec![content])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -224,9 +224,9 @@ where
|
|||
let mut children = layout.children();
|
||||
|
||||
let custom_style = if is_mouse_over {
|
||||
theme.hovered(self.style, self.is_checked)
|
||||
theme.hovered(&self.style, self.is_checked)
|
||||
} else {
|
||||
theme.active(self.style, self.is_checked)
|
||||
theme.active(&self.style, self.is_checked)
|
||||
};
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -242,12 +242,12 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &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,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let style = theme.appearance(self.style);
|
||||
let style = theme.appearance(&self.style);
|
||||
|
||||
draw_background(renderer, &style, layout.bounds());
|
||||
|
||||
|
|
@ -248,12 +248,12 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget().overlay(
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
|
|
@ -293,11 +293,11 @@ pub fn layout<Renderer>(
|
|||
.max_width(max_width)
|
||||
.max_height(max_height)
|
||||
.width(width)
|
||||
.height(height)
|
||||
.pad(padding);
|
||||
.height(height);
|
||||
|
||||
let mut content = layout_content(renderer, &limits.loose());
|
||||
let size = limits.resolve(content.size());
|
||||
let mut content = layout_content(renderer, &limits.pad(padding).loose());
|
||||
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.align(
|
||||
|
|
@ -309,7 +309,7 @@ pub fn layout<Renderer>(
|
|||
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>(
|
||||
renderer: &mut Renderer,
|
||||
appearance: &Appearance,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! Helper functions to create pure widgets.
|
||||
use crate::overlay;
|
||||
use crate::widget;
|
||||
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
|
||||
#[macro_export]
|
||||
|
|
@ -84,6 +85,7 @@ pub fn button<'a, Message, Renderer>(
|
|||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: widget::button::StyleSheet,
|
||||
<Renderer::Theme as widget::button::StyleSheet>::Style: Default,
|
||||
{
|
||||
widget::Button::new(content)
|
||||
}
|
||||
|
|
@ -208,7 +210,12 @@ where
|
|||
T: ToString + Eq + 'static,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ where
|
|||
{
|
||||
// The raw w/h of the underlying image
|
||||
let image_size = {
|
||||
let (width, height) = renderer.dimensions(handle);
|
||||
let Size { width, height } = renderer.dimensions(handle);
|
||||
|
||||
Size::new(width as f32, height as f32)
|
||||
};
|
||||
|
|
@ -149,7 +149,7 @@ where
|
|||
_cursor_position: Point,
|
||||
_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 bounds = layout.bounds();
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let (width, height) = renderer.dimensions(&self.handle);
|
||||
let Size { width, height } = renderer.dimensions(&self.handle);
|
||||
|
||||
let mut size = limits
|
||||
.width(self.width)
|
||||
|
|
@ -409,7 +409,7 @@ pub fn image_size<Renderer>(
|
|||
where
|
||||
Renderer: image::Renderer,
|
||||
{
|
||||
let (width, height) = renderer.dimensions(handle);
|
||||
let Size { width, height } = renderer.dimensions(handle);
|
||||
|
||||
let (width, height) = {
|
||||
let dimensions = (width as f32, height as f32);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
//! Query or update internal widget state.
|
||||
pub mod focusable;
|
||||
pub mod scrollable;
|
||||
pub mod text_input;
|
||||
|
||||
pub use focusable::Focusable;
|
||||
pub use scrollable::Scrollable;
|
||||
pub use text_input::TextInput;
|
||||
|
||||
use crate::widget::Id;
|
||||
|
||||
|
|
@ -28,6 +30,9 @@ pub trait Operation<T> {
|
|||
/// Operates on a widget that can be scrolled.
|
||||
fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
|
||||
|
||||
/// Operates on a widget that has text input.
|
||||
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
|
||||
|
||||
/// Finishes the [`Operation`] and returns its [`Outcome`].
|
||||
fn finish(&self) -> Outcome<T> {
|
||||
Outcome::None
|
||||
|
|
@ -58,3 +63,46 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that applies the given [`Operation`] to the
|
||||
/// children of a container with the given [`Id`].
|
||||
pub fn scoped<T: 'static>(
|
||||
target: Id,
|
||||
operation: impl Operation<T> + 'static,
|
||||
) -> impl Operation<T> {
|
||||
struct ScopedOperation<Message> {
|
||||
target: Id,
|
||||
operation: Box<dyn Operation<Message>>,
|
||||
}
|
||||
|
||||
impl<Message: 'static> Operation<Message> for ScopedOperation<Message> {
|
||||
fn container(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
|
||||
) {
|
||||
if id == Some(&self.target) {
|
||||
operate_on_children(self.operation.as_mut());
|
||||
} else {
|
||||
operate_on_children(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self) -> Outcome<Message> {
|
||||
match self.operation.finish() {
|
||||
Outcome::Chain(next) => {
|
||||
Outcome::Chain(Box::new(ScopedOperation {
|
||||
target: self.target.clone(),
|
||||
operation: next,
|
||||
}))
|
||||
}
|
||||
outcome => outcome,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScopedOperation {
|
||||
target,
|
||||
operation: Box::new(operation),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,3 +167,37 @@ pub fn focus_next<T>() -> impl Operation<T> {
|
|||
|
||||
count(|count| FocusNext { count, current: 0 })
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that searches for the current focused widget
|
||||
/// and stores its ID. This ignores widgets that do not have an ID.
|
||||
pub fn find_focused() -> impl Operation<Id> {
|
||||
struct FindFocused {
|
||||
focused: Option<Id>,
|
||||
}
|
||||
|
||||
impl Operation<Id> for FindFocused {
|
||||
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
|
||||
if state.is_focused() && id.is_some() {
|
||||
self.focused = id.cloned();
|
||||
}
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
|
||||
fn finish(&self) -> Outcome<Id> {
|
||||
if let Some(id) = &self.focused {
|
||||
Outcome::Some(id.clone())
|
||||
} else {
|
||||
Outcome::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FindFocused { focused: None }
|
||||
}
|
||||
|
|
|
|||
131
native/src/widget/operation/text_input.rs
Normal file
131
native/src/widget/operation/text_input.rs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
//! Operate on widgets that have text input.
|
||||
use crate::widget::operation::Operation;
|
||||
use crate::widget::Id;
|
||||
|
||||
/// The internal state of a widget that has text input.
|
||||
pub trait TextInput {
|
||||
/// Moves the cursor of the text input to the front of the input text.
|
||||
fn move_cursor_to_front(&mut self);
|
||||
/// Moves the cursor of the text input to the end of the input text.
|
||||
fn move_cursor_to_end(&mut self);
|
||||
/// Moves the cursor of the text input to an arbitrary location.
|
||||
fn move_cursor_to(&mut self, position: usize);
|
||||
/// Selects all the content of the text input.
|
||||
fn select_all(&mut self);
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the
|
||||
/// front.
|
||||
pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {
|
||||
struct MoveCursor {
|
||||
target: Id,
|
||||
}
|
||||
|
||||
impl<T> Operation<T> for MoveCursor {
|
||||
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||
match id {
|
||||
Some(id) if id == &self.target => {
|
||||
state.move_cursor_to_front();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
|
||||
MoveCursor { target }
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the
|
||||
/// end.
|
||||
pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> {
|
||||
struct MoveCursor {
|
||||
target: Id,
|
||||
}
|
||||
|
||||
impl<T> Operation<T> for MoveCursor {
|
||||
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||
match id {
|
||||
Some(id) if id == &self.target => {
|
||||
state.move_cursor_to_end();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
|
||||
MoveCursor { target }
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the
|
||||
/// provided position.
|
||||
pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> {
|
||||
struct MoveCursor {
|
||||
target: Id,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
impl<T> Operation<T> for MoveCursor {
|
||||
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||
match id {
|
||||
Some(id) if id == &self.target => {
|
||||
state.move_cursor_to(self.position);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
|
||||
MoveCursor { target, position }
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that selects all the content of the widget with the given [`Id`].
|
||||
pub fn select_all<T>(target: Id) -> impl Operation<T> {
|
||||
struct MoveCursor {
|
||||
target: Id,
|
||||
}
|
||||
|
||||
impl<T> Operation<T> for MoveCursor {
|
||||
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
|
||||
match id {
|
||||
Some(id) if id == &self.target => {
|
||||
state.select_all();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
|
||||
MoveCursor { target }
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
|
||||
//! drag and drop, and hotkey support.
|
||||
//!
|
||||
//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid
|
||||
//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid
|
||||
mod axis;
|
||||
mod configuration;
|
||||
mod content;
|
||||
|
|
@ -38,6 +38,7 @@ use crate::mouse;
|
|||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::touch;
|
||||
use crate::widget;
|
||||
use crate::widget::container;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
|
|
@ -85,7 +86,7 @@ use crate::{
|
|||
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
|
||||
///
|
||||
/// let pane_grid =
|
||||
/// PaneGrid::new(&state, |pane, state| {
|
||||
/// PaneGrid::new(&state, |pane, state, is_maximized| {
|
||||
/// pane_grid::Content::new(match state {
|
||||
/// PaneState::SomePane => text("This is some pane"),
|
||||
/// PaneState::AnotherKindOfPane => text("This is another kind of pane"),
|
||||
|
|
@ -100,8 +101,7 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet + container::StyleSheet,
|
||||
{
|
||||
state: &'a state::Internal,
|
||||
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
|
||||
contents: Contents<'a, Content<'a, Message, Renderer>>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
spacing: u16,
|
||||
|
|
@ -119,22 +119,35 @@ where
|
|||
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
|
||||
///
|
||||
/// The view function will be called to display each [`Pane`] present in the
|
||||
/// [`State`].
|
||||
/// [`State`]. [`bool`] is set if the pane is maximized.
|
||||
pub fn new<T>(
|
||||
state: &'a State<T>,
|
||||
view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>,
|
||||
view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>,
|
||||
) -> Self {
|
||||
let elements = {
|
||||
let contents = if let Some((pane, pane_state)) =
|
||||
state.maximized.and_then(|pane| {
|
||||
state.panes.get(&pane).map(|pane_state| (pane, pane_state))
|
||||
}) {
|
||||
Contents::Maximized(
|
||||
pane,
|
||||
view(pane, pane_state, true),
|
||||
Node::Pane(pane),
|
||||
)
|
||||
} else {
|
||||
Contents::All(
|
||||
state
|
||||
.panes
|
||||
.iter()
|
||||
.map(|(pane, pane_state)| (*pane, view(*pane, pane_state)))
|
||||
.collect()
|
||||
.map(|(pane, pane_state)| {
|
||||
(*pane, view(*pane, pane_state, false))
|
||||
})
|
||||
.collect(),
|
||||
&state.internal,
|
||||
)
|
||||
};
|
||||
|
||||
Self {
|
||||
elements,
|
||||
state: &state.internal,
|
||||
contents,
|
||||
width: Length::Fill,
|
||||
height: Length::Fill,
|
||||
spacing: 0,
|
||||
|
|
@ -208,6 +221,12 @@ where
|
|||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn drag_enabled(&self) -> bool {
|
||||
(!self.contents.is_maximized())
|
||||
.then(|| self.on_drag.is_some())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
|
|
@ -225,18 +244,25 @@ where
|
|||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.elements
|
||||
self.contents
|
||||
.iter()
|
||||
.map(|(_, content)| content.state())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children_custom(
|
||||
&self.elements,
|
||||
match &self.contents {
|
||||
Contents::All(contents, _) => tree.diff_children_custom(
|
||||
contents,
|
||||
|state, (_, content)| content.diff(state),
|
||||
|(_, content)| content.state(),
|
||||
)
|
||||
),
|
||||
Contents::Maximized(_, content, _) => tree.diff_children_custom(
|
||||
&[content],
|
||||
|state, content| content.diff(state),
|
||||
|content| content.state(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
|
|
@ -255,15 +281,32 @@ where
|
|||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.state,
|
||||
self.contents.layout(),
|
||||
self.width,
|
||||
self.height,
|
||||
self.spacing,
|
||||
self.elements.iter().map(|(pane, content)| (*pane, content)),
|
||||
|element, renderer, limits| element.layout(renderer, limits),
|
||||
self.contents.iter(),
|
||||
|content, renderer, limits| content.layout(renderer, limits),
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
self.contents
|
||||
.iter()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.for_each(|(((_pane, content), state), layout)| {
|
||||
content.operate(state, layout, operation);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
|
|
@ -276,28 +319,34 @@ where
|
|||
) -> event::Status {
|
||||
let action = tree.state.downcast_mut::<state::Action>();
|
||||
|
||||
let on_drag = if self.drag_enabled() {
|
||||
&self.on_drag
|
||||
} else {
|
||||
&None
|
||||
};
|
||||
|
||||
let event_status = update(
|
||||
action,
|
||||
self.state,
|
||||
self.contents.layout(),
|
||||
&event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
self.spacing,
|
||||
self.elements.iter().map(|(pane, content)| (*pane, content)),
|
||||
self.contents.iter(),
|
||||
&self.on_click,
|
||||
&self.on_drag,
|
||||
on_drag,
|
||||
&self.on_resize,
|
||||
);
|
||||
|
||||
let picked_pane = action.picked_pane().map(|(pane, _)| pane);
|
||||
|
||||
self.elements
|
||||
self.contents
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|(((pane, content), tree), layout)| {
|
||||
let is_picked = picked_pane == Some(*pane);
|
||||
let is_picked = picked_pane == Some(pane);
|
||||
|
||||
content.on_event(
|
||||
tree,
|
||||
|
|
@ -323,14 +372,14 @@ where
|
|||
) -> mouse::Interaction {
|
||||
mouse_interaction(
|
||||
tree.state.downcast_ref(),
|
||||
self.state,
|
||||
self.contents.layout(),
|
||||
layout,
|
||||
cursor_position,
|
||||
self.spacing,
|
||||
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
self.elements
|
||||
self.contents
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
|
|
@ -341,7 +390,7 @@ where
|
|||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
self.on_drag.is_some(),
|
||||
self.drag_enabled(),
|
||||
)
|
||||
})
|
||||
.max()
|
||||
|
|
@ -361,7 +410,7 @@ where
|
|||
) {
|
||||
draw(
|
||||
tree.state.downcast_ref(),
|
||||
self.state,
|
||||
self.contents.layout(),
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
|
|
@ -370,11 +419,11 @@ where
|
|||
viewport,
|
||||
self.spacing,
|
||||
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
|
||||
self.style,
|
||||
self.elements
|
||||
&self.style,
|
||||
self.contents
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.map(|((pane, content), tree)| (*pane, (content, tree))),
|
||||
.map(|((pane, content), tree)| (pane, (content, tree))),
|
||||
|(content, tree),
|
||||
renderer,
|
||||
style,
|
||||
|
|
@ -395,13 +444,13 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
self.elements
|
||||
.iter()
|
||||
self.contents
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.filter_map(|(((_, pane), tree), layout)| {
|
||||
|
|
@ -429,24 +478,24 @@ where
|
|||
pub fn layout<Renderer, T>(
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
state: &state::Internal,
|
||||
node: &Node,
|
||||
width: Length,
|
||||
height: Length,
|
||||
spacing: u16,
|
||||
elements: impl Iterator<Item = (Pane, T)>,
|
||||
layout_element: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node,
|
||||
contents: impl Iterator<Item = (Pane, T)>,
|
||||
layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(width).height(height);
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
let regions = state.pane_regions(f32::from(spacing), size);
|
||||
let children = elements
|
||||
.filter_map(|(pane, element)| {
|
||||
let regions = node.pane_regions(f32::from(spacing), size);
|
||||
let children = contents
|
||||
.filter_map(|(pane, content)| {
|
||||
let region = regions.get(&pane)?;
|
||||
let size = Size::new(region.width, region.height);
|
||||
|
||||
let mut node = layout_element(
|
||||
element,
|
||||
let mut node = layout_content(
|
||||
content,
|
||||
renderer,
|
||||
&layout::Limits::new(size, size),
|
||||
);
|
||||
|
|
@ -464,13 +513,13 @@ pub fn layout<Renderer, T>(
|
|||
/// accordingly.
|
||||
pub fn update<'a, Message, T: Draggable>(
|
||||
action: &mut state::Action,
|
||||
state: &state::Internal,
|
||||
node: &Node,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
spacing: u16,
|
||||
elements: impl Iterator<Item = (Pane, T)>,
|
||||
contents: impl Iterator<Item = (Pane, T)>,
|
||||
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
|
||||
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
|
||||
on_resize: &Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
|
||||
|
|
@ -492,7 +541,7 @@ pub fn update<'a, Message, T: Draggable>(
|
|||
cursor_position.y - bounds.y,
|
||||
);
|
||||
|
||||
let splits = state.split_regions(
|
||||
let splits = node.split_regions(
|
||||
f32::from(spacing),
|
||||
Size::new(bounds.width, bounds.height),
|
||||
);
|
||||
|
|
@ -514,7 +563,7 @@ pub fn update<'a, Message, T: Draggable>(
|
|||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
elements,
|
||||
contents,
|
||||
on_click,
|
||||
on_drag,
|
||||
);
|
||||
|
|
@ -526,7 +575,7 @@ pub fn update<'a, Message, T: Draggable>(
|
|||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
elements,
|
||||
contents,
|
||||
on_click,
|
||||
on_drag,
|
||||
);
|
||||
|
|
@ -539,7 +588,7 @@ pub fn update<'a, Message, T: Draggable>(
|
|||
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
||||
if let Some((pane, _)) = action.picked_pane() {
|
||||
if let Some(on_drag) = on_drag {
|
||||
let mut dropped_region = elements
|
||||
let mut dropped_region = contents
|
||||
.zip(layout.children())
|
||||
.filter(|(_, layout)| {
|
||||
layout.bounds().contains(cursor_position)
|
||||
|
|
@ -570,7 +619,7 @@ pub fn update<'a, Message, T: Draggable>(
|
|||
if let Some((split, _)) = action.picked_split() {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let splits = state.split_regions(
|
||||
let splits = node.split_regions(
|
||||
f32::from(spacing),
|
||||
Size::new(bounds.width, bounds.height),
|
||||
);
|
||||
|
|
@ -609,13 +658,13 @@ fn click_pane<'a, Message, T>(
|
|||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
elements: impl Iterator<Item = (Pane, T)>,
|
||||
contents: impl Iterator<Item = (Pane, T)>,
|
||||
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
|
||||
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
|
||||
) where
|
||||
T: Draggable,
|
||||
{
|
||||
let mut clicked_region = elements
|
||||
let mut clicked_region = contents
|
||||
.zip(layout.children())
|
||||
.filter(|(_, layout)| layout.bounds().contains(cursor_position));
|
||||
|
||||
|
|
@ -642,7 +691,7 @@ fn click_pane<'a, Message, T>(
|
|||
/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`].
|
||||
pub fn mouse_interaction(
|
||||
action: &state::Action,
|
||||
state: &state::Internal,
|
||||
node: &Node,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
spacing: u16,
|
||||
|
|
@ -658,7 +707,7 @@ pub fn mouse_interaction(
|
|||
let bounds = layout.bounds();
|
||||
|
||||
let splits =
|
||||
state.split_regions(f32::from(spacing), bounds.size());
|
||||
node.split_regions(f32::from(spacing), bounds.size());
|
||||
|
||||
let relative_cursor = Point::new(
|
||||
cursor_position.x - bounds.x,
|
||||
|
|
@ -687,7 +736,7 @@ pub fn mouse_interaction(
|
|||
/// Draws a [`PaneGrid`].
|
||||
pub fn draw<Renderer, T>(
|
||||
action: &state::Action,
|
||||
state: &state::Internal,
|
||||
node: &Node,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &mut Renderer,
|
||||
|
|
@ -696,8 +745,8 @@ pub fn draw<Renderer, T>(
|
|||
viewport: &Rectangle,
|
||||
spacing: u16,
|
||||
resize_leeway: Option<u16>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
elements: impl Iterator<Item = (Pane, T)>,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
contents: impl Iterator<Item = (Pane, T)>,
|
||||
draw_pane: impl Fn(
|
||||
T,
|
||||
&mut Renderer,
|
||||
|
|
@ -717,7 +766,7 @@ pub fn draw<Renderer, T>(
|
|||
.and_then(|(split, axis)| {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let splits = state.split_regions(f32::from(spacing), bounds.size());
|
||||
let splits = node.split_regions(f32::from(spacing), bounds.size());
|
||||
|
||||
let (_axis, region, ratio) = splits.get(&split)?;
|
||||
|
||||
|
|
@ -736,7 +785,7 @@ pub fn draw<Renderer, T>(
|
|||
);
|
||||
|
||||
let splits =
|
||||
state.split_regions(f32::from(spacing), bounds.size());
|
||||
node.split_regions(f32::from(spacing), bounds.size());
|
||||
|
||||
let (_split, axis, region) = hovered_split(
|
||||
splits.iter(),
|
||||
|
|
@ -759,7 +808,7 @@ pub fn draw<Renderer, T>(
|
|||
|
||||
let mut render_picked_pane = None;
|
||||
|
||||
for ((id, pane), layout) in elements.zip(layout.children()) {
|
||||
for ((id, pane), layout) in contents.zip(layout.children()) {
|
||||
match picked_pane {
|
||||
Some((dragging, origin)) if id == dragging => {
|
||||
render_picked_pane = Some((pane, origin, layout));
|
||||
|
|
@ -897,3 +946,49 @@ fn hovered_split<'a>(
|
|||
})
|
||||
.next()
|
||||
}
|
||||
|
||||
/// The visible contents of the [`PaneGrid`]
|
||||
#[derive(Debug)]
|
||||
pub enum Contents<'a, T> {
|
||||
/// All panes are visible
|
||||
All(Vec<(Pane, T)>, &'a state::Internal),
|
||||
/// A maximized pane is visible
|
||||
Maximized(Pane, T, Node),
|
||||
}
|
||||
|
||||
impl<'a, T> Contents<'a, T> {
|
||||
/// Returns the layout [`Node`] of the [`Contents`]
|
||||
pub fn layout(&self) -> &Node {
|
||||
match self {
|
||||
Contents::All(_, state) => state.layout(),
|
||||
Contents::Maximized(_, _, layout) => layout,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the values of the [`Contents`]
|
||||
pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> {
|
||||
match self {
|
||||
Contents::All(contents, _) => Box::new(
|
||||
contents.iter().map(|(pane, content)| (*pane, content)),
|
||||
),
|
||||
Contents::Maximized(pane, content, _) => {
|
||||
Box::new(std::iter::once((*pane, content)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> {
|
||||
match self {
|
||||
Contents::All(contents, _) => Box::new(
|
||||
contents.iter_mut().map(|(pane, content)| (*pane, content)),
|
||||
),
|
||||
Contents::Maximized(pane, content, _) => {
|
||||
Box::new(std::iter::once((*pane, content)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_maximized(&self) -> bool {
|
||||
matches!(self, Self::Maximized(..))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::overlay;
|
|||
use crate::renderer;
|
||||
use crate::widget::container;
|
||||
use crate::widget::pane_grid::{Draggable, TitleBar};
|
||||
use crate::widget::Tree;
|
||||
use crate::widget::{self, Tree};
|
||||
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
||||
|
||||
/// The content of a [`Pane`].
|
||||
|
|
@ -87,7 +87,7 @@ where
|
|||
|
||||
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
|
||||
///
|
||||
/// [`Renderer`]: iced_native::Renderer
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
pub fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
|
|
@ -103,7 +103,7 @@ where
|
|||
let bounds = layout.bounds();
|
||||
|
||||
{
|
||||
let style = theme.appearance(self.style);
|
||||
let style = theme.appearance(&self.style);
|
||||
|
||||
container::draw_background(renderer, &style, bounds);
|
||||
}
|
||||
|
|
@ -183,6 +183,33 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
let body_layout = if let Some(title_bar) = &self.title_bar {
|
||||
let mut children = layout.children();
|
||||
|
||||
title_bar.operate(
|
||||
&mut tree.children[1],
|
||||
children.next().unwrap(),
|
||||
operation,
|
||||
);
|
||||
|
||||
children.next().unwrap()
|
||||
} else {
|
||||
layout
|
||||
};
|
||||
|
||||
self.body.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
body_layout,
|
||||
operation,
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
|
|
@ -278,12 +305,12 @@ where
|
|||
}
|
||||
|
||||
pub(crate) fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
if let Some(title_bar) = self.title_bar.as_ref() {
|
||||
if let Some(title_bar) = self.title_bar.as_mut() {
|
||||
let mut children = layout.children();
|
||||
let title_bar_layout = children.next()?;
|
||||
|
||||
|
|
@ -294,14 +321,14 @@ where
|
|||
match title_bar.overlay(title_bar_state, title_bar_layout, renderer)
|
||||
{
|
||||
Some(overlay) => Some(overlay),
|
||||
None => self.body.as_widget().overlay(
|
||||
None => self.body.as_widget_mut().overlay(
|
||||
body_state,
|
||||
children.next()?,
|
||||
renderer,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
self.body.as_widget().overlay(
|
||||
self.body.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
use crate::widget::pane_grid::{
|
||||
Axis, Configuration, Direction, Node, Pane, Split,
|
||||
};
|
||||
use crate::{Point, Rectangle, Size};
|
||||
use crate::{Point, Size};
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// The state of a [`PaneGrid`].
|
||||
///
|
||||
|
|
@ -31,6 +31,11 @@ pub struct State<T> {
|
|||
///
|
||||
/// [`PaneGrid`]: crate::widget::PaneGrid
|
||||
pub internal: Internal,
|
||||
|
||||
/// The maximized [`Pane`] of the [`PaneGrid`].
|
||||
///
|
||||
/// [`PaneGrid`]: crate::widget::PaneGrid
|
||||
pub(super) maximized: Option<Pane>,
|
||||
}
|
||||
|
||||
impl<T> State<T> {
|
||||
|
|
@ -52,7 +57,11 @@ impl<T> State<T> {
|
|||
let internal =
|
||||
Internal::from_configuration(&mut panes, config.into(), 0);
|
||||
|
||||
State { panes, internal }
|
||||
State {
|
||||
panes,
|
||||
internal,
|
||||
maximized: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the total amount of panes in the [`State`].
|
||||
|
|
@ -153,6 +162,7 @@ impl<T> State<T> {
|
|||
node.split(new_split, axis, new_pane);
|
||||
|
||||
let _ = self.panes.insert(new_pane, state);
|
||||
let _ = self.maximized.take();
|
||||
|
||||
Some((new_pane, new_split))
|
||||
}
|
||||
|
|
@ -194,12 +204,39 @@ impl<T> State<T> {
|
|||
/// Closes the given [`Pane`] and returns its internal state and its closest
|
||||
/// sibling, if it exists.
|
||||
pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> {
|
||||
if self.maximized == Some(*pane) {
|
||||
let _ = self.maximized.take();
|
||||
}
|
||||
|
||||
if let Some(sibling) = self.internal.layout.remove(pane) {
|
||||
self.panes.remove(pane).map(|state| (state, sibling))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximize the given [`Pane`]. Only this pane will be rendered by the
|
||||
/// [`PaneGrid`] until [`Self::restore()`] is called.
|
||||
///
|
||||
/// [`PaneGrid`]: crate::widget::PaneGrid
|
||||
pub fn maximize(&mut self, pane: &Pane) {
|
||||
self.maximized = Some(*pane);
|
||||
}
|
||||
|
||||
/// Restore the currently maximized [`Pane`] to it's normal size. All panes
|
||||
/// will be rendered by the [`PaneGrid`].
|
||||
///
|
||||
/// [`PaneGrid`]: crate::widget::PaneGrid
|
||||
pub fn restore(&mut self) {
|
||||
let _ = self.maximized.take();
|
||||
}
|
||||
|
||||
/// Returns the maximized [`Pane`] of the [`PaneGrid`].
|
||||
///
|
||||
/// [`PaneGrid`]: crate::widget::PaneGrid
|
||||
pub fn maximized(&self) -> Option<Pane> {
|
||||
self.maximized
|
||||
}
|
||||
}
|
||||
|
||||
/// The internal state of a [`PaneGrid`].
|
||||
|
|
@ -226,11 +263,13 @@ impl Internal {
|
|||
let Internal {
|
||||
layout: a,
|
||||
last_id: next_id,
|
||||
..
|
||||
} = Self::from_configuration(panes, *a, next_id);
|
||||
|
||||
let Internal {
|
||||
layout: b,
|
||||
last_id: next_id,
|
||||
..
|
||||
} = Self::from_configuration(panes, *b, next_id);
|
||||
|
||||
(
|
||||
|
|
@ -304,25 +343,8 @@ impl Action {
|
|||
}
|
||||
|
||||
impl Internal {
|
||||
/// Calculates the current [`Pane`] regions from the [`PaneGrid`] layout.
|
||||
///
|
||||
/// [`PaneGrid`]: crate::widget::PaneGrid
|
||||
pub fn pane_regions(
|
||||
&self,
|
||||
spacing: f32,
|
||||
size: Size,
|
||||
) -> BTreeMap<Pane, Rectangle> {
|
||||
self.layout.pane_regions(spacing, size)
|
||||
}
|
||||
|
||||
/// Calculates the current [`Split`] regions from the [`PaneGrid`] layout.
|
||||
///
|
||||
/// [`PaneGrid`]: crate::widget::PaneGrid
|
||||
pub fn split_regions(
|
||||
&self,
|
||||
spacing: f32,
|
||||
size: Size,
|
||||
) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
|
||||
self.layout.split_regions(spacing, size)
|
||||
/// The layout [`Node`] of the [`Internal`] state
|
||||
pub fn layout(&self) -> &Node {
|
||||
&self.layout
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::mouse;
|
|||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::widget::container;
|
||||
use crate::widget::Tree;
|
||||
use crate::widget::{self, Tree};
|
||||
use crate::{
|
||||
Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
|
||||
};
|
||||
|
|
@ -114,7 +114,7 @@ where
|
|||
|
||||
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
|
||||
///
|
||||
/// [`Renderer`]: iced_native::Renderer
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
pub fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
|
|
@ -129,7 +129,7 @@ where
|
|||
use container::StyleSheet;
|
||||
|
||||
let bounds = layout.bounds();
|
||||
let style = theme.appearance(self.style);
|
||||
let style = theme.appearance(&self.style);
|
||||
let inherited_style = renderer::Style {
|
||||
text_color: style.text_color.unwrap_or(inherited_style.text_color),
|
||||
};
|
||||
|
|
@ -257,6 +257,44 @@ where
|
|||
layout::Node::with_children(node.size().pad(self.padding), vec![node])
|
||||
}
|
||||
|
||||
pub(crate) fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
let mut children = layout.children();
|
||||
let padded = children.next().unwrap();
|
||||
|
||||
let mut children = padded.children();
|
||||
let title_layout = children.next().unwrap();
|
||||
let mut show_title = true;
|
||||
|
||||
if let Some(controls) = &self.controls {
|
||||
let controls_layout = children.next().unwrap();
|
||||
|
||||
if title_layout.bounds().width + controls_layout.bounds().width
|
||||
> padded.bounds().width
|
||||
{
|
||||
show_title = false;
|
||||
}
|
||||
|
||||
controls.as_widget().operate(
|
||||
&mut tree.children[1],
|
||||
controls_layout,
|
||||
operation,
|
||||
)
|
||||
};
|
||||
|
||||
if show_title {
|
||||
self.content.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
title_layout,
|
||||
operation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
|
|
@ -357,7 +395,7 @@ where
|
|||
}
|
||||
|
||||
pub(crate) fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -377,13 +415,13 @@ where
|
|||
let controls_state = states.next().unwrap();
|
||||
|
||||
content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.overlay(title_state, title_layout, renderer)
|
||||
.or_else(move || {
|
||||
controls.as_ref().and_then(|controls| {
|
||||
controls.as_mut().and_then(|controls| {
|
||||
let controls_layout = children.next()?;
|
||||
|
||||
controls.as_widget().overlay(
|
||||
controls.as_widget_mut().overlay(
|
||||
controls_state,
|
||||
controls_layout,
|
||||
renderer,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ use crate::overlay::menu::{self, Menu};
|
|||
use crate::renderer;
|
||||
use crate::text::{self, Text};
|
||||
use crate::touch;
|
||||
use crate::widget::container;
|
||||
use crate::widget::scrollable;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size,
|
||||
|
|
@ -42,7 +44,12 @@ where
|
|||
T: ToString + Eq,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Renderer::Theme: StyleSheet
|
||||
+ scrollable::StyleSheet
|
||||
+ menu::StyleSheet
|
||||
+ container::StyleSheet,
|
||||
<Renderer::Theme as menu::StyleSheet>::Style:
|
||||
From<<Renderer::Theme as StyleSheet>::Style>,
|
||||
{
|
||||
/// The default padding of a [`PickList`].
|
||||
pub const DEFAULT_PADDING: Padding = Padding::new(5);
|
||||
|
|
@ -114,7 +121,12 @@ where
|
|||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Message: 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Renderer::Theme: StyleSheet
|
||||
+ scrollable::StyleSheet
|
||||
+ menu::StyleSheet
|
||||
+ container::StyleSheet,
|
||||
<Renderer::Theme as menu::StyleSheet>::Style:
|
||||
From<<Renderer::Theme as StyleSheet>::Style>,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State<T>>()
|
||||
|
|
@ -202,12 +214,12 @@ where
|
|||
&self.font,
|
||||
self.placeholder.as_deref(),
|
||||
self.selected.as_ref(),
|
||||
self.style,
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
|
|
@ -221,7 +233,7 @@ where
|
|||
self.text_size,
|
||||
self.font.clone(),
|
||||
&self.options,
|
||||
self.style,
|
||||
self.style.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -233,7 +245,12 @@ where
|
|||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Message: 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Renderer::Theme: StyleSheet
|
||||
+ scrollable::StyleSheet
|
||||
+ menu::StyleSheet
|
||||
+ container::StyleSheet,
|
||||
<Renderer::Theme as menu::StyleSheet>::Style:
|
||||
From<<Renderer::Theme as StyleSheet>::Style>,
|
||||
{
|
||||
fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self {
|
||||
Self::new(pick_list)
|
||||
|
|
@ -456,7 +473,12 @@ where
|
|||
T: Clone + ToString,
|
||||
Message: 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Renderer::Theme: StyleSheet
|
||||
+ scrollable::StyleSheet
|
||||
+ menu::StyleSheet
|
||||
+ container::StyleSheet,
|
||||
<Renderer::Theme as menu::StyleSheet>::Style:
|
||||
From<<Renderer::Theme as StyleSheet>::Style>,
|
||||
{
|
||||
if state.is_open {
|
||||
let bounds = layout.bounds();
|
||||
|
|
@ -493,7 +515,7 @@ pub fn draw<T, Renderer>(
|
|||
font: &Renderer::Font,
|
||||
placeholder: Option<&str>,
|
||||
selected: Option<&T>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ where
|
|||
/ (range_end - range_start)
|
||||
};
|
||||
|
||||
let style = theme.appearance(self.style);
|
||||
let style = theme.appearance(&self.style);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
|
|
|
|||
|
|
@ -230,9 +230,9 @@ where
|
|||
let mut children = layout.children();
|
||||
|
||||
let custom_style = if is_mouse_over {
|
||||
theme.hovered(self.style, self.is_selected)
|
||||
theme.hovered(&self.style, self.is_selected)
|
||||
} else {
|
||||
theme.active(self.style, self.is_selected)
|
||||
theme.active(&self.style, self.is_selected)
|
||||
};
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -229,12 +229,12 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
overlay::from_children(&self.children, tree, layout, renderer)
|
||||
overlay::from_children(&mut self.children, tree, layout, renderer)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ where
|
|||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let style = theme.style(self.style);
|
||||
let style = theme.appearance(&self.style);
|
||||
|
||||
let bounds = if self.is_horizontal {
|
||||
let line_y = (bounds.y + (bounds.height / 2.0)
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ where
|
|||
self.scrollbar_width,
|
||||
self.scrollbar_margin,
|
||||
self.scroller_width,
|
||||
self.style,
|
||||
&self.style,
|
||||
|renderer, layout, cursor_position, viewport| {
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
|
|
@ -276,13 +276,13 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
|
|
@ -334,6 +334,12 @@ impl Id {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Id> for widget::Id {
|
||||
fn from(id: Id) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`]
|
||||
/// to the provided `percentage`.
|
||||
pub fn snap_to<Message: 'static>(id: Id, percentage: f32) -> Command<Message> {
|
||||
|
|
@ -627,7 +633,7 @@ pub fn draw<Renderer>(
|
|||
scrollbar_width: u16,
|
||||
scrollbar_margin: u16,
|
||||
scroller_width: u16,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle),
|
||||
) where
|
||||
Renderer: crate::Renderer,
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ where
|
|||
self.value,
|
||||
&self.range,
|
||||
theme,
|
||||
self.style,
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -353,7 +353,7 @@ pub fn draw<T, R>(
|
|||
value: T,
|
||||
range: &RangeInclusive<T>,
|
||||
style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>,
|
||||
style: <R::Theme as StyleSheet>::Style,
|
||||
style: &<R::Theme as StyleSheet>::Style,
|
||||
) where
|
||||
T: Into<f64> + Copy,
|
||||
R: crate::Renderer,
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ where
|
|||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
// The raw w/h of the underlying image
|
||||
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);
|
||||
|
||||
// The size to be available to the widget prior to `Shrink`ing
|
||||
|
|
@ -120,7 +120,7 @@ where
|
|||
_cursor_position: Point,
|
||||
_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 bounds = layout.bounds();
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Color`] of the [`Text`].
|
||||
/// Sets the style of the [`Text`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ where
|
|||
}
|
||||
|
||||
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
||||
/// [`text_input::Value`] if provided.
|
||||
/// [`Value`] if provided.
|
||||
///
|
||||
/// [`Renderer`]: text::Renderer
|
||||
pub fn draw(
|
||||
|
|
@ -188,7 +188,7 @@ where
|
|||
self.size,
|
||||
&self.font,
|
||||
self.is_secure,
|
||||
self.style,
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -233,6 +233,7 @@ where
|
|||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
operation.focusable(state, self.id.as_ref().map(|id| &id.0));
|
||||
operation.text_input(state, self.id.as_ref().map(|id| &id.0));
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
|
|
@ -284,7 +285,7 @@ where
|
|||
self.size,
|
||||
&self.font,
|
||||
self.is_secure,
|
||||
self.style,
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -332,11 +333,43 @@ impl Id {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Id> for widget::Id {
|
||||
fn from(id: Id) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`].
|
||||
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::focusable::focus(id.0))
|
||||
}
|
||||
|
||||
/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
|
||||
/// end.
|
||||
pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::text_input::move_cursor_to_end(id.0))
|
||||
}
|
||||
|
||||
/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
|
||||
/// front.
|
||||
pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::text_input::move_cursor_to_front(id.0))
|
||||
}
|
||||
|
||||
/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
|
||||
/// provided position.
|
||||
pub fn move_cursor_to<Message: 'static>(
|
||||
id: Id,
|
||||
position: usize,
|
||||
) -> Command<Message> {
|
||||
Command::widget(operation::text_input::move_cursor_to(id.0, position))
|
||||
}
|
||||
|
||||
/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`].
|
||||
pub fn select_all<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::text_input::select_all(id.0))
|
||||
}
|
||||
|
||||
/// Computes the layout of a [`TextInput`].
|
||||
pub fn layout<Renderer>(
|
||||
renderer: &Renderer,
|
||||
|
|
@ -350,6 +383,8 @@ where
|
|||
{
|
||||
let text_size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let padding = padding.fit(Size::ZERO, limits.max());
|
||||
|
||||
let limits = limits
|
||||
.pad(padding)
|
||||
.width(width)
|
||||
|
|
@ -742,7 +777,7 @@ pub fn draw<Renderer>(
|
|||
size: Option<u16>,
|
||||
font: &Renderer::Font,
|
||||
is_secure: bool,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
|
|
@ -997,6 +1032,24 @@ impl operation::Focusable for State {
|
|||
}
|
||||
}
|
||||
|
||||
impl operation::TextInput for State {
|
||||
fn move_cursor_to_front(&mut self) {
|
||||
State::move_cursor_to_front(self)
|
||||
}
|
||||
|
||||
fn move_cursor_to_end(&mut self) {
|
||||
State::move_cursor_to_end(self)
|
||||
}
|
||||
|
||||
fn move_cursor_to(&mut self, position: usize) {
|
||||
State::move_cursor_to(self, position)
|
||||
}
|
||||
|
||||
fn select_all(&mut self) {
|
||||
State::select_all(self)
|
||||
}
|
||||
}
|
||||
|
||||
mod platform {
|
||||
use crate::keyboard;
|
||||
|
||||
|
|
|
|||
|
|
@ -260,9 +260,9 @@ where
|
|||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let style = if is_mouse_over {
|
||||
theme.hovered(self.style, self.is_active)
|
||||
theme.hovered(&self.style, self.is_active)
|
||||
} else {
|
||||
theme.active(self.style, self.is_active)
|
||||
theme.active(&self.style, self.is_active)
|
||||
};
|
||||
|
||||
let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ where
|
|||
self.gap,
|
||||
self.padding,
|
||||
self.snap_within_viewport,
|
||||
self.style,
|
||||
&self.style,
|
||||
|renderer, limits| {
|
||||
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
|
||||
},
|
||||
|
|
@ -221,12 +221,12 @@ where
|
|||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget().overlay(
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
|
|
@ -275,7 +275,7 @@ pub fn draw<Renderer>(
|
|||
gap: u16,
|
||||
padding: u16,
|
||||
snap_within_viewport: bool,
|
||||
style: <Renderer::Theme as container::StyleSheet>::Style,
|
||||
style: &<Renderer::Theme as container::StyleSheet>::Style,
|
||||
layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
|
||||
draw_text: impl FnOnce(
|
||||
&mut Renderer,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ impl Tree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Tree`] for the provided [`Element`].
|
||||
/// Creates a new [`Tree`] for the provided [`Widget`].
|
||||
pub fn new<'a, Message, Renderer>(
|
||||
widget: impl Borrow<dyn Widget<Message, Renderer> + 'a>,
|
||||
) -> Self
|
||||
|
|
@ -46,10 +46,10 @@ impl Tree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reconciliates the current tree with the provided [`Element`].
|
||||
/// Reconciliates the current tree with the provided [`Widget`].
|
||||
///
|
||||
/// If the tag of the [`Element`] matches the tag of the [`Tree`], then the
|
||||
/// [`Element`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
|
||||
/// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the
|
||||
/// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
|
||||
///
|
||||
/// Otherwise, the whole [`Tree`] is recreated.
|
||||
///
|
||||
|
|
@ -67,7 +67,7 @@ impl Tree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reconciliates the children of the tree with the provided list of [`Element`].
|
||||
/// Reconciliates the children of the tree with the provided list of widgets.
|
||||
pub fn diff_children<'a, Message, Renderer>(
|
||||
&mut self,
|
||||
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
|
||||
|
|
@ -81,7 +81,7 @@ impl Tree {
|
|||
)
|
||||
}
|
||||
|
||||
/// Reconciliates the children of the tree with the provided list of [`Element`] using custom
|
||||
/// Reconciliates the children of the tree with the provided list of widgets using custom
|
||||
/// logic both for diffing and creating new widget state.
|
||||
pub fn diff_children_custom<T>(
|
||||
&mut self,
|
||||
|
|
|
|||
|
|
@ -39,15 +39,15 @@ pub use iced_native::application::{Appearance, StyleSheet};
|
|||
/// to listen to time.
|
||||
/// - [`todos`], a todos tracker inspired by [TodoMVC].
|
||||
///
|
||||
/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.4/examples
|
||||
/// [`clock`]: https://github.com/iced-rs/iced/tree/0.4/examples/clock
|
||||
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.4/examples/download_progress
|
||||
/// [`events`]: https://github.com/iced-rs/iced/tree/0.4/examples/events
|
||||
/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.4/examples/game_of_life
|
||||
/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.4/examples/pokedex
|
||||
/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.4/examples/solar_system
|
||||
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.4/examples/stopwatch
|
||||
/// [`todos`]: https://github.com/iced-rs/iced/tree/0.4/examples/todos
|
||||
/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.5/examples
|
||||
/// [`clock`]: https://github.com/iced-rs/iced/tree/0.5/examples/clock
|
||||
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.5/examples/download_progress
|
||||
/// [`events`]: https://github.com/iced-rs/iced/tree/0.5/examples/events
|
||||
/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.5/examples/game_of_life
|
||||
/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.5/examples/pokedex
|
||||
/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.5/examples/solar_system
|
||||
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.5/examples/stopwatch
|
||||
/// [`todos`]: https://github.com/iced-rs/iced/tree/0.5/examples/todos
|
||||
/// [`Sandbox`]: crate::Sandbox
|
||||
/// [`Canvas`]: crate::widget::Canvas
|
||||
/// [PokéAPI]: https://pokeapi.co/
|
||||
|
|
@ -148,9 +148,8 @@ pub trait Application: Sized {
|
|||
Self::Theme::default()
|
||||
}
|
||||
|
||||
/// Returns the current [`Style`] of the [`Theme`].
|
||||
/// Returns the current `Style` of the [`Theme`].
|
||||
///
|
||||
/// [`Style`]: <Self::Theme as StyleSheet>::Style
|
||||
/// [`Theme`]: Self::Theme
|
||||
fn style(&self) -> <Self::Theme as StyleSheet>::Style {
|
||||
<Self::Theme as StyleSheet>::Style::default()
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@
|
|||
//! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md
|
||||
//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/0.4/master/native
|
||||
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.4/wgpu
|
||||
//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.4/winit
|
||||
//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.5/wgpu
|
||||
//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.5/winit
|
||||
//! [`dodrio`]: https://github.com/fitzgen/dodrio
|
||||
//! [web runtime]: https://github.com/iced-rs/iced_web
|
||||
//! [examples]: https://github.com/iced-rs/iced/tree/0.4/examples
|
||||
//! [examples]: https://github.com/iced-rs/iced/tree/0.5/examples
|
||||
//! [repository]: https://github.com/iced-rs/iced
|
||||
//!
|
||||
//! # Overview
|
||||
|
|
|
|||
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