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:
commit
633f405f3f
394 changed files with 17278 additions and 13290 deletions
6
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
6
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
8
.github/workflows/document.yml
vendored
8
.github/workflows/document.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
56
CHANGELOG.md
56
CHANGELOG.md
|
|
@ -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
|
||||
|
|
|
|||
57
Cargo.toml
57
Cargo.toml
|
|
@ -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
33
DEPENDENCIES.md
Normal 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;
|
||||
}
|
||||
```
|
||||
38
README.md
38
README.md
|
|
@ -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
|
||||
|
|
|
|||
14
ROADMAP.md
14
ROADMAP.md
|
|
@ -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].
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
33
core/src/angle.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
22
core/src/border_radius.rs
Normal 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
23
core/src/clipboard.rs
Normal 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) {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
@ -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);
|
||||
116
core/src/font.rs
116
core/src/font.rs
|
|
@ -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
105
core/src/gradient.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//! 
|
||||
//!
|
||||
//! [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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ pub enum Button {
|
|||
Middle,
|
||||
|
||||
/// Some other button.
|
||||
Other(u8),
|
||||
Other(u16),
|
||||
}
|
||||
|
|
|
|||
52
core/src/mouse/cursor.rs
Normal file
52
core/src/mouse/cursor.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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> {
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
212
core/src/text.rs
Normal 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>);
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
@ -24,7 +24,7 @@ impl Id {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Internal {
|
||||
enum Internal {
|
||||
Unique(usize),
|
||||
Custom(borrow::Cow<'static, str>),
|
||||
}
|
||||
226
core/src/widget/operation.rs
Normal file
226
core/src/widget/operation.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
|
|
@ -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)]
|
||||
|
|
@ -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]));
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
#[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
15
core/src/window.rs
Normal 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
80
core/src/window/icon.rs
Normal 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
19
core/src/window/level.rs
Normal 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,
|
||||
}
|
||||
0
core/src/window/position.rs
Normal file
0
core/src/window/position.rs
Normal 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
52
docs/release_summary.py
Normal 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)
|
||||
|
|
@ -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].
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas", "palette"] }
|
||||
palette = "0.6.0"
|
||||
palette = "0.7.0"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,5 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced_native = { path = "../../native" }
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -6,5 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced_native = { path = "../../native" }
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["tokio"] }
|
||||
iced_native = { path = "../../native" }
|
||||
iced_futures = { path = "../../futures" }
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -7,4 +7,3 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
||||
iced_native = { path = "../../native" }
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced_native = { path = "../../native" }
|
||||
iced_graphics = { path = "../../graphics" }
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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()
|
||||
|
|
@ -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.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
use iced_wgpu::wgpu;
|
||||
use iced_winit::Color;
|
||||
use iced_winit::core::Color;
|
||||
|
||||
pub struct Scene {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue