Merge branch 'master' into feat/slider-orientation

This commit is contained in:
Casper Storm 2022-12-13 09:31:57 +01:00
commit 2e6d90f141
No known key found for this signature in database
GPG key ID: BABF49AA70C405C2
174 changed files with 7170 additions and 1989 deletions

View file

@ -6,6 +6,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.6.0] - 2022-12-07
### Added
- Support for non-uniform border radius for `Primitive::Quad`. [#1506](https://github.com/iced-rs/iced/pull/1506)
- Operation to query the current focused widget. [#1526](https://github.com/iced-rs/iced/pull/1526)
- Additional operations for `TextInput`. [#1529](https://github.com/iced-rs/iced/pull/1529)
- Styling support for `Svg`. [#1578](https://github.com/iced-rs/iced/pull/1578)
### Changed
- Triangle geometry using a solid color is now drawn in a single draw call. [#1538](https://github.com/iced-rs/iced/pull/1538)
### Fixed
- Gradients for WebAssembly target. [#1524](https://github.com/iced-rs/iced/pull/1524)
- `Overlay` layout cache not being invalidated. [#1528](https://github.com/iced-rs/iced/pull/1528)
- Operations not working for `PaneGrid`. [#1533](https://github.com/iced-rs/iced/pull/1533)
- Mapped `widget::Operation` always returning `Outcome::None`. [#1536](https://github.com/iced-rs/iced/pull/1536)
- Padding of `TextInput` with `Length::Units` width. [#1539](https://github.com/iced-rs/iced/pull/1539)
- Clipping of `Image` and `Svg` widgets in `iced_glow`. [#1557](https://github.com/iced-rs/iced/pull/1557)
- Invalid links in documentation. [#1560](https://github.com/iced-rs/iced/pull/1560)
- `Custom` style of `PickList` widget. [#1570](https://github.com/iced-rs/iced/pull/1570)
- Scroller in `Scrollable` always being drawn. [#1574](https://github.com/iced-rs/iced/pull/1574)
Many thanks to...
- @bungoboingo
- @l1Dan
- @mmstick
- @mtkennerly
- @PolyMeilex
- @rksm
- @rs017991
- @tarkah
- @wash2
## [0.5.0] - 2022-11-10
### Added
- __[Stabilization of stateless widgets][stateless]__ (#1393)
The old widget API has been completely replaced by stateless widgets (introduced in #1284). Alongside the new API, there are a bunch of new helper functions and macros for easily describing view logic (like `row!` and `column!`).
- __[First-class theming][theming]__ (#1362)
A complete overhaul of our styling primitives, introducing a `Theme` as a first-class concept of the library.
- __[Widget operations][operations]__ (#1399)
An abstraction that can be used to traverse (and operate on) the widget tree of an application in order to query or update some widget state.
- __[`Lazy` widget][lazy]__ (#1400)
A widget that can call some view logic lazily only when some data has changed. Thanks to @nicksenger!
- __[Linear gradient support for `Canvas`][gradient]__ (#1448)
The `Canvas` widget can draw linear gradients now. Thanks to @bungoboingo!
- __[Touch support for `Canvas`][touch]__ (#1305)
The `Canvas` widget now supports touch events. Thanks to @artursapek!
- __[`Image` and `Svg` support for `iced_glow`][image]__ (#1485)
Our OpenGL renderer now is capable of rendering both the `Image` and `Svg` widgets. Thanks to @ids1024!
[stateless]: https://github.com/iced-rs/iced/pull/1393
[theming]: https://github.com/iced-rs/iced/pull/1362
[operations]: https://github.com/iced-rs/iced/pull/1399
[lazy]: https://github.com/iced-rs/iced/pull/1400
[gradient]: https://github.com/iced-rs/iced/pull/1448
[touch]: https://github.com/iced-rs/iced/pull/1305
[image]: https://github.com/iced-rs/iced/pull/1485
## [0.4.2] - 2022-05-03
### Fixed
- `Padding` type not exposed in `iced`.
@ -257,7 +321,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- First release! :tada:
[Unreleased]: https://github.com/iced-rs/iced/compare/0.4.2...HEAD
[Unreleased]: https://github.com/iced-rs/iced/compare/0.6.0...HEAD
[0.6.0]: https://github.com/iced-rs/iced/compare/0.5.0...0.6.0
[0.5.0]: https://github.com/iced-rs/iced/compare/0.4.2...0.5.0
[0.4.2]: https://github.com/iced-rs/iced/compare/0.4.1...0.4.2
[0.4.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

View file

@ -1,6 +1,6 @@
[package]
name = "iced"
version = "0.4.2"
version = "0.6.0"
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/geometry",
"examples/integration_opengl",
"examples/integration_wgpu",
"examples/multitouch",
"examples/pane_grid",
"examples/pick_list",
"examples/pokedex",
"examples/progress_bar",
"examples/qr_code",
"examples/scrollable",
"examples/sierpinski_triangle",
"examples/solar_system",
"examples/stopwatch",
"examples/styling",
"examples/svg",
"examples/system_information",
"examples/todos",
"examples/tooltip",
"examples/tour",
"examples/url_handler",
"examples/websocket",
"examples/slider",
"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.7", path = "native" }
iced_graphics = { version = "0.5", path = "graphics" }
iced_winit = { version = "0.6", path = "winit", features = ["application"] }
iced_glutin = { version = "0.5", path = "glutin", optional = true }
iced_glow = { version = "0.5", 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.7", 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.7", path = "wgpu", features = ["webgl"], optional = true }
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]

View file

@ -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.6"
```
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.6", default-features = false, features = ["glow"] }
```
__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,

View file

@ -1,6 +1,6 @@
[package]
name = "iced_core"
version = "0.5.0"
version = "0.6.2"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "The essential concepts of Iced"

View file

@ -89,6 +89,17 @@ impl Color {
}
}
/// Converts the [`Color`] into its RGBA8 equivalent.
#[must_use]
pub fn into_rgba8(self) -> [u8; 4] {
[
(self.r * 255.0).round() as u8,
(self.g * 255.0).round() as u8,
(self.b * 255.0).round() as u8,
(self.a * 255.0).round() as u8,
]
}
/// Converts the [`Color`] into its linear values.
pub fn into_linear(self) -> [f32; 4] {
// As described in:
@ -148,24 +159,26 @@ impl From<[f32; 4]> for Color {
#[macro_export]
macro_rules! color {
($r:expr, $g:expr, $b:expr) => {
Color::from_rgb8($r, $g, $b)
$crate::Color::from_rgb8($r, $g, $b)
};
($r:expr, $g:expr, $b:expr, $a:expr) => {
Color::from_rgba8($r, $g, $b, $a)
$crate::Color::from_rgba8($r, $g, $b, $a)
};
($hex:expr) => {{
let hex = $hex as u32;
let r = (hex & 0xff0000) >> 16;
let g = (hex & 0xff00) >> 8;
let b = (hex & 0xff);
Color::from_rgb8(r as u8, g as u8, b as u8)
$crate::Color::from_rgb8(r as u8, g as u8, b as u8)
}};
($hex:expr, $a:expr) => {{
let hex = $hex as u32;
let r = (hex & 0xff0000) >> 16;
let g = (hex & 0xff00) >> 8;
let b = (hex & 0xff);
Color::from_rgba8(r as u8, g as u8, b as u8, $a)
$crate::Color::from_rgba8(r as u8, g as u8, b as u8, $a)
}};
}

View file

@ -7,7 +7,7 @@
//! ![The foundations of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true)
//!
//! [Iced]: https://github.com/iced-rs/iced
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native
//! [`iced_web`]: https://github.com/iced-rs/iced_web
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"

View file

@ -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],

View file

@ -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,
}
}
}

View file

@ -2,7 +2,7 @@ use std::{f32::consts::PI, time::Instant};
use iced::executor;
use iced::widget::canvas::{
self, Cache, Canvas, Cursor, Geometry, Path, Stroke,
self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
};
use iced::{
Application, Command, Element, Length, Point, Rectangle, Settings,
@ -52,11 +52,6 @@ impl Application for Arc {
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(std::time::Duration::from_millis(10))
.map(|_| Message::Tick)
}
fn view(&self) -> Element<Message> {
Canvas::new(self)
.width(Length::Fill)
@ -67,6 +62,11 @@ impl Application for Arc {
fn theme(&self) -> Theme {
Theme::Dark
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(std::time::Duration::from_millis(10))
.map(|_| Message::Tick)
}
}
impl<Message> canvas::Program<Message> for Arc {
@ -114,7 +114,7 @@ impl<Message> canvas::Program<Message> for Arc {
frame.stroke(
&path,
Stroke {
color: palette.text,
style: stroke::Style::Solid(palette.text),
width: 10.0,
..Stroke::default()
},

View file

@ -0,0 +1,10 @@
[package]
name = "cached"
version = "0.1.0"
authors = ["Nick Senger <dev@nsenger.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_lazy = { path = "../../lazy" }

139
examples/cached/src/main.rs Normal file
View file

@ -0,0 +1,139 @@
use iced::theme;
use iced::widget::{
button, column, horizontal_space, row, scrollable, text, text_input,
};
use iced::{Element, Length, Sandbox, Settings};
use iced_lazy::lazy;
use std::collections::HashSet;
pub fn main() -> iced::Result {
App::run(Settings::default())
}
struct App {
options: HashSet<String>,
input: String,
order: Order,
}
impl Default for App {
fn default() -> Self {
Self {
options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"]
.into_iter()
.map(ToString::to_string)
.collect(),
input: Default::default(),
order: Order::Ascending,
}
}
}
#[derive(Debug, Clone)]
enum Message {
InputChanged(String),
ToggleOrder,
DeleteOption(String),
AddOption(String),
}
impl Sandbox for App {
type Message = Message;
fn new() -> Self {
Self::default()
}
fn title(&self) -> String {
String::from("Cached - Iced")
}
fn update(&mut self, message: Message) {
match message {
Message::InputChanged(input) => {
self.input = input;
}
Message::ToggleOrder => {
self.order = match self.order {
Order::Ascending => Order::Descending,
Order::Descending => Order::Ascending,
}
}
Message::AddOption(option) => {
self.options.insert(option);
self.input.clear();
}
Message::DeleteOption(option) => {
self.options.remove(&option);
}
}
}
fn view(&self) -> Element<Message> {
let options = lazy((&self.order, self.options.len()), || {
let mut options: Vec<_> = self.options.iter().collect();
options.sort_by(|a, b| match self.order {
Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()),
Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()),
});
column(
options
.into_iter()
.map(|option| {
row![
text(option),
horizontal_space(Length::Fill),
button("Delete")
.on_press(Message::DeleteOption(
option.to_string(),
),)
.style(theme::Button::Destructive)
]
.into()
})
.collect(),
)
.spacing(10)
});
column![
scrollable(options).height(Length::Fill),
row![
text_input(
"Add a new option",
&self.input,
Message::InputChanged,
)
.on_submit(Message::AddOption(self.input.clone())),
button(text(format!("Toggle Order ({})", self.order)))
.on_press(Message::ToggleOrder)
]
.spacing(10)
]
.spacing(20)
.padding(20)
.into()
}
}
#[derive(Debug, Hash)]
enum Order {
Ascending,
Descending,
}
impl std::fmt::Display for Order {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Ascending => "Ascending",
Self::Descending => "Descending",
}
)
}
}

View file

@ -1,5 +1,7 @@
use iced::executor;
use iced::widget::canvas::{Cache, Cursor, Geometry, LineCap, Path, Stroke};
use iced::widget::canvas::{
stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke,
};
use iced::widget::{canvas, container};
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
@ -24,9 +26,9 @@ enum Message {
}
impl Application for Clock {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
@ -59,15 +61,6 @@ impl Application for Clock {
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(std::time::Duration::from_millis(500)).map(|_| {
Message::Tick(
time::OffsetDateTime::now_local()
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
)
})
}
fn view(&self) -> Element<Message> {
let canvas = canvas(self as &Self)
.width(Length::Fill)
@ -79,6 +72,15 @@ impl Application for Clock {
.padding(20)
.into()
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(std::time::Duration::from_millis(500)).map(|_| {
Message::Tick(
time::OffsetDateTime::now_local()
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
)
})
}
}
impl<Message> canvas::Program<Message> for Clock {
@ -104,33 +106,41 @@ impl<Message> canvas::Program<Message> for Clock {
let long_hand =
Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius));
let thin_stroke = Stroke {
width: radius / 100.0,
color: Color::WHITE,
line_cap: LineCap::Round,
..Stroke::default()
let width = radius / 100.0;
let thin_stroke = || -> Stroke {
Stroke {
width,
style: stroke::Style::Solid(Color::WHITE),
line_cap: LineCap::Round,
..Stroke::default()
}
};
let wide_stroke = Stroke {
width: thin_stroke.width * 3.0,
..thin_stroke
let wide_stroke = || -> Stroke {
Stroke {
width: width * 3.0,
style: stroke::Style::Solid(Color::WHITE),
line_cap: LineCap::Round,
..Stroke::default()
}
};
frame.translate(Vector::new(center.x, center.y));
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.hour(), 12));
frame.stroke(&short_hand, wide_stroke);
frame.stroke(&short_hand, wide_stroke());
});
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.minute(), 60));
frame.stroke(&long_hand, wide_stroke);
frame.stroke(&long_hand, wide_stroke());
});
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.second(), 60));
frame.stroke(&long_hand, thin_stroke);
frame.stroke(&long_hand, thin_stroke());
})
});

View file

@ -0,0 +1,10 @@
[package]
name = "custom_quad"
version = "0.1.0"
authors = ["Robert Krahn"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }

View file

@ -0,0 +1,160 @@
//! This example showcases a drawing a quad.
mod quad {
use iced_native::layout::{self, Layout};
use iced_native::renderer;
use iced_native::widget::{self, Widget};
use iced_native::{Color, Element, Length, Point, Rectangle, Size};
pub struct CustomQuad {
size: f32,
radius: [f32; 4],
border_width: f32,
}
impl CustomQuad {
pub fn new(size: f32, radius: [f32; 4], border_width: f32) -> Self {
Self {
size,
radius,
border_width,
}
}
}
impl<Message, Renderer> Widget<Message, Renderer> for CustomQuad
where
Renderer: renderer::Renderer,
{
fn width(&self) -> Length {
Length::Shrink
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
_renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {
layout::Node::new(Size::new(self.size, self.size))
}
fn draw(
&self,
_state: &widget::Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: self.radius.into(),
border_width: self.border_width,
border_color: Color::from_rgb(1.0, 0.0, 0.0),
},
Color::BLACK,
);
}
}
impl<'a, Message, Renderer> From<CustomQuad> for Element<'a, Message, Renderer>
where
Renderer: renderer::Renderer,
{
fn from(circle: CustomQuad) -> Self {
Self::new(circle)
}
}
}
use iced::widget::{column, container, slider, text};
use iced::{Alignment, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Example::run(Settings::default())
}
struct Example {
radius: [f32; 4],
border_width: f32,
}
#[derive(Debug, Clone, Copy)]
#[allow(clippy::enum_variant_names)]
enum Message {
RadiusTopLeftChanged(f32),
RadiusTopRightChanged(f32),
RadiusBottomRightChanged(f32),
RadiusBottomLeftChanged(f32),
BorderWidthChanged(f32),
}
impl Sandbox for Example {
type Message = Message;
fn new() -> Self {
Self {
radius: [50.0; 4],
border_width: 0.0,
}
}
fn title(&self) -> String {
String::from("Custom widget - Iced")
}
fn update(&mut self, message: Message) {
let [tl, tr, br, bl] = self.radius;
match message {
Message::RadiusTopLeftChanged(radius) => {
self.radius = [radius, tr, br, bl];
}
Message::RadiusTopRightChanged(radius) => {
self.radius = [tl, radius, br, bl];
}
Message::RadiusBottomRightChanged(radius) => {
self.radius = [tl, tr, radius, bl];
}
Message::RadiusBottomLeftChanged(radius) => {
self.radius = [tl, tr, br, radius];
}
Message::BorderWidthChanged(width) => {
self.border_width = width;
}
}
}
fn view(&self) -> Element<Message> {
let [tl, tr, br, bl] = self.radius;
let content = column![
quad::CustomQuad::new(200.0, self.radius, self.border_width),
text(format!("Radius: {tl:.2}/{tr:.2}/{br:.2}/{bl:.2}")),
slider(1.0..=100.0, tl, Message::RadiusTopLeftChanged).step(0.01),
slider(1.0..=100.0, tr, Message::RadiusTopRightChanged).step(0.01),
slider(1.0..=100.0, br, Message::RadiusBottomRightChanged)
.step(0.01),
slider(1.0..=100.0, bl, Message::RadiusBottomLeftChanged)
.step(0.01),
slider(1.0..=10.0, self.border_width, Message::BorderWidthChanged)
.step(0.01),
]
.padding(20)
.spacing(20)
.max_width(500)
.align_items(Alignment::Center);
container(content)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
}

View file

@ -61,7 +61,7 @@ mod circle {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: self.radius,
border_radius: self.radius.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},

View file

@ -11,22 +11,18 @@ mod rainbow {
// 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::{
layout, Element, Layout, Length, Point, Rectangle, Size, Vector,
Element, Layout, Length, Point, Rectangle, Size, Vector,
};
#[derive(Default)]
#[derive(Debug, Clone, Copy, Default)]
pub struct Rainbow;
impl Rainbow {
pub fn new() -> Self {
Self
}
}
pub fn rainbow() -> Rainbow {
Rainbow
}
@ -63,7 +59,7 @@ mod rainbow {
cursor_position: Point,
_viewport: &Rectangle,
) {
use iced_graphics::triangle::{Mesh2D, Vertex2D};
use iced_graphics::triangle::Mesh2D;
use iced_native::Renderer as _;
let b = layout.bounds();
@ -95,43 +91,43 @@ mod rainbow {
let posn_bl = [0.0, b.height];
let posn_l = [0.0, b.height / 2.0];
let mesh = Primitive::Mesh2D {
let mesh = Primitive::SolidMesh {
size: b.size(),
buffers: Mesh2D {
vertices: vec![
Vertex2D {
ColoredVertex2D {
position: posn_center,
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex2D {
ColoredVertex2D {
position: posn_tl,
color: color_r,
},
Vertex2D {
ColoredVertex2D {
position: posn_t,
color: color_o,
},
Vertex2D {
ColoredVertex2D {
position: posn_tr,
color: color_y,
},
Vertex2D {
ColoredVertex2D {
position: posn_r,
color: color_g,
},
Vertex2D {
ColoredVertex2D {
position: posn_br,
color: color_gb,
},
Vertex2D {
ColoredVertex2D {
position: posn_b,
color: color_b,
},
Vertex2D {
ColoredVertex2D {
position: posn_bl,
color: color_i,
},
Vertex2D {
ColoredVertex2D {
position: posn_l,
color: color_v,
},
@ -166,7 +162,7 @@ mod rainbow {
}
use iced::widget::{column, container, scrollable};
use iced::{Alignment, Element, Length, Sandbox, Settings};
use iced::{Element, Length, Sandbox, Settings};
use rainbow::rainbow;
pub fn main() -> iced::Result {
@ -179,7 +175,7 @@ impl Sandbox for Example {
type Message = ();
fn new() -> Self {
Example
Self
}
fn title(&self) -> String {
@ -202,8 +198,7 @@ impl Sandbox for Example {
]
.padding(20)
.spacing(20)
.max_width(500)
.align_items(Alignment::Start);
.max_width(500);
let scrollable =
scrollable(container(content).width(Length::Fill).center_x());

10
examples/lazy/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "lazy"
version = "0.1.0"
authors = ["Nick Senger <dev@nsenger.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_lazy = { path = "../../lazy" }

139
examples/lazy/src/main.rs Normal file
View file

@ -0,0 +1,139 @@
use iced::theme;
use iced::widget::{
button, column, horizontal_space, row, scrollable, text, text_input,
};
use iced::{Element, Length, Sandbox, Settings};
use iced_lazy::lazy;
use std::collections::HashSet;
pub fn main() -> iced::Result {
App::run(Settings::default())
}
struct App {
options: HashSet<String>,
input: String,
order: Order,
}
impl Default for App {
fn default() -> Self {
Self {
options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"]
.into_iter()
.map(ToString::to_string)
.collect(),
input: Default::default(),
order: Order::Ascending,
}
}
}
#[derive(Debug, Clone)]
enum Message {
InputChanged(String),
ToggleOrder,
DeleteOption(String),
AddOption(String),
}
impl Sandbox for App {
type Message = Message;
fn new() -> Self {
Self::default()
}
fn title(&self) -> String {
String::from("Cached - Iced")
}
fn update(&mut self, message: Message) {
match message {
Message::InputChanged(input) => {
self.input = input;
}
Message::ToggleOrder => {
self.order = match self.order {
Order::Ascending => Order::Descending,
Order::Descending => Order::Ascending,
}
}
Message::AddOption(option) => {
self.options.insert(option);
self.input.clear();
}
Message::DeleteOption(option) => {
self.options.remove(&option);
}
}
}
fn view(&self) -> Element<Message> {
let options = lazy((&self.order, self.options.len()), || {
let mut options: Vec<_> = self.options.iter().collect();
options.sort_by(|a, b| match self.order {
Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()),
Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()),
});
column(
options
.into_iter()
.map(|option| {
row![
text(option),
horizontal_space(Length::Fill),
button("Delete")
.on_press(Message::DeleteOption(
option.to_string(),
),)
.style(theme::Button::Destructive)
]
.into()
})
.collect(),
)
.spacing(10)
});
column![
scrollable(options).height(Length::Fill),
row![
text_input(
"Add a new option",
&self.input,
Message::InputChanged,
)
.on_submit(Message::AddOption(self.input.clone())),
button(text(format!("Toggle Order ({})", self.order)))
.on_press(Message::ToggleOrder)
]
.spacing(10)
]
.spacing(20)
.padding(20)
.into()
}
}
#[derive(Debug, Hash)]
enum Order {
Ascending,
Descending,
}
impl std::fmt::Display for Order {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Ascending => "Ascending",
Self::Descending => "Descending",
}
)
}
}

10
examples/modal/Cargo.toml Normal file
View 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
View file

@ -0,0 +1,475 @@
use iced::widget::{
self, button, column, container, horizontal_space, row, text, text_input,
};
use iced::{
executor, keyboard, subscription, theme, Alignment, Application, Command,
Element, Event, Length, Settings, Subscription,
};
use self::modal::Modal;
pub fn main() -> iced::Result {
App::run(Settings::default())
}
#[derive(Default)]
struct App {
show_modal: bool,
email: String,
password: String,
}
#[derive(Debug, Clone)]
enum Message {
ShowModal,
HideModal,
Email(String),
Password(String),
Submit,
Event(Event),
}
impl Application for App {
type Executor = executor::Default;
type Message = Message;
type Theme = iced::Theme;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
(App::default(), Command::none())
}
fn title(&self) -> String {
String::from("Modal - Iced")
}
fn subscription(&self) -> Subscription<Self::Message> {
subscription::events().map(Message::Event)
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::ShowModal => {
self.show_modal = true;
widget::focus_next()
}
Message::HideModal => {
self.hide_modal();
Command::none()
}
Message::Email(email) => {
self.email = email;
Command::none()
}
Message::Password(password) => {
self.password = password;
Command::none()
}
Message::Submit => {
if !self.email.is_empty() && !self.password.is_empty() {
self.hide_modal();
}
Command::none()
}
Message::Event(event) => match event {
Event::Keyboard(keyboard::Event::KeyPressed {
key_code: keyboard::KeyCode::Tab,
modifiers,
}) => {
if modifiers.shift() {
widget::focus_previous()
} else {
widget::focus_next()
}
}
Event::Keyboard(keyboard::Event::KeyPressed {
key_code: keyboard::KeyCode::Escape,
..
}) => {
self.hide_modal();
Command::none()
}
_ => Command::none(),
},
}
}
fn view(&self) -> Element<Message> {
let content = container(
column![
row![
text("Top Left"),
horizontal_space(Length::Fill),
text("Top Right")
]
.align_items(Alignment::Start)
.height(Length::Fill),
container(
button(text("Show Modal")).on_press(Message::ShowModal)
)
.center_x()
.center_y()
.width(Length::Fill)
.height(Length::Fill),
row![
text("Bottom Left"),
horizontal_space(Length::Fill),
text("Bottom Right")
]
.align_items(Alignment::End)
.height(Length::Fill),
]
.height(Length::Fill),
)
.padding(10)
.width(Length::Fill)
.height(Length::Fill);
if self.show_modal {
let modal = container(
column![
text("Sign Up").size(24),
column![
column![
text("Email").size(12),
text_input(
"abc@123.com",
&self.email,
Message::Email
)
.on_submit(Message::Submit)
.padding(5),
]
.spacing(5),
column![
text("Password").size(12),
text_input("", &self.password, Message::Password)
.on_submit(Message::Submit)
.password()
.padding(5),
]
.spacing(5),
button(text("Submit")).on_press(Message::HideModal),
]
.spacing(10)
]
.spacing(20),
)
.width(Length::Units(300))
.padding(10)
.style(theme::Container::Box);
Modal::new(content, modal)
.on_blur(Message::HideModal)
.into()
} else {
content.into()
}
}
}
impl App {
fn hide_modal(&mut self) {
self.show_modal = false;
self.email.clear();
self.password.clear();
}
}
mod modal {
use iced_native::alignment::Alignment;
use iced_native::widget::{self, Tree};
use iced_native::{
event, layout, mouse, overlay, renderer, Clipboard, Color, Element,
Event, Layout, Length, Point, Rectangle, Shell, Size, Widget,
};
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
base: Element<'a, Message, Renderer>,
modal: Element<'a, Message, Renderer>,
on_blur: Option<Message>,
}
impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
/// Returns a new [`Modal`]
pub fn new(
base: impl Into<Element<'a, Message, Renderer>>,
modal: impl Into<Element<'a, Message, Renderer>>,
) -> Self {
Self {
base: base.into(),
modal: modal.into(),
on_blur: None,
}
}
/// Sets the message that will be produces when the background
/// of the [`Modal`] is pressed
pub fn on_blur(self, on_blur: Message) -> Self {
Self {
on_blur: Some(on_blur),
..self
}
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Modal<'a, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn children(&self) -> Vec<Tree> {
vec![Tree::new(&self.base), Tree::new(&self.modal)]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&[&self.base, &self.modal]);
}
fn width(&self) -> Length {
self.base.as_widget().width()
}
fn height(&self) -> Length {
self.base.as_widget().height()
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.base.as_widget().layout(renderer, limits)
}
fn on_event(
&mut self,
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.base.as_widget_mut().on_event(
&mut state.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
&state.children[0],
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
Some(overlay::Element::new(
layout.position(),
Box::new(Overlay {
content: &mut self.modal,
tree: &mut state.children[1],
size: layout.bounds().size(),
on_blur: self.on_blur.clone(),
}),
))
}
fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
cursor_position,
viewport,
renderer,
)
}
fn operate(
&self,
state: &mut Tree,
layout: Layout<'_>,
operation: &mut dyn widget::Operation<Message>,
) {
self.base.as_widget().operate(
&mut state.children[0],
layout,
operation,
);
}
}
struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>,
tree: &'b mut Tree,
size: Size,
on_blur: Option<Message>,
}
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer>
where
Renderer: iced_native::Renderer,
Message: Clone,
{
fn layout(
&self,
renderer: &Renderer,
_bounds: Size,
position: Point,
) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, self.size)
.width(Length::Fill)
.height(Length::Fill);
let mut child = self.content.as_widget().layout(renderer, &limits);
child.align(Alignment::Center, Alignment::Center, limits.max());
let mut node = layout::Node::with_children(self.size, vec![child]);
node.move_to(position);
node
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let content_bounds = layout.children().next().unwrap().bounds();
if let Some(message) = self.on_blur.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left,
)) = &event
{
if !content_bounds.contains(cursor_position) {
shell.publish(message.clone());
return event::Status::Captured;
}
}
}
self.content.as_widget_mut().on_event(
self.tree,
event,
layout.children().next().unwrap(),
cursor_position,
renderer,
clipboard,
shell,
)
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: renderer::BorderRadius::from(0.0),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color {
a: 0.80,
..Color::BLACK
},
);
self.content.as_widget().draw(
self.tree,
renderer,
theme,
style,
layout.children().next().unwrap(),
cursor_position,
&layout.bounds(),
);
}
fn operate(
&mut self,
layout: Layout<'_>,
operation: &mut dyn widget::Operation<Message>,
) {
self.content.as_widget().operate(
self.tree,
layout.children().next().unwrap(),
operation,
);
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
cursor_position,
viewport,
renderer,
)
}
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + iced_native::Renderer,
Message: 'a + Clone,
{
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
Element::new(modal)
}
}
}

View file

@ -0,0 +1,11 @@
[package]
name = "modern_art"
version = "0.1.0"
authors = ["Bingus <shankern@protonmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
rand = "0.8.5"
env_logger = "0.9"

View file

@ -0,0 +1,142 @@
use iced::widget::canvas::{
self, gradient::Location, gradient::Position, Cache, Canvas, Cursor, Frame,
Geometry, Gradient,
};
use iced::{
executor, Application, Color, Command, Element, Length, Point, Rectangle,
Renderer, Settings, Size, Theme,
};
use rand::{thread_rng, Rng};
fn main() -> iced::Result {
env_logger::builder().format_timestamp(None).init();
ModernArt::run(Settings {
antialiasing: true,
..Settings::default()
})
}
#[derive(Debug, Clone, Copy)]
enum Message {}
struct ModernArt {
cache: Cache,
}
impl Application for ModernArt {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Flags = ();
fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
(
ModernArt {
cache: Default::default(),
},
Command::none(),
)
}
fn title(&self) -> String {
String::from("Modern Art")
}
fn update(&mut self, _message: Message) -> Command<Message> {
Command::none()
}
fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
Canvas::new(self)
.width(Length::Fill)
.height(Length::Fill)
.into()
}
}
impl<Message> canvas::Program<Message> for ModernArt {
type State = ();
fn draw(
&self,
_state: &Self::State,
_theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
) -> Vec<Geometry> {
let geometry = self.cache.draw(bounds.size(), |frame| {
let num_squares = thread_rng().gen_range(0..1200);
let mut i = 0;
while i <= num_squares {
generate_box(frame, bounds.size());
i += 1;
}
});
vec![geometry]
}
}
fn random_direction() -> Location {
match thread_rng().gen_range(0..8) {
0 => Location::TopLeft,
1 => Location::Top,
2 => Location::TopRight,
3 => Location::Right,
4 => Location::BottomRight,
5 => Location::Bottom,
6 => Location::BottomLeft,
7 => Location::Left,
_ => Location::TopLeft,
}
}
fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
let solid = rand::random::<bool>();
let random_color = || -> Color {
Color::from_rgb(
thread_rng().gen_range(0.0..1.0),
thread_rng().gen_range(0.0..1.0),
thread_rng().gen_range(0.0..1.0),
)
};
let gradient = |top_left: Point, size: Size| -> Gradient {
let mut builder = Gradient::linear(Position::Relative {
top_left,
size,
start: random_direction(),
end: random_direction(),
});
let stops = thread_rng().gen_range(1..15u32);
let mut i = 0;
while i <= stops {
builder = builder.add_stop(i as f32 / stops as f32, random_color());
i += 1;
}
builder.build().unwrap()
};
let top_left = Point::new(
thread_rng().gen_range(0.0..bounds.width),
thread_rng().gen_range(0.0..bounds.height),
);
let size = Size::new(
thread_rng().gen_range(50.0..200.0),
thread_rng().gen_range(50.0..200.0),
);
if solid {
frame.fill_rectangle(top_left, size, random_color());
} else {
frame.fill_rectangle(top_left, size, gradient(top_left, size));
};
solid
}

View file

@ -2,7 +2,8 @@
//! a circle around each fingertip. This only works on touch-enabled
//! computers like Microsoft Surface.
use iced::widget::canvas::event;
use iced::widget::canvas::{self, Canvas, Cursor, Geometry, Stroke};
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{self, Canvas, Cursor, Geometry};
use iced::{
executor, touch, window, Application, Color, Command, Element, Length,
Point, Rectangle, Settings, Subscription, Theme,
@ -186,7 +187,7 @@ impl canvas::Program<Message> for State {
frame.stroke(
&path,
Stroke {
color: Color::BLACK,
style: stroke::Style::Solid(Color::BLACK),
width: 3.0,
..Stroke::default()
},

View file

@ -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 {

View file

@ -14,9 +14,15 @@ struct ScrollableDemo {
variants: Vec<Variant>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum ThemeType {
Light,
Dark,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
ThemeChanged(ThemeType),
ScrollToTop(usize),
ScrollToBottom(usize),
Scrolled(usize, f32),
@ -45,7 +51,10 @@ impl Application for ScrollableDemo {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::ThemeChanged(theme) => {
self.theme = theme;
self.theme = match theme {
ThemeType::Light => Theme::Light,
ThemeType::Dark => Theme::Dark,
};
Command::none()
}
@ -78,17 +87,15 @@ impl Application for ScrollableDemo {
}
fn view(&self) -> Element<Message> {
let ScrollableDemo {
theme, variants, ..
} = self;
let ScrollableDemo { variants, .. } = self;
let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
let choose_theme = [ThemeType::Light, ThemeType::Dark].iter().fold(
column!["Choose a theme:"].spacing(10),
|column, option| {
column.push(radio(
format!("{:?}", option),
*option,
Some(*theme),
Some(*option),
Message::ThemeChanged,
))
},
@ -198,7 +205,7 @@ impl Application for ScrollableDemo {
}
fn theme(&self) -> Theme {
self.theme
self.theme.clone()
}
}

View file

@ -11,7 +11,9 @@ use iced::executor;
use iced::theme::{self, Theme};
use iced::time;
use iced::widget::canvas;
use iced::widget::canvas::{Cursor, Path, Stroke};
use iced::widget::canvas::gradient::{self, Gradient};
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{Cursor, Path};
use iced::window;
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
@ -37,9 +39,9 @@ enum Message {
}
impl Application for SolarSystem {
type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
@ -65,10 +67,6 @@ impl Application for SolarSystem {
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
time::every(std::time::Duration::from_millis(10)).map(Message::Tick)
}
fn view(&self) -> Element<Message> {
canvas(&self.state)
.width(Length::Fill)
@ -81,10 +79,18 @@ impl Application for SolarSystem {
}
fn style(&self) -> theme::Application {
theme::Application::Custom(|_theme| application::Appearance {
background_color: Color::BLACK,
text_color: Color::WHITE,
})
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> {
time::every(time::Duration::from_millis(10)).map(Message::Tick)
}
}
@ -178,8 +184,10 @@ impl<Message> canvas::Program<Message> for State {
frame.stroke(
&orbit,
Stroke {
style: stroke::Style::Solid(Color::from_rgba8(
0, 153, 255, 0.1,
)),
width: 1.0,
color: Color::from_rgba8(0, 153, 255, 0.1),
line_dash: canvas::LineDash {
offset: 0,
segments: &[3.0, 6.0],
@ -198,15 +206,18 @@ impl<Message> canvas::Program<Message> for State {
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
let shadow = Path::rectangle(
Point::new(0.0, -Self::EARTH_RADIUS),
Size::new(
Self::EARTH_RADIUS * 4.0,
Self::EARTH_RADIUS * 2.0,
),
);
frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6));
let earth_fill =
Gradient::linear(gradient::Position::Absolute {
start: Point::new(-Self::EARTH_RADIUS, 0.0),
end: Point::new(Self::EARTH_RADIUS, 0.0),
})
.add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
.add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47))
.build()
.expect("Build Earth fill gradient");
frame.fill(&earth, earth_fill);
frame.with_save(|frame| {
frame.rotate(rotation * 10.0);
@ -215,14 +226,6 @@ impl<Message> canvas::Program<Message> for State {
let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
frame.fill(&moon, Color::WHITE);
});
frame.fill(
&shadow,
Color {
a: 0.7,
..Color::BLACK
},
);
});
});

View file

@ -1,9 +1,10 @@
use iced::theme::{self, Theme};
use iced::widget::{
button, checkbox, column, container, horizontal_rule, progress_bar, radio,
row, scrollable, slider, text, text_input, toggler, vertical_rule,
vertical_space,
};
use iced::{Alignment, Element, Length, Sandbox, Settings, Theme};
use iced::{Alignment, Color, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Styling::run(Settings::default())
@ -18,9 +19,16 @@ struct Styling {
toggler_value: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum ThemeType {
Light,
Dark,
Custom,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
ThemeChanged(ThemeType),
InputChanged(String),
ButtonPressed,
SliderChanged(f32),
@ -41,7 +49,19 @@ impl Sandbox for Styling {
fn update(&mut self, message: Message) {
match message {
Message::ThemeChanged(theme) => self.theme = theme,
Message::ThemeChanged(theme) => {
self.theme = match theme {
ThemeType::Light => Theme::Light,
ThemeType::Dark => Theme::Dark,
ThemeType::Custom => Theme::custom(theme::Palette {
background: Color::from_rgb(1.0, 0.9, 1.0),
text: Color::BLACK,
primary: Color::from_rgb(0.5, 0.5, 0.0),
success: Color::from_rgb(0.0, 1.0, 0.0),
danger: Color::from_rgb(1.0, 0.0, 0.0),
}),
}
}
Message::InputChanged(value) => self.input_value = value,
Message::ButtonPressed => {}
Message::SliderChanged(value) => self.slider_value = value,
@ -51,17 +71,24 @@ impl Sandbox for Styling {
}
fn view(&self) -> Element<Message> {
let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
column![text("Choose a theme:")].spacing(10),
|column, theme| {
column.push(radio(
format!("{:?}", theme),
*theme,
Some(self.theme),
Message::ThemeChanged,
))
},
);
let choose_theme =
[ThemeType::Light, ThemeType::Dark, ThemeType::Custom]
.iter()
.fold(
column![text("Choose a theme:")].spacing(10),
|column, theme| {
column.push(radio(
format!("{:?}", theme),
*theme,
Some(match self.theme {
Theme::Light => ThemeType::Light,
Theme::Dark => ThemeType::Dark,
Theme::Custom { .. } => ThemeType::Custom,
}),
Message::ThemeChanged,
))
},
);
let text_input = text_input(
"Type something...",
@ -132,6 +159,6 @@ impl Sandbox for Styling {
}
fn theme(&self) -> Theme {
self.theme
self.theme.clone()
}
}

View file

@ -1,39 +1,76 @@
use iced::widget::{container, svg};
use iced::{Element, Length, Sandbox, Settings};
use iced::theme;
use iced::widget::{checkbox, column, container, svg};
use iced::{color, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Tiger::run(Settings::default())
}
struct Tiger;
#[derive(Debug, Default)]
struct Tiger {
apply_color_filter: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum Message {
ToggleColorFilter(bool),
}
impl Sandbox for Tiger {
type Message = ();
type Message = Message;
fn new() -> Self {
Tiger
Tiger::default()
}
fn title(&self) -> String {
String::from("SVG - Iced")
}
fn update(&mut self, _message: ()) {}
fn update(&mut self, message: Self::Message) {
match message {
Message::ToggleColorFilter(apply_color_filter) => {
self.apply_color_filter = apply_color_filter;
}
}
}
fn view(&self) -> Element<()> {
let svg = svg(svg::Handle::from_path(format!(
fn view(&self) -> Element<Self::Message> {
let handle = svg::Handle::from_path(format!(
"{}/resources/tiger.svg",
env!("CARGO_MANIFEST_DIR")
)))
.width(Length::Fill)
.height(Length::Fill);
));
container(svg)
let svg = svg(handle).width(Length::Fill).height(Length::Fill).style(
if self.apply_color_filter {
theme::Svg::custom_fn(|_theme| svg::Appearance {
color: Some(color!(0x0000ff)),
})
} else {
theme::Svg::Default
},
);
let apply_color_filter = checkbox(
"Apply a color filter",
self.apply_color_filter,
Message::ToggleColorFilter,
);
container(
column![
svg,
container(apply_color_filter).width(Length::Fill).center_x()
]
.spacing(20)
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
.center_x()
.center_y()
.into()
.height(Length::Fill),
)
.width(Length::Fill)
.height(Length::Fill)
.padding(20)
.center_x()
.center_y()
.into()
}
}

View file

@ -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()
}

View file

@ -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"

View file

@ -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.6/examples
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.6/examples/download_progress
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.6/examples/stopwatch
pub trait Recipe<Hasher: std::hash::Hasher, Event> {
/// The events that will be produced by a [`Subscription`] with this
/// [`Recipe`].

View file

@ -1,6 +1,6 @@
[package]
name = "iced_glow"
version = "0.3.0"
version = "0.5.0"
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.7"
path = "../native"
[dependencies.iced_graphics]
version = "0.3"
version = "0.5"
path = "../graphics"
features = ["font-fallback", "font-icons", "opengl"]

View file

@ -1,7 +1,8 @@
use crate::program;
#[cfg(any(feature = "image", feature = "svg"))]
use crate::image;
use crate::quad;
use crate::text;
use crate::triangle;
use crate::{program, triangle};
use crate::{Settings, Transformation, Viewport};
use iced_graphics::backend;
@ -16,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,
@ -33,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,
@ -71,6 +78,9 @@ impl Backend {
viewport_size.height,
);
}
#[cfg(any(feature = "image", feature = "svg"))]
self.image_pipeline.trim_cache(gl);
}
fn flush(
@ -105,11 +115,26 @@ impl Backend {
* Transformation::scale(scale_factor, scale_factor);
self.triangle_pipeline.draw(
&layer.meshes,
gl,
target_height,
scaled,
scale_factor,
&layer.meshes,
);
}
#[cfg(any(feature = "image", feature = "svg"))]
if !layer.images.is_empty() {
let scaled = transformation
* Transformation::scale(scale_factor, scale_factor);
self.image_pipeline.draw(
gl,
target_height,
scaled,
scale_factor,
&layer.images,
bounds,
);
}
@ -239,8 +264,8 @@ impl backend::Text for Backend {
#[cfg(feature = "image")]
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)
}
}
@ -248,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)
}
}

248
glow/src/image.rs Normal file
View file

@ -0,0 +1,248 @@
mod storage;
use storage::Storage;
pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
use crate::program::{self, Shader};
use crate::Transformation;
#[cfg(feature = "image")]
use iced_graphics::image::raster;
#[cfg(feature = "svg")]
use iced_graphics::image::vector;
use iced_graphics::layer;
use iced_graphics::Rectangle;
use iced_graphics::Size;
use glow::HasContext;
use std::cell::RefCell;
#[derive(Debug)]
pub(crate) struct Pipeline {
program: <glow::Context as HasContext>::Program,
vertex_array: <glow::Context as HasContext>::VertexArray,
vertex_buffer: <glow::Context as HasContext>::Buffer,
transform_location: <glow::Context as HasContext>::UniformLocation,
storage: Storage,
#[cfg(feature = "image")]
raster_cache: RefCell<raster::Cache<Storage>>,
#[cfg(feature = "svg")]
vector_cache: RefCell<vector::Cache<Storage>>,
}
impl Pipeline {
pub fn new(
gl: &glow::Context,
shader_version: &program::Version,
) -> Pipeline {
let program = unsafe {
let vertex_shader = Shader::vertex(
gl,
shader_version,
include_str!("shader/common/image.vert"),
);
let fragment_shader = Shader::fragment(
gl,
shader_version,
include_str!("shader/common/image.frag"),
);
program::create(
gl,
&[vertex_shader, fragment_shader],
&[(0, "i_Position")],
)
};
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Get transform location");
unsafe {
gl.use_program(Some(program));
let transform: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
&transform,
);
gl.use_program(None);
}
let vertex_buffer =
unsafe { gl.create_buffer().expect("Create vertex buffer") };
let vertex_array =
unsafe { gl.create_vertex_array().expect("Create vertex array") };
unsafe {
gl.bind_vertex_array(Some(vertex_array));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1];
gl.buffer_data_size(
glow::ARRAY_BUFFER,
vertices.len() as i32,
glow::STATIC_DRAW,
);
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
0,
bytemuck::cast_slice(vertices),
);
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(
0,
2,
glow::UNSIGNED_BYTE,
false,
0,
0,
);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
gl.bind_vertex_array(None);
}
Pipeline {
program,
vertex_array,
vertex_buffer,
transform_location,
storage: Storage::default(),
#[cfg(feature = "image")]
raster_cache: RefCell::new(raster::Cache::default()),
#[cfg(feature = "svg")]
vector_cache: RefCell::new(vector::Cache::default()),
}
}
#[cfg(feature = "image")]
pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
self.raster_cache.borrow_mut().load(handle).dimensions()
}
#[cfg(feature = "svg")]
pub fn viewport_dimensions(
&self,
handle: &iced_native::svg::Handle,
) -> Size<u32> {
let mut cache = self.vector_cache.borrow_mut();
let svg = cache.load(handle);
svg.viewport_dimensions()
}
pub fn draw(
&mut self,
mut gl: &glow::Context,
target_height: u32,
transformation: Transformation,
_scale_factor: f32,
images: &[layer::Image],
layer_bounds: Rectangle<u32>,
) {
unsafe {
gl.use_program(Some(self.program));
gl.bind_vertex_array(Some(self.vertex_array));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
gl.enable(glow::SCISSOR_TEST);
}
#[cfg(feature = "image")]
let mut raster_cache = self.raster_cache.borrow_mut();
#[cfg(feature = "svg")]
let mut vector_cache = self.vector_cache.borrow_mut();
for image in images {
let (entry, bounds) = match &image {
#[cfg(feature = "image")]
layer::Image::Raster { handle, bounds } => (
raster_cache.upload(handle, &mut gl, &mut self.storage),
bounds,
),
#[cfg(not(feature = "image"))]
layer::Image::Raster { handle: _, bounds } => (None, bounds),
#[cfg(feature = "svg")]
layer::Image::Vector {
handle,
color,
bounds,
} => {
let size = [bounds.width, bounds.height];
(
vector_cache.upload(
handle,
*color,
size,
_scale_factor,
&mut gl,
&mut self.storage,
),
bounds,
)
}
#[cfg(not(feature = "svg"))]
layer::Image::Vector { bounds, .. } => (None, bounds),
};
unsafe {
gl.scissor(
layer_bounds.x as i32,
(target_height - (layer_bounds.y + layer_bounds.height))
as i32,
layer_bounds.width as i32,
layer_bounds.height as i32,
);
if let Some(storage::Entry { texture, .. }) = entry {
gl.bind_texture(glow::TEXTURE_2D, Some(*texture))
} else {
continue;
}
let translate = Transformation::translate(bounds.x, bounds.y);
let scale = Transformation::scale(bounds.width, bounds.height);
let transformation = transformation * translate * scale;
let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
&matrix,
);
gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
gl.bind_texture(glow::TEXTURE_2D, None);
}
}
unsafe {
gl.bind_buffer(glow::ARRAY_BUFFER, None);
gl.bind_vertex_array(None);
gl.use_program(None);
gl.disable(glow::SCISSOR_TEST);
}
}
pub fn trim_cache(&mut self, mut gl: &glow::Context) {
#[cfg(feature = "image")]
self.raster_cache
.borrow_mut()
.trim(&mut self.storage, &mut gl);
#[cfg(feature = "svg")]
self.vector_cache
.borrow_mut()
.trim(&mut self.storage, &mut gl);
}
}

78
glow/src/image/storage.rs Normal file
View 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
}
}

View file

@ -3,7 +3,7 @@
//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
//!
//! [`glow`]: https://github.com/grovesNL/glow
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.6/native
#![doc(
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;

View file

@ -70,11 +70,10 @@ impl Pipeline {
unsafe {
gl.use_program(Some(program));
let matrix: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
&matrix,
Transformation::identity().as_ref(),
);
gl.uniform_1_f32(Some(&scale_location), 1.0);
@ -139,11 +138,10 @@ impl Pipeline {
if transformation != self.current_transform {
unsafe {
let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
&matrix,
transformation.as_ref(),
);
self.current_transform = transformation;
@ -256,7 +254,7 @@ unsafe fn create_buffers(
gl.enable_vertex_attrib_array(4);
gl.vertex_attrib_pointer_f32(
4,
1,
4,
glow::FLOAT,
false,
stride,
@ -270,7 +268,7 @@ unsafe fn create_buffers(
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4 + 1),
4 * (2 + 2 + 4 + 4 + 4),
);
gl.enable_vertex_attrib_array(6);
@ -280,7 +278,7 @@ unsafe fn create_buffers(
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4 + 1 + 1),
4 * (2 + 2 + 4 + 4 + 4 + 1),
);
gl.bind_vertex_array(None);
@ -309,7 +307,7 @@ pub struct Vertex {
pub border_color: [f32; 4],
/// The border radius of the [`Vertex`].
pub border_radius: f32,
pub border_radius: [f32; 4],
/// The border width of the [`Vertex`].
pub border_width: f32,

View file

@ -65,11 +65,10 @@ impl Pipeline {
unsafe {
gl.use_program(Some(program));
let matrix: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
&matrix,
Transformation::identity().as_ref(),
);
gl.uniform_1_f32(Some(&scale_location), 1.0);
@ -119,11 +118,10 @@ impl Pipeline {
if transformation != self.current_transform {
unsafe {
let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
&matrix,
transformation.as_ref(),
);
self.current_transform = transformation;
@ -220,7 +218,7 @@ unsafe fn create_instance_buffer(
gl.enable_vertex_attrib_array(4);
gl.vertex_attrib_pointer_f32(
4,
1,
4,
glow::FLOAT,
false,
stride,
@ -235,7 +233,7 @@ unsafe fn create_instance_buffer(
glow::FLOAT,
false,
stride,
4 * (2 + 2 + 4 + 4 + 1),
4 * (2 + 2 + 4 + 4 + 4),
);
gl.vertex_attrib_divisor(5, 1);

View file

@ -0,0 +1,59 @@
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
#ifdef HIGHER_THAN_300
layout (location = 0) out vec4 fragColor;
#define gl_FragColor fragColor
#endif
in vec2 raw_position;
uniform vec4 gradient_direction;
uniform int color_stops_size;
// GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops
//stored as color(vec4) -> offset(vec4) sequentially;
uniform vec4 color_stops[32];
//TODO: rewrite without branching to make ALUs happy
void main() {
vec2 start = gradient_direction.xy;
vec2 end = gradient_direction.zw;
vec2 gradient_vec = vec2(end - start);
vec2 current_vec = vec2(raw_position.xy - start);
vec2 unit = normalize(gradient_vec);
float coord_offset = dot(unit, current_vec) / length(gradient_vec);
//if a gradient has a start/end stop that is identical, the mesh will have a transparent fill
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
float min_offset = color_stops[1].x;
float max_offset = color_stops[color_stops_size - 1].x;
for (int i = 0; i < color_stops_size - 2; i += 2) {
float curr_offset = color_stops[i+1].x;
float next_offset = color_stops[i+3].x;
if (coord_offset <= min_offset) {
//current coordinate is before the first defined offset, set it to the start color
gl_FragColor = color_stops[0];
}
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
//current fragment is between the current offset processing & the next one, interpolate colors
gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep(
curr_offset,
next_offset,
coord_offset
));
}
if (coord_offset >= max_offset) {
//current coordinate is before the last defined offset, set it to the last color
gl_FragColor = color_stops[color_stops_size - 2];
}
}
}

View file

@ -0,0 +1,9 @@
uniform mat4 u_Transform;
in vec2 i_Position;
out vec2 raw_position;
void main() {
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
raw_position = i_Position;
}

View 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);
}

View 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;
}

View file

@ -12,7 +12,7 @@ varying vec4 v_Color;
varying vec4 v_BorderColor;
varying vec2 v_Pos;
varying vec2 v_Scale;
varying float v_BorderRadius;
varying vec4 v_BorderRadius;
varying float v_BorderWidth;
float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius)
@ -33,10 +33,26 @@ float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius)
return sqrt(distance.x * distance.x + distance.y * distance.y);
}
float selectBorderRadius(vec4 radi, vec2 position, vec2 center)
{
float rx = radi.x;
float ry = radi.y;
rx = position.x > center.x ? radi.y : radi.x;
ry = position.x > center.x ? radi.z : radi.w;
rx = position.y > center.y ? ry : rx;
return rx;
}
void main() {
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0);
float border_radius = selectBorderRadius(
v_BorderRadius,
fragCoord,
(v_Pos + v_Scale * 0.5).xy
);
float internal_border = max(border_radius - v_BorderWidth, 0.0);
float internal_distance = _distance(
fragCoord,
@ -57,11 +73,11 @@ void main() {
fragCoord,
v_Pos,
v_Scale,
v_BorderRadius
border_radius
);
float radius_alpha =
1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d);
1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d);
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
}

View file

@ -5,7 +5,7 @@ attribute vec2 i_Pos;
attribute vec2 i_Scale;
attribute vec4 i_Color;
attribute vec4 i_BorderColor;
attribute float i_BorderRadius;
attribute vec4 i_BorderRadius;
attribute float i_BorderWidth;
attribute vec2 q_Pos;
@ -13,7 +13,7 @@ varying vec4 v_Color;
varying vec4 v_BorderColor;
varying vec2 v_Pos;
varying vec2 v_Scale;
varying float v_BorderRadius;
varying vec4 v_BorderRadius;
varying float v_BorderWidth;
@ -21,9 +21,11 @@ void main() {
vec2 p_Pos = i_Pos * u_Scale;
vec2 p_Scale = i_Scale * u_Scale;
float i_BorderRadius = min(
i_BorderRadius,
min(i_Scale.x, i_Scale.y) / 2.0
vec4 i_BorderRadius = vec4(
min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0)
);
mat4 i_Transform = mat4(

View file

@ -17,7 +17,7 @@ in vec4 v_Color;
in vec4 v_BorderColor;
in vec2 v_Pos;
in vec2 v_Scale;
in float v_BorderRadius;
in vec4 v_BorderRadius;
in float v_BorderWidth;
float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius)
@ -38,14 +38,30 @@ float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius)
return sqrt(distance.x * distance.x + distance.y * distance.y);
}
float selectBorderRadius(vec4 radi, vec2 position, vec2 center)
{
float rx = radi.x;
float ry = radi.y;
rx = position.x > center.x ? radi.y : radi.x;
ry = position.x > center.x ? radi.z : radi.w;
rx = position.y > center.y ? ry : rx;
return rx;
}
void main() {
vec4 mixed_color;
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
float border_radius = selectBorderRadius(
v_BorderRadius,
fragCoord,
(v_Pos + v_Scale * 0.5).xy
);
// TODO: Remove branching (?)
if(v_BorderWidth > 0.0) {
float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0);
float internal_border = max(border_radius - v_BorderWidth, 0.0);
float internal_distance = fDistance(
fragCoord,
@ -69,11 +85,11 @@ void main() {
fragCoord,
v_Pos,
v_Scale,
v_BorderRadius
border_radius
);
float radius_alpha =
1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d);
1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d);
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
}

View file

@ -5,14 +5,14 @@ in vec2 i_Pos;
in vec2 i_Scale;
in vec4 i_Color;
in vec4 i_BorderColor;
in float i_BorderRadius;
in vec4 i_BorderRadius;
in float i_BorderWidth;
out vec4 v_Color;
out vec4 v_BorderColor;
out vec2 v_Pos;
out vec2 v_Scale;
out float v_BorderRadius;
out vec4 v_BorderRadius;
out float v_BorderWidth;
vec2 positions[4] = vec2[](
@ -27,9 +27,11 @@ void main() {
vec2 p_Pos = i_Pos * u_Scale;
vec2 p_Scale = i_Scale * u_Scale;
float i_BorderRadius = min(
i_BorderRadius,
min(i_Scale.x, i_Scale.y) / 2.0
vec4 i_BorderRadius = vec4(
min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0),
min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0)
);
mat4 i_Transform = mat4(

View file

@ -1,204 +1,145 @@
//! Draw meshes of triangles.
use crate::program::{self, Shader};
use crate::program;
use crate::Transformation;
use iced_graphics::gradient::Gradient;
use iced_graphics::layer::mesh::{self, Mesh};
use iced_graphics::triangle::{ColoredVertex2D, Vertex2D};
use glow::HasContext;
use iced_graphics::layer;
use std::marker::PhantomData;
pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
const VERTEX_BUFFER_SIZE: usize = 10_000;
const INDEX_BUFFER_SIZE: usize = 10_000;
const DEFAULT_VERTICES: usize = 1_000;
const DEFAULT_INDICES: usize = 1_000;
#[derive(Debug)]
pub(crate) struct Pipeline {
program: <glow::Context as HasContext>::Program,
vertex_array: <glow::Context as HasContext>::VertexArray,
vertices: Buffer<Vertex2D>,
indices: Buffer<u32>,
transform_location: <glow::Context as HasContext>::UniformLocation,
current_transform: Transformation,
solid: solid::Program,
gradient: gradient::Program,
}
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/triangle.vert"),
);
let fragment_shader = Shader::fragment(
gl,
shader_version,
include_str!("shader/common/triangle.frag"),
);
program::create(
gl,
&[vertex_shader, fragment_shader],
&[(0, "i_Position"), (1, "i_Color")],
)
};
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Get transform location");
unsafe {
gl.use_program(Some(program));
let transform: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
&transform,
);
gl.use_program(None);
}
let vertex_array =
unsafe { gl.create_vertex_array().expect("Create vertex array") };
unsafe {
gl.bind_vertex_array(Some(vertex_array));
}
let vertices = unsafe {
Buffer::new(
gl,
glow::ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
VERTEX_BUFFER_SIZE,
)
};
let indices = unsafe {
pub fn new(gl: &glow::Context, shader_version: &program::Version) -> Self {
let mut indices = unsafe {
Buffer::new(
gl,
glow::ELEMENT_ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
INDEX_BUFFER_SIZE,
DEFAULT_INDICES,
)
};
let solid = solid::Program::new(gl, shader_version);
let gradient = gradient::Program::new(gl, shader_version);
unsafe {
let stride = std::mem::size_of::<Vertex2D>() as i32;
gl.bind_vertex_array(Some(solid.vertex_array));
indices.bind(gl, 0);
gl.enable_vertex_attrib_array(0);
gl.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(Some(gradient.vertex_array));
indices.bind(gl, 0);
gl.bind_vertex_array(None);
}
Pipeline {
program,
vertex_array,
vertices,
Self {
indices,
transform_location,
current_transform: Transformation::identity(),
solid,
gradient,
}
}
pub fn draw(
&mut self,
meshes: &[Mesh<'_>],
gl: &glow::Context,
target_height: u32,
transformation: Transformation,
scale_factor: f32,
meshes: &[layer::Mesh<'_>],
) {
unsafe {
gl.enable(glow::MULTISAMPLE);
gl.enable(glow::SCISSOR_TEST);
gl.use_program(Some(self.program));
gl.bind_vertex_array(Some(self.vertex_array));
}
// This looks a bit crazy, but we are just counting how many vertices
// and indices we will need to handle.
// TODO: Improve readability
let (total_vertices, total_indices) = meshes
.iter()
.map(|layer::Mesh { buffers, .. }| {
(buffers.vertices.len(), buffers.indices.len())
})
.fold((0, 0), |(total_v, total_i), (v, i)| {
(total_v + v, total_i + i)
});
// Count the total amount of vertices & indices we need to handle
let count = mesh::attribute_count_of(meshes);
// Then we ensure the current buffers are big enough, resizing if
// necessary
// 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 last_vertex = 0;
let mut last_index = 0;
let mut solid_vertex_offset = 0;
let mut gradient_vertex_offset = 0;
let mut index_offset = 0;
for mesh in meshes {
let indices = mesh.indices();
for layer::Mesh { buffers, .. } in meshes {
unsafe {
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
(last_vertex * std::mem::size_of::<Vertex2D>()) as i32,
bytemuck::cast_slice(&buffers.vertices),
);
gl.buffer_sub_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
(last_index * std::mem::size_of::<u32>()) as i32,
bytemuck::cast_slice(&buffers.indices),
(index_offset * std::mem::size_of::<u32>()) as i32,
bytemuck::cast_slice(indices),
);
last_vertex += buffers.vertices.len();
last_index += 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 layer::Mesh {
buffers,
origin,
clip_bounds,
} in meshes
{
for mesh in meshes {
let indices = mesh.indices();
let origin = mesh.origin();
let transform =
transformation * Transformation::translate(origin.x, origin.y);
let clip_bounds = (*clip_bounds * scale_factor).snap();
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
unsafe {
if self.current_transform != transform {
let matrix: [f32; 16] = transform.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
&matrix,
);
self.current_transform = transform;
}
gl.scissor(
clip_bounds.x as i32,
(target_height - (clip_bounds.y + clip_bounds.height))
@ -206,56 +147,138 @@ impl Pipeline {
clip_bounds.width as i32,
clip_bounds.height as i32,
);
gl.draw_elements_base_vertex(
glow::TRIANGLES,
buffers.indices.len() as i32,
glow::UNSIGNED_INT,
(last_index * std::mem::size_of::<u32>()) as i32,
last_vertex as i32,
);
last_vertex += buffers.vertices.len();
last_index += buffers.indices.len();
}
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,
indices.len() as i32,
glow::UNSIGNED_INT,
(last_index * std::mem::size_of::<u32>()) as i32,
last_solid_vertex as i32,
);
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 {
gl.bind_vertex_array(None);
gl.use_program(None);
gl.disable(glow::SCISSOR_TEST);
gl.disable(glow::MULTISAMPLE);
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct Uniforms {
transform: [f32; 16],
}
unsafe impl bytemuck::Zeroable for Uniforms {}
unsafe impl bytemuck::Pod for Uniforms {}
impl Default for Uniforms {
fn default() -> Self {
Self {
transform: *Transformation::identity().as_ref(),
}
}
}
impl From<Transformation> for Uniforms {
fn from(transformation: Transformation) -> Uniforms {
Self {
transform: transformation.into(),
}
}
}
#[derive(Debug)]
struct Buffer<T> {
pub struct Buffer<T> {
raw: <glow::Context as HasContext>::Buffer,
target: u32,
usage: u32,
@ -299,3 +322,268 @@ impl<T> Buffer<T> {
}
}
}
mod solid {
use crate::program;
use crate::triangle;
use glow::{Context, HasContext, NativeProgram};
use iced_graphics::triangle::ColoredVertex2D;
use iced_graphics::Transformation;
#[derive(Debug)]
pub struct Program {
pub program: <Context as HasContext>::Program,
pub vertex_array: <glow::Context as HasContext>::VertexArray,
pub vertices: triangle::Buffer<ColoredVertex2D>,
pub uniforms: Uniforms,
}
impl Program {
pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
let program = unsafe {
let vertex_shader = program::Shader::vertex(
gl,
shader_version,
include_str!("shader/common/solid.vert"),
);
let fragment_shader = program::Shader::fragment(
gl,
shader_version,
include_str!("shader/common/solid.frag"),
);
program::create(
gl,
&[vertex_shader, fragment_shader],
&[(0, "i_Position"), (1, "i_Color")],
)
};
let vertex_array = unsafe {
gl.create_vertex_array().expect("Create vertex array")
};
let vertices = unsafe {
triangle::Buffer::new(
gl,
glow::ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
super::DEFAULT_VERTICES,
)
};
unsafe {
gl.bind_vertex_array(Some(vertex_array));
let stride = std::mem::size_of::<ColoredVertex2D>() as i32;
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(
0,
2,
glow::FLOAT,
false,
stride,
0,
);
gl.enable_vertex_attrib_array(1);
gl.vertex_attrib_pointer_f32(
1,
4,
glow::FLOAT,
false,
stride,
4 * 2,
);
gl.bind_vertex_array(None);
};
Self {
program,
vertex_array,
vertices,
uniforms: Uniforms::new(gl, program),
}
}
}
#[derive(Debug)]
pub struct Uniforms {
pub transform: Transformation,
pub transform_location: <Context as HasContext>::UniformLocation,
}
impl Uniforms {
fn new(gl: &Context, program: NativeProgram) -> Self {
let transform = Transformation::identity();
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Solid - Get u_Transform.");
unsafe {
gl.use_program(Some(program));
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
transform.as_ref(),
);
gl.use_program(None);
}
Self {
transform,
transform_location,
}
}
}
}
mod gradient {
use crate::program;
use crate::triangle;
use glow::{Context, HasContext, NativeProgram};
use iced_graphics::gradient::{self, Gradient};
use iced_graphics::triangle::Vertex2D;
use iced_graphics::Transformation;
#[derive(Debug)]
pub struct Program {
pub program: <Context as HasContext>::Program,
pub vertex_array: <glow::Context as HasContext>::VertexArray,
pub vertices: triangle::Buffer<Vertex2D>,
pub uniforms: Uniforms,
}
impl Program {
pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
let program = unsafe {
let vertex_shader = program::Shader::vertex(
gl,
shader_version,
include_str!("shader/common/gradient.vert"),
);
let fragment_shader = program::Shader::fragment(
gl,
shader_version,
include_str!("shader/common/gradient.frag"),
);
program::create(
gl,
&[vertex_shader, fragment_shader],
&[(0, "i_Position")],
)
};
let vertex_array = unsafe {
gl.create_vertex_array().expect("Create vertex array")
};
let vertices = unsafe {
triangle::Buffer::new(
gl,
glow::ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
super::DEFAULT_VERTICES,
)
};
unsafe {
gl.bind_vertex_array(Some(vertex_array));
let stride = std::mem::size_of::<Vertex2D>() as i32;
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(
0,
2,
glow::FLOAT,
false,
stride,
0,
);
gl.bind_vertex_array(None);
};
Self {
program,
vertex_array,
vertices,
uniforms: Uniforms::new(gl, program),
}
}
}
#[derive(Debug)]
pub struct Uniforms {
pub gradient: Gradient,
pub transform: Transformation,
pub locations: Locations,
}
#[derive(Debug)]
pub struct Locations {
pub gradient_direction: <Context as HasContext>::UniformLocation,
pub color_stops_size: <Context as HasContext>::UniformLocation,
//currently the maximum number of stops is 16 due to lack of SSBO in GL2.1
pub color_stops: <Context as HasContext>::UniformLocation,
pub transform: <Context as HasContext>::UniformLocation,
}
impl Uniforms {
fn new(gl: &Context, program: NativeProgram) -> Self {
let gradient_direction = unsafe {
gl.get_uniform_location(program, "gradient_direction")
}
.expect("Gradient - Get gradient_direction.");
let color_stops_size =
unsafe { gl.get_uniform_location(program, "color_stops_size") }
.expect("Gradient - Get color_stops_size.");
let color_stops = unsafe {
gl.get_uniform_location(program, "color_stops")
.expect("Gradient - Get color_stops.")
};
let transform = Transformation::identity();
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Solid - Get u_Transform.");
unsafe {
gl.use_program(Some(program));
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
transform.as_ref(),
);
gl.use_program(None);
}
Self {
gradient: Gradient::Linear(gradient::Linear {
start: Default::default(),
end: Default::default(),
color_stops: vec![],
}),
transform: Transformation::identity(),
locations: Locations {
gradient_direction,
color_stops_size,
color_stops,
transform: transform_location,
},
}
}
}
}

View file

@ -26,8 +26,11 @@ impl<Theme> iced_graphics::window::GLCompositor for Compositor<Theme> {
log::info!("{:#?}", settings);
let version = gl.version();
log::info!("Version: {:?}", version);
log::info!("Embedded: {}", version.is_embedded);
log::info!(
"OpenGL version: {:?} (Embedded: {})",
version,
version.is_embedded
);
let renderer = gl.get_parameter_string(glow::RENDERER);
log::info!("Renderer: {}", renderer);

View file

@ -1,6 +1,6 @@
[package]
name = "iced_glutin"
version = "0.3.0"
version = "0.5.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A glutin runtime for Iced"
@ -23,14 +23,15 @@ git = "https://github.com/iced-rs/glutin"
rev = "da8d291486b4c9bec12487a46c119c4b1d386abf"
[dependencies.iced_native]
version = "0.5"
version = "0.7"
path = "../native"
[dependencies.iced_winit]
version = "0.4"
version = "0.6"
path = "../winit"
features = ["application"]
[dependencies.iced_graphics]
version = "0.3"
version = "0.5"
path = "../graphics"
features = ["opengl"]

View file

@ -1,6 +1,6 @@
[package]
name = "iced_graphics"
version = "0.3.1"
version = "0.5.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.10"
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.7"
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

View file

@ -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>;
}

117
graphics/src/gradient.rs Normal file
View file

@ -0,0 +1,117 @@
//! For creating a Gradient.
pub mod linear;
pub use linear::Linear;
use crate::{Color, Point, Size};
#[derive(Debug, Clone, PartialEq)]
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
/// or conically (TBD).
pub enum Gradient {
/// A linear gradient interpolates colors along a direction from its `start` to its `end`
/// point.
Linear(Linear),
}
impl Gradient {
/// Creates a new linear [`linear::Builder`].
pub fn linear(position: impl Into<Position>) -> linear::Builder {
linear::Builder::new(position.into())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
/// A point along the gradient vector where the specified [`color`] is unmixed.
///
/// [`color`]: Self::color
pub struct ColorStop {
/// Offset along the gradient vector.
pub offset: f32,
/// The color of the gradient at the specified [`offset`].
///
/// [`offset`]: Self::offset
pub color: Color,
}
#[derive(Debug)]
/// The position of the gradient within its bounds.
pub enum Position {
/// The gradient will be positioned with respect to two points.
Absolute {
/// The starting point of the gradient.
start: Point,
/// The ending point of the gradient.
end: Point,
},
/// The gradient will be positioned relative to the provided bounds.
Relative {
/// The top left position of the bounds.
top_left: Point,
/// The width & height of the bounds.
size: Size,
/// The start [Location] of the gradient.
start: Location,
/// The end [Location] of the gradient.
end: Location,
},
}
impl From<(Point, Point)> for Position {
fn from((start, end): (Point, Point)) -> Self {
Self::Absolute { start, end }
}
}
#[derive(Debug)]
/// The location of a relatively-positioned gradient.
pub enum Location {
/// Top left.
TopLeft,
/// Top.
Top,
/// Top right.
TopRight,
/// Right.
Right,
/// Bottom right.
BottomRight,
/// Bottom.
Bottom,
/// Bottom left.
BottomLeft,
/// Left.
Left,
}
impl Location {
fn to_absolute(&self, top_left: Point, size: Size) -> Point {
match self {
Location::TopLeft => top_left,
Location::Top => {
Point::new(top_left.x + size.width / 2.0, top_left.y)
}
Location::TopRight => {
Point::new(top_left.x + size.width, top_left.y)
}
Location::Right => Point::new(
top_left.x + size.width,
top_left.y + size.height / 2.0,
),
Location::BottomRight => {
Point::new(top_left.x + size.width, top_left.y + size.height)
}
Location::Bottom => Point::new(
top_left.x + size.width / 2.0,
top_left.y + size.height,
),
Location::BottomLeft => {
Point::new(top_left.x, top_left.y + size.height)
}
Location::Left => {
Point::new(top_left.x, top_left.y + size.height / 2.0)
}
}
}
}

View file

@ -0,0 +1,112 @@
//! Linear gradient builder & definition.
use crate::gradient::{ColorStop, Gradient, Position};
use crate::{Color, Point};
/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`].
///
/// [`Fill`]: crate::widget::canvas::Fill
/// [`Stroke`]: crate::widget::canvas::Stroke
#[derive(Debug, Clone, PartialEq)]
pub struct Linear {
/// The point where the linear gradient begins.
pub start: Point,
/// The point where the linear gradient ends.
pub end: Point,
/// [`ColorStop`]s along the linear gradient path.
pub color_stops: Vec<ColorStop>,
}
/// A [`Linear`] builder.
#[derive(Debug)]
pub struct Builder {
start: Point,
end: Point,
stops: Vec<ColorStop>,
error: Option<BuilderError>,
}
impl Builder {
/// Creates a new [`Builder`].
pub fn new(position: Position) -> Self {
let (start, end) = match position {
Position::Absolute { start, end } => (start, end),
Position::Relative {
top_left,
size,
start,
end,
} => (
start.to_absolute(top_left, size),
end.to_absolute(top_left, size),
),
};
Self {
start,
end,
stops: vec![],
error: None,
}
}
/// Adds a new stop, defined by an offset and a color, to the gradient.
///
/// `offset` must be between `0.0` and `1.0` or the gradient cannot be built.
///
/// Note: when using the [`glow`] backend, any color stop added after the 16th
/// will not be displayed.
///
/// On the [`wgpu`] backend this limitation does not exist (technical limit is 524,288 stops).
///
/// [`glow`]: https://docs.rs/iced_glow
/// [`wgpu`]: https://docs.rs/iced_wgpu
pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
if offset.is_finite() && (0.0..=1.0).contains(&offset) {
match self.stops.binary_search_by(|stop| {
stop.offset.partial_cmp(&offset).unwrap()
}) {
Ok(_) => {
self.error = Some(BuilderError::DuplicateOffset(offset))
}
Err(index) => {
self.stops.insert(index, ColorStop { offset, color });
}
}
} else {
self.error = Some(BuilderError::InvalidOffset(offset))
};
self
}
/// Builds the linear [`Gradient`] of this [`Builder`].
///
/// Returns `BuilderError` if gradient in invalid.
pub fn build(self) -> Result<Gradient, BuilderError> {
if self.stops.is_empty() {
Err(BuilderError::MissingColorStop)
} else if let Some(error) = self.error {
Err(error)
} else {
Ok(Gradient::Linear(Linear {
start: self.start,
end: self.end,
color_stops: self.stops,
}))
}
}
}
/// An error that happened when building a [`Linear`] gradient.
#[derive(Debug, thiserror::Error)]
pub enum BuilderError {
#[error("Gradients must contain at least one color stop.")]
/// Gradients must contain at least one color stop.
MissingColorStop,
#[error("Offset {0} must be a unique, finite number.")]
/// Offsets in a gradient must all be unique & finite.
DuplicateOffset(f32),
#[error("Offset {0} must be between 0.0..=1.0.")]
/// Offsets in a gradient must be between 0.0..=1.0.
InvalidOffset(f32),
}

10
graphics/src/image.rs Normal file
View 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;

View file

@ -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;

View 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>;
}

View file

@ -1,46 +1,48 @@
use crate::image::atlas::{self, Atlas};
//! Vector image loading and caching
use crate::image::Storage;
use crate::Color;
use iced_native::svg;
use iced_native::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, ColorFilter), T::Entry>,
svg_hits: HashSet<u64>,
rasterized_hits: HashSet<(u64, u32, u32)>,
rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>,
}
impl Cache {
pub fn new() -> Self {
Self {
svgs: HashMap::new(),
rasterized: HashMap::new(),
svg_hits: HashSet::new(),
rasterized_hits: HashSet::new(),
}
}
type ColorFilter = Option<[u8; 4]>;
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 +75,16 @@ impl Cache {
self.svgs.get(&handle.id()).unwrap()
}
/// Load svg and upload raster data
pub fn upload(
&mut self,
handle: &svg::Handle,
color: Option<Color>,
[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) = (
@ -89,15 +92,18 @@ impl Cache {
(scale * height).ceil() as u32,
);
let color = color.map(Color::into_rgba8);
let key = (id, width, height, color);
// TODO: Optimize!
// We currently rerasterize the SVG when its size changes. This is slow
// as heck. A GPU rasterizer like `pathfinder` may perform better.
// It would be cool to be able to smooth resize the `svg` example.
if self.rasterized.contains_key(&(id, width, height)) {
if self.rasterized.contains_key(&key) {
let _ = self.svg_hits.insert(id);
let _ = self.rasterized_hits.insert((id, width, height));
let _ = self.rasterized_hits.insert(key);
return self.rasterized.get(&(id, width, height));
return self.rasterized.get(&key);
}
match self.load(handle) {
@ -123,28 +129,32 @@ impl Cache {
)?;
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,
)?;
if let Some(color) = color {
rgba.chunks_exact_mut(4).for_each(|rgba| {
if rgba[3] > 0 {
rgba[0] = color[0];
rgba[1] = color[1];
rgba[2] = color[2];
}
});
}
let allocation = storage.upload(width, height, &rgba, state)?;
log::debug!("allocating {} {}x{}", id, width, height);
let _ = self.svg_hits.insert(id);
let _ = self.rasterized_hits.insert((id, width, height));
let _ = self.rasterized.insert((id, width, height), allocation);
let _ = self.rasterized_hits.insert(key);
let _ = self.rasterized.insert(key, allocation);
self.rasterized.get(&(id, width, height))
self.rasterized.get(&key)
}
Svg::NotFound => None,
}
}
pub fn trim(&mut self, atlas: &mut Atlas) {
/// Load svg and upload raster data
pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) {
let svg_hits = &self.svg_hits;
let rasterized_hits = &self.rasterized_hits;
@ -153,7 +163,7 @@ impl Cache {
let retain = rasterized_hits.contains(k);
if !retain {
atlas.remove(entry);
storage.remove(entry, state);
}
retain
@ -163,6 +173,17 @@ impl Cache {
}
}
impl<T: Storage> Default for Cache<T> {
fn default() -> Self {
Self {
svgs: HashMap::new(),
rasterized: HashMap::new(),
svg_hits: HashSet::new(),
rasterized_hits: HashSet::new(),
}
}
}
impl std::fmt::Debug for Svg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {

View file

@ -1,15 +1,22 @@
//! Organize rendering primitives into a flattened list of layers.
mod image;
mod quad;
mod text;
pub mod mesh;
pub use image::Image;
pub use mesh::Mesh;
pub use quad::Quad;
pub use text::Text;
use crate::alignment;
use crate::triangle;
use crate::{
Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport,
};
use iced_native::image;
use iced_native::svg;
/// A group of primitives that should be clipped together.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Layer<'a> {
/// The clipping bounds of the [`Layer`].
pub bounds: Rectangle,
@ -159,7 +166,7 @@ impl<'a> Layer<'a> {
border_color: border_color.into_linear(),
});
}
Primitive::Mesh2D { buffers, size } => {
Primitive::SolidMesh { buffers, size } => {
let layer = &mut layers[current_layer];
let bounds = Rectangle::new(
@ -169,13 +176,35 @@ 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::Solid {
origin: Point::new(translation.x, translation.y),
buffers,
clip_bounds,
});
}
}
Primitive::GradientMesh {
buffers,
size,
gradient,
} => {
let layer = &mut layers[current_layer];
let bounds = Rectangle::new(
Point::new(translation.x, translation.y),
*size,
);
// Only draw visible content
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
layer.meshes.push(Mesh::Gradient {
origin: Point::new(translation.x, translation.y),
buffers,
clip_bounds,
gradient,
});
}
}
Primitive::Clip { bounds, content } => {
let layer = &mut layers[current_layer];
let translated_bounds = *bounds + translation;
@ -222,104 +251,19 @@ impl<'a> Layer<'a> {
bounds: *bounds + translation,
});
}
Primitive::Svg { handle, bounds } => {
Primitive::Svg {
handle,
color,
bounds,
} => {
let layer = &mut layers[current_layer];
layer.images.push(Image::Vector {
handle: handle.clone(),
color: *color,
bounds: *bounds + translation,
});
}
}
}
}
/// A colored rectangle with a border.
///
/// This type can be directly uploaded to GPU memory.
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct Quad {
/// The position of the [`Quad`].
pub position: [f32; 2],
/// The size of the [`Quad`].
pub size: [f32; 2],
/// The color of the [`Quad`], in __linear RGB__.
pub color: [f32; 4],
/// The border color of the [`Quad`], in __linear RGB__.
pub border_color: [f32; 4],
/// The border radius of the [`Quad`].
pub border_radius: f32,
/// The border width of the [`Quad`].
pub border_width: f32,
}
/// A mesh of triangles.
#[derive(Debug, Clone, Copy)]
pub struct Mesh<'a> {
/// The origin of the vertices of the [`Mesh`].
pub origin: Point,
/// The vertex and index buffers of the [`Mesh`].
pub buffers: &'a triangle::Mesh2D,
/// The clipping bounds of the [`Mesh`].
pub clip_bounds: Rectangle<f32>,
}
/// A paragraph of text.
#[derive(Debug, Clone, Copy)]
pub struct Text<'a> {
/// The content of the [`Text`].
pub content: &'a str,
/// The layout bounds of the [`Text`].
pub bounds: Rectangle,
/// The color of the [`Text`], in __linear RGB_.
pub color: [f32; 4],
/// The size of the [`Text`].
pub size: f32,
/// The font of the [`Text`].
pub font: Font,
/// The horizontal alignment of the [`Text`].
pub horizontal_alignment: alignment::Horizontal,
/// The vertical alignment of the [`Text`].
pub vertical_alignment: alignment::Vertical,
}
/// A raster or vector image.
#[derive(Debug, Clone)]
pub enum Image {
/// A raster image.
Raster {
/// The handle of a raster image.
handle: image::Handle,
/// The bounds of the image.
bounds: Rectangle,
},
/// A vector image.
Vector {
/// The handle of a vector image.
handle: svg::Handle,
/// The bounds of the image.
bounds: Rectangle,
},
}
#[allow(unsafe_code)]
unsafe impl bytemuck::Zeroable for Quad {}
#[allow(unsafe_code)]
unsafe impl bytemuck::Pod for Quad {}

View file

@ -0,0 +1,27 @@
use crate::{Color, Rectangle};
use iced_native::{image, svg};
/// A raster or vector image.
#[derive(Debug, Clone)]
pub enum Image {
/// A raster image.
Raster {
/// The handle of a raster image.
handle: image::Handle,
/// The bounds of the image.
bounds: Rectangle,
},
/// A vector image.
Vector {
/// The handle of a vector image.
handle: svg::Handle,
/// The [`Color`] filter
color: Option<Color>,
/// The bounds of the image.
bounds: Rectangle,
},
}

View file

@ -0,0 +1,93 @@
//! A collection of triangle primitives.
use crate::triangle;
use crate::{Gradient, Point, Rectangle};
/// A mesh of triangles.
#[derive(Debug, Clone, Copy)]
pub enum Mesh<'a> {
/// A mesh of triangles with a solid color.
Solid {
/// The origin of the vertices of the [`Mesh`].
origin: Point,
/// The vertex and index buffers of the [`Mesh`].
buffers: &'a triangle::Mesh2D<triangle::ColoredVertex2D>,
/// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>,
},
/// A mesh of triangles with a gradient color.
Gradient {
/// The origin of the vertices of the [`Mesh`].
origin: Point,
/// The vertex and index buffers of the [`Mesh`].
buffers: &'a triangle::Mesh2D<triangle::Vertex2D>,
/// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>,
/// The gradient to apply to the [`Mesh`].
gradient: &'a Gradient,
},
}
impl Mesh<'_> {
/// Returns the origin of the [`Mesh`].
pub fn origin(&self) -> Point {
match self {
Self::Solid { origin, .. } | Self::Gradient { origin, .. } => {
*origin
}
}
}
/// Returns the indices of the [`Mesh`].
pub fn indices(&self) -> &[u32] {
match self {
Self::Solid { buffers, .. } => &buffers.indices,
Self::Gradient { buffers, .. } => &buffers.indices,
}
}
/// Returns the clip bounds of the [`Mesh`].
pub fn clip_bounds(&self) -> Rectangle<f32> {
match self {
Self::Solid { clip_bounds, .. }
| Self::Gradient { clip_bounds, .. } => *clip_bounds,
}
}
}
/// The result of counting the attributes of a set of meshes.
#[derive(Debug, Clone, Copy, Default)]
pub struct AttributeCount {
/// The total amount of solid vertices.
pub solid_vertices: usize,
/// The total amount of gradient vertices.
pub gradient_vertices: usize,
/// The total amount of indices.
pub indices: usize,
}
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
meshes
.iter()
.fold(AttributeCount::default(), |mut count, mesh| {
match mesh {
Mesh::Solid { buffers, .. } => {
count.solid_vertices += buffers.vertices.len();
count.indices += buffers.indices.len();
}
Mesh::Gradient { buffers, .. } => {
count.gradient_vertices += buffers.vertices.len();
count.indices += buffers.indices.len();
}
}
count
})
}

View file

@ -0,0 +1,30 @@
/// A colored rectangle with a border.
///
/// This type can be directly uploaded to GPU memory.
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct Quad {
/// The position of the [`Quad`].
pub position: [f32; 2],
/// The size of the [`Quad`].
pub size: [f32; 2],
/// The color of the [`Quad`], in __linear RGB__.
pub color: [f32; 4],
/// The border color of the [`Quad`], in __linear RGB__.
pub border_color: [f32; 4],
/// The border radius of the [`Quad`].
pub border_radius: [f32; 4],
/// The border width of the [`Quad`].
pub border_width: f32,
}
#[allow(unsafe_code)]
unsafe impl bytemuck::Zeroable for Quad {}
#[allow(unsafe_code)]
unsafe impl bytemuck::Pod for Quad {}

View file

@ -0,0 +1,26 @@
use crate::{alignment, Font, Rectangle};
/// A paragraph of text.
#[derive(Debug, Clone, Copy)]
pub struct Text<'a> {
/// The content of the [`Text`].
pub content: &'a str,
/// The layout bounds of the [`Text`].
pub bounds: Rectangle,
/// The color of the [`Text`], in __linear RGB_.
pub color: [f32; 4],
/// The size of the [`Text`].
pub size: f32,
/// The font of the [`Text`].
pub font: Font,
/// The horizontal alignment of the [`Text`].
pub horizontal_alignment: alignment::Horizontal,
/// The vertical alignment of the [`Text`].
pub vertical_alignment: alignment::Vertical,
}

View file

@ -29,6 +29,8 @@ mod viewport;
pub mod backend;
pub mod font;
pub mod gradient;
pub mod image;
pub mod layer;
pub mod overlay;
pub mod renderer;
@ -39,6 +41,7 @@ pub mod window;
pub use antialiasing::Antialiasing;
pub use backend::Backend;
pub use error::Error;
pub use gradient::Gradient;
pub use layer::Layer;
pub use primitive::Primitive;
pub use renderer::Renderer;

View file

@ -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;
@ -41,7 +42,7 @@ pub enum Primitive {
/// The background of the quad
background: Background,
/// The border radius of the quad
border_radius: f32,
border_radius: [f32; 4],
/// The border width of the quad
border_width: f32,
/// The border color of the quad
@ -59,6 +60,9 @@ pub enum Primitive {
/// The path of the SVG file
handle: svg::Handle,
/// The [`Color`] filter
color: Option<Color>,
/// The bounds of the viewport
bounds: Rectangle,
},
@ -77,18 +81,33 @@ 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 [`Gradient`] to apply to the mesh.
gradient: Gradient,
},
/// A cached primitive.
///
/// This can be useful if you are implementing a widget where primitive

View file

@ -6,7 +6,7 @@ use iced_native::layout;
use iced_native::renderer;
use iced_native::svg;
use iced_native::text::{self, Text};
use iced_native::{Background, Element, Font, Point, Rectangle, Size};
use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size};
pub use iced_native::renderer::Style;
@ -109,7 +109,7 @@ where
self.primitives.push(Primitive::Quad {
bounds: quad.bounds,
background: background.into(),
border_radius: quad.border_radius,
border_radius: quad.border_radius.into(),
border_width: quad.border_width,
border_color: quad.border_color,
});
@ -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,11 +196,20 @@ 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)
}
fn draw(&mut self, handle: svg::Handle, bounds: Rectangle) {
self.draw_primitive(Primitive::Svg { handle, bounds })
fn draw(
&mut self,
handle: svg::Handle,
color: Option<Color>,
bounds: Rectangle,
) {
self.draw_primitive(Primitive::Svg {
handle,
color,
bounds,
})
}
}

View file

@ -8,7 +8,7 @@ pub struct Transformation(Mat4);
impl Transformation {
/// Get the identity transformation.
pub fn identity() -> Transformation {
Transformation(Mat4::identity())
Transformation(Mat4::IDENTITY)
}
/// Creates an orthographic projection.
@ -51,3 +51,9 @@ impl From<Transformation> for [f32; 16] {
*t.as_ref()
}
}
impl From<Transformation> for Mat4 {
fn from(transformation: Transformation) -> Self {
transformation.0
}
}

View file

@ -3,23 +3,31 @@ 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.
/// Therefore, this list should always have a length that is a multiple of 3.
pub indices: Vec<u32>,
}
/// A two-dimensional vertex with some color in __linear__ RGBA.
/// A two-dimensional vertex.
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
#[repr(C)]
pub struct Vertex2D {
/// The vertex position
/// The vertex position in 2D space.
pub position: [f32; 2],
/// The vertex color in __linear__ RGBA.
}
/// A two-dimensional vertex with a color.
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
#[repr(C)]
pub struct ColoredVertex2D {
/// The vertex position in 2D space.
pub position: [f32; 2],
/// The color of the vertex in __linear__ RGBA.
pub color: [f32; 4],
}

View file

@ -3,19 +3,20 @@
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
//! and more!
pub mod event;
pub mod fill;
pub mod path;
pub mod stroke;
mod cache;
mod cursor;
mod fill;
mod frame;
mod geometry;
mod program;
mod stroke;
mod style;
mod text;
pub use crate::gradient::{self, Gradient};
pub use cache::Cache;
pub use cursor::Cursor;
pub use event::Event;
@ -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};

View file

@ -1,12 +1,15 @@
use iced_native::Color;
//! Fill [crate::widget::canvas::Geometry] with a certain style.
use crate::{Color, Gradient};
pub use crate::widget::canvas::Style;
/// The style used to fill geometry.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct Fill {
/// The color used to fill geometry.
/// The color or gradient of the fill.
///
/// By default, it is set to `BLACK`.
pub color: Color,
/// 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
/// outside of a shape.
@ -20,9 +23,9 @@ pub struct Fill {
}
impl Default for Fill {
fn default() -> Fill {
Fill {
color: Color::BLACK,
fn default() -> Self {
Self {
style: Style::Solid(Color::BLACK),
rule: FillRule::NonZero,
}
}
@ -31,12 +34,21 @@ impl Default for Fill {
impl From<Color> for Fill {
fn from(color: Color) -> Fill {
Fill {
color,
style: Style::Solid(color),
..Fill::default()
}
}
}
impl From<Gradient> for Fill {
fn from(gradient: Gradient) -> Self {
Fill {
style: Style::Gradient(gradient),
..Default::default()
}
}
}
/// The fill rule defines how to determine what is inside and what is outside of
/// a shape.
///

View file

@ -1,13 +1,13 @@
use std::borrow::Cow;
use crate::gradient::Gradient;
use crate::triangle;
use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text};
use crate::Primitive;
use iced_native::{Point, Rectangle, Size, Vector};
use crate::triangle;
use crate::widget::canvas::path;
use crate::widget::canvas::{Fill, Geometry, Path, Stroke, Text};
use crate::Primitive;
use lyon::geom::euclid;
use lyon::tessellation;
use std::borrow::Cow;
/// The frame of a [`Canvas`].
///
@ -15,13 +15,91 @@ use lyon::tessellation;
#[allow(missing_debug_implementations)]
pub struct Frame {
size: Size,
buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>,
buffers: BufferStack,
primitives: Vec<Primitive>,
transforms: Transforms,
fill_tessellator: tessellation::FillTessellator,
stroke_tessellator: tessellation::StrokeTessellator,
}
enum Buffer {
Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>),
Gradient(
tessellation::VertexBuffers<triangle::Vertex2D, u32>,
Gradient,
),
}
struct BufferStack {
stack: Vec<Buffer>,
}
impl BufferStack {
fn new() -> Self {
Self { stack: Vec::new() }
}
fn get_mut(&mut self, style: &Style) -> &mut Buffer {
match style {
Style::Solid(_) => match self.stack.last() {
Some(Buffer::Solid(_)) => {}
_ => {
self.stack.push(Buffer::Solid(
tessellation::VertexBuffers::new(),
));
}
},
Style::Gradient(gradient) => match self.stack.last() {
Some(Buffer::Gradient(_, last)) if gradient == last => {}
_ => {
self.stack.push(Buffer::Gradient(
tessellation::VertexBuffers::new(),
gradient.clone(),
));
}
},
}
self.stack.last_mut().unwrap()
}
fn get_fill<'a>(
&'a mut self,
style: &Style,
) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
match (style, self.get_mut(style)) {
(Style::Solid(color), Buffer::Solid(buffer)) => {
Box::new(tessellation::BuffersBuilder::new(
buffer,
TriangleVertex2DBuilder(color.into_linear()),
))
}
(Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
),
_ => unreachable!(),
}
}
fn get_stroke<'a>(
&'a mut self,
style: &Style,
) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
match (style, self.get_mut(style)) {
(Style::Solid(color), Buffer::Solid(buffer)) => {
Box::new(tessellation::BuffersBuilder::new(
buffer,
TriangleVertex2DBuilder(color.into_linear()),
))
}
(Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
),
_ => unreachable!(),
}
}
}
#[derive(Debug)]
struct Transforms {
previous: Vec<Transform>,
@ -34,6 +112,35 @@ struct Transform {
is_identity: bool,
}
impl Transform {
/// Transforms the given [Point] by the transformation matrix.
fn transform_point(&self, point: &mut Point) {
let transformed = self
.raw
.transform_point(euclid::Point2D::new(point.x, point.y));
point.x = transformed.x;
point.y = transformed.y;
}
fn transform_style(&self, style: Style) -> Style {
match style {
Style::Solid(color) => Style::Solid(color),
Style::Gradient(gradient) => {
Style::Gradient(self.transform_gradient(gradient))
}
}
}
fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
let (start, end) = match &mut gradient {
Gradient::Linear(linear) => (&mut linear.start, &mut linear.end),
};
self.transform_point(start);
self.transform_point(end);
gradient
}
}
impl Frame {
/// Creates a new empty [`Frame`] with the given dimensions.
///
@ -42,7 +149,7 @@ impl Frame {
pub fn new(size: Size) -> Frame {
Frame {
size,
buffers: lyon::tessellation::VertexBuffers::new(),
buffers: BufferStack::new(),
primitives: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
@ -83,21 +190,20 @@ impl Frame {
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
/// provided style.
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
let Fill { color, rule } = fill.into();
let Fill { style, rule } = fill.into();
let mut buffers = tessellation::BuffersBuilder::new(
&mut self.buffers,
FillVertex(color.into_linear()),
);
let mut buffer = self
.buffers
.get_fill(&self.transforms.current.transform_style(style));
let options =
tessellation::FillOptions::default().with_fill_rule(rule.into());
let result = if self.transforms.current.is_identity {
if self.transforms.current.is_identity {
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
&mut buffers,
buffer.as_mut(),
)
} else {
let path = path.transformed(&self.transforms.current.raw);
@ -105,11 +211,10 @@ impl Frame {
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
&mut buffers,
buffer.as_mut(),
)
};
result.expect("Tessellate path");
}
.expect("Tessellate path.");
}
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
@ -120,12 +225,11 @@ impl Frame {
size: Size,
fill: impl Into<Fill>,
) {
let Fill { color, rule } = fill.into();
let Fill { style, rule } = fill.into();
let mut buffers = tessellation::BuffersBuilder::new(
&mut self.buffers,
FillVertex(color.into_linear()),
);
let mut buffer = self
.buffers
.get_fill(&self.transforms.current.transform_style(style));
let top_left =
self.transforms.current.raw.transform_point(
@ -144,7 +248,7 @@ impl Frame {
.tessellate_rectangle(
&lyon::math::Box2D::new(top_left, top_left + size),
&options,
&mut buffers,
buffer.as_mut(),
)
.expect("Fill rectangle");
}
@ -154,10 +258,9 @@ impl Frame {
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
let stroke = stroke.into();
let mut buffers = tessellation::BuffersBuilder::new(
&mut self.buffers,
StrokeVertex(stroke.color.into_linear()),
);
let mut buffer = self
.buffers
.get_stroke(&self.transforms.current.transform_style(stroke.style));
let mut options = tessellation::StrokeOptions::default();
options.line_width = stroke.width;
@ -171,11 +274,11 @@ impl Frame {
Cow::Owned(path::dashed(path, stroke.line_dash))
};
let result = if self.transforms.current.is_identity {
if self.transforms.current.is_identity {
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
&mut buffers,
buffer.as_mut(),
)
} else {
let path = path.transformed(&self.transforms.current.raw);
@ -183,11 +286,10 @@ impl Frame {
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
&mut buffers,
buffer.as_mut(),
)
};
result.expect("Stroke path");
}
.expect("Stroke path");
}
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
@ -206,8 +308,6 @@ impl Frame {
///
/// [`Canvas`]: crate::widget::Canvas
pub fn fill_text(&mut self, text: impl Into<Text>) {
use std::f32;
let text = text.into();
let position = if self.transforms.current.is_identity {
@ -304,7 +404,7 @@ impl Frame {
self.transforms.current.is_identity = false;
}
/// Applies a rotation to the current transform of the [`Frame`].
/// Applies a rotation in radians to the current transform of the [`Frame`].
#[inline]
pub fn rotate(&mut self, angle: f32) {
self.transforms.current.raw = self
@ -331,50 +431,98 @@ impl Frame {
}
fn into_primitives(mut self) -> Vec<Primitive> {
if !self.buffers.indices.is_empty() {
self.primitives.push(Primitive::Mesh2D {
buffers: triangle::Mesh2D {
vertices: self.buffers.vertices,
indices: self.buffers.indices,
},
size: self.size,
});
for buffer in self.buffers.stack {
match buffer {
Buffer::Solid(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::SolidMesh {
buffers: triangle::Mesh2D {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
})
}
}
Buffer::Gradient(buffer, gradient) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::GradientMesh {
buffers: triangle::Mesh2D {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
gradient,
})
}
}
}
}
self.primitives
}
}
struct FillVertex([f32; 4]);
struct Vertex2DBuilder;
impl lyon::tessellation::FillVertexConstructor<triangle::Vertex2D>
for FillVertex
impl tessellation::FillVertexConstructor<triangle::Vertex2D>
for Vertex2DBuilder
{
fn new_vertex(
&mut self,
vertex: lyon::tessellation::FillVertex<'_>,
vertex: tessellation::FillVertex<'_>,
) -> triangle::Vertex2D {
let position = vertex.position();
triangle::Vertex2D {
position: [position.x, position.y],
}
}
}
impl tessellation::StrokeVertexConstructor<triangle::Vertex2D>
for Vertex2DBuilder
{
fn new_vertex(
&mut self,
vertex: tessellation::StrokeVertex<'_, '_>,
) -> triangle::Vertex2D {
let position = vertex.position();
triangle::Vertex2D {
position: [position.x, position.y],
}
}
}
struct TriangleVertex2DBuilder([f32; 4]);
impl tessellation::FillVertexConstructor<triangle::ColoredVertex2D>
for TriangleVertex2DBuilder
{
fn new_vertex(
&mut self,
vertex: tessellation::FillVertex<'_>,
) -> triangle::ColoredVertex2D {
let position = vertex.position();
triangle::ColoredVertex2D {
position: [position.x, position.y],
color: self.0,
}
}
}
struct StrokeVertex([f32; 4]);
impl lyon::tessellation::StrokeVertexConstructor<triangle::Vertex2D>
for StrokeVertex
impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D>
for TriangleVertex2DBuilder
{
fn new_vertex(
&mut self,
vertex: lyon::tessellation::StrokeVertex<'_, '_>,
) -> triangle::Vertex2D {
vertex: tessellation::StrokeVertex<'_, '_>,
) -> triangle::ColoredVertex2D {
let position = vertex.position();
triangle::Vertex2D {
triangle::ColoredVertex2D {
position: [position.x, position.y],
color: self.0,
}

View file

@ -1,10 +1,15 @@
//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles.
pub use crate::widget::canvas::Style;
use iced_native::Color;
/// The style of a stroke.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct Stroke<'a> {
/// The color of the stroke.
pub color: Color,
/// The color or gradient of the stroke.
///
/// 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,
/// The shape to be used at the end of open subpaths when they are stroked.
@ -19,7 +24,10 @@ pub struct Stroke<'a> {
impl<'a> Stroke<'a> {
/// Sets the color of the [`Stroke`].
pub fn with_color(self, color: Color) -> Self {
Stroke { color, ..self }
Stroke {
style: Style::Solid(color),
..self
}
}
/// Sets the width of the [`Stroke`].
@ -41,7 +49,7 @@ impl<'a> Stroke<'a> {
impl<'a> Default for Stroke<'a> {
fn default() -> Self {
Stroke {
color: Color::BLACK,
style: Style::Solid(Color::BLACK),
width: 1.0,
line_cap: LineCap::default(),
line_join: LineJoin::default(),

View 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)
}
}

View file

@ -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`].

View file

@ -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

View file

@ -1,6 +1,6 @@
[package]
name = "iced_lazy"
version = "0.1.1"
version = "0.3.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.7"
path = "../native"

View file

@ -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,

353
lazy/src/lazy.rs Normal file
View file

@ -0,0 +1,353 @@
use iced_native::event;
use iced_native::layout::{self, Layout};
use iced_native::mouse;
use iced_native::overlay;
use iced_native::renderer;
use iced_native::widget::tree::{self, Tree};
use iced_native::widget::{self, Widget};
use iced_native::Element;
use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size};
use ouroboros::self_referencing;
use std::cell::{Ref, RefCell, RefMut};
use std::hash::{Hash, Hasher as H};
use std::marker::PhantomData;
use std::rc::Rc;
#[allow(missing_debug_implementations)]
pub struct Lazy<'a, Message, Renderer, Dependency, View> {
dependency: Dependency,
view: Box<dyn Fn() -> View + 'a>,
element: RefCell<Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>,
}
impl<'a, Message, Renderer, Dependency, View>
Lazy<'a, Message, Renderer, Dependency, View>
where
Dependency: Hash + 'a,
View: Into<Element<'static, Message, Renderer>>,
{
pub fn new(dependency: Dependency, view: impl Fn() -> View + 'a) -> Self {
Self {
dependency,
view: Box::new(view),
element: RefCell::new(None),
}
}
fn with_element<T>(
&self,
f: impl FnOnce(Ref<Element<Message, Renderer>>) -> T,
) -> T {
f(self.element.borrow().as_ref().unwrap().borrow())
}
fn with_element_mut<T>(
&self,
f: impl FnOnce(RefMut<Element<Message, Renderer>>) -> T,
) -> T {
f(self.element.borrow().as_ref().unwrap().borrow_mut())
}
}
struct Internal<Message, Renderer> {
element: Rc<RefCell<Element<'static, Message, Renderer>>>,
hash: u64,
}
impl<'a, Message, Renderer, Dependency, View> Widget<Message, Renderer>
for Lazy<'a, Message, Renderer, Dependency, View>
where
View: Into<Element<'static, Message, Renderer>> + 'static,
Dependency: Hash + 'a,
Message: 'static,
Renderer: iced_native::Renderer + 'static,
{
fn tag(&self) -> tree::Tag {
struct Tag<T>(T);
tree::Tag::of::<Tag<View>>()
}
fn state(&self) -> tree::State {
let mut hasher = Hasher::default();
self.dependency.hash(&mut hasher);
let hash = hasher.finish();
let element = Rc::new(RefCell::new((self.view)().into()));
(*self.element.borrow_mut()) = Some(element.clone());
tree::State::new(Internal { element, hash })
}
fn children(&self) -> Vec<Tree> {
vec![Tree::new(
self.element.borrow().as_ref().unwrap().borrow().as_widget(),
)]
}
fn diff(&self, tree: &mut Tree) {
let current = tree.state.downcast_mut::<Internal<Message, Renderer>>();
let mut hasher = Hasher::default();
self.dependency.hash(&mut hasher);
let new_hash = hasher.finish();
if current.hash != new_hash {
current.hash = new_hash;
let element = (self.view)().into();
current.element = Rc::new(RefCell::new(element));
(*self.element.borrow_mut()) = Some(current.element.clone());
tree.diff_children(std::slice::from_ref(
&self.element.borrow().as_ref().unwrap().borrow().as_widget(),
));
} else {
(*self.element.borrow_mut()) = Some(current.element.clone());
}
}
fn width(&self) -> Length {
self.with_element(|element| element.as_widget().width())
}
fn height(&self) -> Length {
self.with_element(|element| element.as_widget().height())
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.with_element(|element| {
element.as_widget().layout(renderer, limits)
})
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
operation: &mut dyn widget::Operation<Message>,
) {
self.with_element(|element| {
element.as_widget().operate(
&mut tree.children[0],
layout,
operation,
);
});
}
fn on_event(
&mut self,
tree: &mut Tree,
event: iced_native::Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.with_element_mut(|mut element| {
element.as_widget_mut().on_event(
&mut tree.children[0],
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
})
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_element(|element| {
element.as_widget().mouse_interaction(
&tree.children[0],
layout,
cursor_position,
viewport,
renderer,
)
})
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
) {
self.with_element(|element| {
element.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
layout,
cursor_position,
viewport,
)
})
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
let overlay = OverlayBuilder {
cached: self,
tree: &mut tree.children[0],
types: PhantomData,
overlay_builder: |cached, tree| {
Rc::get_mut(cached.element.get_mut().as_mut().unwrap())
.unwrap()
.get_mut()
.as_widget_mut()
.overlay(tree, layout, renderer)
},
}
.build();
let has_overlay = overlay.with_overlay(|overlay| {
overlay.as_ref().map(overlay::Element::position)
});
has_overlay
.map(|position| overlay::Element::new(position, Box::new(overlay)))
}
}
#[self_referencing]
struct Overlay<'a, 'b, Message, Renderer, Dependency, View> {
cached: &'a mut Lazy<'b, Message, Renderer, Dependency, View>,
tree: &'a mut Tree,
types: PhantomData<(Message, Dependency, View)>,
#[borrows(mut cached, mut tree)]
#[covariant]
overlay: Option<overlay::Element<'this, Message, Renderer>>,
}
impl<'a, 'b, Message, Renderer, Dependency, View>
Overlay<'a, 'b, Message, Renderer, Dependency, View>
{
fn with_overlay_maybe<T>(
&self,
f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T,
) -> Option<T> {
self.borrow_overlay().as_ref().map(f)
}
fn with_overlay_mut_maybe<T>(
&mut self,
f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T,
) -> Option<T> {
self.with_overlay_mut(|overlay| overlay.as_mut().map(f))
}
}
impl<'a, 'b, Message, Renderer, Dependency, View>
overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer, Dependency, View>
where
Renderer: iced_native::Renderer,
{
fn layout(
&self,
renderer: &Renderer,
bounds: Size,
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
let vector = position - overlay.position();
overlay.layout(renderer, bounds).translate(vector)
})
.unwrap_or_default()
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
) {
let _ = self.with_overlay_maybe(|overlay| {
overlay.draw(renderer, theme, style, layout, cursor_position);
});
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_overlay_maybe(|overlay| {
overlay.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
})
.unwrap_or_default()
}
fn on_event(
&mut self,
event: iced_native::Event,
layout: Layout<'_>,
cursor_position: Point,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.with_overlay_mut_maybe(|overlay| {
overlay.on_event(
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
})
.unwrap_or(iced_native::event::Status::Ignored)
}
}
impl<'a, Message, Renderer, Dependency, View>
From<Lazy<'a, Message, Renderer, Dependency, View>>
for Element<'a, Message, Renderer>
where
View: Into<Element<'static, Message, Renderer>> + 'static,
Renderer: iced_native::Renderer + 'static,
Message: 'static,
Dependency: Hash + 'a,
{
fn from(lazy: Lazy<'a, Message, Renderer, Dependency, View>) -> Self {
Self::new(lazy)
}
}

View file

@ -17,15 +17,30 @@
clippy::type_complexity
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod lazy;
pub mod component;
pub mod responsive;
pub use component::Component;
pub use lazy::Lazy;
pub use responsive::Responsive;
mod cache;
use iced_native::{Element, Size};
use std::hash::Hash;
pub fn lazy<'a, Message, Renderer, Dependency, View>(
dependency: Dependency,
view: impl Fn() -> View + 'a,
) -> Lazy<'a, Message, Renderer, Dependency, View>
where
Dependency: Hash + 'a,
View: Into<Element<'static, Message, Renderer>>,
{
Lazy::new(dependency, view)
}
/// Turns an implementor of [`Component`] into an [`Element`] that can be
/// embedded in any application.

View file

@ -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();

View file

@ -1,6 +1,6 @@
[package]
name = "iced_native"
version = "0.5.1"
version = "0.7.0"
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.1"
path = "../style"

View file

@ -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,
@ -503,7 +519,7 @@ where
bounds: layout.bounds(),
border_color: color,
border_width: 1.0,
border_radius: 0.0,
border_radius: 0.0.into(),
},
Color::TRANSPARENT,
);
@ -544,7 +560,7 @@ where
}
fn overlay<'b>(
&'b self,
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,

View file

@ -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`.

View file

@ -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.6/core
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.6/winit
//! [`druid`]: https://github.com/xi-editor/druid
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
//! [renderer]: crate::renderer

View file

@ -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()
}

View file

@ -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,

View file

@ -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(
@ -299,7 +287,7 @@ where
},
border_color: appearance.border_color,
border_width: appearance.border_width,
border_radius: appearance.border_radius,
border_radius: appearance.border_radius.into(),
},
appearance.background,
);
@ -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 =
@ -491,7 +479,7 @@ where
bounds,
border_color: Color::TRANSPARENT,
border_width: 0.0,
border_radius: appearance.border_radius,
border_radius: appearance.border_radius.into(),
},
appearance.selected_background,
);

View file

@ -50,7 +50,7 @@ pub struct Quad {
pub bounds: Rectangle,
/// The border radius of the [`Quad`].
pub border_radius: f32,
pub border_radius: BorderRadius,
/// The border width of the [`Quad`].
pub border_width: f32,
@ -59,6 +59,29 @@ pub struct Quad {
pub border_color: Color,
}
/// The border radi for the corners of a graphics primitive in the order:
/// top-left, top-right, bottom-right, bottom-left.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct BorderRadius([f32; 4]);
impl From<f32> for BorderRadius {
fn from(w: f32) -> Self {
Self([w; 4])
}
}
impl From<[f32; 4]> for BorderRadius {
fn from(radi: [f32; 4]) -> Self {
Self(radi)
}
}
impl From<BorderRadius> for [f32; 4] {
fn from(radi: BorderRadius) -> Self {
radi.0
}
}
/// The styling attributes of a [`Renderer`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {

View file

@ -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.6/examples/websocket
pub fn unfold<I, T, Fut, Message>(
id: I,
initial: T,

View file

@ -1,6 +1,7 @@
//! Load and draw vector graphics.
use crate::{Hasher, Rectangle};
use crate::{Color, Hasher, Rectangle, Size};
use std::borrow::Cow;
use std::hash::{Hash, Hasher as _};
use std::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,8 +82,8 @@ 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);
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle);
}

View file

@ -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.6/examples/integration_opengl
/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.6/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(
&mut self.state,
Layout::new(&self.base),
renderer,
) {
overlay.operate(Layout::new(layout), operation);
if let Some(mut overlay) = self.root.as_widget_mut().overlay(
&mut self.state,
Layout::new(&self.base),
renderer,
) {
if self.overlay.is_none() {
self.overlay = Some(overlay.layout(renderer, self.bounds));
}
overlay.operate(
Layout::new(self.overlay.as_ref().unwrap()),
operation,
);
}
}

View file

@ -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.6/examples
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.6/examples/bezier_tool
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.6/examples/custom_widget
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.6/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.6/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,

View file

@ -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(),
}))
}
}
}
}

View file

@ -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
@ -393,7 +393,7 @@ where
y: bounds.y + styling.shadow_offset.y,
..bounds
},
border_radius: styling.border_radius,
border_radius: styling.border_radius.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@ -404,7 +404,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: styling.border_radius,
border_radius: styling.border_radius.into(),
border_width: styling.border_width,
border_color: styling.border_color,
},
@ -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])
}

View file

@ -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)
};
{
@ -236,7 +236,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: custom_style.border_radius,
border_radius: custom_style.border_radius.into(),
border_width: custom_style.border_width,
border_color: custom_style.border_color,
},

View file

@ -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)
}
}

View file

@ -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,
@ -321,7 +321,7 @@ pub fn draw_background<Renderer>(
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: appearance.border_radius,
border_radius: appearance.border_radius.into(),
border_width: appearance.border_width,
border_color: appearance.border_color,
},

View file

@ -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)
}
@ -278,6 +285,12 @@ where
///
/// [`Svg`]: widget::Svg
/// [`Handle`]: widget::svg::Handle
pub fn svg(handle: impl Into<widget::svg::Handle>) -> widget::Svg {
pub fn svg<Renderer>(
handle: impl Into<widget::svg::Handle>,
) -> widget::Svg<Renderer>
where
Renderer: crate::svg::Renderer,
Renderer::Theme: widget::svg::StyleSheet,
{
widget::Svg::new(handle)
}

View file

@ -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();

Some files were not shown because too many files have changed in this diff Show more