Merge remote-tracking branch 'origin/master' into feat/multi-window-support

# Conflicts:
#	Cargo.toml
#	core/src/window/icon.rs
#	core/src/window/id.rs
#	core/src/window/position.rs
#	core/src/window/settings.rs
#	examples/integration/src/main.rs
#	examples/integration_opengl/src/main.rs
#	glutin/src/application.rs
#	native/src/subscription.rs
#	native/src/window.rs
#	runtime/src/window/action.rs
#	src/lib.rs
#	src/window.rs
#	winit/Cargo.toml
#	winit/src/application.rs
#	winit/src/icon.rs
#	winit/src/settings.rs
#	winit/src/window.rs
This commit is contained in:
Bingus 2023-07-12 12:23:18 -07:00
commit 633f405f3f
No known key found for this signature in database
GPG key ID: 5F84D2AA40A9F170
394 changed files with 17278 additions and 13290 deletions

View file

@ -62,15 +62,15 @@ body:
If you are using an older release, please upgrade to the latest one before filing an issue.
options:
- crates.io release
- master
- 0.7
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operative System
description: Which operative system are you using?
label: Operating System
description: Which operating system are you using?
options:
- Windows
- macOS

View file

@ -20,13 +20,13 @@ jobs:
-p iced_core \
-p iced_style \
-p iced_futures \
-p iced_native \
-p iced_lazy \
-p iced_runtime \
-p iced_graphics \
-p iced_wgpu \
-p iced_glow \
-p iced_tiny_skia \
-p iced_renderer \
-p iced_widget \
-p iced_winit \
-p iced_glutin \
-p iced
- name: Write CNAME file
run: echo 'docs.iced.rs' > ./target/doc/CNAME

View file

@ -37,5 +37,5 @@ jobs:
run: cargo build --package tour --target wasm32-unknown-unknown
- name: Check compilation of `todos` example
run: cargo build --package todos --target wasm32-unknown-unknown
- name: Check compilation of `integration_wgpu` example
run: cargo build --package integration_wgpu --target wasm32-unknown-unknown
- name: Check compilation of `integration` example
run: cargo build --package integration --target wasm32-unknown-unknown

View file

@ -6,6 +6,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.9.0] - 2023-04-13
### Added
- `MouseArea` widget. [#1594](https://github.com/iced-rs/iced/pull/1594)
- `channel` helper in `subscription`. [#1786](https://github.com/iced-rs/iced/pull/1786)
- Configurable `width` for `Scrollable`. [#1749](https://github.com/iced-rs/iced/pull/1749)
- Support for disabled `TextInput`. [#1744](https://github.com/iced-rs/iced/pull/1744)
- Platform-specific window settings. [#1730](https://github.com/iced-rs/iced/pull/1730)
- Left and right colors for sliders. [#1643](https://github.com/iced-rs/iced/pull/1643)
- Icon for `TextInput`. [#1702](https://github.com/iced-rs/iced/pull/1702)
- Mouse over scrollbar flag for `scrollable::StyleSheet`. [#1669](https://github.com/iced-rs/iced/pull/1669)
- Better example for `Radio`. [#1762](https://github.com/iced-rs/iced/pull/1762)
### Changed
- `wgpu` has been updated to `0.15` in `iced_wgpu`. [#1789](https://github.com/iced-rs/iced/pull/1789)
- `resvg` has been updated to `0.29` in `iced_graphics`. [#1733](https://github.com/iced-rs/iced/pull/1733)
- `subscription::run` now takes a function pointer. [#1723](https://github.com/iced-rs/iced/pull/1723)
### Fixed
- Redundant `on_scroll` messages for `Scrollable`. [#1788](https://github.com/iced-rs/iced/pull/1788)
- Outdated items in `ROADMAP.md` [#1782](https://github.com/iced-rs/iced/pull/1782)
- Colons in shader labels causing compilation issues in `iced_wgpu`. [#1779](https://github.com/iced-rs/iced/pull/1779)
- Re-expose winit features for window servers in Linux. [#1777](https://github.com/iced-rs/iced/pull/1777)
- Replacement of application node in Wasm. [#1765](https://github.com/iced-rs/iced/pull/1765)
- `clippy` lints for Rust 1.68. [#1755](https://github.com/iced-rs/iced/pull/1755)
- Unnecessary `Component` rebuilds. [#1754](https://github.com/iced-rs/iced/pull/1754)
- Incorrect package name in checkbox example docs. [#1750](https://github.com/iced-rs/iced/pull/1750)
- Fullscreen only working on primary monitor. [#1742](https://github.com/iced-rs/iced/pull/1742)
- `Padding::fit` on irregular values for an axis. [#1734](https://github.com/iced-rs/iced/pull/1734)
- `Debug` implementation of `Font` displaying its bytes. [#1731](https://github.com/iced-rs/iced/pull/1731)
- Sliders bleeding over their rail. [#1721](https://github.com/iced-rs/iced/pull/1721)
### Removed
- `Fill` variant for `Alignment`. [#1735](https://github.com/iced-rs/iced/pull/1735)
Many thanks to...
- @ahoneybun
- @bq-wrongway
- @bungoboingo
- @casperstorm
- @Davidster
- @ElhamAryanpur
- @FinnPerry
- @GyulyVGC
- @JungleTryne
- @lupd
- @mmstick
- @nicksenger
- @Night-Hunter-NF
- @tarkah
- @traxys
- @Xaeroxe
## [0.8.0] - 2023-02-18
### Added
- Generic pixel units. [#1711](https://github.com/iced-rs/iced/pull/1711)
@ -414,7 +467,8 @@ Many thanks to...
### Added
- First release! :tada:
[Unreleased]: https://github.com/iced-rs/iced/compare/0.8.0...HEAD
[Unreleased]: https://github.com/iced-rs/iced/compare/0.9.0...HEAD
[0.9.0]: https://github.com/iced-rs/iced/compare/0.8.0...0.9.0
[0.8.0]: https://github.com/iced-rs/iced/compare/0.7.0...0.8.0
[0.7.0]: https://github.com/iced-rs/iced/compare/0.6.0...0.7.0
[0.6.0]: https://github.com/iced-rs/iced/compare/0.5.0...0.6.0

View file

@ -1,6 +1,6 @@
[package]
name = "iced"
version = "0.8.0"
version = "0.9.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A cross-platform GUI library inspired by Elm"
@ -13,20 +13,18 @@ categories = ["gui"]
[features]
default = ["wgpu"]
# Enable the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu"]
# Enables the `Image` widget
image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"]
image = ["iced_widget/image", "image_rs"]
# Enables the `Svg` widget
svg = ["iced_wgpu?/svg", "iced_glow?/svg"]
svg = ["iced_widget/svg"]
# Enables the `Canvas` widget
canvas = ["iced_graphics/canvas"]
canvas = ["iced_widget/canvas"]
# Enables the `QRCode` widget
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", "iced_glow?/default_system_font"]
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
glow = ["iced_glow", "iced_glutin"]
qr_code = ["iced_widget/qr_code"]
# Enables lazy widgets
lazy = ["iced_widget/lazy"]
# Enables a debug view in native platforms (press F12)
debug = ["iced_winit/debug"]
# Enables `tokio` as the `executor::Default` on native platforms
@ -39,13 +37,10 @@ smol = ["iced_futures/smol"]
palette = ["iced_core/palette"]
# Enables querying system information
system = ["iced_winit/system"]
# Enables chrome traces
chrome-trace = [
"iced_winit/chrome-trace",
"iced_glutin?/trace",
"iced_wgpu?/tracing",
"iced_glow?/tracing",
]
# Enables broken "sRGB linear" blending to reproduce color management of the Web
web-colors = ["iced_renderer/web-colors"]
# Enables the advanced module
advanced = []
# Enables experimental multi-window support for iced_winit + wgpu.
multi-window = ["iced_winit/multi-window"]
@ -57,37 +52,29 @@ members = [
"core",
"futures",
"graphics",
"glow",
"glutin",
"lazy",
"native",
"runtime",
"renderer",
"style",
"tiny_skia",
"wgpu",
"widget",
"winit",
"examples/*",
]
[dependencies]
iced_core = { version = "0.8", path = "core" }
iced_core = { version = "0.9", path = "core" }
iced_futures = { version = "0.6", path = "futures" }
iced_native = { version = "0.9", path = "native" }
iced_graphics = { version = "0.7", path = "graphics" }
iced_winit = { version = "0.8", path = "winit", features = ["application"] }
iced_glutin = { version = "0.7", path = "glutin", optional = true }
iced_glow = { version = "0.7", path = "glow", optional = true }
thiserror = "1.0"
iced_renderer = { version = "0.1", path = "renderer" }
iced_widget = { version = "0.1", path = "widget" }
iced_winit = { version = "0.9", path = "winit", features = ["application"] }
thiserror = "1"
[dependencies.image_rs]
version = "0.24"
package = "image"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
iced_wgpu = { version = "0.9", path = "wgpu", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"], optional = true }
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
features = ["image", "svg", "canvas", "qr_code"]

33
DEPENDENCIES.md Normal file
View file

@ -0,0 +1,33 @@
# Dependencies
Iced requires some system dependencies to work, and not
all operating systems come with them installed.
You can follow the provided instructions for your system to
get them, if your system isn't here, add it!
## NixOS
You can add this `shell.nix` to your project and use it by running `nix-shell`:
```nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell rec {
buildInputs = with pkgs; [
expat
fontconfig
freetype
freetype.dev
libGL
pkgconfig
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXrandr
];
LD_LIBRARY_PATH =
builtins.foldl' (a: b: "${a}:${b}/lib") "${pkgs.vulkan-loader}/lib" buildInputs;
}
```

View file

@ -35,9 +35,9 @@ Inspired by [Elm].
* First-class support for async actions (use futures!)
* [Modular ecosystem] split into reusable parts:
* A [renderer-agnostic native runtime] enabling integration with existing systems
* Two [built-in renderers] leveraging [`wgpu`] and [`glow`]
* Two [built-in renderers] leveraging [`wgpu`] and [`tiny-skia`]
* [`iced_wgpu`] supporting Vulkan, Metal and DX12
* [`iced_glow`] supporting OpenGL 2.1+ and OpenGL ES 2.0+
* [`iced_tiny_skia`] offering a software alternative as a fallback
* A [windowing shell]
* A [web runtime] leveraging the DOM
@ -52,9 +52,9 @@ __Iced is currently experimental software.__ [Take a look at the roadmap],
[Modular ecosystem]: ECOSYSTEM.md
[renderer-agnostic native runtime]: native/
[`wgpu`]: https://github.com/gfx-rs/wgpu
[`glow`]: https://github.com/grovesNL/glow
[`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
[`iced_wgpu`]: wgpu/
[`iced_glow`]: glow/
[`iced_tiny_skia`]: tiny_skia/
[built-in renderers]: ECOSYSTEM.md#Renderers
[windowing shell]: winit/
[`dodrio`]: https://github.com/fitzgen/dodrio
@ -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.8"
iced = "0.9"
```
If your project is using a Rust edition older than 2021, then you will need to
@ -196,34 +196,6 @@ end-user-oriented GUI library, while keeping [the ecosystem] modular:
[`ggez`]: https://github.com/ggez/ggez
[the ecosystem]: ECOSYSTEM.md
## Troubleshooting
### `GraphicsAdapterNotFound`
This occurs when the selected [built-in renderer] is not able to create a context.
Often this will occur while using [`iced_wgpu`] as the renderer without
supported hardware (needs Vulkan, Metal or DX12). In this case, you could try using the
[`iced_glow`] renderer:
First, check if it works with
```console
cargo run --features iced/glow --package game_of_life
```
and then use it in your project with
```toml
iced = { version = "0.8", default-features = false, features = ["glow"] }
```
__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
but if you don't, right now there's no software fallback, so it means your hardware
doesn't support Iced.
[built-in renderer]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md#Renderers
## Contributing / Feedback
Contributions are greatly appreciated! If you want to contribute, please

View file

@ -19,6 +19,7 @@ Once a step is completed, it is collapsed and added to this list:
* [x] Custom styling ([#146])
* [x] Canvas for 2D graphics ([#193])
* [x] Basic overlay support ([#444])
* [x] Animations [#31]
[#24]: https://github.com/iced-rs/iced/issues/24
[#25]: https://github.com/iced-rs/iced/issues/25
@ -29,6 +30,7 @@ Once a step is completed, it is collapsed and added to this list:
[#146]: https://github.com/iced-rs/iced/pull/146
[#193]: https://github.com/iced-rs/iced/pull/193
[#444]: https://github.com/iced-rs/iced/pull/444
[#31]: https://github.com/iced-rs/iced/issues/31
### Multi-window support ([#27])
Open and control multiple windows at runtime.
@ -39,16 +41,7 @@ This approach should also allow us to perform custom optimizations for this part
[#27]: https://github.com/iced-rs/iced/issues/27
### Animations ([#31])
Allow widgets to request a redraw at a specific time.
This is a necessary feature to render loading spinners, a blinking text cursor, GIF images, etc.
[`winit`] allows flexible control of its event loop. We may be able to use [`ControlFlow::WaitUntil`](https://docs.rs/winit/0.20.0-alpha3/winit/event_loop/enum.ControlFlow.html#variant.WaitUntil) for this purpose.
[#31]: https://github.com/iced-rs/iced/issues/31
### Canvas widget for 3D graphics ([#32])
### Canvas widget for 3D graphics (~~[#32]~~ [#343])
A widget to draw freely in 3D. It could be used to draw charts, implement a Paint clone, a CAD application, etc.
As a first approach, we could expose the underlying renderer directly here, and couple this widget with it ([`wgpu`] for now). Once [`wgpu`] gets WebGL or WebGPU support, this widget will be able to run on the web too. The renderer primitive could be a simple texture that the widget draws to.
@ -56,6 +49,7 @@ As a first approach, we could expose the underlying renderer directly here, and
In the long run, we could expose a renderer-agnostic abstraction to perform the drawing.
[#32]: https://github.com/iced-rs/iced/issues/32
[#343]: https://github.com/iced-rs/iced/issues/343
### Text shaping and font fallback ([#33])
[`wgpu_glyph`] uses [`glyph_brush`], which in turn uses [`rusttype`]. While the current implementation is able to layout text quite nicely, it does not perform any [text shaping].

View file

@ -1,6 +1,6 @@
[package]
name = "iced_core"
version = "0.8.1"
version = "0.9.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "The essential concepts of Iced"
@ -9,10 +9,16 @@ repository = "https://github.com/iced-rs/iced"
[dependencies]
bitflags = "1.2"
thiserror = "1"
log = "0.4.17"
twox-hash = { version = "1.5", default-features = false }
[dependencies.palette]
version = "0.6"
version = "0.7"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant = "0.1"
[dev-dependencies]
approx = "0.5"

View file

@ -18,7 +18,7 @@ This crate is meant to be a starting point for an Iced runtime.
Add `iced_core` as a dependency in your `Cargo.toml`:
```toml
iced_core = "0.8"
iced_core = "0.9"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If

33
core/src/angle.rs Normal file
View file

@ -0,0 +1,33 @@
use crate::{Point, Rectangle, Vector};
use std::f32::consts::PI;
#[derive(Debug, Copy, Clone, PartialEq)]
/// Degrees
pub struct Degrees(pub f32);
#[derive(Debug, Copy, Clone, PartialEq)]
/// Radians
pub struct Radians(pub f32);
impl From<Degrees> for Radians {
fn from(degrees: Degrees) -> Self {
Radians(degrees.0 * PI / 180.0)
}
}
impl Radians {
/// Calculates the line in which the [`Angle`] intercepts the `bounds`.
pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) {
let v1 = Vector::new(f32::cos(self.0), f32::sin(self.0));
let distance_to_rect = f32::min(
f32::abs((bounds.y - bounds.center().y) / v1.y),
f32::abs(((bounds.x + bounds.width) - bounds.center().x) / v1.x),
);
let start = bounds.center() + v1 * distance_to_rect;
let end = bounds.center() - v1 * distance_to_rect;
(start, end)
}
}

View file

@ -1,11 +1,14 @@
use crate::gradient::{self, Gradient};
use crate::Color;
/// The background of some element.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Background {
/// A solid color
/// A solid color.
Color(Color),
// TODO: Add gradient and image variants
/// Linearly interpolate between several colors.
Gradient(Gradient),
// TODO: Add image variant
}
impl From<Color> for Background {
@ -14,8 +17,14 @@ impl From<Color> for Background {
}
}
impl From<Color> for Option<Background> {
fn from(color: Color) -> Self {
Some(Background::from(color))
impl From<Gradient> for Background {
fn from(gradient: Gradient) -> Self {
Background::Gradient(gradient)
}
}
impl From<gradient::Linear> for Background {
fn from(gradient: gradient::Linear) -> Self {
Background::Gradient(Gradient::Linear(gradient))
}
}

22
core/src/border_radius.rs Normal file
View file

@ -0,0 +1,22 @@
/// The border radii 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
}
}

23
core/src/clipboard.rs Normal file
View file

@ -0,0 +1,23 @@
//! Access the clipboard.
/// A buffer for short-term storage and transfer within and between
/// applications.
pub trait Clipboard {
/// Reads the current content of the [`Clipboard`] as text.
fn read(&self) -> Option<String>;
/// Writes the given text contents to the [`Clipboard`].
fn write(&mut self, contents: String);
}
/// A null implementation of the [`Clipboard`] trait.
#[derive(Debug, Clone, Copy)]
pub struct Null;
impl Clipboard for Null {
fn read(&self) -> Option<String> {
None
}
fn write(&mut self, _contents: String) {}
}

View file

@ -183,15 +183,15 @@ macro_rules! color {
}
#[cfg(feature = "palette")]
/// Converts from palette's `Srgba` type to a [`Color`].
/// Converts from palette's `Rgba` type to a [`Color`].
impl From<Srgba> for Color {
fn from(srgba: Srgba) -> Self {
Color::new(srgba.red, srgba.green, srgba.blue, srgba.alpha)
fn from(rgba: Srgba) -> Self {
Color::new(rgba.red, rgba.green, rgba.blue, rgba.alpha)
}
}
#[cfg(feature = "palette")]
/// Converts from [`Color`] to palette's `Srgba` type.
/// Converts from [`Color`] to palette's `Rgba` type.
impl From<Color> for Srgba {
fn from(c: Color) -> Self {
Srgba::new(c.r, c.g, c.b, c.a)
@ -199,15 +199,15 @@ impl From<Color> for Srgba {
}
#[cfg(feature = "palette")]
/// Converts from palette's `Srgb` type to a [`Color`].
/// Converts from palette's `Rgb` type to a [`Color`].
impl From<Srgb> for Color {
fn from(srgb: Srgb) -> Self {
Color::new(srgb.red, srgb.green, srgb.blue, 1.0)
fn from(rgb: Srgb) -> Self {
Color::new(rgb.red, rgb.green, rgb.blue, 1.0)
}
}
#[cfg(feature = "palette")]
/// Converts from [`Color`] to palette's `Srgb` type.
/// Converts from [`Color`] to palette's `Rgb` type.
impl From<Color> for Srgb {
fn from(c: Color) -> Self {
Srgb::new(c.r, c.g, c.b)
@ -218,12 +218,12 @@ impl From<Color> for Srgb {
#[cfg(test)]
mod tests {
use super::*;
use palette::Blend;
use palette::blend::Blend;
#[test]
fn srgba_traits() {
let c = Color::from_rgb(0.5, 0.4, 0.3);
// Round-trip conversion to the palette:Srgba type
// Round-trip conversion to the palette::Srgba type
let s: Srgba = c.into();
let r: Color = s.into();
assert_eq!(c, r);
@ -231,6 +231,8 @@ mod tests {
#[test]
fn color_manipulation() {
use approx::assert_relative_eq;
let c1 = Color::from_rgb(0.5, 0.4, 0.3);
let c2 = Color::from_rgb(0.2, 0.5, 0.3);
@ -238,19 +240,15 @@ mod tests {
let l1 = Srgba::from(c1).into_linear();
let l2 = Srgba::from(c2).into_linear();
// Take the lighter of each of the RGB components
// Take the lighter of each of the sRGB components
let lighter = l1.lighten(l2);
// Convert back to our Color
let r: Color = Srgba::from_linear(lighter).into();
assert_eq!(
r,
Color {
r: 0.5,
g: 0.5,
b: 0.3,
a: 1.0
}
);
let result: Color = Srgba::from_linear(lighter).into();
assert_relative_eq!(result.r, 0.5);
assert_relative_eq!(result.g, 0.5);
assert_relative_eq!(result.b, 0.3);
assert_relative_eq!(result.a, 1.0);
}
}

View file

@ -5,9 +5,7 @@ use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget,
};
use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget};
use std::any::Any;
use std::borrow::Borrow;
@ -90,41 +88,65 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
/// We compose the previous __messages__ with the index of the counter
/// producing them. Let's implement our __view logic__ now:
///
/// ```
/// ```no_run
/// # mod counter {
/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>;
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn view(&mut self) -> Text {
/// # Text::new("")
/// # pub fn view(
/// # &self,
/// # ) -> iced_core::Element<Message, iced_core::renderer::Null> {
/// # unimplemented!()
/// # }
/// # }
/// # }
/// #
/// # mod iced_wgpu {
/// # pub use iced_native::renderer::Null as Renderer;
/// # mod iced {
/// # pub use iced_core::renderer::Null as Renderer;
/// # pub use iced_core::Element;
/// #
/// # pub mod widget {
/// # pub struct Row<Message> {
/// # _t: std::marker::PhantomData<Message>,
/// # }
/// #
/// # impl<Message> Row<Message> {
/// # pub fn new() -> Self {
/// # unimplemented!()
/// # }
/// #
/// # pub fn spacing(mut self, _: u32) -> Self {
/// # unimplemented!()
/// # }
/// #
/// # pub fn push(
/// # mut self,
/// # _: iced_core::Element<Message, iced_core::renderer::Null>,
/// # ) -> Self {
/// # unimplemented!()
/// # }
/// # }
/// # }
/// # }
/// #
/// # use counter::Counter;
/// #
/// # struct ManyCounters {
/// # counters: Vec<Counter>,
/// # }
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {
/// # Counter(usize, counter::Message)
/// # }
/// use iced_native::Element;
/// use iced_native::widget::Row;
/// use iced_wgpu::Renderer;
/// use counter::Counter;
///
/// use iced::widget::Row;
/// use iced::{Element, Renderer};
///
/// struct ManyCounters {
/// counters: Vec<Counter>,
/// }
///
/// #[derive(Debug, Clone, Copy)]
/// pub enum Message {
/// Counter(usize, counter::Message),
/// }
///
/// impl ManyCounters {
/// pub fn view(&mut self) -> Row<Message, Renderer> {
/// pub fn view(&mut self) -> Row<Message> {
/// // We can quickly populate a `Row` by folding over our counters
/// self.counters.iter_mut().enumerate().fold(
/// Row::new().spacing(20),
@ -137,9 +159,10 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
/// // Here we turn our `Element<counter::Message>` into
/// // an `Element<Message>` by combining the `index` and the
/// // message of the `element`.
/// element.map(move |message| Message::Counter(index, message))
/// element
/// .map(move |message| Message::Counter(index, message)),
/// )
/// }
/// },
/// )
/// }
/// }
@ -353,7 +376,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
@ -365,7 +388,7 @@ where
tree,
event,
layout,
cursor_position,
cursor,
renderer,
clipboard,
&mut local_shell,
@ -383,35 +406,23 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.widget.draw(
tree,
renderer,
theme,
style,
layout,
cursor_position,
viewport,
)
self.widget
.draw(tree, renderer, theme, style, layout, cursor, viewport)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.widget.mouse_interaction(
tree,
layout,
cursor_position,
viewport,
renderer,
)
self.widget
.mouse_interaction(tree, layout, cursor, viewport, renderer)
}
fn overlay<'b>(
@ -496,20 +507,14 @@ where
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.element.widget.on_event(
state,
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
self.element
.widget
.on_event(state, event, layout, cursor, renderer, clipboard, shell)
}
fn draw(
@ -519,7 +524,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
fn explain_layout<Renderer: crate::Renderer>(
@ -542,15 +547,9 @@ where
}
}
self.element.widget.draw(
state,
renderer,
theme,
style,
layout,
cursor_position,
viewport,
);
self.element
.widget
.draw(state, renderer, theme, style, layout, cursor, viewport);
explain_layout(renderer, self.color, layout);
}
@ -559,17 +558,13 @@ where
&self,
state: &Tree,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.element.widget.mouse_interaction(
state,
layout,
cursor_position,
viewport,
renderer,
)
self.element
.widget
.mouse_interaction(state, layout, cursor, viewport, renderer)
}
fn overlay<'b>(

View file

@ -62,7 +62,7 @@ impl Status {
/// `Captured` takes precedence over `Ignored`:
///
/// ```
/// use iced_native::event::Status;
/// use iced_core::event::Status;
///
/// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored);
/// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured);

View file

@ -1,24 +1,102 @@
//! Load and use fonts.
use std::hash::Hash;
/// A font.
#[derive(Debug, Clone, Copy)]
pub enum Font {
/// The default font.
///
/// This is normally a font configured in a renderer or loaded from the
/// system.
Default,
/// An external font.
External {
/// The name of the external font
name: &'static str,
/// The bytes of the external font
bytes: &'static [u8],
},
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Font {
/// The [`Family`] of the [`Font`].
pub family: Family,
/// The [`Weight`] of the [`Font`].
pub weight: Weight,
/// The [`Stretch`] of the [`Font`].
pub stretch: Stretch,
/// Whether if the [`Font`] is monospaced or not.
pub monospaced: bool,
}
impl Default for Font {
fn default() -> Font {
Font::Default
impl Font {
/// A non-monospaced sans-serif font with normal [`Weight`].
pub const DEFAULT: Font = Font {
family: Family::SansSerif,
weight: Weight::Normal,
stretch: Stretch::Normal,
monospaced: false,
};
/// A monospaced font with normal [`Weight`].
pub const MONOSPACE: Font = Font {
family: Family::Monospace,
monospaced: true,
..Self::DEFAULT
};
/// Creates a non-monospaced [`Font`] with the given [`Family::Name`] and
/// normal [`Weight`].
pub const fn with_name(name: &'static str) -> Self {
Font {
family: Family::Name(name),
..Self::DEFAULT
}
}
}
/// A font family.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Family {
/// The name of a font family of choice.
Name(&'static str),
/// Serif fonts represent the formal text style for a script.
Serif,
/// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low
/// contrast and have stroke endings that are plain — without any flaring,
/// cross stroke, or other ornamentation.
#[default]
SansSerif,
/// Glyphs in cursive fonts generally use a more informal script style, and
/// the result looks more like handwritten pen or brush writing than printed
/// letterwork.
Cursive,
/// Fantasy fonts are primarily decorative or expressive fonts that contain
/// decorative or expressive representations of characters.
Fantasy,
/// The sole criterion of a monospace font is that all glyphs have the same
/// fixed width.
Monospace,
}
/// The weight of some text.
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Weight {
Thin,
ExtraLight,
Light,
#[default]
Normal,
Medium,
Semibold,
Bold,
ExtraBold,
Black,
}
/// The width of some text.
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Stretch {
UltraCondensed,
ExtraCondensed,
Condensed,
SemiCondensed,
#[default]
Normal,
SemiExpanded,
Expanded,
ExtraExpanded,
UltraExpanded,
}

105
core/src/gradient.rs Normal file
View file

@ -0,0 +1,105 @@
//! Colors that transition progressively.
use crate::{Color, Radians};
use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq)]
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
/// or conically (TBD).
///
/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`].
pub enum Gradient {
/// A linear gradient interpolates colors along a direction at a specific [`Angle`].
Linear(Linear),
}
impl Gradient {
/// Adjust the opacity of the gradient by a multiplier applied to each color stop.
pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self {
match &mut self {
Gradient::Linear(linear) => {
for stop in linear.stops.iter_mut().flatten() {
stop.color.a *= alpha_multiplier;
}
}
}
self
}
}
impl From<Linear> for Gradient {
fn from(gradient: Linear) -> Self {
Self::Linear(gradient)
}
}
#[derive(Debug, Default, 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,
}
/// A linear gradient.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Linear {
/// How the [`Gradient`] is angled within its bounds.
pub angle: Radians,
/// [`ColorStop`]s along the linear gradient path.
pub stops: [Option<ColorStop>; 8],
}
impl Linear {
/// Creates a new [`Linear`] gradient with the given angle in [`Radians`].
pub fn new(angle: impl Into<Radians>) -> Self {
Self {
angle: angle.into(),
stops: [None; 8],
}
}
/// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient.
///
/// Any `offset` that is not within `0.0..=1.0` will be silently ignored.
///
/// Any stop added after the 8th will be silently ignored.
pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
if offset.is_finite() && (0.0..=1.0).contains(&offset) {
let (Ok(index) | Err(index)) =
self.stops.binary_search_by(|stop| match stop {
None => Ordering::Greater,
Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
});
if index < 8 {
self.stops[index] = Some(ColorStop { offset, color });
}
} else {
log::warn!("Gradient color stop must be within 0.0..=1.0 range.");
};
self
}
/// Adds multiple [`ColorStop`]s to the gradient.
///
/// Any stop added after the 8th will be silently ignored.
pub fn add_stops(
mut self,
stops: impl IntoIterator<Item = ColorStop>,
) -> Self {
for stop in stops.into_iter() {
self = self.add_stop(stop.offset, stop.color)
}
self
}
}

View file

@ -6,7 +6,7 @@ use std::path::PathBuf;
use std::sync::Arc;
/// A handle of some image data.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
id: u64,
data: Data,
@ -110,6 +110,14 @@ impl std::hash::Hash for Bytes {
}
}
impl PartialEq for Bytes {
fn eq(&self, other: &Self) -> bool {
self.as_ref() == other.as_ref()
}
}
impl Eq for Bytes {}
impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
self.0.as_ref().as_ref()
@ -125,7 +133,7 @@ impl std::ops::Deref for Bytes {
}
/// The data of a raster image.
#[derive(Clone, Hash)]
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Data {
/// File data
Path(PathBuf),

View file

@ -1,3 +1,5 @@
use crate::Pixels;
/// The strategy used to fill space in a specific dimension.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Length {
@ -36,6 +38,12 @@ impl Length {
}
}
impl From<Pixels> for Length {
fn from(amount: Pixels) -> Self {
Length::Fixed(f32::from(amount))
}
}
impl From<f32> for Length {
fn from(amount: f32) -> Self {
Length::Fixed(amount)

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.8/native
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/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"
@ -25,31 +25,61 @@
#![forbid(unsafe_code, rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
pub mod alignment;
pub mod clipboard;
pub mod event;
pub mod font;
pub mod gradient;
pub mod image;
pub mod keyboard;
pub mod layout;
pub mod mouse;
pub mod overlay;
pub mod renderer;
pub mod svg;
pub mod text;
pub mod time;
pub mod touch;
pub mod widget;
pub mod window;
mod angle;
mod background;
mod border_radius;
mod color;
mod content_fit;
mod font;
mod element;
mod hasher;
mod length;
mod padding;
mod pixels;
mod point;
mod rectangle;
mod shell;
mod size;
mod vector;
pub use alignment::Alignment;
pub use angle::{Degrees, Radians};
pub use background::Background;
pub use border_radius::BorderRadius;
pub use clipboard::Clipboard;
pub use color::Color;
pub use content_fit::ContentFit;
pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use gradient::Gradient;
pub use hasher::Hasher;
pub use layout::Layout;
pub use length::Length;
pub use overlay::Overlay;
pub use padding::Padding;
pub use pixels::Pixels;
pub use point::Point;
pub use rectangle::Rectangle;
pub use renderer::Renderer;
pub use shell::Shell;
pub use size::Size;
pub use text::Text;
pub use vector::Vector;
pub use widget::Widget;

View file

@ -1,8 +1,13 @@
//! Handle mouse events.
pub mod click;
mod button;
mod cursor;
mod event;
mod interaction;
pub use button::Button;
pub use click::Click;
pub use cursor::Cursor;
pub use event::{Event, ScrollDelta};
pub use interaction::Interaction;

View file

@ -11,5 +11,5 @@ pub enum Button {
Middle,
/// Some other button.
Other(u8),
Other(u16),
}

52
core/src/mouse/cursor.rs Normal file
View file

@ -0,0 +1,52 @@
use crate::{Point, Rectangle, Vector};
/// The mouse cursor state.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Cursor {
/// The cursor has a defined position.
Available(Point),
/// The cursor is currently unavailable (i.e. out of bounds or busy).
#[default]
Unavailable,
}
impl Cursor {
/// Returns the absolute position of the [`Cursor`], if available.
pub fn position(self) -> Option<Point> {
match self {
Cursor::Available(position) => Some(position),
Cursor::Unavailable => None,
}
}
/// Returns the absolute position of the [`Cursor`], if available and inside
/// the given bounds.
///
/// If the [`Cursor`] is not over the provided bounds, this method will
/// return `None`.
pub fn position_over(self, bounds: Rectangle) -> Option<Point> {
self.position().filter(|p| bounds.contains(*p))
}
/// Returns the relative position of the [`Cursor`] inside the given bounds,
/// if available.
///
/// If the [`Cursor`] is not over the provided bounds, this method will
/// return `None`.
pub fn position_in(self, bounds: Rectangle) -> Option<Point> {
self.position_over(bounds)
.map(|p| p - Vector::new(bounds.x, bounds.y))
}
/// Returns the relative position of the [`Cursor`] from the given origin,
/// if available.
pub fn position_from(self, origin: Point) -> Option<Point> {
self.position().map(|p| p - Vector::new(origin.x, origin.y))
}
/// Returns true if the [`Cursor`] is over the given `bounds`.
pub fn is_over(self, bounds: Rectangle) -> bool {
self.position_over(bounds).is_some()
}
}

View file

@ -1,7 +1,8 @@
/// The interaction of a mouse cursor.
#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)]
#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Default)]
#[allow(missing_docs)]
pub enum Interaction {
#[default]
Idle,
Pointer,
Grab,
@ -11,10 +12,5 @@ pub enum Interaction {
Grabbing,
ResizingHorizontally,
ResizingVertically,
}
impl Default for Interaction {
fn default() -> Interaction {
Interaction::Idle
}
NotAllowed,
}

View file

@ -2,11 +2,8 @@
mod element;
mod group;
pub mod menu;
pub use element::Element;
pub use group::Group;
pub use menu::Menu;
use crate::event::{self, Event};
use crate::layout;
@ -41,7 +38,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
);
/// Applies a [`widget::Operation`] to the [`Overlay`].
@ -69,7 +66,7 @@ where
&mut self,
_event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
_cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
@ -83,7 +80,7 @@ where
fn mouse_interaction(
&self,
_layout: Layout<'_>,
_cursor_position: Point,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
@ -94,9 +91,23 @@ where
///
/// By default, it returns true if the bounds of the `layout` contain
/// the `cursor_position`.
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
fn is_over(
&self,
layout: Layout<'_>,
_renderer: &Renderer,
cursor_position: Point,
) -> bool {
layout.bounds().contains(cursor_position)
}
/// Returns the nested overlay of the [`Overlay`], if there is any.
fn overlay<'a>(
&'a mut self,
_layout: Layout<'_>,
_renderer: &Renderer,
) -> Option<Element<'a, Message, Renderer>> {
None
}
}
/// Returns a [`Group`] of overlay [`Element`] children.

View file

@ -68,35 +68,25 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.overlay.on_event(
event,
layout,
cursor_position,
renderer,
clipboard,
shell,
)
self.overlay
.on_event(event, layout, cursor, renderer, clipboard, shell)
}
/// Returns the current [`mouse::Interaction`] of the [`Element`].
pub fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.overlay.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
self.overlay
.mouse_interaction(layout, cursor, viewport, renderer)
}
/// Draws the [`Element`] and its children using the given [`Layout`].
@ -106,10 +96,9 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
) {
self.overlay
.draw(renderer, theme, style, layout, cursor_position)
self.overlay.draw(renderer, theme, style, layout, cursor)
}
/// Applies a [`widget::Operation`] to the [`Element`].
@ -123,8 +112,22 @@ where
}
/// Returns true if the cursor is over the [`Element`].
pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
self.overlay.is_over(layout, cursor_position)
pub fn is_over(
&self,
layout: Layout<'_>,
renderer: &Renderer,
cursor_position: Point,
) -> bool {
self.overlay.is_over(layout, renderer, cursor_position)
}
/// Returns the nested overlay of the [`Element`], if there is any.
pub fn overlay<'b>(
&'b mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<Element<'b, Message, Renderer>> {
self.overlay.overlay(layout, renderer)
}
}
@ -215,7 +218,7 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
@ -226,7 +229,7 @@ where
let event_status = self.content.on_event(
event,
layout,
cursor_position,
cursor,
renderer,
clipboard,
&mut local_shell,
@ -240,16 +243,12 @@ where
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
self.content
.mouse_interaction(layout, cursor, viewport, renderer)
}
fn draw(
@ -258,13 +257,27 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
) {
self.content
.draw(renderer, theme, style, layout, cursor_position)
self.content.draw(renderer, theme, style, layout, cursor)
}
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
self.content.is_over(layout, cursor_position)
fn is_over(
&self,
layout: Layout<'_>,
renderer: &Renderer,
cursor_position: Point,
) -> bool {
self.content.is_over(layout, renderer, cursor_position)
}
fn overlay<'b>(
&'b mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<Element<'b, B, Renderer>> {
self.content
.overlay(layout, renderer)
.map(|overlay| overlay.map(self.mapper))
}
}

View file

@ -1,12 +1,10 @@
use iced_core::{Point, Rectangle, Size};
use crate::event;
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::{Clipboard, Event, Layout, Overlay, Shell};
use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
/// children.
@ -83,7 +81,7 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@ -95,7 +93,7 @@ where
child.on_event(
event.clone(),
layout,
cursor_position,
cursor,
renderer,
clipboard,
shell,
@ -110,17 +108,17 @@ where
theme: &<Renderer as crate::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
) {
for (child, layout) in self.children.iter().zip(layout.children()) {
child.draw(renderer, theme, style, layout, cursor_position);
child.draw(renderer, theme, style, layout, cursor);
}
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@ -128,12 +126,7 @@ where
.iter()
.zip(layout.children())
.map(|(child, layout)| {
child.mouse_interaction(
layout,
cursor_position,
viewport,
renderer,
)
child.mouse_interaction(layout, cursor, viewport, renderer)
})
.max()
.unwrap_or_default()
@ -154,11 +147,33 @@ where
});
}
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
fn is_over(
&self,
layout: Layout<'_>,
renderer: &Renderer,
cursor_position: Point,
) -> bool {
self.children
.iter()
.zip(layout.children())
.any(|(child, layout)| child.is_over(layout, cursor_position))
.any(|(child, layout)| {
child.is_over(layout, renderer, cursor_position)
})
}
fn overlay<'b>(
&'b mut self,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
let children = self
.children
.iter_mut()
.zip(layout.children())
.filter_map(|(child, layout)| child.overlay(layout, renderer))
.collect::<Vec<_>>();
(!children.is_empty()).then(|| Group::with_children(children).overlay())
}
}

View file

@ -20,3 +20,9 @@ impl From<u16> for Pixels {
Self(f32::from(amount))
}
}
impl From<Pixels> for f32 {
fn from(pixels: Pixels) -> Self {
pixels.0
}
}

View file

@ -66,6 +66,11 @@ impl Rectangle<f32> {
Size::new(self.width, self.height)
}
/// Returns the area of the [`Rectangle`].
pub fn area(&self) -> f32 {
self.width * self.height
}
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
pub fn contains(&self, point: Point) -> bool {
self.x <= point.x
@ -74,6 +79,15 @@ impl Rectangle<f32> {
&& point.y <= self.y + self.height
}
/// Returns true if the current [`Rectangle`] is completely within the given
/// `container`.
pub fn is_within(&self, container: &Rectangle) -> bool {
container.contains(self.position())
&& container.contains(
self.position() + Vector::new(self.width, self.height),
)
}
/// Computes the intersection with the given [`Rectangle`].
pub fn intersection(
&self,
@ -100,6 +114,30 @@ impl Rectangle<f32> {
}
}
/// Returns whether the [`Rectangle`] intersects with the given one.
pub fn intersects(&self, other: &Self) -> bool {
self.intersection(other).is_some()
}
/// Computes the union with the given [`Rectangle`].
pub fn union(&self, other: &Self) -> Self {
let x = self.x.min(other.x);
let y = self.y.min(other.y);
let lower_right_x = (self.x + self.width).max(other.x + other.width);
let lower_right_y = (self.y + self.height).max(other.y + other.height);
let width = lower_right_x - x;
let height = lower_right_y - y;
Rectangle {
x,
y,
width,
height,
}
}
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
pub fn snap(self) -> Rectangle<u32> {
Rectangle {
@ -109,6 +147,16 @@ impl Rectangle<f32> {
height: self.height as u32,
}
}
/// Expands the [`Rectangle`] a given amount.
pub fn expand(self, amount: f32) -> Self {
Self {
x: self.x - amount,
y: self.y - amount,
width: self.width + amount * 2.0,
height: self.height + amount * 2.0,
}
}
}
impl std::ops::Mul<f32> for Rectangle<f32> {

View file

@ -1,11 +1,12 @@
//! Write your own renderer.
#[cfg(debug_assertions)]
mod null;
#[cfg(debug_assertions)]
pub use null::Null;
use crate::layout;
use crate::{Background, Color, Element, Rectangle, Vector};
use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};
/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer: Sized {
@ -59,29 +60,6 @@ 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

@ -1,6 +1,8 @@
use crate::renderer::{self, Renderer};
use crate::text::{self, Text};
use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector};
use crate::{Background, Font, Point, Rectangle, Size, Vector};
use std::borrow::Cow;
/// A renderer that does nothing.
///
@ -16,7 +18,7 @@ impl Null {
}
impl Renderer for Null {
type Theme = Theme;
type Theme = ();
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}
@ -40,30 +42,40 @@ impl Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
const ICON_FONT: Font = Font::Default;
const ICON_FONT: Font = Font::DEFAULT;
const CHECKMARK_ICON: char = '0';
const ARROW_DOWN_ICON: char = '0';
fn default_size(&self) -> f32 {
20.0
fn default_font(&self) -> Self::Font {
Font::default()
}
fn default_size(&self) -> f32 {
16.0
}
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
fn measure(
&self,
_content: &str,
_size: f32,
_line_height: text::LineHeight,
_font: Font,
_bounds: Size,
) -> (f32, f32) {
(0.0, 20.0)
_shaping: text::Shaping,
) -> Size {
Size::new(0.0, 20.0)
}
fn hit_test(
&self,
_contents: &str,
_size: f32,
_line_height: text::LineHeight,
_font: Self::Font,
_bounds: Size,
_shaping: text::Shaping,
_point: Point,
_nearest_only: bool,
) -> Option<text::Hit> {

View file

@ -1,7 +1,7 @@
use crate::{Padding, Vector};
/// An amount of space in 2 dimensions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Size<T = f32> {
/// The width.
pub width: T,

View file

@ -7,7 +7,7 @@ use std::path::PathBuf;
use std::sync::Arc;
/// A handle of Svg data.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
id: u64,
data: Arc<Data>,
@ -57,7 +57,7 @@ impl Hash for Handle {
}
/// The data of a vectorial image.
#[derive(Clone, Hash)]
#[derive(Clone, Hash, PartialEq, Eq)]
pub enum Data {
/// File data
Path(PathBuf),

212
core/src/text.rs Normal file
View file

@ -0,0 +1,212 @@
//! Draw and interact with text.
use crate::alignment;
use crate::{Color, Pixels, Point, Rectangle, Size};
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
/// A paragraph.
#[derive(Debug, Clone, Copy)]
pub struct Text<'a, Font> {
/// The content of the paragraph.
pub content: &'a str,
/// The bounds of the paragraph.
pub bounds: Rectangle,
/// The size of the [`Text`] in logical pixels.
pub size: f32,
/// The line height of the [`Text`].
pub line_height: LineHeight,
/// The color of the [`Text`].
pub color: Color,
/// 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,
/// The [`Shaping`] strategy of the [`Text`].
pub shaping: Shaping,
}
/// The shaping strategy of some text.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Shaping {
/// No shaping and no font fallback.
///
/// This shaping strategy is very cheap, but it will not display complex
/// scripts properly nor try to find missing glyphs in your system fonts.
///
/// You should use this strategy when you have complete control of the text
/// and the font you are displaying in your application.
///
/// This is the default.
#[default]
Basic,
/// Advanced text shaping and font fallback.
///
/// You will need to enable this flag if the text contains a complex
/// script, the font used needs it, and/or multiple fonts in your system
/// may be needed to display all of the glyphs.
///
/// Advanced shaping is expensive! You should only enable it when necessary.
Advanced,
}
/// The height of a line of text in a paragraph.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineHeight {
/// A factor of the size of the text.
Relative(f32),
/// An absolute height in logical pixels.
Absolute(Pixels),
}
impl LineHeight {
/// Returns the [`LineHeight`] in absolute logical pixels.
pub fn to_absolute(self, text_size: Pixels) -> Pixels {
match self {
Self::Relative(factor) => Pixels(factor * text_size.0),
Self::Absolute(pixels) => pixels,
}
}
}
impl Default for LineHeight {
fn default() -> Self {
Self::Relative(1.3)
}
}
impl From<f32> for LineHeight {
fn from(factor: f32) -> Self {
Self::Relative(factor)
}
}
impl From<Pixels> for LineHeight {
fn from(pixels: Pixels) -> Self {
Self::Absolute(pixels)
}
}
impl Hash for LineHeight {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Relative(factor) => {
state.write_u8(0);
factor.to_bits().hash(state);
}
Self::Absolute(pixels) => {
state.write_u8(1);
f32::from(*pixels).to_bits().hash(state);
}
}
}
}
/// The result of hit testing on text.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Hit {
/// The point was within the bounds of the returned character index.
CharOffset(usize),
}
impl Hit {
/// Computes the cursor position of the [`Hit`] .
pub fn cursor(self) -> usize {
match self {
Self::CharOffset(i) => i,
}
}
}
/// A renderer capable of measuring and drawing [`Text`].
pub trait Renderer: crate::Renderer {
/// The font type used.
type Font: Copy;
/// The icon font of the backend.
const ICON_FONT: Self::Font;
/// The `char` representing a ✔ icon in the [`ICON_FONT`].
///
/// [`ICON_FONT`]: Self::ICON_FONT
const CHECKMARK_ICON: char;
/// The `char` representing a ▼ icon in the built-in [`ICON_FONT`].
///
/// [`ICON_FONT`]: Self::ICON_FONT
const ARROW_DOWN_ICON: char;
/// Returns the default [`Self::Font`].
fn default_font(&self) -> Self::Font;
/// Returns the default size of [`Text`].
fn default_size(&self) -> f32;
/// Measures the text in the given bounds and returns the minimum boundaries
/// that can fit the contents.
fn measure(
&self,
content: &str,
size: f32,
line_height: LineHeight,
font: Self::Font,
bounds: Size,
shaping: Shaping,
) -> Size;
/// Measures the width of the text as if it were laid out in a single line.
fn measure_width(
&self,
content: &str,
size: f32,
font: Self::Font,
shaping: Shaping,
) -> f32 {
let bounds = self.measure(
content,
size,
LineHeight::Absolute(Pixels(size)),
font,
Size::INFINITY,
shaping,
);
bounds.width
}
/// Tests whether the provided point is within the boundaries of text
/// laid out with the given parameters, returning information about
/// the nearest character.
///
/// If `nearest_only` is true, the hit test does not consider whether the
/// the point is interior to any glyph bounds, returning only the character
/// with the nearest centeroid.
fn hit_test(
&self,
contents: &str,
size: f32,
line_height: LineHeight,
font: Self::Font,
bounds: Size,
shaping: Shaping,
point: Point,
nearest_only: bool,
) -> Option<Hit>;
/// Loads a [`Self::Font`] from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>);
/// Draws the given [`Text`].
fn fill_text(&mut self, text: Text<'_, Self::Font>);
}

View file

@ -1,98 +1,21 @@
//! Use the built-in widgets or create your own.
//!
//! # Built-in widgets
//! Every built-in drawable widget has its own module with a `Renderer` trait
//! that must be implemented by a [renderer] before being able to use it as
//! a [`Widget`].
//!
//! # Custom widgets
//! If you want to implement a custom widget, you simply need to implement the
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
//! source of inspiration.
//!
//! [renderer]: crate::renderer
pub mod button;
pub mod checkbox;
pub mod column;
pub mod container;
pub mod helpers;
pub mod image;
//! Create custom widgets and operate on them.
pub mod operation;
pub mod pane_grid;
pub mod pick_list;
pub mod progress_bar;
pub mod radio;
pub mod row;
pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod space;
pub mod svg;
pub mod text;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
pub mod tree;
pub mod vertical_slider;
mod action;
mod id;
#[doc(no_inline)]
pub use button::Button;
#[doc(no_inline)]
pub use checkbox::Checkbox;
#[doc(no_inline)]
pub use column::Column;
#[doc(no_inline)]
pub use container::Container;
#[doc(no_inline)]
pub use helpers::*;
#[doc(no_inline)]
pub use image::Image;
#[doc(no_inline)]
pub use pane_grid::PaneGrid;
#[doc(no_inline)]
pub use pick_list::PickList;
#[doc(no_inline)]
pub use progress_bar::ProgressBar;
#[doc(no_inline)]
pub use radio::Radio;
#[doc(no_inline)]
pub use row::Row;
#[doc(no_inline)]
pub use rule::Rule;
#[doc(no_inline)]
pub use scrollable::Scrollable;
#[doc(no_inline)]
pub use slider::Slider;
#[doc(no_inline)]
pub use space::Space;
#[doc(no_inline)]
pub use svg::Svg;
#[doc(no_inline)]
pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
#[doc(no_inline)]
pub use tree::Tree;
#[doc(no_inline)]
pub use vertical_slider::VerticalSlider;
pub use action::Action;
pub use id::Id;
pub use operation::Operation;
pub use text::Text;
pub use tree::Tree;
use crate::event::{self, Event};
use crate::layout;
use crate::layout::{self, Layout};
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell};
use crate::{Clipboard, Length, Rectangle, Shell};
/// A component that displays information and allows interaction.
///
@ -110,12 +33,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.8/examples
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry
/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
@ -144,7 +67,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
viewport: &Rectangle,
);
@ -188,7 +111,7 @@ where
_state: &mut Tree,
_event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
_cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
@ -203,7 +126,7 @@ where
&self,
_state: &Tree,
_layout: Layout<'_>,
_cursor_position: Point,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {

View file

@ -24,7 +24,7 @@ impl Id {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Internal {
enum Internal {
Unique(usize),
Custom(borrow::Cow<'static, str>),
}

View file

@ -0,0 +1,226 @@
//! Query or update internal widget state.
pub mod focusable;
pub mod scrollable;
pub mod text_input;
pub use focusable::Focusable;
pub use scrollable::Scrollable;
pub use text_input::TextInput;
use crate::widget::Id;
use std::any::Any;
use std::fmt;
use std::rc::Rc;
/// A piece of logic that can traverse the widget tree of an application in
/// order to query or update some widget state.
pub trait Operation<T> {
/// Operates on a widget that contains other widgets.
///
/// The `operate_on_children` function can be called to return control to
/// the widget tree and keep traversing it.
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
);
/// Operates on a widget that can be focused.
fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
/// Operates on a widget that can be scrolled.
fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
/// Operates on a widget that has text input.
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
/// Operates on a custom widget with some state.
fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {}
/// Finishes the [`Operation`] and returns its [`Outcome`].
fn finish(&self) -> Outcome<T> {
Outcome::None
}
}
/// The result of an [`Operation`].
pub enum Outcome<T> {
/// The [`Operation`] produced no result.
None,
/// The [`Operation`] produced some result.
Some(T),
/// The [`Operation`] needs to be followed by another [`Operation`].
Chain(Box<dyn Operation<T>>),
}
impl<T> fmt::Debug for Outcome<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "Outcome::None"),
Self::Some(output) => write!(f, "Outcome::Some({output:?})"),
Self::Chain(_) => write!(f, "Outcome::Chain(...)"),
}
}
}
/// Maps the output of an [`Operation`] using the given function.
pub fn map<A, B>(
operation: Box<dyn Operation<A>>,
f: impl Fn(A) -> B + 'static,
) -> impl Operation<B>
where
A: 'static,
B: 'static,
{
#[allow(missing_debug_implementations)]
struct Map<A, B> {
operation: Box<dyn Operation<A>>,
f: Rc<dyn Fn(A) -> B>,
}
impl<A, B> Operation<B> for Map<A, B>
where
A: 'static,
B: 'static,
{
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
struct MapRef<'a, A> {
operation: &'a mut dyn Operation<A>,
}
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, .. } = self;
operation.container(id, &mut |operation| {
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);
}
fn text_input(
&mut self,
state: &mut dyn TextInput,
id: Option<&Id>,
) {
self.operation.text_input(state, id);
}
fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
self.operation.custom(state, id);
}
}
let Self { operation, .. } = self;
MapRef {
operation: operation.as_mut(),
}
.container(id, operate_on_children);
}
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
self.operation.focusable(state, id);
}
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
self.operation.scrollable(state, id);
}
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
self.operation.text_input(state, id);
}
fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
self.operation.custom(state, id);
}
fn finish(&self) -> Outcome<B> {
match self.operation.finish() {
Outcome::None => Outcome::None,
Outcome::Some(output) => Outcome::Some((self.f)(output)),
Outcome::Chain(next) => Outcome::Chain(Box::new(Map {
operation: next,
f: self.f.clone(),
})),
}
}
}
Map {
operation,
f: Rc::new(f),
}
}
/// Produces an [`Operation`] that applies the given [`Operation`] to the
/// children of a container with the given [`Id`].
pub fn scope<T: 'static>(
target: Id,
operation: impl Operation<T> + 'static,
) -> impl Operation<T> {
struct ScopedOperation<Message> {
target: Id,
operation: Box<dyn Operation<Message>>,
}
impl<Message: 'static> Operation<Message> for ScopedOperation<Message> {
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
) {
if id == Some(&self.target) {
operate_on_children(self.operation.as_mut());
} else {
operate_on_children(self);
}
}
fn finish(&self) -> Outcome<Message> {
match self.operation.finish() {
Outcome::Chain(next) => {
Outcome::Chain(Box::new(ScopedOperation {
target: self.target.clone(),
operation: next,
}))
}
outcome => outcome,
}
}
}
ScopedOperation {
target,
operation: Box::new(operation),
}
}

View file

@ -5,6 +5,9 @@ use crate::widget::{Id, Operation};
pub trait Scrollable {
/// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis.
fn snap_to(&mut self, offset: RelativeOffset);
/// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis.
fn scroll_to(&mut self, offset: AbsoluteOffset);
}
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
@ -34,7 +37,43 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
SnapTo { target, offset }
}
/// The amount of offset in each direction of a [`Scrollable`].
/// Produces an [`Operation`] that scrolls the widget with the given [`Id`] to
/// the provided [`AbsoluteOffset`].
pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
struct ScrollTo {
target: Id,
offset: AbsoluteOffset,
}
impl<T> Operation<T> for ScrollTo {
fn container(
&mut self,
_id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self)
}
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
if Some(&self.target) == id {
state.scroll_to(self.offset);
}
}
}
ScrollTo { target, offset }
}
/// The amount of absolute offset in each direction of a [`Scrollable`].
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct AbsoluteOffset {
/// The amount of horizontal offset
pub x: f32,
/// The amount of vertical offset
pub y: f32,
}
/// The amount of relative offset in each direction of a [`Scrollable`].
///
/// A value of `0.0` means start, while `1.0` means end.
#[derive(Debug, Clone, Copy, PartialEq, Default)]

View file

@ -1,30 +1,17 @@
//! Write some text for your users to read.
use crate::alignment;
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::text;
use crate::widget::Tree;
use crate::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget};
use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget};
use std::borrow::Cow;
pub use iced_style::text::{Appearance, StyleSheet};
pub use text::{LineHeight, Shaping};
/// A paragraph of text.
///
/// # Example
///
/// ```
/// # use iced_native::Color;
/// #
/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>;
/// #
/// Text::new("I <3 iced!")
/// .size(40)
/// .style(Color::from([0.0, 0.0, 1.0]));
/// ```
///
/// ![Text drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
#[allow(missing_debug_implementations)]
pub struct Text<'a, Renderer>
where
@ -33,11 +20,13 @@ where
{
content: Cow<'a, str>,
size: Option<f32>,
line_height: LineHeight,
width: Length,
height: Length,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
font: Renderer::Font,
font: Option<Renderer::Font>,
shaping: Shaping,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -51,11 +40,13 @@ where
Text {
content: content.into(),
size: None,
font: Default::default(),
line_height: LineHeight::default(),
font: None,
width: Length::Shrink,
height: Length::Shrink,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: Shaping::Basic,
style: Default::default(),
}
}
@ -66,11 +57,17 @@ where
self
}
/// Sets the [`LineHeight`] of the [`Text`].
pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
self.line_height = line_height.into();
self
}
/// Sets the [`Font`] of the [`Text`].
///
/// [`Font`]: crate::text::Renderer::Font
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = font.into();
self.font = Some(font.into());
self
}
@ -112,6 +109,12 @@ where
self.vertical_alignment = alignment;
self
}
/// Sets the [`Shaping`] strategy of the [`Text`].
pub fn shaping(mut self, shaping: Shaping) -> Self {
self.shaping = shaping;
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>
@ -136,12 +139,16 @@ where
let size = self.size.unwrap_or_else(|| renderer.default_size());
let bounds = limits.max();
let bounds = renderer.measure(
&self.content,
size,
self.line_height,
self.font.unwrap_or_else(|| renderer.default_font()),
limits.max(),
self.shaping,
);
let (width, height) =
renderer.measure(&self.content, size, self.font.clone(), bounds);
let size = limits.resolve(Size::new(width, height));
let size = limits.resolve(bounds);
layout::Node::new(size)
}
@ -153,7 +160,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_cursor_position: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
@ -162,10 +169,12 @@ where
layout,
&self.content,
self.size,
self.font.clone(),
theme.appearance(self.style),
self.line_height,
self.font,
theme.appearance(self.style.clone()),
self.horizontal_alignment,
self.vertical_alignment,
self.shaping,
);
}
}
@ -186,10 +195,12 @@ pub fn draw<Renderer>(
layout: Layout<'_>,
content: &str,
size: Option<f32>,
font: Renderer::Font,
line_height: LineHeight,
font: Option<Renderer::Font>,
appearance: Appearance,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
) where
Renderer: text::Renderer,
{
@ -207,14 +218,18 @@ pub fn draw<Renderer>(
alignment::Vertical::Bottom => bounds.y + bounds.height,
};
renderer.fill_text(crate::text::Text {
let size = size.unwrap_or_else(|| renderer.default_size());
renderer.fill_text(crate::Text {
content,
size: size.unwrap_or_else(|| renderer.default_size()),
size,
line_height,
bounds: Rectangle { x, y, ..bounds },
color: appearance.color.unwrap_or(style.text_color),
font,
font: font.unwrap_or_else(|| renderer.default_font()),
horizontal_alignment,
vertical_alignment,
shaping,
});
}
@ -238,22 +253,52 @@ where
Self {
content: self.content.clone(),
size: self.size,
line_height: self.line_height,
width: self.width,
height: self.height,
horizontal_alignment: self.horizontal_alignment,
vertical_alignment: self.vertical_alignment,
font: self.font.clone(),
style: self.style,
font: self.font,
style: self.style.clone(),
shaping: self.shaping,
}
}
}
impl<'a, Renderer> From<&'a str> for Text<'a, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(content: &'a str) -> Self {
Self::new(content)
}
}
impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer>
where
Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet,
{
fn from(contents: &'a str) -> Self {
Text::new(contents).into()
fn from(content: &'a str) -> Self {
Text::from(content).into()
}
}
/// The style sheet of some text.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default + Clone;
/// Produces the [`Appearance`] of some text.
fn appearance(&self, style: Self::Style) -> Appearance;
}
/// The apperance of some text.
#[derive(Debug, Clone, Copy, Default)]
pub struct Appearance {
/// The [`Color`] of the text.
///
/// The default, `None`, means using the inherited color.
pub color: Option<Color>,
}

15
core/src/window.rs Normal file
View file

@ -0,0 +1,15 @@
//! Build window-based GUI applications.
pub mod icon;
mod event;
mod level;
mod mode;
mod redraw_request;
mod user_attention;
pub use event::Event;
pub use icon::Icon;
pub use level::Level;
pub use mode::Mode;
pub use redraw_request::RedrawRequest;
pub use user_attention::UserAttention;

80
core/src/window/icon.rs Normal file
View file

@ -0,0 +1,80 @@
//! Change the icon of a window.
use crate::Size;
use std::mem;
/// Builds an [`Icon`] from its RGBA pixels in the sRGB color space.
pub fn from_rgba(
rgba: Vec<u8>,
width: u32,
height: u32,
) -> Result<Icon, Error> {
const PIXEL_SIZE: usize = mem::size_of::<u8>() * 4;
if rgba.len() % PIXEL_SIZE != 0 {
return Err(Error::ByteCountNotDivisibleBy4 {
byte_count: rgba.len(),
});
}
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
return Err(Error::DimensionsVsPixelCount {
width,
height,
width_x_height: (width * height) as usize,
pixel_count,
});
}
Ok(Icon {
rgba,
size: Size::new(width, height),
})
}
/// An window icon normally used for the titlebar or taskbar.
#[derive(Debug, Clone)]
pub struct Icon {
rgba: Vec<u8>,
size: Size<u32>,
}
impl Icon {
/// Returns the raw data of the [`Icon`].
pub fn into_raw(self) -> (Vec<u8>, Size<u32>) {
(self.rgba, self.size)
}
}
#[derive(Debug, thiserror::Error)]
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
pub enum Error {
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels.
#[error(
"The provided RGBA data (with length {byte_count}) isn't divisible \
by 4. Therefore, it cannot be safely interpreted as 32bpp RGBA pixels"
)]
ByteCountNotDivisibleBy4 {
/// The length of the provided RGBA data.
byte_count: usize,
},
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
/// At least one of your arguments is incorrect.
#[error(
"The number of RGBA pixels ({pixel_count}) does not match the \
provided dimensions ({width}x{height})."
)]
DimensionsVsPixelCount {
/// The provided width.
width: u32,
/// The provided height.
height: u32,
/// The product of `width` and `height`.
width_x_height: usize,
/// The amount of pixels of the provided RGBA data.
pixel_count: usize,
},
}

19
core/src/window/level.rs Normal file
View file

@ -0,0 +1,19 @@
/// A window level groups windows with respect to their z-position.
///
/// The relative ordering between windows in different window levels is fixed.
/// The z-order of a window within the same window level may change dynamically
/// on user interaction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Level {
/// The default behavior.
#[default]
Normal,
/// The window will always be below normal windows.
///
/// This is useful for a widget-based app.
AlwaysOnBottom,
/// The window will always be on top of normal windows.
AlwaysOnTop,
}

View file

View file

@ -1,4 +1,6 @@
use crate::window::{Icon, Position};
use crate::window::{Icon, Level, Position};
pub use iced_winit::settings::PlatformSpecific;
/// The window settings of an application.
#[derive(Debug, Clone)]
@ -27,11 +29,14 @@ pub struct Settings {
/// Whether the window should be transparent.
pub transparent: bool,
/// Whether the window will always be on top of other windows.
pub always_on_top: bool,
/// The window [`Level`].
pub level: Level,
/// The icon of the window.
pub icon: Option<Icon>,
/// Platform specific settings.
pub platform_specific: PlatformSpecific,
}
impl Default for Settings {
@ -45,8 +50,27 @@ impl Default for Settings {
resizable: true,
decorations: true,
transparent: false,
always_on_top: false,
level: Level::default(),
icon: None,
platform_specific: Default::default(),
}
}
}
impl From<Settings> for iced_winit::settings::Window {
fn from(settings: Settings) -> Self {
Self {
size: settings.size,
position: iced_winit::Position::from(settings.position),
min_size: settings.min_size,
max_size: settings.max_size,
visible: settings.visible,
resizable: settings.resizable,
decorations: settings.decorations,
transparent: settings.transparent,
level: settings.level,
icon: settings.icon.map(Icon::into),
platform_specific: settings.platform_specific,
}
}
}

52
docs/release_summary.py Normal file
View file

@ -0,0 +1,52 @@
import re
import sys
import requests
from typing import List, Tuple
if len(sys.argv) < 3:
print("Usage: python release_summary.py <personal_access_token> <previous_release_branch>")
exit(1)
TOKEN = sys.argv[1]
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
PR_COMMIT_REGEX = re.compile(r"(?i)Merge pull request #(\d+).*")
def get_merged_prs_since_release(repo: str, previous_release_branch: str) -> List[Tuple[str, int, str, str]]:
prs = []
compare_url = f"https://api.github.com/repos/{repo}/compare/{previous_release_branch}...master"
compare_response = requests.get(compare_url, headers=HEADERS)
if compare_response.status_code == 200:
compare_data = compare_response.json()
for commit in compare_data["commits"]:
match = PR_COMMIT_REGEX.search(commit["commit"]["message"])
if match:
pr_number = int(match.group(1))
pr_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
pr_response = requests.get(pr_url, headers=HEADERS)
if pr_response.status_code == 200:
pr_data = pr_response.json()
prs.append((pr_data["title"], pr_number, pr_data["html_url"], pr_data["user"]["login"]))
else:
print(f"Error fetching PR {pr_number}: {pr_response.status_code}")
else:
print(f"Error comparing branches: {compare_response.status_code}")
return prs
def print_pr_list(prs: List[Tuple[str, int, str, str]]):
for pr in prs:
print(f"- {pr[0]}. [#{pr[1]}]({pr[2]})")
def print_authors(prs: List[Tuple[str, int, str, str]]):
authors = set(pr[3] for pr in prs)
print("\nAuthors:")
for author in sorted(authors, key=str.casefold):
print(f"- @{author}")
if __name__ == "__main__":
repo = "iced-rs/iced"
previous_release_branch = sys.argv[2]
merged_prs = get_merged_prs_since_release(repo, previous_release_branch)
print_pr_list(merged_prs)
print_authors(merged_prs)

View file

@ -93,8 +93,7 @@ A bunch of simpler examples exist:
- [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
- [`events`](events), a log of native events displayed using a conditional `Subscription`.
- [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu).
- [`integration_opengl`](integration_opengl), a demonstration of how to integrate Iced in an existing OpenGL application.
- [`integration_wgpu`](integration_wgpu), a demonstration of how to integrate Iced in an existing [`wgpu`] application.
- [`integration`](integration), a demonstration of how to integrate Iced in an existing [`wgpu`] application.
- [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized.
- [`pick_list`](pick_list), a dropdown list of selectable options.
- [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI].

View file

@ -1,12 +1,13 @@
use std::{f32::consts::PI, time::Instant};
use iced::executor;
use iced::mouse;
use iced::widget::canvas::{
self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
self, stroke, Cache, Canvas, Geometry, Path, Stroke,
};
use iced::{
Application, Command, Element, Length, Point, Rectangle, Settings,
Subscription, Theme,
Application, Command, Element, Length, Point, Rectangle, Renderer,
Settings, Subscription, Theme,
};
pub fn main() -> iced::Result {
@ -75,11 +76,12 @@ impl<Message> canvas::Program<Message> for Arc {
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let geometry = self.cache.draw(bounds.size(), |frame| {
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
let palette = theme.palette();
let center = frame.center();

View file

@ -61,10 +61,8 @@ impl Sandbox for Example {
mod bezier {
use iced::mouse;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{
self, Canvas, Cursor, Frame, Geometry, Path, Stroke,
};
use iced::{Element, Length, Point, Rectangle, Theme};
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
#[derive(Default)]
pub struct State {
@ -100,10 +98,10 @@ mod bezier {
state: &mut Self::State,
event: Event,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> (event::Status, Option<Curve>) {
let cursor_position =
if let Some(position) = cursor.position_in(&bounds) {
if let Some(position) = cursor.position_in(bounds) {
position
} else {
return (event::Status::Ignored, None);
@ -152,22 +150,26 @@ mod bezier {
fn draw(
&self,
state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> Vec<Geometry> {
let content =
self.state.cache.draw(bounds.size(), |frame: &mut Frame| {
let content = self.state.cache.draw(
renderer,
bounds.size(),
|frame: &mut Frame| {
Curve::draw_all(self.curves, frame);
frame.stroke(
&Path::rectangle(Point::ORIGIN, frame.size()),
Stroke::default().with_width(2.0),
);
});
},
);
if let Some(pending) = state {
let pending_curve = pending.draw(bounds, cursor);
let pending_curve = pending.draw(renderer, bounds, cursor);
vec![content, pending_curve]
} else {
@ -179,9 +181,9 @@ mod bezier {
&self,
_state: &Self::State,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> mouse::Interaction {
if cursor.is_over(&bounds) {
if cursor.is_over(bounds) {
mouse::Interaction::Crosshair
} else {
mouse::Interaction::default()
@ -216,10 +218,15 @@ mod bezier {
}
impl Pending {
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry {
let mut frame = Frame::new(bounds.size());
fn draw(
&self,
renderer: &Renderer,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Geometry {
let mut frame = Frame::new(renderer, bounds.size());
if let Some(cursor_position) = cursor.position_in(&bounds) {
if let Some(cursor_position) = cursor.position_in(bounds) {
match *self {
Pending::One { from } => {
let line = Path::line(from, cursor_position);

Binary file not shown.

View file

@ -1,10 +1,9 @@
use iced::widget::{checkbox, column, container};
use iced::{Element, Font, Length, Sandbox, Settings};
use iced::executor;
use iced::font::{self, Font};
use iced::widget::{checkbox, column, container, text};
use iced::{Application, Command, Element, Length, Settings, Theme};
const ICON_FONT: Font = Font::External {
name: "Icons",
bytes: include_bytes!("../fonts/icons.ttf"),
};
const ICON_FONT: Font = Font::with_name("icons");
pub fn main() -> iced::Result {
Example::run(Settings::default())
@ -20,24 +19,35 @@ struct Example {
enum Message {
DefaultChecked(bool),
CustomChecked(bool),
FontLoaded(Result<(), font::Error>),
}
impl Sandbox for Example {
impl Application for Example {
type Message = Message;
type Flags = ();
type Executor = executor::Default;
type Theme = Theme;
fn new() -> Self {
Default::default()
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
(
Self::default(),
font::load(include_bytes!("../fonts/icons.ttf").as_slice())
.map(Message::FontLoaded),
)
}
fn title(&self) -> String {
String::from("Checkbox - Iced")
}
fn update(&mut self, message: Message) {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::DefaultChecked(value) => self.default_checkbox = value,
Message::CustomChecked(value) => self.custom_checkbox = value,
Message::FontLoaded(_) => (),
}
Command::none()
}
fn view(&self) -> Element<Message> {
@ -49,6 +59,8 @@ impl Sandbox for Example {
font: ICON_FONT,
code_point: '\u{e901}',
size: None,
line_height: text::LineHeight::Relative(1.0),
shaping: text::Shaping::Basic,
});
let content = column![default_checkbox, custom_checkbox].spacing(22);

View file

@ -1,11 +1,10 @@
use iced::executor;
use iced::widget::canvas::{
stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke,
};
use iced::mouse;
use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
use iced::widget::{canvas, container};
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
Subscription, Theme, Vector,
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
Settings, Subscription, Theme, Vector,
};
pub fn main() -> iced::Result {
@ -83,17 +82,18 @@ impl Application for Clock {
}
}
impl<Message> canvas::Program<Message> for Clock {
impl<Message> canvas::Program<Message, Renderer> for Clock {
type State = ();
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: Cursor,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let clock = self.clock.draw(bounds.size(), |frame| {
let clock = self.clock.draw(renderer, bounds.size(), |frame| {
let center = frame.center();
let radius = frame.width().min(frame.height()) / 2.0;

View file

@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "palette"] }
palette = "0.6.0"
palette = "0.7.0"

View file

@ -1,10 +1,14 @@
use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path};
use iced::alignment::{self, Alignment};
use iced::mouse;
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
use iced::widget::{column, row, text, Slider};
use iced::{
alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox,
Settings, Size, Vector,
Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings,
Size, Vector,
};
use palette::{
self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
};
use palette::{self, convert::FromColor, Hsl, Srgb};
use std::marker::PhantomData;
use std::ops::RangeInclusive;
@ -49,12 +53,12 @@ impl Sandbox for ColorPalette {
fn update(&mut self, message: Message) {
let srgb = match message {
Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb),
Message::HslColorChanged(hsl) => palette::Srgb::from_color(hsl),
Message::HsvColorChanged(hsv) => palette::Srgb::from_color(hsv),
Message::HwbColorChanged(hwb) => palette::Srgb::from_color(hwb),
Message::LabColorChanged(lab) => palette::Srgb::from_color(lab),
Message::LchColorChanged(lch) => palette::Srgb::from_color(lch),
Message::RgbColorChanged(rgb) => Rgb::from(rgb),
Message::HslColorChanged(hsl) => Rgb::from_color(hsl),
Message::HsvColorChanged(hsv) => Rgb::from_color(hsv),
Message::HwbColorChanged(hwb) => Rgb::from_color(hwb),
Message::LabColorChanged(lab) => Rgb::from_color(lab),
Message::LchColorChanged(lch) => Rgb::from_color(lch),
};
self.theme = Theme::new(srgb);
@ -63,7 +67,7 @@ impl Sandbox for ColorPalette {
fn view(&self) -> Element<Message> {
let base = self.theme.base;
let srgb = palette::Srgb::from(base);
let srgb = Rgb::from(base);
let hsl = palette::Hsl::from_color(srgb);
let hsv = palette::Hsv::from_color(srgb);
let hwb = palette::Hwb::from_color(srgb);
@ -95,12 +99,10 @@ struct Theme {
impl Theme {
pub fn new(base: impl Into<Color>) -> Theme {
use palette::{Hue, Shade};
let base = base.into();
// Convert to HSL color for manipulation
let hsl = Hsl::from_color(Srgb::from(base));
let hsl = Hsl::from_color(Rgb::from(base));
let lower = [
hsl.shift_hue(-135.0).lighten(0.075),
@ -119,12 +121,12 @@ impl Theme {
Theme {
lower: lower
.iter()
.map(|&color| Srgb::from_color(color).into())
.map(|&color| Rgb::from_color(color).into())
.collect(),
base,
higher: higher
.iter()
.map(|&color| Srgb::from_color(color).into())
.map(|&color| Rgb::from_color(color).into())
.collect(),
canvas_cache: canvas::Cache::default(),
}
@ -209,14 +211,14 @@ impl Theme {
text.vertical_alignment = alignment::Vertical::Bottom;
let hsl = Hsl::from_color(Srgb::from(self.base));
let hsl = Hsl::from_color(Rgb::from(self.base));
for i in 0..self.len() {
let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0);
let graded = Hsl {
lightness: 1.0 - pct,
..hsl
};
let color: Color = Srgb::from_color(graded).into();
let color: Color = Rgb::from_color(graded).into();
let anchor = Point {
x: (i as f32) * box_size.width,
@ -243,11 +245,12 @@ impl<Message> canvas::Program<Message> for Theme {
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &iced::Theme,
bounds: Rectangle,
_cursor: Cursor,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
let theme = self.canvas_cache.draw(bounds.size(), |frame| {
let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
self.draw(frame);
});
@ -351,7 +354,7 @@ impl ColorSpace for palette::Hsl {
fn components(&self) -> [f32; 3] {
[
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
self.saturation,
self.lightness,
]
@ -360,7 +363,7 @@ impl ColorSpace for palette::Hsl {
fn to_string(&self) -> String {
format!(
"hsl({:.1}, {:.1}%, {:.1}%)",
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
100.0 * self.saturation,
100.0 * self.lightness
)
@ -377,13 +380,17 @@ impl ColorSpace for palette::Hsv {
}
fn components(&self) -> [f32; 3] {
[self.hue.to_positive_degrees(), self.saturation, self.value]
[
self.hue.into_positive_degrees(),
self.saturation,
self.value,
]
}
fn to_string(&self) -> String {
format!(
"hsv({:.1}, {:.1}%, {:.1}%)",
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
100.0 * self.saturation,
100.0 * self.value
)
@ -405,7 +412,7 @@ impl ColorSpace for palette::Hwb {
fn components(&self) -> [f32; 3] {
[
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
self.whiteness,
self.blackness,
]
@ -414,7 +421,7 @@ impl ColorSpace for palette::Hwb {
fn to_string(&self) -> String {
format!(
"hwb({:.1}, {:.1}%, {:.1}%)",
self.hue.to_positive_degrees(),
self.hue.into_positive_degrees(),
100.0 * self.whiteness,
100.0 * self.blackness
)
@ -449,7 +456,7 @@ impl ColorSpace for palette::Lch {
}
fn components(&self) -> [f32; 3] {
[self.l, self.chroma, self.hue.to_positive_degrees()]
[self.l, self.chroma, self.hue.into_positive_degrees()]
}
fn to_string(&self) -> String {
@ -457,7 +464,7 @@ impl ColorSpace for palette::Lch {
"Lch({:.1}, {:.1}, {:.1})",
self.l,
self.chroma,
self.hue.to_positive_degrees()
self.hue.into_positive_degrees()
)
}
}

View file

@ -6,6 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_native = { path = "../../native" }
iced_lazy = { path = "../../lazy" }
iced = { path = "../..", features = ["debug", "lazy"] }

View file

@ -47,9 +47,8 @@ impl Sandbox for Component {
mod numeric_input {
use iced::alignment::{self, Alignment};
use iced::widget::{self, button, row, text, text_input};
use iced::{Element, Length};
use iced_lazy::{self, Component};
use iced::widget::{button, component, row, text, text_input, Component};
use iced::{Element, Length, Renderer};
pub struct NumericInput<Message> {
value: Option<u32>,
@ -82,13 +81,7 @@ mod numeric_input {
}
}
impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message>
where
Renderer: iced_native::text::Renderer + 'static,
Renderer::Theme: widget::button::StyleSheet
+ widget::text_input::StyleSheet
+ widget::text::StyleSheet,
{
impl<Message> Component<Message, Renderer> for NumericInput<Message> {
type State = ();
type Event = Event;
@ -141,8 +134,8 @@ mod numeric_input {
.map(u32::to_string)
.as_deref()
.unwrap_or(""),
Event::InputChanged,
)
.on_input(Event::InputChanged)
.padding(10),
button("+", Event::IncrementPressed),
]
@ -152,17 +145,12 @@ mod numeric_input {
}
}
impl<'a, Message, Renderer> From<NumericInput<Message>>
for Element<'a, Message, Renderer>
impl<'a, Message> From<NumericInput<Message>> for Element<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'static + iced_native::text::Renderer,
Renderer::Theme: widget::button::StyleSheet
+ widget::text_input::StyleSheet
+ widget::text::StyleSheet,
{
fn from(numeric_input: NumericInput<Message>) -> Self {
iced_lazy::component(numeric_input)
component(numeric_input)
}
}
}

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
iced = { path = "../..", features = ["advanced"] }

View file

@ -1,9 +1,10 @@
//! 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};
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::mouse;
use iced::{Color, Element, Length, Rectangle, Size};
pub struct CustomQuad {
size: f32,
@ -48,7 +49,7 @@ mod quad {
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
iced = { path = "../..", features = ["advanced"] }

View file

@ -9,10 +9,11 @@ mod circle {
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
use iced_native::layout::{self, Layout};
use iced_native::renderer;
use iced_native::widget::{self, Widget};
use iced_native::{Color, Element, Length, Point, Rectangle, Size};
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::mouse;
use iced::{Color, Element, Length, Rectangle, Size};
pub struct Circle {
radius: f32,
@ -55,7 +56,7 @@ mod circle {
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(

View file

@ -7,8 +7,6 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["tokio"] }
iced_native = { path = "../../native" }
iced_futures = { path = "../../futures" }
[dependencies.reqwest]
version = "0.11"

View file

@ -1,4 +1,4 @@
use iced_native::subscription;
use iced::subscription;
use std::hash::Hash;
@ -18,10 +18,7 @@ pub struct Download<I> {
url: String,
}
async fn download<I: Copy>(
id: I,
state: State,
) -> (Option<(I, Progress)>, State) {
async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) {
match state {
State::Ready(url) => {
let response = reqwest::get(&url).await;
@ -30,7 +27,7 @@ async fn download<I: Copy>(
Ok(response) => {
if let Some(total) = response.content_length() {
(
Some((id, Progress::Started)),
(id, Progress::Started),
State::Downloading {
response,
total,
@ -38,10 +35,10 @@ async fn download<I: Copy>(
},
)
} else {
(Some((id, Progress::Errored)), State::Finished)
((id, Progress::Errored), State::Finished)
}
}
Err(_) => (Some((id, Progress::Errored)), State::Finished),
Err(_) => ((id, Progress::Errored), State::Finished),
}
}
State::Downloading {
@ -55,7 +52,7 @@ async fn download<I: Copy>(
let percentage = (downloaded as f32 / total as f32) * 100.0;
(
Some((id, Progress::Advanced(percentage))),
(id, Progress::Advanced(percentage)),
State::Downloading {
response,
total,
@ -63,8 +60,8 @@ async fn download<I: Copy>(
},
)
}
Ok(None) => (Some((id, Progress::Finished)), State::Finished),
Err(_) => (Some((id, Progress::Errored)), State::Finished),
Ok(None) => ((id, Progress::Finished), State::Finished),
Err(_) => ((id, Progress::Errored), State::Finished),
},
State::Finished => {
// We do not let the stream die, as it would start a

View file

@ -7,4 +7,3 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_native = { path = "../../native" }

View file

@ -1,12 +1,13 @@
use iced::alignment;
use iced::executor;
use iced::subscription;
use iced::widget::{button, checkbox, container, text, Column};
use iced::window;
use iced::Event;
use iced::{
Alignment, Application, Command, Element, Length, Settings, Subscription,
Theme,
};
use iced_native::Event;
pub fn main() -> iced::Result {
Events::run(Settings {
@ -17,13 +18,13 @@ pub fn main() -> iced::Result {
#[derive(Debug, Default)]
struct Events {
last: Vec<iced_native::Event>,
last: Vec<Event>,
enabled: bool,
}
#[derive(Debug, Clone)]
enum Message {
EventOccurred(iced_native::Event),
EventOccurred(Event),
Toggled(bool),
Exit(window::Id),
}
@ -70,7 +71,7 @@ impl Application for Events {
}
fn subscription(&self) -> Subscription<Message> {
iced_native::subscription::events().map(Message::EventOccurred)
subscription::events().map(Message::EventOccurred)
}
fn view(&self) -> Element<Message> {

View file

@ -145,7 +145,7 @@ impl Application for GameOfLife {
self.grid
.view()
.map(move |message| Message::Grid(message, version)),
controls
controls,
];
container(content)
@ -204,15 +204,14 @@ fn view_controls<'a>(
mod grid {
use crate::Preset;
use iced::alignment;
use iced::mouse;
use iced::touch;
use iced::widget::canvas;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{
Cache, Canvas, Cursor, Frame, Geometry, Path, Text,
};
use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
use iced::{
alignment, mouse, Color, Element, Length, Point, Rectangle, Size,
Theme, Vector,
Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
};
use rustc_hash::{FxHashMap, FxHashSet};
use std::future::Future;
@ -401,14 +400,14 @@ mod grid {
interaction: &mut Interaction,
event: Event,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
*interaction = Interaction::None;
}
let cursor_position =
if let Some(position) = cursor.position_in(&bounds) {
if let Some(position) = cursor.position_in(bounds) {
position
} else {
return (event::Status::Ignored, None);
@ -536,13 +535,14 @@ mod grid {
fn draw(
&self,
_interaction: &Interaction,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> Vec<Geometry> {
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
let life = self.life_cache.draw(bounds.size(), |frame| {
let life = self.life_cache.draw(renderer, bounds.size(), |frame| {
let background = Path::rectangle(Point::ORIGIN, frame.size());
frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B));
@ -565,12 +565,11 @@ mod grid {
});
let overlay = {
let mut frame = Frame::new(bounds.size());
let mut frame = Frame::new(renderer, bounds.size());
let hovered_cell =
cursor.position_in(&bounds).map(|position| {
Cell::at(self.project(position, frame.size()))
});
let hovered_cell = cursor.position_in(bounds).map(|position| {
Cell::at(self.project(position, frame.size()))
});
if let Some(cell) = hovered_cell {
frame.with_save(|frame| {
@ -626,38 +625,40 @@ mod grid {
if self.scaling < 0.2 || !self.show_lines {
vec![life, overlay]
} else {
let grid = self.grid_cache.draw(bounds.size(), |frame| {
frame.translate(center);
frame.scale(self.scaling);
frame.translate(self.translation);
frame.scale(Cell::SIZE as f32);
let grid =
self.grid_cache.draw(renderer, bounds.size(), |frame| {
frame.translate(center);
frame.scale(self.scaling);
frame.translate(self.translation);
frame.scale(Cell::SIZE as f32);
let region = self.visible_region(frame.size());
let rows = region.rows();
let columns = region.columns();
let (total_rows, total_columns) =
(rows.clone().count(), columns.clone().count());
let width = 2.0 / Cell::SIZE as f32;
let color = Color::from_rgb8(70, 74, 83);
let region = self.visible_region(frame.size());
let rows = region.rows();
let columns = region.columns();
let (total_rows, total_columns) =
(rows.clone().count(), columns.clone().count());
let width = 2.0 / Cell::SIZE as f32;
let color = Color::from_rgb8(70, 74, 83);
frame.translate(Vector::new(-width / 2.0, -width / 2.0));
frame
.translate(Vector::new(-width / 2.0, -width / 2.0));
for row in region.rows() {
frame.fill_rectangle(
Point::new(*columns.start() as f32, row as f32),
Size::new(total_columns as f32, width),
color,
);
}
for row in region.rows() {
frame.fill_rectangle(
Point::new(*columns.start() as f32, row as f32),
Size::new(total_columns as f32, width),
color,
);
}
for column in region.columns() {
frame.fill_rectangle(
Point::new(column as f32, *rows.start() as f32),
Size::new(width, total_rows as f32),
color,
);
}
});
for column in region.columns() {
frame.fill_rectangle(
Point::new(column as f32, *rows.start() as f32),
Size::new(width, total_rows as f32),
color,
);
}
});
vec![life, grid, overlay]
}
@ -667,13 +668,13 @@ mod grid {
&self,
interaction: &Interaction,
bounds: Rectangle,
cursor: Cursor,
cursor: mouse::Cursor,
) -> mouse::Interaction {
match interaction {
Interaction::Drawing => mouse::Interaction::Crosshair,
Interaction::Erasing => mouse::Interaction::Crosshair,
Interaction::Panning { .. } => mouse::Interaction::Grabbing,
Interaction::None if cursor.is_over(&bounds) => {
Interaction::None if cursor.is_over(bounds) => {
mouse::Interaction::Crosshair
}
_ => mouse::Interaction::default(),

View file

@ -6,6 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
iced_graphics = { path = "../../graphics" }
iced = { path = "../..", features = ["advanced"] }

View file

@ -1,24 +1,12 @@
//! This example showcases a simple native custom widget that renders using
//! arbitrary low-level geometry.
mod rainbow {
// For now, to implement a custom native widget you will need to add
// `iced_native` and `iced_wgpu` to your dependencies.
//
// Then, you simply need to define your widget type and implement the
// `iced_native::Widget` trait with the `iced_wgpu::Renderer`.
//
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
use iced_graphics::renderer::{self, Renderer};
use iced_graphics::triangle::ColoredVertex2D;
use iced_graphics::{Backend, Primitive};
use iced_native::layout;
use iced_native::widget::{self, Widget};
use iced_native::{
Element, Layout, Length, Point, Rectangle, Size, Vector,
};
use iced::advanced::graphics::color;
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::mouse;
use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
#[derive(Debug, Clone, Copy, Default)]
pub struct Rainbow;
@ -27,10 +15,7 @@ mod rainbow {
Rainbow
}
impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow
where
B: Backend,
{
impl<Message> Widget<Message, Renderer> for Rainbow {
fn width(&self) -> Length {
Length::Fill
}
@ -41,7 +26,7 @@ mod rainbow {
fn layout(
&self,
_renderer: &Renderer<B, T>,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let size = limits.width(Length::Fill).resolve(Size::ZERO);
@ -52,17 +37,17 @@ mod rainbow {
fn draw(
&self,
_tree: &widget::Tree,
renderer: &mut Renderer<B, T>,
_theme: &T,
renderer: &mut Renderer,
_theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
cursor_position: Point,
cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
use iced_graphics::triangle::Mesh2D;
use iced_native::Renderer as _;
use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
use iced::advanced::Renderer as _;
let b = layout.bounds();
let bounds = layout.bounds();
// R O Y G B I V
let color_r = [1.0, 0.0, 0.0, 1.0];
@ -75,61 +60,61 @@ mod rainbow {
let color_v = [0.75, 0.0, 0.5, 1.0];
let posn_center = {
if b.contains(cursor_position) {
[cursor_position.x - b.x, cursor_position.y - b.y]
if let Some(cursor_position) = cursor.position_in(bounds) {
[cursor_position.x, cursor_position.y]
} else {
[b.width / 2.0, b.height / 2.0]
[bounds.width / 2.0, bounds.height / 2.0]
}
};
let posn_tl = [0.0, 0.0];
let posn_t = [b.width / 2.0, 0.0];
let posn_tr = [b.width, 0.0];
let posn_r = [b.width, b.height / 2.0];
let posn_br = [b.width, b.height];
let posn_b = [(b.width / 2.0), b.height];
let posn_bl = [0.0, b.height];
let posn_l = [0.0, b.height / 2.0];
let posn_t = [bounds.width / 2.0, 0.0];
let posn_tr = [bounds.width, 0.0];
let posn_r = [bounds.width, bounds.height / 2.0];
let posn_br = [bounds.width, bounds.height];
let posn_b = [(bounds.width / 2.0), bounds.height];
let posn_bl = [0.0, bounds.height];
let posn_l = [0.0, bounds.height / 2.0];
let mesh = Primitive::SolidMesh {
size: b.size(),
buffers: Mesh2D {
let mesh = Mesh::Solid {
size: bounds.size(),
buffers: mesh::Indexed {
vertices: vec![
ColoredVertex2D {
SolidVertex2D {
position: posn_center,
color: [1.0, 1.0, 1.0, 1.0],
color: color::pack([1.0, 1.0, 1.0, 1.0]),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_tl,
color: color_r,
color: color::pack(color_r),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_t,
color: color_o,
color: color::pack(color_o),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_tr,
color: color_y,
color: color::pack(color_y),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_r,
color: color_g,
color: color::pack(color_g),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_br,
color: color_gb,
color: color::pack(color_gb),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_b,
color: color_b,
color: color::pack(color_b),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_bl,
color: color_i,
color: color::pack(color_i),
},
ColoredVertex2D {
SolidVertex2D {
position: posn_l,
color: color_v,
color: color::pack(color_v),
},
],
indices: vec![
@ -145,16 +130,16 @@ mod rainbow {
},
};
renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
renderer.draw_primitive(mesh);
});
renderer.with_translation(
Vector::new(bounds.x, bounds.y),
|renderer| {
renderer.draw_mesh(mesh);
},
);
}
}
impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>>
where
B: Backend,
{
impl<'a, Message> From<Rainbow> for Element<'a, Message, Renderer> {
fn from(rainbow: Rainbow) -> Self {
Self::new(rainbow)
}

View file

@ -1,5 +1,5 @@
[package]
name = "integration_wgpu"
name = "integration"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
@ -7,8 +7,10 @@ publish = false
[dependencies]
iced_winit = { path = "../../winit" }
iced_wgpu = { path = "../../wgpu", features = ["webgl"] }
env_logger = "0.8"
iced_wgpu = { path = "../../wgpu" }
iced_widget = { path = "../../widget" }
iced_renderer = { path = "../../renderer", features = ["wgpu"] }
env_logger = "0.10"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"

View file

@ -8,8 +8,8 @@
<h1>integration_wgpu</h1>
<canvas id="iced_canvas"></canvas>
<script type="module">
import init from "./integration_wgpu.js";
init('./integration_wgpu_bg.wasm');
import init from "./integration.js";
init('./integration_bg.wasm');
</script>
<style>
body {

View file

@ -1,6 +1,8 @@
use iced_wgpu::Renderer;
use iced_winit::widget::{slider, text_input, Column, Row, Text};
use iced_winit::{Alignment, Color, Command, Element, Length, Program};
use iced_widget::{slider, text_input, Column, Row, Text};
use iced_winit::core::{Alignment, Color, Element, Length};
use iced_winit::runtime::{Command, Program};
use iced_winit::style::Theme;
pub struct Controls {
background_color: Color,
@ -27,7 +29,7 @@ impl Controls {
}
impl Program for Controls {
type Renderer = Renderer;
type Renderer = Renderer<Theme>;
type Message = Message;
fn update(&mut self, message: Message) -> Command<Message> {
@ -43,7 +45,7 @@ impl Program for Controls {
Command::none()
}
fn view(&self) -> Element<Message, Renderer> {
fn view(&self) -> Element<Message, Renderer<Theme>> {
let background_color = self.background_color;
let text = &self.text;
@ -100,11 +102,10 @@ impl Program for Controls {
.size(14)
.style(Color::WHITE),
)
.push(text_input(
"Placeholder",
text,
Message::TextChanged,
)),
.push(
text_input("Placeholder", text)
.on_input(Message::TextChanged),
),
),
)
.into()

View file

@ -4,14 +4,17 @@ mod scene;
use controls::Controls;
use scene::Scene;
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
use iced_winit::{
conversion, futures, program, renderer, window, winit, Clipboard, Color,
Debug, Size,
};
use iced_wgpu::graphics::Viewport;
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
use iced_winit::core::mouse;
use iced_winit::core::renderer;
use iced_winit::core::{Color, Size};
use iced_winit::runtime::program;
use iced_winit::runtime::Debug;
use iced_winit::style::Theme;
use iced_winit::{conversion, futures, winit, Clipboard};
use winit::{
dpi::PhysicalPosition,
event::{Event, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
};
@ -23,19 +26,20 @@ use web_sys::HtmlCanvasElement;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::WindowBuilderExtWebSys;
pub fn main() {
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_arch = "wasm32")]
let canvas_element = {
console_log::init_with_level(log::Level::Debug)
.expect("could not initialize logger");
console_log::init_with_level(log::Level::Debug)?;
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
.expect("Canvas with id `iced_canvas` is missing")
.expect("Get canvas element")
};
#[cfg(not(target_arch = "wasm32"))]
env_logger::init();
@ -45,23 +49,21 @@ pub fn main() {
#[cfg(target_arch = "wasm32")]
let window = winit::window::WindowBuilder::new()
.with_canvas(Some(canvas_element))
.build(&event_loop)
.expect("Failed to build winit window");
.build(&event_loop)?;
#[cfg(not(target_arch = "wasm32"))]
let window = winit::window::Window::new(&event_loop).unwrap();
let window = winit::window::Window::new(&event_loop)?;
let physical_size = window.inner_size();
let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height),
window.scale_factor(),
);
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
let mut cursor_position = None;
let mut modifiers = ModifiersState::default();
let mut clipboard = Clipboard::connect(&window);
// Initialize wgpu
#[cfg(target_arch = "wasm32")]
let default_backend = wgpu::Backends::GL;
#[cfg(not(target_arch = "wasm32"))]
@ -70,46 +72,55 @@ pub fn main() {
let backend =
wgpu::util::backend_bits_from_env().unwrap_or(default_backend);
let instance = wgpu::Instance::new(backend);
let surface = unsafe { instance.create_surface(&window) };
let (format, (device, queue)) = futures::executor::block_on(async {
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
&instance,
backend,
Some(&surface),
)
.await
.expect("No suitable GPU adapters found on the system!");
let adapter_features = adapter.features();
#[cfg(target_arch = "wasm32")]
let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits());
#[cfg(not(target_arch = "wasm32"))]
let needed_limits = wgpu::Limits::default();
(
surface
.get_supported_formats(&adapter)
.first()
.copied()
.expect("Get preferred format"),
adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: adapter_features & wgpu::Features::default(),
limits: needed_limits,
},
None,
)
.await
.expect("Request device"),
)
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: backend,
..Default::default()
});
let surface = unsafe { instance.create_surface(&window) }?;
let (format, (device, queue)) =
futures::futures::executor::block_on(async {
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
&instance,
backend,
Some(&surface),
)
.await
.expect("Create adapter");
let adapter_features = adapter.features();
#[cfg(target_arch = "wasm32")]
let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits());
#[cfg(not(target_arch = "wasm32"))]
let needed_limits = wgpu::Limits::default();
let capabilities = surface.get_capabilities(&adapter);
(
capabilities
.formats
.iter()
.copied()
.find(wgpu::TextureFormat::is_srgb)
.or_else(|| capabilities.formats.first().copied())
.expect("Get preferred format"),
adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: adapter_features
& wgpu::Features::default(),
limits: needed_limits,
},
None,
)
.await
.expect("Request device"),
)
});
surface.configure(
&device,
@ -120,22 +131,24 @@ pub fn main() {
height: physical_size.height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
},
);
let mut resized = false;
// Initialize staging belt
let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
// Initialize scene and GUI controls
let scene = Scene::new(&device, format);
let controls = Controls::new();
// Initialize iced
let mut debug = Debug::new();
let mut renderer =
Renderer::new(Backend::new(&device, Settings::default(), format));
let mut renderer = Renderer::new(Backend::new(
&device,
&queue,
Settings::default(),
format,
));
let mut state = program::State::new(
controls,
@ -153,7 +166,7 @@ pub fn main() {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CursorMoved { position, .. } => {
cursor_position = position;
cursor_position = Some(position);
}
WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers;
@ -183,13 +196,20 @@ pub fn main() {
// We update iced
let _ = state.update(
viewport.logical_size(),
conversion::cursor_position(
cursor_position,
viewport.scale_factor(),
),
cursor_position
.map(|p| {
conversion::cursor_position(
p,
viewport.scale_factor(),
)
})
.map(mouse::Cursor::Available)
.unwrap_or(mouse::Cursor::Unavailable),
&mut renderer,
&iced_wgpu::Theme::Dark,
&renderer::Style { text_color: Color::WHITE },
&Theme::Dark,
&renderer::Style {
text_color: Color::WHITE,
},
&mut clipboard,
&mut debug,
);
@ -215,7 +235,8 @@ pub fn main() {
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: wgpu::CompositeAlphaMode::Auto
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
},
);
@ -230,7 +251,9 @@ pub fn main() {
let program = state.program();
let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
let view = frame.texture.create_view(
&wgpu::TextureViewDescriptor::default(),
);
{
// We clear the frame
@ -248,8 +271,9 @@ pub fn main() {
renderer.with_primitives(|backend, primitive| {
backend.present(
&device,
&mut staging_belt,
&queue,
&mut encoder,
None,
&view,
primitive,
&viewport,
@ -258,24 +282,22 @@ pub fn main() {
});
// Then we submit the work
staging_belt.finish();
queue.submit(Some(encoder.finish()));
frame.present();
// Update the mouse cursor
window.set_cursor_icon(
iced_winit::conversion::mouse_interaction(
state.mouse_interaction(),
),
);
// And recall staging buffers
staging_belt.recall();
window.set_cursor_icon(
iced_winit::conversion::mouse_interaction(
state.mouse_interaction(),
),
);
}
Err(error) => match error {
wgpu::SurfaceError::OutOfMemory => {
panic!("Swapchain error: {error}. Rendering cannot continue.")
panic!(
"Swapchain error: {error}. \
Rendering cannot continue."
)
}
_ => {
// Try rendering again next frame.

View file

@ -1,5 +1,5 @@
use iced_wgpu::wgpu;
use iced_winit::Color;
use iced_winit::core::Color;
pub struct Scene {
pipeline: wgpu::RenderPipeline,

View file

@ -1,12 +0,0 @@
[package]
name = "integration_opengl"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
publish = false
[dependencies]
iced_glutin = { path = "../../glutin" }
iced_glow = { path = "../../glow" }
iced_winit = { path = "../../winit" }
env_logger = "0.8"

View file

@ -1,16 +0,0 @@
## OpenGL integration
A demonstration of how to integrate Iced in an existing graphical OpenGL application.
The __[`main`]__ file contains all the code of the example.
<div align="center">
<a href="https://imgbox.com/9P9ETcod" target="_blank"><img src="https://images2.imgbox.com/2a/51/9P9ETcod_o.gif" alt="image host"/></a>
</div>
You can run it with `cargo run`:
```
cargo run --package integration_opengl
```
[`main`]: src/main.rs

View file

@ -1,101 +0,0 @@
use iced_glow::Renderer;
use iced_glutin::widget::Slider;
use iced_glutin::widget::{Column, Row, Text};
use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
pub struct Controls {
background_color: Color,
}
#[derive(Debug, Clone)]
pub enum Message {
BackgroundColorChanged(Color),
}
impl Controls {
pub fn new() -> Controls {
Controls {
background_color: Color::BLACK,
}
}
pub fn background_color(&self) -> Color {
self.background_color
}
}
impl Program for Controls {
type Renderer = Renderer;
type Message = Message;
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::BackgroundColorChanged(color) => {
self.background_color = color;
}
}
Command::none()
}
fn view(&self) -> Element<Message, Renderer> {
let background_color = self.background_color;
let sliders = Row::new()
.width(500)
.spacing(20)
.push(
Slider::new(0.0..=1.0, background_color.r, move |r| {
Message::BackgroundColorChanged(Color {
r,
..background_color
})
})
.step(0.01),
)
.push(
Slider::new(0.0..=1.0, background_color.g, move |g| {
Message::BackgroundColorChanged(Color {
g,
..background_color
})
})
.step(0.01),
)
.push(
Slider::new(0.0..=1.0, background_color.b, move |b| {
Message::BackgroundColorChanged(Color {
b,
..background_color
})
})
.step(0.01),
);
Row::new()
.width(Length::Fill)
.height(Length::Fill)
.align_items(Alignment::End)
.push(
Column::new()
.width(Length::Fill)
.align_items(Alignment::End)
.push(
Column::new()
.padding(10)
.spacing(10)
.push(
Text::new("Background color")
.style(Color::WHITE),
)
.push(sliders)
.push(
Text::new(format!("{background_color:?}"))
.size(14)
.style(Color::WHITE),
),
),
)
.into()
}
}

View file

@ -1,188 +0,0 @@
mod controls;
mod scene;
use controls::Controls;
use scene::Scene;
use glow::*;
use glutin::dpi::PhysicalPosition;
use glutin::event::{Event, ModifiersState, WindowEvent};
use glutin::event_loop::ControlFlow;
use iced_glow::glow;
use iced_glow::{Backend, Renderer, Settings, Viewport};
use iced_glutin::conversion;
use iced_glutin::glutin;
use iced_glutin::renderer;
use iced_glutin::{program, Clipboard, Color, Debug, Size};
pub fn main() {
env_logger::init();
let (gl, event_loop, windowed_context, shader_version) = {
let el = glutin::event_loop::EventLoop::new();
let wb = glutin::window::WindowBuilder::new()
.with_title("OpenGL integration example")
.with_inner_size(glutin::dpi::LogicalSize::new(1024.0, 768.0));
let windowed_context = glutin::ContextBuilder::new()
.with_vsync(true)
.build_windowed(wb, &el)
.unwrap();
unsafe {
let windowed_context = windowed_context.make_current().unwrap();
let gl = glow::Context::from_loader_function(|s| {
windowed_context.get_proc_address(s) as *const _
});
// Enable auto-conversion from/to sRGB
gl.enable(glow::FRAMEBUFFER_SRGB);
// Enable alpha blending
gl.enable(glow::BLEND);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
// Disable multisampling by default
gl.disable(glow::MULTISAMPLE);
(gl, el, windowed_context, "#version 410")
}
};
let physical_size = windowed_context.window().inner_size();
let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height),
windowed_context.window().scale_factor(),
);
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
let mut modifiers = ModifiersState::default();
let mut clipboard = Clipboard::connect(windowed_context.window());
let mut renderer = Renderer::new(Backend::new(&gl, Settings::default()));
let mut debug = Debug::new();
let controls = Controls::new();
let mut state = program::State::new(
controls,
viewport.logical_size(),
&mut renderer,
&mut debug,
);
let mut resized = false;
let scene = Scene::new(&gl, shader_version);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CursorMoved { position, .. } => {
cursor_position = position;
}
WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers;
}
WindowEvent::Resized(physical_size) => {
viewport = Viewport::with_physical_size(
Size::new(
physical_size.width,
physical_size.height,
),
windowed_context.window().scale_factor(),
);
resized = true;
}
WindowEvent::CloseRequested => {
scene.cleanup(&gl);
*control_flow = ControlFlow::Exit
}
_ => (),
}
// Map window event to iced event
if let Some(event) = iced_winit::conversion::window_event(
iced_winit::window::Id::MAIN,
&event,
windowed_context.window().scale_factor(),
modifiers,
) {
state.queue_event(event);
}
}
Event::MainEventsCleared => {
// If there are events pending
if !state.is_queue_empty() {
// We update iced
let _ = state.update(
viewport.logical_size(),
conversion::cursor_position(
cursor_position,
viewport.scale_factor(),
),
&mut renderer,
&iced_glow::Theme::Dark,
&renderer::Style {
text_color: Color::WHITE,
},
&mut clipboard,
&mut debug,
);
// and request a redraw
windowed_context.window().request_redraw();
}
}
Event::RedrawRequested(_) => {
if resized {
let size = windowed_context.window().inner_size();
unsafe {
gl.viewport(
0,
0,
size.width as i32,
size.height as i32,
);
}
resized = false;
}
let program = state.program();
{
// We clear the frame
scene.clear(&gl, program.background_color());
// Draw the scene
scene.draw(&gl);
}
// And then iced on top
renderer.with_primitives(|backend, primitive| {
backend.present(
&gl,
primitive,
&viewport,
&debug.overlay(),
);
});
// Update the mouse cursor
windowed_context.window().set_cursor_icon(
iced_winit::conversion::mouse_interaction(
state.mouse_interaction(),
),
);
windowed_context.swap_buffers().unwrap();
}
_ => (),
}
});
}

View file

@ -1,102 +0,0 @@
use glow::*;
use iced_glow::glow;
use iced_glow::Color;
pub struct Scene {
program: glow::Program,
vertex_array: glow::VertexArray,
}
impl Scene {
pub fn new(gl: &glow::Context, shader_version: &str) -> Self {
unsafe {
let vertex_array = gl
.create_vertex_array()
.expect("Cannot create vertex array");
gl.bind_vertex_array(Some(vertex_array));
let program = gl.create_program().expect("Cannot create program");
let (vertex_shader_source, fragment_shader_source) = (
r#"const vec2 verts[3] = vec2[3](
vec2(0.5f, 1.0f),
vec2(0.0f, 0.0f),
vec2(1.0f, 0.0f)
);
out vec2 vert;
void main() {
vert = verts[gl_VertexID];
gl_Position = vec4(vert - 0.5, 0.0, 1.0);
}"#,
r#"precision highp float;
in vec2 vert;
out vec4 color;
void main() {
color = vec4(vert, 0.5, 1.0);
}"#,
);
let shader_sources = [
(glow::VERTEX_SHADER, vertex_shader_source),
(glow::FRAGMENT_SHADER, fragment_shader_source),
];
let mut shaders = Vec::with_capacity(shader_sources.len());
for (shader_type, shader_source) in shader_sources.iter() {
let shader = gl
.create_shader(*shader_type)
.expect("Cannot create shader");
gl.shader_source(
shader,
&format!("{shader_version}\n{shader_source}"),
);
gl.compile_shader(shader);
if !gl.get_shader_compile_status(shader) {
panic!("{}", gl.get_shader_info_log(shader));
}
gl.attach_shader(program, shader);
shaders.push(shader);
}
gl.link_program(program);
if !gl.get_program_link_status(program) {
panic!("{}", gl.get_program_info_log(program));
}
for shader in shaders {
gl.detach_shader(program, shader);
gl.delete_shader(shader);
}
gl.use_program(Some(program));
Self {
program,
vertex_array,
}
}
}
pub fn clear(&self, gl: &glow::Context, background_color: Color) {
let [r, g, b, a] = background_color.into_linear();
unsafe {
gl.clear_color(r, g, b, a);
gl.clear(glow::COLOR_BUFFER_BIT);
}
}
pub fn draw(&self, gl: &glow::Context) {
unsafe {
gl.bind_vertex_array(Some(self.vertex_array));
gl.use_program(Some(self.program));
gl.draw_arrays(glow::TRIANGLES, 0, 3);
}
}
pub fn cleanup(&self, gl: &glow::Context) {
unsafe {
gl.delete_program(self.program);
gl.delete_vertex_array(self.vertex_array);
}
}
}

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug"] }
iced_lazy = { path = "../../lazy" }
iced = { path = "../..", features = ["debug", "lazy"] }

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