mirror of
https://github.com/emilk/egui.git
synced 2026-06-28 07:23:13 -04:00
Merge branch 'emilk:master' into common-panels
This commit is contained in:
4
.github/workflows/rust.yml
vendored
4
.github/workflows/rust.yml
vendored
@@ -9,7 +9,7 @@ env:
|
||||
|
||||
jobs:
|
||||
fmt-crank-check-test:
|
||||
name: Format + check + test
|
||||
name: Format + check
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -223,7 +223,7 @@ jobs:
|
||||
|
||||
tests:
|
||||
name: Run tests
|
||||
# We run the tests on macOS because it will run with a actual GPU
|
||||
# We run the tests on macOS because it will run with an actual GPU
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
|
||||
10
.typos.toml
10
.typos.toml
@@ -7,5 +7,15 @@ ime = "ime" # Input Method Editor
|
||||
nknown = "nknown" # part of @55nknown username
|
||||
ro = "ro" # read-only, also part of the username @Phen-Ro
|
||||
|
||||
# I mistype these so often
|
||||
tesalator = "tessellator"
|
||||
teselator = "tessellator"
|
||||
tessalator = "tessellator"
|
||||
tesselator = "tessellator"
|
||||
tesalation = "tessellation"
|
||||
teselation = "tessellation"
|
||||
tessalation = "tessellation"
|
||||
tesselation = "tessellation"
|
||||
|
||||
[files]
|
||||
extend-exclude = ["web_demo/egui_demo_app.js"] # auto-generated
|
||||
|
||||
82
CHANGELOG.md
82
CHANGELOG.md
@@ -14,6 +14,88 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.31.0 - 2025-02-04 - Scene container, improved rendering quality
|
||||
|
||||
### Highlights ✨
|
||||
|
||||
#### Scene container
|
||||
This release adds the `Scene` container to egui. It is a pannable, zoomable canvas that can contain `Widget`s and child `Ui`s.
|
||||
This will make it easier to e.g. implement a graph editor.
|
||||
|
||||

|
||||
|
||||
#### Clearer, pixel perfect rendering
|
||||
The tessellator has been updated for improved rendering quality and better performance. It will produce fewer vertices
|
||||
and shapes will have less overdraw. We've also defined what `CornerRadius` (previously `Rounding`) means.
|
||||
|
||||
We've also added a tessellator test to the [demo app](https://www.egui.rs/), where you can play around with different
|
||||
values to see what's produced:
|
||||
|
||||
|
||||
https://github.com/user-attachments/assets/adf55e3b-fb48-4df0-aaa2-150ee3163684
|
||||
|
||||
|
||||
Check the [PR](https://github.com/emilk/egui/pull/5669) for more details.
|
||||
|
||||
#### `CornerRadius`, `Margin`, `Shadow` size reduction
|
||||
In order to pave the path for more complex and customizable styling solutions, we've reduced the size of
|
||||
`CornerRadius`, `Margin` and `Shadow` values to `i8` and `u8`.
|
||||
|
||||
|
||||
|
||||
### Migration guide
|
||||
- Add a `StrokeKind` to all your `Painter::rect` calls [#5648](https://github.com/emilk/egui/pull/5648)
|
||||
- `StrokeKind::default` was removed, since the 'normal' value depends on the context [#5658](https://github.com/emilk/egui/pull/5658)
|
||||
- You probably want to use `StrokeKind::Inside` when drawing rectangles
|
||||
- You probably want to use `StrokeKind::Middle` when drawing open paths
|
||||
- Rename `Rounding` to `CornerRadius` [#5673](https://github.com/emilk/egui/pull/5673)
|
||||
- `CornerRadius`, `Margin` and `Shadow` have been updated to use `i8` and `u8` [#5563](https://github.com/emilk/egui/pull/5563), [#5567](https://github.com/emilk/egui/pull/5567), [#5568](https://github.com/emilk/egui/pull/5568)
|
||||
- Remove the .0 from your values
|
||||
- Cast dynamic values with `as i8` / `as u8` or `as _` if you want Rust to infer the type
|
||||
- Rust will do a 'saturating' cast, so if your `f32` value is bigger than `127` it will be clamped to `127`
|
||||
- `RectShape` parameters changed [#5565](https://github.com/emilk/egui/pull/5565)
|
||||
- Prefer to use the builder methods to create it instead of initializing it directly
|
||||
- `Frame` now takes the `Stroke` width into account for its sizing, so check all views of your app to make sure they still look right.
|
||||
Read the [PR](https://github.com/emilk/egui/pull/5575) for more info.
|
||||
|
||||
### ⭐ Added
|
||||
* Add `egui::Scene` for panning/zooming a `Ui` [#5505](https://github.com/emilk/egui/pull/5505) by [@grtlr](https://github.com/grtlr)
|
||||
* Animated WebP support [#5470](https://github.com/emilk/egui/pull/5470) by [@Aely0](https://github.com/Aely0)
|
||||
* Improve tessellation quality [#5669](https://github.com/emilk/egui/pull/5669) by [@emilk](https://github.com/emilk)
|
||||
* Add `OutputCommand` for copying text and opening URL:s [#5532](https://github.com/emilk/egui/pull/5532) by [@emilk](https://github.com/emilk)
|
||||
* Add `Context::copy_image` [#5533](https://github.com/emilk/egui/pull/5533) by [@emilk](https://github.com/emilk)
|
||||
* Add `WidgetType::Image` and `Image::alt_text` [#5534](https://github.com/emilk/egui/pull/5534) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add `epaint::Brush` for controlling `RectShape` texturing [#5565](https://github.com/emilk/egui/pull/5565) by [@emilk](https://github.com/emilk)
|
||||
* Implement `nohash_hasher::IsEnabled` for `Id` [#5628](https://github.com/emilk/egui/pull/5628) by [@emilk](https://github.com/emilk)
|
||||
* Add keys for `!`, `{`, `}` [#5548](https://github.com/emilk/egui/pull/5548) by [@Its-Just-Nans](https://github.com/Its-Just-Nans)
|
||||
* Add `RectShape::stroke_kind ` to control if stroke is inside/outside/centered [#5647](https://github.com/emilk/egui/pull/5647) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### 🔧 Changed
|
||||
* ⚠️ `Frame` now includes stroke width as part of padding [#5575](https://github.com/emilk/egui/pull/5575) by [@emilk](https://github.com/emilk)
|
||||
* Rename `Rounding` to `CornerRadius` [#5673](https://github.com/emilk/egui/pull/5673) by [@emilk](https://github.com/emilk)
|
||||
* Require a `StrokeKind` when painting rectangles with strokes [#5648](https://github.com/emilk/egui/pull/5648) by [@emilk](https://github.com/emilk)
|
||||
* Round widget coordinates to even multiple of 1/32 [#5517](https://github.com/emilk/egui/pull/5517) by [@emilk](https://github.com/emilk)
|
||||
* Make all lines and rectangles crisp [#5518](https://github.com/emilk/egui/pull/5518) by [@emilk](https://github.com/emilk)
|
||||
* Tweak window resize handles [#5524](https://github.com/emilk/egui/pull/5524) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### 🔥 Removed
|
||||
* Remove `egui::special_emojis::TWITTER` [#5622](https://github.com/emilk/egui/pull/5622) by [@emilk](https://github.com/emilk)
|
||||
* Remove `StrokeKind::default` [#5658](https://github.com/emilk/egui/pull/5658) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### 🐛 Fixed
|
||||
* Use correct minimum version of `profiling` crate [#5494](https://github.com/emilk/egui/pull/5494) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Fix interactive widgets sometimes being incorrectly marked as hovered [#5523](https://github.com/emilk/egui/pull/5523) by [@emilk](https://github.com/emilk)
|
||||
* Fix panic due to non-total ordering in `Area::compare_order()` [#5569](https://github.com/emilk/egui/pull/5569) by [@HactarCE](https://github.com/HactarCE)
|
||||
* Fix hovering through custom menu button [#5555](https://github.com/emilk/egui/pull/5555) by [@M4tthewDE](https://github.com/M4tthewDE)
|
||||
|
||||
### 🚀 Performance
|
||||
* Use `u8` in `CornerRadius`, and introduce `CornerRadiusF32` [#5563](https://github.com/emilk/egui/pull/5563) by [@emilk](https://github.com/emilk)
|
||||
* Store `Margin` using `i8` to reduce its size [#5567](https://github.com/emilk/egui/pull/5567) by [@emilk](https://github.com/emilk)
|
||||
* Shrink size of `Shadow` by using `i8/u8` instead of `f32` [#5568](https://github.com/emilk/egui/pull/5568) by [@emilk](https://github.com/emilk)
|
||||
* Avoid allocations for loader cache lookup [#5584](https://github.com/emilk/egui/pull/5584) by [@mineichen](https://github.com/mineichen)
|
||||
* Use bitfield instead of bools in `Response` and `Sense` [#5556](https://github.com/emilk/egui/pull/5556) by [@polwel](https://github.com/polwel)
|
||||
|
||||
|
||||
## 0.30.0 - 2024-12-16 - Modals and better layer support
|
||||
|
||||
### ✨ Highlights
|
||||
|
||||
30
Cargo.lock
30
Cargo.lock
@@ -1197,7 +1197,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
|
||||
|
||||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cint",
|
||||
@@ -1209,7 +1209,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "eframe"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1249,7 +1249,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"ahash",
|
||||
@@ -1267,7 +1267,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-wgpu"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1285,7 +1285,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"accesskit_winit",
|
||||
"ahash",
|
||||
@@ -1306,7 +1306,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_demo_app"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"chrono",
|
||||
@@ -1333,7 +1333,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_demo_lib"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"criterion",
|
||||
@@ -1347,7 +1347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_extras"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"chrono",
|
||||
@@ -1366,7 +1366,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_glow"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1386,7 +1386,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_kittest"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"dify",
|
||||
"document-features",
|
||||
@@ -1421,7 +1421,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
@@ -1512,7 +1512,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"ahash",
|
||||
@@ -1533,7 +1533,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "epaint_default_fonts"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
@@ -3099,7 +3099,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||
|
||||
[[package]]
|
||||
name = "popups"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"env_logger",
|
||||
@@ -5105,7 +5105,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
|
||||
26
Cargo.toml
26
Cargo.toml
@@ -24,7 +24,7 @@ members = [
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.81"
|
||||
version = "0.30.0"
|
||||
version = "0.31.0"
|
||||
|
||||
|
||||
[profile.release]
|
||||
@@ -55,18 +55,18 @@ opt-level = 2
|
||||
|
||||
|
||||
[workspace.dependencies]
|
||||
emath = { version = "0.30.0", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.30.0", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.30.0", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.30.0", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.30.0", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.30.0", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.30.0", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.30.0", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.30.0", path = "crates/egui_demo_lib", default-features = false }
|
||||
egui_glow = { version = "0.30.0", path = "crates/egui_glow", default-features = false }
|
||||
egui_kittest = { version = "0.30.0", path = "crates/egui_kittest", default-features = false }
|
||||
eframe = { version = "0.30.0", path = "crates/eframe", default-features = false }
|
||||
emath = { version = "0.31.0", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.31.0", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.31.0", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.31.0", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.31.0", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.31.0", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.31.0", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.31.0", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.31.0", path = "crates/egui_demo_lib", default-features = false }
|
||||
egui_glow = { version = "0.31.0", path = "crates/egui_glow", default-features = false }
|
||||
egui_kittest = { version = "0.31.0", path = "crates/egui_kittest", default-features = false }
|
||||
eframe = { version = "0.31.0", path = "crates/eframe", default-features = false }
|
||||
|
||||
ahash = { version = "0.8.11", default-features = false, features = [
|
||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||
|
||||
15
RELEASES.md
15
RELEASES.md
@@ -36,14 +36,9 @@ We don't update the MSRV in a patch release, unless we really, really need to.
|
||||
|
||||
## Release testing
|
||||
* [ ] `cargo r -p egui_demo_app` and click around for while
|
||||
* [ ] `./scripts/build_demo_web.sh --release -g`
|
||||
- check frame-rate and wasm size
|
||||
- test on mobile
|
||||
- test on chromium
|
||||
- check the in-browser profiler
|
||||
* [ ] check the color test
|
||||
* [ ] update `eframe_template` and test
|
||||
* [ ] update `egui_plot` and test
|
||||
* [ ] update `egui_table` and test
|
||||
* [ ] update `egui_tiles` and test
|
||||
* [ ] test with Rerun
|
||||
* [ ] `./scripts/check.sh`
|
||||
@@ -51,7 +46,7 @@ We don't update the MSRV in a patch release, unless we really, really need to.
|
||||
|
||||
## Preparation
|
||||
* [ ] run `scripts/generate_example_screenshots.sh` if needed
|
||||
* [ ] write a short release note that fits in a tweet
|
||||
* [ ] write a short release note that fits in a bluesky post
|
||||
* [ ] record gif for `CHANGELOG.md` release note (and later bluesky post)
|
||||
* [ ] update changelogs using `scripts/generate_changelog.py --version 0.x.0 --write`
|
||||
* [ ] bump version numbers in workspace `Cargo.toml`
|
||||
@@ -60,9 +55,9 @@ We don't update the MSRV in a patch release, unless we really, really need to.
|
||||
I usually do this all on the `master` branch, but doing it in a release branch is also fine, as long as you remember to merge it into `master` later.
|
||||
|
||||
* [ ] Run `typos`
|
||||
* [ ] `git commit -m 'Release 0.x.0 - summary'`
|
||||
* [ ] `git commit -m 'Release 0.x.0 - <release title>'`
|
||||
* [ ] `cargo publish` (see below)
|
||||
* [ ] `git tag -a 0.x.0 -m 'Release 0.x.0 - summary'`
|
||||
* [ ] `git tag -a 0.x.0 -m 'Release 0.x.0 - <release title>'`
|
||||
* [ ] `git pull --tags ; git tag -d latest && git tag -a latest -m 'Latest release' && git push --tags origin latest --force ; git push --tags`
|
||||
* [ ] merge release PR or push to `master`
|
||||
* [ ] check that CI is green
|
||||
@@ -98,3 +93,5 @@ I usually do this all on the `master` branch, but doing it in a release branch i
|
||||
* [ ] publish new `egui_plot`
|
||||
* [ ] publish new `egui_table`
|
||||
* [ ] publish new `egui_tiles`
|
||||
* [ ] make a PR to `egui_commonmark`
|
||||
* [ ] make a PR to `rerun`
|
||||
|
||||
@@ -6,6 +6,10 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.31.0 - 2025-02-04
|
||||
* Add `Color32::CYAN` and `Color32::MAGENTA` [#5663](https://github.com/emilk/egui/pull/5663) by [@juancampa](https://github.com/juancampa)
|
||||
|
||||
|
||||
## 0.30.0 - 2024-12-16
|
||||
* Use boxed slice for lookup table to avoid stack overflow [#5212](https://github.com/emilk/egui/pull/5212) by [@YgorSouza](https://github.com/YgorSouza)
|
||||
* Add `Color32::mul` [#5437](https://github.com/emilk/egui/pull/5437) by [@emilk](https://github.com/emilk)
|
||||
|
||||
@@ -56,7 +56,10 @@ impl Color32 {
|
||||
pub const RED: Self = Self::from_rgb(255, 0, 0);
|
||||
pub const LIGHT_RED: Self = Self::from_rgb(255, 128, 128);
|
||||
|
||||
pub const CYAN: Self = Self::from_rgb(0, 255, 255);
|
||||
pub const MAGENTA: Self = Self::from_rgb(255, 0, 255);
|
||||
pub const YELLOW: Self = Self::from_rgb(255, 255, 0);
|
||||
|
||||
pub const ORANGE: Self = Self::from_rgb(255, 165, 0);
|
||||
pub const LIGHT_YELLOW: Self = Self::from_rgb(255, 255, 0xE0);
|
||||
pub const KHAKI: Self = Self::from_rgb(240, 230, 140);
|
||||
@@ -69,6 +72,8 @@ impl Color32 {
|
||||
pub const BLUE: Self = Self::from_rgb(0, 0, 255);
|
||||
pub const LIGHT_BLUE: Self = Self::from_rgb(0xAD, 0xD8, 0xE6);
|
||||
|
||||
pub const PURPLE: Self = Self::from_rgb(0x80, 0, 0x80);
|
||||
|
||||
pub const GOLD: Self = Self::from_rgb(255, 215, 0);
|
||||
|
||||
pub const DEBUG_COLOR: Self = Self::from_rgba_premultiplied(0, 200, 0, 128);
|
||||
@@ -230,6 +235,23 @@ impl Color32 {
|
||||
])
|
||||
}
|
||||
|
||||
/// Multiply with 127 to make color half as opaque, perceptually.
|
||||
///
|
||||
/// Fast multiplication in gamma-space.
|
||||
///
|
||||
/// This is perceptually even, and faster that [`Self::linear_multiply`].
|
||||
#[inline]
|
||||
pub fn gamma_multiply_u8(self, factor: u8) -> Self {
|
||||
let Self([r, g, b, a]) = self;
|
||||
let factor = factor as u32;
|
||||
Self([
|
||||
((r as u32 * factor + 127) / 255) as u8,
|
||||
((g as u32 * factor + 127) / 255) as u8,
|
||||
((b as u32 * factor + 127) / 255) as u8,
|
||||
((a as u32 * factor + 127) / 255) as u8,
|
||||
])
|
||||
}
|
||||
|
||||
/// Multiply with 0.5 to make color half as opaque in linear space.
|
||||
///
|
||||
/// This is using linear space, which is not perceptually even.
|
||||
@@ -268,6 +290,11 @@ impl Color32 {
|
||||
fast_round(lerp((self[3] as f32)..=(other[3] as f32), t)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Blend two colors, so that `self` is behind the argument.
|
||||
pub fn blend(self, on_top: Self) -> Self {
|
||||
self.gamma_multiply_u8(255 - on_top.a()) + on_top
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul for Color32 {
|
||||
@@ -284,3 +311,17 @@ impl std::ops::Mul for Color32 {
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Color32 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self([
|
||||
self[0].saturating_add(other[0]),
|
||||
self[1].saturating_add(other[1]),
|
||||
self[2].saturating_add(other[2]),
|
||||
self[3].saturating_add(other[3]),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,14 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.31.0 - 2025-02-04
|
||||
* Web: Fix incorrect scale when moving to screen with new DPI [#5631](https://github.com/emilk/egui/pull/5631) by [@emilk](https://github.com/emilk)
|
||||
* Re-enable IME support on Linux [#5198](https://github.com/emilk/egui/pull/5198) by [@YgorSouza](https://github.com/YgorSouza)
|
||||
* Serialize window maximized state in `WindowSettings` [#5554](https://github.com/emilk/egui/pull/5554) by [@landaire](https://github.com/landaire)
|
||||
* Save state on suspend on Android and iOS [#5601](https://github.com/emilk/egui/pull/5601) by [@Pandicon](https://github.com/Pandicon)
|
||||
* Eframe web: forward cmd-S/O to egui app (stop default browser action) [#5655](https://github.com/emilk/egui/pull/5655) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.30.0 - 2024-12-16 - Android support
|
||||
NOTE: you now need to enable the `wayland` or `x11` features to get Linux support, including getting it to work on most CI systems.
|
||||
|
||||
|
||||
@@ -219,9 +219,16 @@ fn should_prevent_default_for_key(
|
||||
// * cmd-shift-C (debug tools)
|
||||
// * cmd/ctrl-c/v/x (lest we prevent copy/paste/cut events)
|
||||
|
||||
// Prevent ctrl-P from opening the print dialog. Users may want to use it for a command palette.
|
||||
if egui_key == egui::Key::P && (modifiers.ctrl || modifiers.command || modifiers.mac_cmd) {
|
||||
return true;
|
||||
// Prevent cmd/ctrl plus these keys from triggering the default browser action:
|
||||
let keys = [
|
||||
egui::Key::O, // open
|
||||
egui::Key::P, // print (cmd-P is common for command palette)
|
||||
egui::Key::S, // save
|
||||
];
|
||||
for key in keys {
|
||||
if egui_key == key && (modifiers.ctrl || modifiers.command || modifiers.mac_cmd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if egui_key == egui::Key::Space && !runner.text_agent.has_focus() {
|
||||
|
||||
@@ -6,6 +6,12 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.31.0 - 2025-02-04
|
||||
* Upgrade to wgpu 24 [#5610](https://github.com/emilk/egui/pull/5610) by [@torokati44](https://github.com/torokati44)
|
||||
* Extend `WgpuSetup`, `egui_kittest` now prefers software rasterizers for testing [#5506](https://github.com/emilk/egui/pull/5506) by [@Wumpf](https://github.com/Wumpf)
|
||||
* Wgpu resources are no longer wrapped in `Arc` (since they are now `Clone`) [#5612](https://github.com/emilk/egui/pull/5612) by [@Wumpf](https://github.com/Wumpf)
|
||||
|
||||
|
||||
## 0.30.0 - 2024-12-16
|
||||
* Fix docs.rs build [#5204](https://github.com/emilk/egui/pull/5204) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Free textures after submitting queue instead of before with wgpu renderer [#5226](https://github.com/emilk/egui/pull/5226) by [@Rusty-Cube](https://github.com/Rusty-Cube)
|
||||
|
||||
@@ -5,6 +5,11 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.31.0 - 2025-02-04
|
||||
* Re-enable IME support on Linux [#5198](https://github.com/emilk/egui/pull/5198) by [@YgorSouza](https://github.com/YgorSouza)
|
||||
* Update to winit 0.30.7 [#5516](https://github.com/emilk/egui/pull/5516) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.30.0 - 2024-12-16
|
||||
* iOS: Support putting UI next to the dynamic island [#5211](https://github.com/emilk/egui/pull/5211) by [@frederik-uni](https://github.com/frederik-uni)
|
||||
* Remove implicit `accesskit_winit` feature [#5316](https://github.com/emilk/egui/pull/5316) by [@waywardmonkeys](https://github.com/waywardmonkeys)
|
||||
|
||||
@@ -573,7 +573,7 @@ impl CollapsingHeader {
|
||||
if ui.visuals().collapsing_header_frame || show_background {
|
||||
ui.painter().add(epaint::RectShape::new(
|
||||
header_response.rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
StrokeKind::Inside,
|
||||
@@ -586,7 +586,7 @@ impl CollapsingHeader {
|
||||
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
visuals.bg_fill,
|
||||
visuals.bg_stroke,
|
||||
StrokeKind::Inside,
|
||||
|
||||
@@ -471,7 +471,7 @@ fn button_frame(
|
||||
where_to_put_background,
|
||||
epaint::RectShape::new(
|
||||
outer_rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
epaint::StrokeKind::Inside,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
epaint, layers::ShapeIdx, InnerResponse, Response, Sense, Style, Ui, UiBuilder, UiKind,
|
||||
UiStackInfo,
|
||||
};
|
||||
use epaint::{Color32, Margin, Marginf, Rect, Rounding, Shadow, Shape, Stroke};
|
||||
use epaint::{Color32, CornerRadius, Margin, Marginf, Rect, Shadow, Shape, Stroke};
|
||||
|
||||
/// A frame around some content, including margin, colors, etc.
|
||||
///
|
||||
@@ -115,8 +115,11 @@ pub struct Frame {
|
||||
#[doc(alias = "border")]
|
||||
pub stroke: Stroke,
|
||||
|
||||
/// The rounding of the corners of [`Self::stroke`] and [`Self::fill`].
|
||||
pub rounding: Rounding,
|
||||
/// The rounding of the _outer_ corner of the [`Self::stroke`]
|
||||
/// (or, if there is no stroke, the outer corner of [`Self::fill`]).
|
||||
///
|
||||
/// In other words, this is the corner radius of the _widget rect_.
|
||||
pub corner_radius: CornerRadius,
|
||||
|
||||
/// Margin outside the painted frame.
|
||||
///
|
||||
@@ -158,11 +161,14 @@ impl Frame {
|
||||
inner_margin: Margin::ZERO,
|
||||
stroke: Stroke::NONE,
|
||||
fill: Color32::TRANSPARENT,
|
||||
rounding: Rounding::ZERO,
|
||||
corner_radius: CornerRadius::ZERO,
|
||||
outer_margin: Margin::ZERO,
|
||||
shadow: Shadow::NONE,
|
||||
};
|
||||
|
||||
/// No colors, no margins, no border.
|
||||
///
|
||||
/// Same as [`Frame::NONE`].
|
||||
pub const fn new() -> Self {
|
||||
Self::NONE
|
||||
}
|
||||
@@ -176,7 +182,7 @@ impl Frame {
|
||||
pub fn group(style: &Style) -> Self {
|
||||
Self::new()
|
||||
.inner_margin(6)
|
||||
.rounding(style.visuals.widgets.noninteractive.rounding)
|
||||
.corner_radius(style.visuals.widgets.noninteractive.corner_radius)
|
||||
.stroke(style.visuals.widgets.noninteractive.bg_stroke)
|
||||
}
|
||||
|
||||
@@ -193,7 +199,7 @@ impl Frame {
|
||||
pub fn window(style: &Style) -> Self {
|
||||
Self::new()
|
||||
.inner_margin(style.spacing.window_margin)
|
||||
.rounding(style.visuals.window_rounding)
|
||||
.corner_radius(style.visuals.window_corner_radius)
|
||||
.shadow(style.visuals.window_shadow)
|
||||
.fill(style.visuals.window_fill())
|
||||
.stroke(style.visuals.window_stroke())
|
||||
@@ -202,7 +208,7 @@ impl Frame {
|
||||
pub fn menu(style: &Style) -> Self {
|
||||
Self::new()
|
||||
.inner_margin(style.spacing.menu_margin)
|
||||
.rounding(style.visuals.menu_rounding)
|
||||
.corner_radius(style.visuals.menu_corner_radius)
|
||||
.shadow(style.visuals.popup_shadow)
|
||||
.fill(style.visuals.window_fill())
|
||||
.stroke(style.visuals.window_stroke())
|
||||
@@ -211,7 +217,7 @@ impl Frame {
|
||||
pub fn popup(style: &Style) -> Self {
|
||||
Self::new()
|
||||
.inner_margin(style.spacing.menu_margin)
|
||||
.rounding(style.visuals.menu_rounding)
|
||||
.corner_radius(style.visuals.menu_corner_radius)
|
||||
.shadow(style.visuals.popup_shadow)
|
||||
.fill(style.visuals.window_fill())
|
||||
.stroke(style.visuals.window_stroke())
|
||||
@@ -224,7 +230,7 @@ impl Frame {
|
||||
pub fn canvas(style: &Style) -> Self {
|
||||
Self::new()
|
||||
.inner_margin(2)
|
||||
.rounding(style.visuals.widgets.noninteractive.rounding)
|
||||
.corner_radius(style.visuals.widgets.noninteractive.corner_radius)
|
||||
.fill(style.visuals.extreme_bg_color)
|
||||
.stroke(style.visuals.window_stroke())
|
||||
}
|
||||
@@ -266,13 +272,26 @@ impl Frame {
|
||||
self
|
||||
}
|
||||
|
||||
/// The rounding of the corners of [`Self::stroke`] and [`Self::fill`].
|
||||
/// The rounding of the _outer_ corner of the [`Self::stroke`]
|
||||
/// (or, if there is no stroke, the outer corner of [`Self::fill`]).
|
||||
///
|
||||
/// In other words, this is the corner radius of the _widget rect_.
|
||||
#[inline]
|
||||
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
|
||||
self.rounding = rounding.into();
|
||||
pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.corner_radius = corner_radius.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// The rounding of the _outer_ corner of the [`Self::stroke`]
|
||||
/// (or, if there is no stroke, the outer corner of [`Self::fill`]).
|
||||
///
|
||||
/// In other words, this is the corner radius of the _widget rect_.
|
||||
#[inline]
|
||||
#[deprecated = "Renamed to `corner_radius`"]
|
||||
pub fn rounding(self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.corner_radius(corner_radius)
|
||||
}
|
||||
|
||||
/// Margin outside the painted frame.
|
||||
///
|
||||
/// Similar to what is called `margin` in CSS.
|
||||
@@ -415,26 +434,25 @@ impl Frame {
|
||||
inner_margin: _,
|
||||
fill,
|
||||
stroke,
|
||||
rounding,
|
||||
corner_radius,
|
||||
outer_margin: _,
|
||||
shadow,
|
||||
} = *self;
|
||||
|
||||
let fill_rect = self.fill_rect(content_rect);
|
||||
let widget_rect = self.widget_rect(content_rect);
|
||||
|
||||
let frame_shape = Shape::Rect(epaint::RectShape::new(
|
||||
fill_rect,
|
||||
rounding,
|
||||
widget_rect,
|
||||
corner_radius,
|
||||
fill,
|
||||
stroke,
|
||||
epaint::StrokeKind::Outside,
|
||||
epaint::StrokeKind::Inside,
|
||||
));
|
||||
|
||||
if shadow == Default::default() {
|
||||
frame_shape
|
||||
} else {
|
||||
let shadow = shadow.as_shape(widget_rect, rounding);
|
||||
let shadow = shadow.as_shape(widget_rect, corner_radius);
|
||||
Shape::Vec(vec![Shape::from(shadow), frame_shape])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1240,7 +1240,7 @@ impl Prepared {
|
||||
// Background:
|
||||
ui.painter().add(epaint::Shape::rect_filled(
|
||||
outer_scroll_bar_rect,
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
ui.visuals()
|
||||
.extreme_bg_color
|
||||
.gamma_multiply(background_opacity),
|
||||
@@ -1249,7 +1249,7 @@ impl Prepared {
|
||||
// Handle:
|
||||
ui.painter().add(epaint::Shape::rect_filled(
|
||||
handle_rect,
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
handle_color.gamma_multiply(handle_opacity),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use emath::GuiRounding as _;
|
||||
use epaint::{RectShape, Roundingf};
|
||||
use epaint::{CornerRadiusF32, RectShape};
|
||||
|
||||
use crate::collapsing_header::CollapsingState;
|
||||
use crate::*;
|
||||
@@ -485,8 +485,8 @@ impl Window<'_> {
|
||||
.at_least(style.spacing.interact_size.y);
|
||||
let title_bar_inner_height = title_bar_inner_height + window_frame.inner_margin.sum().y;
|
||||
let half_height = (title_bar_inner_height / 2.0).round() as _;
|
||||
window_frame.rounding.ne = window_frame.rounding.ne.clamp(0, half_height);
|
||||
window_frame.rounding.nw = window_frame.rounding.nw.clamp(0, half_height);
|
||||
window_frame.corner_radius.ne = window_frame.corner_radius.ne.clamp(0, half_height);
|
||||
window_frame.corner_radius.nw = window_frame.corner_radius.nw.clamp(0, half_height);
|
||||
|
||||
let title_content_spacing = if is_collapsed {
|
||||
0.0
|
||||
@@ -607,11 +607,10 @@ impl Window<'_> {
|
||||
title_bar.inner_rect = outer_rect.shrink(window_frame.stroke.width);
|
||||
title_bar.inner_rect.max.y =
|
||||
title_bar.inner_rect.min.y + title_bar_height_with_margin;
|
||||
title_bar.inner_rect =
|
||||
title_bar.inner_rect.round_to_pixels(ctx.pixels_per_point());
|
||||
|
||||
if on_top && area_content_ui.visuals().window_highlight_topmost {
|
||||
let mut round = window_frame.rounding;
|
||||
let mut round =
|
||||
window_frame.corner_radius - window_frame.stroke.width.round() as u8;
|
||||
|
||||
if !is_collapsed {
|
||||
round.se = 0;
|
||||
@@ -666,28 +665,28 @@ fn paint_resize_corner(
|
||||
window_frame: &Frame,
|
||||
i: ResizeInteraction,
|
||||
) {
|
||||
let rounding = window_frame.rounding;
|
||||
let cr = window_frame.corner_radius;
|
||||
|
||||
let (corner, radius, corner_response) = if possible.resize_right && possible.resize_bottom {
|
||||
(Align2::RIGHT_BOTTOM, rounding.se, i.right & i.bottom)
|
||||
(Align2::RIGHT_BOTTOM, cr.se, i.right & i.bottom)
|
||||
} else if possible.resize_left && possible.resize_bottom {
|
||||
(Align2::LEFT_BOTTOM, rounding.sw, i.left & i.bottom)
|
||||
(Align2::LEFT_BOTTOM, cr.sw, i.left & i.bottom)
|
||||
} else if possible.resize_left && possible.resize_top {
|
||||
(Align2::LEFT_TOP, rounding.nw, i.left & i.top)
|
||||
(Align2::LEFT_TOP, cr.nw, i.left & i.top)
|
||||
} else if possible.resize_right && possible.resize_top {
|
||||
(Align2::RIGHT_TOP, rounding.ne, i.right & i.top)
|
||||
(Align2::RIGHT_TOP, cr.ne, i.right & i.top)
|
||||
} else {
|
||||
// We're not in two directions, but it is still nice to tell the user
|
||||
// we're resizable by painting the resize corner in the expected place
|
||||
// (i.e. for windows only resizable in one direction):
|
||||
if possible.resize_right || possible.resize_bottom {
|
||||
(Align2::RIGHT_BOTTOM, rounding.se, i.right & i.bottom)
|
||||
(Align2::RIGHT_BOTTOM, cr.se, i.right & i.bottom)
|
||||
} else if possible.resize_left || possible.resize_bottom {
|
||||
(Align2::LEFT_BOTTOM, rounding.sw, i.left & i.bottom)
|
||||
(Align2::LEFT_BOTTOM, cr.sw, i.left & i.bottom)
|
||||
} else if possible.resize_left || possible.resize_top {
|
||||
(Align2::LEFT_TOP, rounding.nw, i.left & i.top)
|
||||
(Align2::LEFT_TOP, cr.nw, i.left & i.top)
|
||||
} else if possible.resize_right || possible.resize_top {
|
||||
(Align2::RIGHT_TOP, rounding.ne, i.right & i.top)
|
||||
(Align2::RIGHT_TOP, cr.ne, i.right & i.top)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -1053,7 +1052,7 @@ fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction)
|
||||
bottom = interaction.bottom.hover;
|
||||
}
|
||||
|
||||
let rounding = Roundingf::from(ui.visuals().window_rounding);
|
||||
let cr = CornerRadiusF32::from(ui.visuals().window_corner_radius);
|
||||
|
||||
// Put the rect in the center of the fixed window stroke:
|
||||
let rect = rect.shrink(interaction.window_frame.stroke.width / 2.0);
|
||||
@@ -1071,56 +1070,36 @@ fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction)
|
||||
let mut points = Vec::new();
|
||||
|
||||
if right && !bottom && !top {
|
||||
points.push(pos2(max.x, min.y + rounding.ne));
|
||||
points.push(pos2(max.x, max.y - rounding.se));
|
||||
points.push(pos2(max.x, min.y + cr.ne));
|
||||
points.push(pos2(max.x, max.y - cr.se));
|
||||
}
|
||||
if right && bottom {
|
||||
points.push(pos2(max.x, min.y + rounding.ne));
|
||||
points.push(pos2(max.x, max.y - rounding.se));
|
||||
add_circle_quadrant(
|
||||
&mut points,
|
||||
pos2(max.x - rounding.se, max.y - rounding.se),
|
||||
rounding.se,
|
||||
0.0,
|
||||
);
|
||||
points.push(pos2(max.x, min.y + cr.ne));
|
||||
points.push(pos2(max.x, max.y - cr.se));
|
||||
add_circle_quadrant(&mut points, pos2(max.x - cr.se, max.y - cr.se), cr.se, 0.0);
|
||||
}
|
||||
if bottom {
|
||||
points.push(pos2(max.x - rounding.se, max.y));
|
||||
points.push(pos2(min.x + rounding.sw, max.y));
|
||||
points.push(pos2(max.x - cr.se, max.y));
|
||||
points.push(pos2(min.x + cr.sw, max.y));
|
||||
}
|
||||
if left && bottom {
|
||||
add_circle_quadrant(
|
||||
&mut points,
|
||||
pos2(min.x + rounding.sw, max.y - rounding.sw),
|
||||
rounding.sw,
|
||||
1.0,
|
||||
);
|
||||
add_circle_quadrant(&mut points, pos2(min.x + cr.sw, max.y - cr.sw), cr.sw, 1.0);
|
||||
}
|
||||
if left {
|
||||
points.push(pos2(min.x, max.y - rounding.sw));
|
||||
points.push(pos2(min.x, min.y + rounding.nw));
|
||||
points.push(pos2(min.x, max.y - cr.sw));
|
||||
points.push(pos2(min.x, min.y + cr.nw));
|
||||
}
|
||||
if left && top {
|
||||
add_circle_quadrant(
|
||||
&mut points,
|
||||
pos2(min.x + rounding.nw, min.y + rounding.nw),
|
||||
rounding.nw,
|
||||
2.0,
|
||||
);
|
||||
add_circle_quadrant(&mut points, pos2(min.x + cr.nw, min.y + cr.nw), cr.nw, 2.0);
|
||||
}
|
||||
if top {
|
||||
points.push(pos2(min.x + rounding.nw, min.y));
|
||||
points.push(pos2(max.x - rounding.ne, min.y));
|
||||
points.push(pos2(min.x + cr.nw, min.y));
|
||||
points.push(pos2(max.x - cr.ne, min.y));
|
||||
}
|
||||
if right && top {
|
||||
add_circle_quadrant(
|
||||
&mut points,
|
||||
pos2(max.x - rounding.ne, min.y + rounding.ne),
|
||||
rounding.ne,
|
||||
3.0,
|
||||
);
|
||||
points.push(pos2(max.x, min.y + rounding.ne));
|
||||
points.push(pos2(max.x, max.y - rounding.se));
|
||||
add_circle_quadrant(&mut points, pos2(max.x - cr.ne, min.y + cr.ne), cr.ne, 3.0);
|
||||
points.push(pos2(max.x, min.y + cr.ne));
|
||||
points.push(pos2(max.x, max.y - cr.se));
|
||||
}
|
||||
|
||||
ui.painter().add(Shape::line(points, stroke));
|
||||
@@ -1230,7 +1209,7 @@ impl TitleBar {
|
||||
.center();
|
||||
let button_size = Vec2::splat(ui.spacing().icon_width);
|
||||
let button_rect = Rect::from_center_size(button_center, button_size);
|
||||
let button_rect = button_rect.round_to_pixels(ui.pixels_per_point());
|
||||
let button_rect = button_rect.round_ui();
|
||||
|
||||
ui.allocate_new_ui(UiBuilder::new().max_rect(button_rect), |ui| {
|
||||
collapsing.show_default_button_with_size(ui, button_size);
|
||||
|
||||
@@ -160,7 +160,7 @@ impl Widget for &mut epaint::TessellationOptions {
|
||||
.on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain.");
|
||||
|
||||
if *feathering {
|
||||
ui.add(crate::DragValue::new(feathering_size_in_pixels).range(0.0..=10.0).speed(0.1).suffix(" px"));
|
||||
ui.add(crate::DragValue::new(feathering_size_in_pixels).range(0.0..=10.0).speed(0.025).suffix(" px"));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -464,8 +464,8 @@ pub use epaint::{
|
||||
mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
|
||||
textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta},
|
||||
ClippedPrimitive, ColorImage, FontImage, ImageData, Margin, Mesh, PaintCallback,
|
||||
PaintCallbackInfo, Rounding, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId,
|
||||
ClippedPrimitive, ColorImage, CornerRadius, FontImage, ImageData, Margin, Mesh, PaintCallback,
|
||||
PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId,
|
||||
};
|
||||
|
||||
pub mod text {
|
||||
@@ -510,6 +510,9 @@ pub use self::{
|
||||
widgets::*,
|
||||
};
|
||||
|
||||
#[deprecated = "Renamed to CornerRadius"]
|
||||
pub type Rounding = CornerRadius;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Helper function that adds a label when compiling with debug assertions enabled.
|
||||
@@ -538,7 +541,7 @@ pub fn warn_if_debug_build(ui: &mut crate::Ui) {
|
||||
/// ui.add(
|
||||
/// egui::Image::new(egui::include_image!("../assets/ferris.png"))
|
||||
/// .max_width(200.0)
|
||||
/// .rounding(10.0),
|
||||
/// .corner_radius(10),
|
||||
/// );
|
||||
///
|
||||
/// let image_source: egui::ImageSource = egui::include_image!("../assets/ferris.png");
|
||||
|
||||
@@ -580,7 +580,7 @@ impl SubMenuButton {
|
||||
if ui.visuals().button_frame {
|
||||
ui.painter().rect_filled(
|
||||
rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
visuals.weak_bg_fill,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use emath::GuiRounding as _;
|
||||
use epaint::{
|
||||
text::{Fonts, Galley, LayoutJob},
|
||||
CircleShape, ClippedShape, PathStroke, RectShape, Rounding, Shape, Stroke, StrokeKind,
|
||||
CircleShape, ClippedShape, CornerRadius, PathStroke, RectShape, Shape, Stroke, StrokeKind,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -412,14 +412,14 @@ impl Painter {
|
||||
pub fn rect(
|
||||
&self,
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
corner_radius: impl Into<CornerRadius>,
|
||||
fill_color: impl Into<Color32>,
|
||||
stroke: impl Into<Stroke>,
|
||||
stroke_kind: StrokeKind,
|
||||
) -> ShapeIdx {
|
||||
self.add(RectShape::new(
|
||||
rect,
|
||||
rounding,
|
||||
corner_radius,
|
||||
fill_color,
|
||||
stroke,
|
||||
stroke_kind,
|
||||
@@ -429,21 +429,20 @@ impl Painter {
|
||||
pub fn rect_filled(
|
||||
&self,
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
corner_radius: impl Into<CornerRadius>,
|
||||
fill_color: impl Into<Color32>,
|
||||
) -> ShapeIdx {
|
||||
self.add(RectShape::filled(rect, rounding, fill_color))
|
||||
self.add(RectShape::filled(rect, corner_radius, fill_color))
|
||||
}
|
||||
|
||||
/// The stroke extends _outside_ the [`Rect`].
|
||||
pub fn rect_stroke(
|
||||
&self,
|
||||
rect: Rect,
|
||||
rounding: impl Into<Rounding>,
|
||||
corner_radius: impl Into<CornerRadius>,
|
||||
stroke: impl Into<Stroke>,
|
||||
stroke_kind: StrokeKind,
|
||||
) -> ShapeIdx {
|
||||
self.add(RectShape::stroke(rect, rounding, stroke, stroke_kind))
|
||||
self.add(RectShape::stroke(rect, corner_radius, stroke, stroke_kind))
|
||||
}
|
||||
|
||||
/// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`.
|
||||
@@ -472,7 +471,7 @@ impl Painter {
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let rect = egui::Rect::from_min_size(Default::default(), egui::Vec2::splat(100.0));
|
||||
/// egui::Image::new(egui::include_image!("../assets/ferris.png"))
|
||||
/// .rounding(5.0)
|
||||
/// .corner_radius(5)
|
||||
/// .tint(egui::Color32::LIGHT_BLUE)
|
||||
/// .paint_at(ui, rect);
|
||||
/// # });
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
|
||||
|
||||
use emath::Align;
|
||||
use epaint::{text::FontTweak, Rounding, Shadow, Stroke};
|
||||
use epaint::{text::FontTweak, CornerRadius, Shadow, Stroke};
|
||||
|
||||
use crate::{
|
||||
ecolor::Color32,
|
||||
@@ -915,7 +915,7 @@ pub struct Visuals {
|
||||
/// A good color for error text (e.g. red).
|
||||
pub error_fg_color: Color32,
|
||||
|
||||
pub window_rounding: Rounding,
|
||||
pub window_corner_radius: CornerRadius,
|
||||
pub window_shadow: Shadow,
|
||||
pub window_fill: Color32,
|
||||
pub window_stroke: Stroke,
|
||||
@@ -923,7 +923,7 @@ pub struct Visuals {
|
||||
/// Highlight the topmost window.
|
||||
pub window_highlight_topmost: bool,
|
||||
|
||||
pub menu_rounding: Rounding,
|
||||
pub menu_corner_radius: CornerRadius,
|
||||
|
||||
/// Panel background color
|
||||
pub panel_fill: Color32,
|
||||
@@ -1107,7 +1107,7 @@ pub struct WidgetVisuals {
|
||||
pub bg_stroke: Stroke,
|
||||
|
||||
/// Button frames etc.
|
||||
pub rounding: Rounding,
|
||||
pub corner_radius: CornerRadius,
|
||||
|
||||
/// Stroke and text color of the interactive part of a component (button text, slider grab, check-mark, …).
|
||||
pub fg_stroke: Stroke,
|
||||
@@ -1121,6 +1121,11 @@ impl WidgetVisuals {
|
||||
pub fn text_color(&self) -> Color32 {
|
||||
self.fg_stroke.color
|
||||
}
|
||||
|
||||
#[deprecated = "Renamed to corner_radius"]
|
||||
pub fn rounding(&self) -> CornerRadius {
|
||||
self.corner_radius
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for help debug egui by adding extra visualization
|
||||
@@ -1291,7 +1296,7 @@ impl Visuals {
|
||||
warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
|
||||
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
||||
|
||||
window_rounding: Rounding::same(6),
|
||||
window_corner_radius: CornerRadius::same(6),
|
||||
window_shadow: Shadow {
|
||||
offset: [10, 20],
|
||||
blur: 15,
|
||||
@@ -1302,7 +1307,7 @@ impl Visuals {
|
||||
window_stroke: Stroke::new(1.0, Color32::from_gray(60)),
|
||||
window_highlight_topmost: true,
|
||||
|
||||
menu_rounding: Rounding::same(6),
|
||||
menu_corner_radius: CornerRadius::same(6),
|
||||
|
||||
panel_fill: Color32::from_gray(27),
|
||||
|
||||
@@ -1412,7 +1417,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(27),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
|
||||
rounding: Rounding::same(2),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 0.0,
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
@@ -1420,7 +1425,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(60), // checkbox background
|
||||
bg_stroke: Default::default(),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
|
||||
rounding: Rounding::same(2),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 0.0,
|
||||
},
|
||||
hovered: WidgetVisuals {
|
||||
@@ -1428,7 +1433,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(70),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
|
||||
fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
|
||||
rounding: Rounding::same(3),
|
||||
corner_radius: CornerRadius::same(3),
|
||||
expansion: 1.0,
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
@@ -1436,7 +1441,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(55),
|
||||
bg_stroke: Stroke::new(1.0, Color32::WHITE),
|
||||
fg_stroke: Stroke::new(2.0, Color32::WHITE),
|
||||
rounding: Rounding::same(2),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 1.0,
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
@@ -1444,7 +1449,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(27),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
|
||||
rounding: Rounding::same(2),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 0.0,
|
||||
},
|
||||
}
|
||||
@@ -1457,7 +1462,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(248),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), // normal text color
|
||||
rounding: Rounding::same(2),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 0.0,
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
@@ -1465,7 +1470,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(230), // checkbox background
|
||||
bg_stroke: Default::default(),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // button text
|
||||
rounding: Rounding::same(2),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 0.0,
|
||||
},
|
||||
hovered: WidgetVisuals {
|
||||
@@ -1473,7 +1478,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(220),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button
|
||||
fg_stroke: Stroke::new(1.5, Color32::BLACK),
|
||||
rounding: Rounding::same(3),
|
||||
corner_radius: CornerRadius::same(3),
|
||||
expansion: 1.0,
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
@@ -1481,7 +1486,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(165),
|
||||
bg_stroke: Stroke::new(1.0, Color32::BLACK),
|
||||
fg_stroke: Stroke::new(2.0, Color32::BLACK),
|
||||
rounding: Rounding::same(2),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 1.0,
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
@@ -1489,7 +1494,7 @@ impl Widgets {
|
||||
bg_fill: Color32::from_gray(220),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(160)),
|
||||
fg_stroke: Stroke::new(1.0, Color32::BLACK),
|
||||
rounding: Rounding::same(2),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 0.0,
|
||||
},
|
||||
}
|
||||
@@ -1924,7 +1929,7 @@ impl WidgetVisuals {
|
||||
weak_bg_fill,
|
||||
bg_fill: mandatory_bg_fill,
|
||||
bg_stroke,
|
||||
rounding,
|
||||
corner_radius,
|
||||
fg_stroke,
|
||||
expansion,
|
||||
} = self;
|
||||
@@ -1948,8 +1953,8 @@ impl WidgetVisuals {
|
||||
ui.add(bg_stroke);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Rounding");
|
||||
ui.add(rounding);
|
||||
ui.label("Corner radius");
|
||||
ui.add(corner_radius);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Foreground stroke (text)");
|
||||
@@ -1978,13 +1983,13 @@ impl Visuals {
|
||||
warn_fg_color,
|
||||
error_fg_color,
|
||||
|
||||
window_rounding,
|
||||
window_corner_radius,
|
||||
window_shadow,
|
||||
window_fill,
|
||||
window_stroke,
|
||||
window_highlight_topmost,
|
||||
|
||||
menu_rounding,
|
||||
menu_corner_radius,
|
||||
|
||||
panel_fill,
|
||||
|
||||
@@ -2066,8 +2071,8 @@ impl Visuals {
|
||||
ui.add(window_stroke);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Rounding");
|
||||
ui.add(window_rounding);
|
||||
ui.label("Corner radius");
|
||||
ui.add(window_corner_radius);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Shadow");
|
||||
@@ -2084,8 +2089,8 @@ impl Visuals {
|
||||
.spacing([12.0, 8.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Rounding");
|
||||
ui.add(menu_rounding);
|
||||
ui.label("Corner radius");
|
||||
ui.add(menu_corner_radius);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Shadow");
|
||||
@@ -2388,7 +2393,7 @@ impl Widget for &mut Margin {
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &mut Rounding {
|
||||
impl Widget for &mut CornerRadius {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let mut same = self.is_same();
|
||||
|
||||
@@ -2398,37 +2403,39 @@ impl Widget for &mut Rounding {
|
||||
|
||||
let mut cr = self.nw;
|
||||
ui.add(DragValue::new(&mut cr).range(0.0..=f32::INFINITY));
|
||||
*self = Rounding::same(cr);
|
||||
*self = CornerRadius::same(cr);
|
||||
})
|
||||
.response
|
||||
} else {
|
||||
ui.vertical(|ui| {
|
||||
ui.checkbox(&mut same, "same");
|
||||
|
||||
crate::Grid::new("rounding").num_columns(2).show(ui, |ui| {
|
||||
ui.label("NW");
|
||||
ui.add(DragValue::new(&mut self.nw).range(0.0..=f32::INFINITY));
|
||||
ui.end_row();
|
||||
crate::Grid::new("Corner radius")
|
||||
.num_columns(2)
|
||||
.show(ui, |ui| {
|
||||
ui.label("NW");
|
||||
ui.add(DragValue::new(&mut self.nw).range(0.0..=f32::INFINITY));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("NE");
|
||||
ui.add(DragValue::new(&mut self.ne).range(0.0..=f32::INFINITY));
|
||||
ui.end_row();
|
||||
ui.label("NE");
|
||||
ui.add(DragValue::new(&mut self.ne).range(0.0..=f32::INFINITY));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("SW");
|
||||
ui.add(DragValue::new(&mut self.sw).range(0.0..=f32::INFINITY));
|
||||
ui.end_row();
|
||||
ui.label("SW");
|
||||
ui.add(DragValue::new(&mut self.sw).range(0.0..=f32::INFINITY));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("SE");
|
||||
ui.add(DragValue::new(&mut self.se).range(0.0..=f32::INFINITY));
|
||||
ui.end_row();
|
||||
});
|
||||
ui.label("SE");
|
||||
ui.add(DragValue::new(&mut self.se).range(0.0..=f32::INFINITY));
|
||||
ui.end_row();
|
||||
});
|
||||
})
|
||||
.response
|
||||
};
|
||||
|
||||
// Apply the checkbox:
|
||||
if same {
|
||||
*self = Rounding::from(self.average());
|
||||
*self = CornerRadius::from(self.average());
|
||||
} else {
|
||||
// Make sure we aren't same:
|
||||
if self.is_same() {
|
||||
@@ -2513,7 +2520,7 @@ impl Widget for &mut crate::Frame {
|
||||
let crate::Frame {
|
||||
inner_margin,
|
||||
outer_margin,
|
||||
rounding,
|
||||
corner_radius,
|
||||
shadow,
|
||||
fill,
|
||||
stroke,
|
||||
@@ -2533,8 +2540,8 @@ impl Widget for &mut crate::Frame {
|
||||
ui.push_id("outer", |ui| ui.add(outer_margin));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Rounding");
|
||||
ui.add(rounding);
|
||||
ui.label("Corner radius");
|
||||
ui.add(corner_radius);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Shadow");
|
||||
|
||||
@@ -2127,7 +2127,7 @@ impl Ui {
|
||||
/// ui.add(
|
||||
/// egui::Image::new(egui::include_image!("../assets/ferris.png"))
|
||||
/// .max_width(200.0)
|
||||
/// .rounding(10.0),
|
||||
/// .corner_radius(10),
|
||||
/// );
|
||||
/// # });
|
||||
/// ```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
widgets, Align, Color32, Image, NumExt, Rect, Response, Rounding, Sense, Stroke, TextStyle,
|
||||
widgets, Align, Color32, CornerRadius, Image, NumExt, Rect, Response, Sense, Stroke, TextStyle,
|
||||
TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType,
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ pub struct Button<'a> {
|
||||
small: bool,
|
||||
frame: Option<bool>,
|
||||
min_size: Vec2,
|
||||
rounding: Option<Rounding>,
|
||||
corner_radius: Option<CornerRadius>,
|
||||
selected: bool,
|
||||
image_tint_follows_text_color: bool,
|
||||
}
|
||||
@@ -69,7 +69,7 @@ impl<'a> Button<'a> {
|
||||
small: false,
|
||||
frame: None,
|
||||
min_size: Vec2::ZERO,
|
||||
rounding: None,
|
||||
corner_radius: None,
|
||||
selected: false,
|
||||
image_tint_follows_text_color: false,
|
||||
}
|
||||
@@ -153,11 +153,17 @@ impl<'a> Button<'a> {
|
||||
|
||||
/// Set the rounding of the button.
|
||||
#[inline]
|
||||
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
|
||||
self.rounding = Some(rounding.into());
|
||||
pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.corner_radius = Some(corner_radius.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[deprecated = "Renamed to `corner_radius`"]
|
||||
pub fn rounding(self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.corner_radius(corner_radius)
|
||||
}
|
||||
|
||||
/// If true, the tint of the image is multiplied by the widget text color.
|
||||
///
|
||||
/// This makes sense for images that are white, that should have the same color as the text color.
|
||||
@@ -202,7 +208,7 @@ impl Widget for Button<'_> {
|
||||
small,
|
||||
frame,
|
||||
min_size,
|
||||
rounding,
|
||||
corner_radius,
|
||||
selected,
|
||||
image_tint_follows_text_color,
|
||||
} = self;
|
||||
@@ -292,11 +298,11 @@ impl Widget for Button<'_> {
|
||||
if ui.is_rect_visible(rect) {
|
||||
let visuals = ui.style().interact(&response);
|
||||
|
||||
let (frame_expansion, frame_rounding, frame_fill, frame_stroke) = if selected {
|
||||
let (frame_expansion, frame_cr, frame_fill, frame_stroke) = if selected {
|
||||
let selection = ui.visuals().selection;
|
||||
(
|
||||
Vec2::ZERO,
|
||||
Rounding::ZERO,
|
||||
CornerRadius::ZERO,
|
||||
selection.bg_fill,
|
||||
selection.stroke,
|
||||
)
|
||||
@@ -304,19 +310,19 @@ impl Widget for Button<'_> {
|
||||
let expansion = Vec2::splat(visuals.expansion);
|
||||
(
|
||||
expansion,
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let frame_rounding = rounding.unwrap_or(frame_rounding);
|
||||
let frame_cr = corner_radius.unwrap_or(frame_cr);
|
||||
let frame_fill = fill.unwrap_or(frame_fill);
|
||||
let frame_stroke = stroke.unwrap_or(frame_stroke);
|
||||
ui.painter().rect(
|
||||
rect.expand2(frame_expansion),
|
||||
frame_rounding,
|
||||
frame_cr,
|
||||
frame_fill,
|
||||
frame_stroke,
|
||||
epaint::StrokeKind::Inside,
|
||||
|
||||
@@ -104,7 +104,7 @@ impl Widget for Checkbox<'_> {
|
||||
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
||||
ui.painter().add(epaint::RectShape::new(
|
||||
big_icon_rect.expand(visuals.expansion),
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
visuals.bg_fill,
|
||||
visuals.bg_stroke,
|
||||
epaint::StrokeKind::Inside,
|
||||
|
||||
@@ -100,10 +100,10 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response {
|
||||
let stroke_width = 1.0;
|
||||
show_color_at(ui.painter(), color, rect.shrink(stroke_width));
|
||||
|
||||
let rounding = visuals.rounding.at_most(2); // Can't do more rounding because the background grid doesn't do any rounding
|
||||
let corner_radius = visuals.corner_radius.at_most(2); // Can't do more rounding because the background grid doesn't do any rounding
|
||||
ui.painter().rect_stroke(
|
||||
rect,
|
||||
rounding,
|
||||
corner_radius,
|
||||
(stroke_width, visuals.bg_fill), // Using fill for stroke is intentional, because default style has no border
|
||||
StrokeKind::Inside,
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ use epaint::{
|
||||
|
||||
use crate::{
|
||||
load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll},
|
||||
pos2, Color32, Context, Id, Mesh, Painter, Rect, Response, Rounding, Sense, Shape, Spinner,
|
||||
pos2, Color32, Context, CornerRadius, Id, Mesh, Painter, Rect, Response, Sense, Shape, Spinner,
|
||||
TextStyle, TextureOptions, Ui, Vec2, Widget, WidgetInfo, WidgetType,
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::{
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// ui.add(
|
||||
/// egui::Image::new(egui::include_image!("../../assets/ferris.png"))
|
||||
/// .rounding(5.0)
|
||||
/// .corner_radius(5)
|
||||
/// );
|
||||
/// # });
|
||||
/// ```
|
||||
@@ -39,7 +39,7 @@ use crate::{
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let rect = egui::Rect::from_min_size(Default::default(), egui::Vec2::splat(100.0));
|
||||
/// egui::Image::new(egui::include_image!("../../assets/ferris.png"))
|
||||
/// .rounding(5.0)
|
||||
/// .corner_radius(5)
|
||||
/// .tint(egui::Color32::LIGHT_BLUE)
|
||||
/// .paint_at(ui, rect);
|
||||
/// # });
|
||||
@@ -233,25 +233,37 @@ impl<'a> Image<'a> {
|
||||
#[inline]
|
||||
pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self {
|
||||
self.image_options.rotation = Some((Rot2::from_angle(angle), origin));
|
||||
self.image_options.rounding = Rounding::ZERO; // incompatible with rotation
|
||||
self.image_options.corner_radius = CornerRadius::ZERO; // incompatible with rotation
|
||||
self
|
||||
}
|
||||
|
||||
/// Round the corners of the image.
|
||||
///
|
||||
/// The default is no rounding ([`Rounding::ZERO`]).
|
||||
/// The default is no rounding ([`CornerRadius::ZERO`]).
|
||||
///
|
||||
/// Due to limitations in the current implementation,
|
||||
/// this will turn off any rotation of the image.
|
||||
#[inline]
|
||||
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
|
||||
self.image_options.rounding = rounding.into();
|
||||
if self.image_options.rounding != Rounding::ZERO {
|
||||
pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.image_options.corner_radius = corner_radius.into();
|
||||
if self.image_options.corner_radius != CornerRadius::ZERO {
|
||||
self.image_options.rotation = None; // incompatible with rounding
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Round the corners of the image.
|
||||
///
|
||||
/// The default is no rounding ([`CornerRadius::ZERO`]).
|
||||
///
|
||||
/// Due to limitations in the current implementation,
|
||||
/// this will turn off any rotation of the image.
|
||||
#[inline]
|
||||
#[deprecated = "Renamed to `corner_radius`"]
|
||||
pub fn rounding(self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.corner_radius(corner_radius)
|
||||
}
|
||||
|
||||
/// Show a spinner when the image is loading.
|
||||
///
|
||||
/// By default this uses the value of [`crate::Visuals::image_loading_spinners`].
|
||||
@@ -354,7 +366,7 @@ impl<'a> Image<'a> {
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let rect = egui::Rect::from_min_size(Default::default(), egui::Vec2::splat(100.0));
|
||||
/// egui::Image::new(egui::include_image!("../../assets/ferris.png"))
|
||||
/// .rounding(5.0)
|
||||
/// .corner_radius(5)
|
||||
/// .tint(egui::Color32::LIGHT_BLUE)
|
||||
/// .paint_at(ui, rect);
|
||||
/// # });
|
||||
@@ -778,11 +790,11 @@ pub struct ImageOptions {
|
||||
|
||||
/// Round the corners of the image.
|
||||
///
|
||||
/// The default is no rounding ([`Rounding::ZERO`]).
|
||||
/// The default is no rounding ([`CornerRadius::ZERO`]).
|
||||
///
|
||||
/// Due to limitations in the current implementation,
|
||||
/// this will turn off any rotation of the image.
|
||||
pub rounding: Rounding,
|
||||
pub corner_radius: CornerRadius,
|
||||
}
|
||||
|
||||
impl Default for ImageOptions {
|
||||
@@ -792,7 +804,7 @@ impl Default for ImageOptions {
|
||||
bg_fill: Default::default(),
|
||||
tint: Color32::WHITE,
|
||||
rotation: None,
|
||||
rounding: Rounding::ZERO,
|
||||
corner_radius: CornerRadius::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -804,7 +816,11 @@ pub fn paint_texture_at(
|
||||
texture: &SizedTexture,
|
||||
) {
|
||||
if options.bg_fill != Default::default() {
|
||||
painter.add(RectShape::filled(rect, options.rounding, options.bg_fill));
|
||||
painter.add(RectShape::filled(
|
||||
rect,
|
||||
options.corner_radius,
|
||||
options.bg_fill,
|
||||
));
|
||||
}
|
||||
|
||||
match options.rotation {
|
||||
@@ -812,7 +828,7 @@ pub fn paint_texture_at(
|
||||
// TODO(emilk): implement this using `PathShape` (add texture support to it).
|
||||
// This will also give us anti-aliasing of rotated images.
|
||||
debug_assert!(
|
||||
options.rounding == Rounding::ZERO,
|
||||
options.corner_radius == CornerRadius::ZERO,
|
||||
"Image had both rounding and rotation. Please pick only one"
|
||||
);
|
||||
|
||||
@@ -823,7 +839,7 @@ pub fn paint_texture_at(
|
||||
}
|
||||
None => {
|
||||
painter.add(
|
||||
RectShape::filled(rect, options.rounding, options.tint)
|
||||
RectShape::filled(rect, options.corner_radius, options.tint)
|
||||
.with_texture(texture.id, options.uv),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
widgets, Color32, Image, Rect, Response, Rounding, Sense, Ui, Vec2, Widget, WidgetInfo,
|
||||
widgets, Color32, CornerRadius, Image, Rect, Response, Sense, Ui, Vec2, Widget, WidgetInfo,
|
||||
WidgetType,
|
||||
};
|
||||
|
||||
@@ -62,13 +62,24 @@ impl<'a> ImageButton<'a> {
|
||||
}
|
||||
|
||||
/// Set rounding for the `ImageButton`.
|
||||
///
|
||||
/// If the underlying image already has rounding, this
|
||||
/// will override that value.
|
||||
#[inline]
|
||||
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
|
||||
self.image = self.image.rounding(rounding.into());
|
||||
pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.image = self.image.corner_radius(corner_radius.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set rounding for the `ImageButton`.
|
||||
///
|
||||
/// If the underlying image already has rounding, this
|
||||
/// will override that value.
|
||||
#[inline]
|
||||
#[deprecated = "Renamed to `corner_radius`"]
|
||||
pub fn rounding(self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.corner_radius(corner_radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ImageButton<'_> {
|
||||
@@ -100,7 +111,7 @@ impl Widget for ImageButton<'_> {
|
||||
let selection = ui.visuals().selection;
|
||||
(
|
||||
Vec2::ZERO,
|
||||
self.image.image_options().rounding,
|
||||
self.image.image_options().corner_radius,
|
||||
selection.bg_fill,
|
||||
selection.stroke,
|
||||
)
|
||||
@@ -109,7 +120,7 @@ impl Widget for ImageButton<'_> {
|
||||
let expansion = Vec2::splat(visuals.expansion);
|
||||
(
|
||||
expansion,
|
||||
self.image.image_options().rounding,
|
||||
self.image.image_options().corner_radius,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
lerp, vec2, Color32, NumExt, Pos2, Rect, Response, Rgba, Rounding, Sense, Shape, Stroke,
|
||||
lerp, vec2, Color32, CornerRadius, NumExt, Pos2, Rect, Response, Rgba, Sense, Shape, Stroke,
|
||||
TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType,
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct ProgressBar {
|
||||
text: Option<ProgressBarText>,
|
||||
fill: Option<Color32>,
|
||||
animate: bool,
|
||||
rounding: Option<Rounding>,
|
||||
corner_radius: Option<CornerRadius>,
|
||||
}
|
||||
|
||||
impl ProgressBar {
|
||||
@@ -32,7 +32,7 @@ impl ProgressBar {
|
||||
text: None,
|
||||
fill: None,
|
||||
animate: false,
|
||||
rounding: None,
|
||||
corner_radius: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ impl ProgressBar {
|
||||
/// Note that this will cause the UI to be redrawn.
|
||||
/// Defaults to `false`.
|
||||
///
|
||||
/// If [`Self::rounding`] and [`Self::animate`] are used simultaneously, the animation is not
|
||||
/// If [`Self::corner_radius`] and [`Self::animate`] are used simultaneously, the animation is not
|
||||
/// rendered, since it requires a perfect circle to render correctly. However, the UI is still
|
||||
/// redrawn.
|
||||
#[inline]
|
||||
@@ -86,14 +86,20 @@ impl ProgressBar {
|
||||
|
||||
/// Set the rounding of the progress bar.
|
||||
///
|
||||
/// If [`Self::rounding`] and [`Self::animate`] are used simultaneously, the animation is not
|
||||
/// If [`Self::corner_radius`] and [`Self::animate`] are used simultaneously, the animation is not
|
||||
/// rendered, since it requires a perfect circle to render correctly. However, the UI is still
|
||||
/// redrawn.
|
||||
#[inline]
|
||||
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
|
||||
self.rounding = Some(rounding.into());
|
||||
pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.corner_radius = Some(corner_radius.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[deprecated = "Renamed to `corner_radius`"]
|
||||
pub fn rounding(self, corner_radius: impl Into<CornerRadius>) -> Self {
|
||||
self.corner_radius(corner_radius)
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ProgressBar {
|
||||
@@ -105,7 +111,7 @@ impl Widget for ProgressBar {
|
||||
text,
|
||||
fill,
|
||||
animate,
|
||||
rounding,
|
||||
corner_radius,
|
||||
} = self;
|
||||
|
||||
let animate = animate && progress < 1.0;
|
||||
@@ -133,13 +139,13 @@ impl Widget for ProgressBar {
|
||||
}
|
||||
|
||||
let visuals = ui.style().visuals.clone();
|
||||
let is_custom_rounding = rounding.is_some();
|
||||
let corner_radius = outer_rect.height() / 2.0;
|
||||
let rounding = rounding.unwrap_or_else(|| corner_radius.into());
|
||||
let has_custom_cr = corner_radius.is_some();
|
||||
let half_height = outer_rect.height() / 2.0;
|
||||
let corner_radius = corner_radius.unwrap_or_else(|| half_height.into());
|
||||
ui.painter()
|
||||
.rect_filled(outer_rect, rounding, visuals.extreme_bg_color);
|
||||
.rect_filled(outer_rect, corner_radius, visuals.extreme_bg_color);
|
||||
let min_width =
|
||||
2.0 * f32::max(rounding.sw as _, rounding.nw as _).at_most(corner_radius);
|
||||
2.0 * f32::max(corner_radius.sw as _, corner_radius.nw as _).at_most(half_height);
|
||||
let filled_width = (outer_rect.width() * progress).at_least(min_width);
|
||||
let inner_rect =
|
||||
Rect::from_min_size(outer_rect.min, vec2(filled_width, outer_rect.height()));
|
||||
@@ -154,25 +160,25 @@ impl Widget for ProgressBar {
|
||||
|
||||
ui.painter().rect_filled(
|
||||
inner_rect,
|
||||
rounding,
|
||||
corner_radius,
|
||||
Color32::from(
|
||||
Rgba::from(fill.unwrap_or(visuals.selection.bg_fill)) * color_factor as f32,
|
||||
),
|
||||
);
|
||||
|
||||
if animate && !is_custom_rounding {
|
||||
if animate && !has_custom_cr {
|
||||
let n_points = 20;
|
||||
let time = ui.input(|i| i.time);
|
||||
let start_angle = time * std::f64::consts::TAU;
|
||||
let end_angle = start_angle + 240f64.to_radians() * time.sin();
|
||||
let circle_radius = corner_radius - 2.0;
|
||||
let circle_radius = half_height - 2.0;
|
||||
let points: Vec<Pos2> = (0..n_points)
|
||||
.map(|i| {
|
||||
let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64);
|
||||
let (sin, cos) = angle.sin_cos();
|
||||
inner_rect.right_center()
|
||||
+ circle_radius * vec2(cos as f32, sin as f32)
|
||||
+ vec2(-corner_radius, 0.0)
|
||||
+ vec2(-half_height, 0.0)
|
||||
})
|
||||
.collect();
|
||||
ui.painter()
|
||||
|
||||
@@ -71,7 +71,7 @@ impl Widget for SelectableLabel {
|
||||
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
epaint::StrokeKind::Inside,
|
||||
|
||||
@@ -760,10 +760,10 @@ impl Slider<'_> {
|
||||
|
||||
let rail_radius = (spacing.slider_rail_height / 2.0).at_least(0.0);
|
||||
let rail_rect = self.rail_rect(rect, rail_radius);
|
||||
let rounding = widget_visuals.inactive.rounding;
|
||||
let corner_radius = widget_visuals.inactive.corner_radius;
|
||||
|
||||
ui.painter()
|
||||
.rect_filled(rail_rect, rounding, widget_visuals.inactive.bg_fill);
|
||||
.rect_filled(rail_rect, corner_radius, widget_visuals.inactive.bg_fill);
|
||||
|
||||
let position_1d = self.position_from_value(value, position_range);
|
||||
let center = self.marker_center(position_1d, &rail_rect);
|
||||
@@ -780,16 +780,16 @@ impl Slider<'_> {
|
||||
// The trailing rect has to be drawn differently depending on the orientation.
|
||||
match self.orientation {
|
||||
SliderOrientation::Horizontal => {
|
||||
trailing_rail_rect.max.x = center.x + rounding.nw as f32;
|
||||
trailing_rail_rect.max.x = center.x + corner_radius.nw as f32;
|
||||
}
|
||||
SliderOrientation::Vertical => {
|
||||
trailing_rail_rect.min.y = center.y - rounding.se as f32;
|
||||
trailing_rail_rect.min.y = center.y - corner_radius.se as f32;
|
||||
}
|
||||
};
|
||||
|
||||
ui.painter().rect_filled(
|
||||
trailing_rail_rect,
|
||||
rounding,
|
||||
corner_radius,
|
||||
ui.visuals().selection.bg_fill,
|
||||
);
|
||||
}
|
||||
@@ -817,7 +817,7 @@ impl Slider<'_> {
|
||||
let rect = Rect::from_center_size(center, 2.0 * v);
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
visuals.bg_fill,
|
||||
visuals.fg_stroke,
|
||||
epaint::StrokeKind::Inside,
|
||||
|
||||
@@ -442,7 +442,7 @@ impl TextEdit<'_> {
|
||||
if output.response.has_focus() {
|
||||
epaint::RectShape::new(
|
||||
frame_rect,
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
background_color,
|
||||
ui.visuals().selection.stroke,
|
||||
StrokeKind::Inside,
|
||||
@@ -450,7 +450,7 @@ impl TextEdit<'_> {
|
||||
} else {
|
||||
epaint::RectShape::new(
|
||||
frame_rect,
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
background_color,
|
||||
visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
StrokeKind::Inside,
|
||||
@@ -460,7 +460,7 @@ impl TextEdit<'_> {
|
||||
let visuals = &ui.style().visuals.widgets.inactive;
|
||||
epaint::RectShape::stroke(
|
||||
frame_rect,
|
||||
visuals.rounding,
|
||||
visuals.corner_radius,
|
||||
visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
|
||||
StrokeKind::Inside,
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@ impl FrameHistory {
|
||||
let mut shapes = Vec::with_capacity(3 + 2 * history.len());
|
||||
shapes.push(Shape::Rect(epaint::RectShape::new(
|
||||
rect,
|
||||
style.rounding,
|
||||
style.corner_radius,
|
||||
ui.visuals().extreme_bg_color,
|
||||
ui.style().noninteractive().bg_stroke,
|
||||
egui::StrokeKind::Inside,
|
||||
|
||||
@@ -47,7 +47,7 @@ impl eframe::App for FractalClockApp {
|
||||
.frame(
|
||||
egui::Frame::dark_canvas(&ctx.style())
|
||||
.stroke(egui::Stroke::NONE)
|
||||
.rounding(0),
|
||||
.corner_radius(0),
|
||||
)
|
||||
.show(ctx, |ui| {
|
||||
self.fractal_clock
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4aeab31841dd95b5e0f4bd0af0c0ba49a862d50836dbafdf2172fbbab950c105
|
||||
size 327741
|
||||
oid sha256:2c15a74a1b1ed3b52a53966a3df2901ca520b92fbfbd10503e32ddb8431e1467
|
||||
size 335399
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7bcf6e2977bed682d7bdaa0b6a6786e528662dd0791d2e6f83cf1b4852035838
|
||||
size 182833
|
||||
oid sha256:d8f1046ee5d50d73a17009fd1f11f056b5828fedc62908d00730a6aa77125473
|
||||
size 182900
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2292f0f80bfd3c80055a72eb983549ac2875d36acb333732bd0a67e51b24ae4f
|
||||
size 102983
|
||||
oid sha256:57274cec5ee7e5522073249b931ea65ead22752aea1de40666543e765c1b6b85
|
||||
size 102929
|
||||
|
||||
@@ -2,6 +2,7 @@ use egui::accesskit::Role;
|
||||
use egui::Vec2;
|
||||
use egui_demo_app::{Anchor, WrapApp};
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use egui_kittest::SnapshotResults;
|
||||
|
||||
#[test]
|
||||
fn test_demo_app() {
|
||||
@@ -27,7 +28,7 @@ fn test_demo_app() {
|
||||
"Expected to find the Custom3d app.",
|
||||
);
|
||||
|
||||
let mut results = vec![];
|
||||
let mut results = SnapshotResults::new();
|
||||
|
||||
for (name, anchor) in apps {
|
||||
harness.get_by_role_and_label(Role::Button, name).click();
|
||||
@@ -68,12 +69,6 @@ fn test_demo_app() {
|
||||
// Can't use Harness::run because fractal clock keeps requesting repaints
|
||||
harness.run_steps(2);
|
||||
|
||||
if let Err(e) = harness.try_snapshot(&anchor.to_string()) {
|
||||
results.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(error) = results.first() {
|
||||
panic!("{error}");
|
||||
results.add(harness.try_snapshot(&anchor.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ impl Default for DemoGroups {
|
||||
Box::<super::tests::InputTest>::default(),
|
||||
Box::<super::tests::LayoutTest>::default(),
|
||||
Box::<super::tests::ManualLayoutTest>::default(),
|
||||
Box::<super::tests::TessellationTest>::default(),
|
||||
Box::<super::tests::WindowResizeTest>::default(),
|
||||
]),
|
||||
}
|
||||
@@ -365,13 +366,13 @@ mod tests {
|
||||
use crate::{demo::demo_app_windows::DemoGroups, Demo};
|
||||
use egui::Vec2;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use egui_kittest::{Harness, SnapshotOptions};
|
||||
use egui_kittest::{Harness, SnapshotOptions, SnapshotResults};
|
||||
|
||||
#[test]
|
||||
fn demos_should_match_snapshot() {
|
||||
let demos = DemoGroups::default().demos;
|
||||
|
||||
let mut errors = Vec::new();
|
||||
let mut results = SnapshotResults::new();
|
||||
|
||||
for mut demo in demos.demos {
|
||||
// Widget Gallery needs to be customized (to set a specific date) and has its own test
|
||||
@@ -405,12 +406,7 @@ mod tests {
|
||||
options.threshold = 2.1;
|
||||
}
|
||||
|
||||
let result = harness.try_snapshot_options(&format!("demos/{name}"), &options);
|
||||
if let Err(err) = result {
|
||||
errors.push(err.to_string());
|
||||
}
|
||||
results.add(harness.try_snapshot_options(&format!("demos/{name}"), &options));
|
||||
}
|
||||
|
||||
assert!(errors.is_empty(), "Errors: {errors:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ impl Default for FrameDemo {
|
||||
frame: egui::Frame::new()
|
||||
.inner_margin(12)
|
||||
.outer_margin(24)
|
||||
.rounding(14)
|
||||
.corner_radius(14)
|
||||
.shadow(egui::Shadow {
|
||||
offset: [8, 12],
|
||||
blur: 16,
|
||||
@@ -56,7 +56,7 @@ impl crate::View for FrameDemo {
|
||||
// We want to paint a background around the outer margin of the demonstration frame, so we use another frame around it:
|
||||
egui::Frame::default()
|
||||
.stroke(ui.visuals().widgets.noninteractive.bg_stroke)
|
||||
.rounding(ui.visuals().widgets.noninteractive.rounding)
|
||||
.corner_radius(ui.visuals().widgets.noninteractive.corner_radius)
|
||||
.show(ui, |ui| {
|
||||
self.frame.show(ui, |ui| {
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||
|
||||
@@ -124,9 +124,9 @@ impl View for MiscDemoWindow {
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
self.checklist
|
||||
.iter_mut()
|
||||
.for_each(|checked| *checked = all_checked);
|
||||
for check in &mut self.checklist {
|
||||
*check = all_checked;
|
||||
}
|
||||
}
|
||||
for (i, checked) in self.checklist.iter_mut().enumerate() {
|
||||
ui.checkbox(checked, format!("Item {}", i + 1));
|
||||
@@ -358,7 +358,7 @@ impl ColorWidgets {
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
struct BoxPainting {
|
||||
size: Vec2,
|
||||
rounding: f32,
|
||||
corner_radius: f32,
|
||||
stroke_width: f32,
|
||||
num_boxes: usize,
|
||||
}
|
||||
@@ -367,7 +367,7 @@ impl Default for BoxPainting {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: vec2(64.0, 32.0),
|
||||
rounding: 5.0,
|
||||
corner_radius: 5.0,
|
||||
stroke_width: 2.0,
|
||||
num_boxes: 1,
|
||||
}
|
||||
@@ -378,7 +378,7 @@ impl BoxPainting {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.add(Slider::new(&mut self.size.x, 0.0..=500.0).text("width"));
|
||||
ui.add(Slider::new(&mut self.size.y, 0.0..=500.0).text("height"));
|
||||
ui.add(Slider::new(&mut self.rounding, 0.0..=50.0).text("rounding"));
|
||||
ui.add(Slider::new(&mut self.corner_radius, 0.0..=50.0).text("corner_radius"));
|
||||
ui.add(Slider::new(&mut self.stroke_width, 0.0..=10.0).text("stroke_width"));
|
||||
ui.add(Slider::new(&mut self.num_boxes, 0..=8).text("num_boxes"));
|
||||
|
||||
@@ -387,7 +387,7 @@ impl BoxPainting {
|
||||
let (rect, _response) = ui.allocate_at_least(self.size, Sense::hover());
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
self.rounding,
|
||||
self.corner_radius,
|
||||
ui.visuals().text_color().gamma_multiply(0.5),
|
||||
Stroke::new(self.stroke_width, Color32::WHITE),
|
||||
egui::StrokeKind::Inside,
|
||||
|
||||
@@ -165,7 +165,7 @@ mod tests {
|
||||
use egui::accesskit::Role;
|
||||
use egui::Key;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::{Harness, SnapshotResults};
|
||||
|
||||
#[test]
|
||||
fn clicking_escape_when_popup_open_should_not_close_modal() {
|
||||
@@ -233,22 +233,18 @@ mod tests {
|
||||
initial_state,
|
||||
);
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut results = SnapshotResults::new();
|
||||
|
||||
harness.run();
|
||||
results.push(harness.try_snapshot("modals_1"));
|
||||
results.add(harness.try_snapshot("modals_1"));
|
||||
|
||||
harness.get_by_label("Save").click();
|
||||
harness.run_ok();
|
||||
results.push(harness.try_snapshot("modals_2"));
|
||||
results.add(harness.try_snapshot("modals_2"));
|
||||
|
||||
harness.get_by_label("Yes Please").click();
|
||||
harness.run_ok();
|
||||
results.push(harness.try_snapshot("modals_3"));
|
||||
|
||||
for result in results {
|
||||
result.unwrap();
|
||||
}
|
||||
results.add(harness.try_snapshot("modals_3"));
|
||||
}
|
||||
|
||||
// This tests whether the backdrop actually prevents interaction with lower layers.
|
||||
|
||||
@@ -11,7 +11,7 @@ pub struct SceneDemo {
|
||||
impl Default for SceneDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
widget_gallery: Default::default(),
|
||||
widget_gallery: widget_gallery::WidgetGallery::default().with_date_button(false), // disable date button so that we don't fail the snapshot test
|
||||
scene_rect: Rect::ZERO, // `egui::Scene` will initialize this to something valid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ mod input_event_history;
|
||||
mod input_test;
|
||||
mod layout_test;
|
||||
mod manual_layout_test;
|
||||
mod tessellation_test;
|
||||
mod window_resize_test;
|
||||
|
||||
pub use clipboard_test::ClipboardTest;
|
||||
@@ -16,4 +17,5 @@ pub use input_event_history::InputEventHistory;
|
||||
pub use input_test::InputTest;
|
||||
pub use layout_test::LayoutTest;
|
||||
pub use manual_layout_test::ManualLayoutTest;
|
||||
pub use tessellation_test::TessellationTest;
|
||||
pub use window_resize_test::WindowResizeTest;
|
||||
|
||||
379
crates/egui_demo_lib/src/demo/tests/tessellation_test.rs
Normal file
379
crates/egui_demo_lib/src/demo/tests/tessellation_test.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
use egui::{
|
||||
emath::{GuiRounding, TSTransform},
|
||||
epaint::{self, RectShape},
|
||||
vec2, Color32, Pos2, Rect, Sense, StrokeKind, Vec2,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TessellationTest {
|
||||
shape: RectShape,
|
||||
|
||||
magnification_pixel_size: f32,
|
||||
tessellation_options: epaint::TessellationOptions,
|
||||
paint_edges: bool,
|
||||
}
|
||||
|
||||
impl Default for TessellationTest {
|
||||
fn default() -> Self {
|
||||
let shape = Self::interesting_shapes()[0].1.clone();
|
||||
Self {
|
||||
shape,
|
||||
magnification_pixel_size: 12.0,
|
||||
tessellation_options: Default::default(),
|
||||
paint_edges: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TessellationTest {
|
||||
fn interesting_shapes() -> Vec<(&'static str, RectShape)> {
|
||||
fn sized(size: impl Into<Vec2>) -> Rect {
|
||||
Rect::from_center_size(Pos2::ZERO, size.into())
|
||||
}
|
||||
|
||||
let baby_blue = Color32::from_rgb(0, 181, 255);
|
||||
|
||||
let mut shapes = vec![
|
||||
(
|
||||
"Normal",
|
||||
RectShape::new(
|
||||
sized([20.0, 16.0]),
|
||||
2.0,
|
||||
baby_blue,
|
||||
(1.0, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Minimal rounding",
|
||||
RectShape::new(
|
||||
sized([20.0, 16.0]),
|
||||
1.0,
|
||||
baby_blue,
|
||||
(1.0, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Thin filled",
|
||||
RectShape::filled(sized([20.0, 0.5]), 2.0, baby_blue),
|
||||
),
|
||||
(
|
||||
"Thin stroked",
|
||||
RectShape::new(
|
||||
sized([20.0, 0.5]),
|
||||
2.0,
|
||||
baby_blue,
|
||||
(0.5, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Blurred",
|
||||
RectShape::filled(sized([20.0, 16.0]), 2.0, baby_blue).with_blur_width(50.0),
|
||||
),
|
||||
(
|
||||
"Thick stroke, minimal rounding",
|
||||
RectShape::new(
|
||||
sized([20.0, 16.0]),
|
||||
1.0,
|
||||
baby_blue,
|
||||
(3.0, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Blurred stroke",
|
||||
RectShape::new(
|
||||
sized([20.0, 16.0]),
|
||||
0.0,
|
||||
baby_blue,
|
||||
(5.0, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
)
|
||||
.with_blur_width(5.0),
|
||||
),
|
||||
(
|
||||
"Additive rectangle",
|
||||
RectShape::new(
|
||||
sized([24.0, 12.0]),
|
||||
0.0,
|
||||
egui::Color32::LIGHT_RED.additive().linear_multiply(0.025),
|
||||
(
|
||||
1.0,
|
||||
egui::Color32::LIGHT_BLUE.additive().linear_multiply(0.1),
|
||||
),
|
||||
StrokeKind::Outside,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
for (_name, shape) in &mut shapes {
|
||||
shape.round_to_pixels = Some(true);
|
||||
}
|
||||
|
||||
shapes
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Demo for TessellationTest {
|
||||
fn name(&self) -> &'static str {
|
||||
"Tessellation Test"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.resizable(false)
|
||||
.open(open)
|
||||
.show(ctx, |ui| {
|
||||
use crate::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::View for TessellationTest {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
egui::reset_button(ui, self, "Reset");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
rect_shape_ui(ui, &mut self.shape);
|
||||
});
|
||||
});
|
||||
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Real size");
|
||||
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
||||
let (resp, painter) =
|
||||
ui.allocate_painter(Vec2::splat(128.0), Sense::hover());
|
||||
let canvas = resp.rect;
|
||||
|
||||
let pixels_per_point = ui.pixels_per_point();
|
||||
let pixel_size = 1.0 / pixels_per_point;
|
||||
let mut shape = self.shape.clone();
|
||||
shape.rect = Rect::from_center_size(canvas.center(), shape.rect.size())
|
||||
.round_to_pixel_center(pixels_per_point)
|
||||
.translate(Vec2::new(pixel_size / 3.0, pixel_size / 5.0)); // Intentionally offset to test the effect of rounding
|
||||
painter.add(shape);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ui.group(|ui| {
|
||||
ui.heading("Zoomed in");
|
||||
let magnification_pixel_size = &mut self.magnification_pixel_size;
|
||||
let tessellation_options = &mut self.tessellation_options;
|
||||
|
||||
egui::Grid::new("TessellationOptions")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 8.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Magnification");
|
||||
ui.add(
|
||||
egui::DragValue::new(magnification_pixel_size)
|
||||
.speed(0.5)
|
||||
.range(1.0..=32.0),
|
||||
);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Feathering width");
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut tessellation_options.feathering, "");
|
||||
ui.add_enabled(
|
||||
tessellation_options.feathering,
|
||||
egui::DragValue::new(
|
||||
&mut tessellation_options.feathering_size_in_pixels,
|
||||
)
|
||||
.speed(0.1)
|
||||
.range(0.0..=4.0)
|
||||
.suffix(" px"),
|
||||
);
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Paint edges");
|
||||
ui.checkbox(&mut self.paint_edges, "");
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
let magnification_pixel_size = *magnification_pixel_size;
|
||||
|
||||
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
||||
let (resp, painter) = ui.allocate_painter(
|
||||
magnification_pixel_size * (self.shape.rect.size() + Vec2::splat(8.0)),
|
||||
Sense::hover(),
|
||||
);
|
||||
let canvas = resp.rect;
|
||||
|
||||
let mut shape = self.shape.clone();
|
||||
shape.rect = shape.rect.translate(Vec2::new(1.0 / 3.0, 1.0 / 5.0)); // Intentionally offset to test the effect of rounding
|
||||
|
||||
let mut mesh = epaint::Mesh::default();
|
||||
let mut tessellator = epaint::Tessellator::new(
|
||||
1.0,
|
||||
*tessellation_options,
|
||||
ui.fonts(|f| f.font_image_size()),
|
||||
vec![],
|
||||
);
|
||||
tessellator.tessellate_rect(&shape, &mut mesh);
|
||||
|
||||
// Scale and position the mesh:
|
||||
mesh.transform(
|
||||
TSTransform::from_translation(canvas.center().to_vec2())
|
||||
* TSTransform::from_scaling(magnification_pixel_size),
|
||||
);
|
||||
let mesh = std::sync::Arc::new(mesh);
|
||||
painter.add(epaint::Shape::mesh(mesh.clone()));
|
||||
|
||||
if self.paint_edges {
|
||||
let stroke = epaint::Stroke::new(0.5, Color32::MAGENTA);
|
||||
for triangle in mesh.triangles() {
|
||||
let a = mesh.vertices[triangle[0] as usize];
|
||||
let b = mesh.vertices[triangle[1] as usize];
|
||||
let c = mesh.vertices[triangle[2] as usize];
|
||||
|
||||
painter.line_segment([a.pos, b.pos], stroke);
|
||||
painter.line_segment([b.pos, c.pos], stroke);
|
||||
painter.line_segment([c.pos, a.pos], stroke);
|
||||
}
|
||||
}
|
||||
|
||||
if 3.0 < magnification_pixel_size {
|
||||
// Draw pixel centers:
|
||||
let pixel_radius = 0.75;
|
||||
let pixel_color = Color32::GRAY;
|
||||
for yi in 0.. {
|
||||
let y = (yi as f32 + 0.5) * magnification_pixel_size;
|
||||
if y > canvas.height() / 2.0 {
|
||||
break;
|
||||
}
|
||||
for xi in 0.. {
|
||||
let x = (xi as f32 + 0.5) * magnification_pixel_size;
|
||||
if x > canvas.width() / 2.0 {
|
||||
break;
|
||||
}
|
||||
for offset in [vec2(x, y), vec2(x, -y), vec2(-x, y), vec2(-x, -y)] {
|
||||
painter.circle_filled(
|
||||
canvas.center() + offset,
|
||||
pixel_radius,
|
||||
pixel_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn rect_shape_ui(ui: &mut egui::Ui, shape: &mut RectShape) {
|
||||
egui::ComboBox::from_id_salt("prefabs")
|
||||
.selected_text("Prefabs")
|
||||
.show_ui(ui, |ui| {
|
||||
for (name, prefab) in TessellationTest::interesting_shapes() {
|
||||
ui.selectable_value(shape, prefab, name);
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
let RectShape {
|
||||
rect,
|
||||
corner_radius,
|
||||
fill,
|
||||
stroke,
|
||||
stroke_kind,
|
||||
blur_width,
|
||||
round_to_pixels,
|
||||
brush: _,
|
||||
} = shape;
|
||||
|
||||
let round_to_pixels = round_to_pixels.get_or_insert(true);
|
||||
|
||||
egui::Grid::new("RectShape")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 8.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Size");
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = rect.size();
|
||||
ui.add(
|
||||
egui::DragValue::new(&mut size.x)
|
||||
.speed(0.2)
|
||||
.range(0.0..=64.0),
|
||||
);
|
||||
ui.add(
|
||||
egui::DragValue::new(&mut size.y)
|
||||
.speed(0.2)
|
||||
.range(0.0..=64.0),
|
||||
);
|
||||
*rect = Rect::from_center_size(Pos2::ZERO, size);
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Corner radius");
|
||||
ui.add(corner_radius);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Fill");
|
||||
ui.color_edit_button_srgba(fill);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Stroke");
|
||||
ui.add(stroke);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Stroke kind");
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(stroke_kind, StrokeKind::Inside, "Inside");
|
||||
ui.selectable_value(stroke_kind, StrokeKind::Middle, "Middle");
|
||||
ui.selectable_value(stroke_kind, StrokeKind::Outside, "Outside");
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Blur width");
|
||||
ui.add(
|
||||
egui::DragValue::new(blur_width)
|
||||
.speed(0.5)
|
||||
.range(0.0..=20.0),
|
||||
);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Round to pixels");
|
||||
ui.checkbox(round_to_pixels, "");
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::View as _;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn snapshot_tessellation_test() {
|
||||
for (name, shape) in TessellationTest::interesting_shapes() {
|
||||
let mut test = TessellationTest {
|
||||
shape,
|
||||
..Default::default()
|
||||
};
|
||||
let mut harness = egui_kittest::Harness::new_ui(|ui| {
|
||||
test.ui(ui);
|
||||
});
|
||||
|
||||
harness.fit_contents();
|
||||
harness.run();
|
||||
|
||||
harness.snapshot(&format!("tessellation_test/{name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ pub struct WidgetGallery {
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
date: Option<chrono::NaiveDate>,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
with_date_button: bool,
|
||||
}
|
||||
|
||||
impl Default for WidgetGallery {
|
||||
@@ -38,10 +41,24 @@ impl Default for WidgetGallery {
|
||||
animate_progress_bar: false,
|
||||
#[cfg(feature = "chrono")]
|
||||
date: None,
|
||||
#[cfg(feature = "chrono")]
|
||||
with_date_button: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetGallery {
|
||||
#[allow(unused_mut)] // if not chrono
|
||||
#[inline]
|
||||
pub fn with_date_button(mut self, _with_date_button: bool) -> Self {
|
||||
#[cfg(feature = "chrono")]
|
||||
{
|
||||
self.with_date_button = _with_date_button;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Demo for WidgetGallery {
|
||||
fn name(&self) -> &'static str {
|
||||
"🗄 Widget Gallery"
|
||||
@@ -124,6 +141,8 @@ impl WidgetGallery {
|
||||
animate_progress_bar,
|
||||
#[cfg(feature = "chrono")]
|
||||
date,
|
||||
#[cfg(feature = "chrono")]
|
||||
with_date_button,
|
||||
} = self;
|
||||
|
||||
ui.add(doc_link_label("Label", "label"));
|
||||
@@ -226,7 +245,7 @@ impl WidgetGallery {
|
||||
ui.end_row();
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
{
|
||||
if *with_date_button {
|
||||
let date = date.get_or_insert_with(|| chrono::offset::Utc::now().date_naive());
|
||||
ui.add(doc_link_label_with_crate(
|
||||
"egui_extras",
|
||||
|
||||
@@ -688,10 +688,11 @@ fn mul_color_gamma(left: Color32, right: Color32) -> Color32 {
|
||||
mod tests {
|
||||
use crate::ColorTest;
|
||||
use egui_kittest::kittest::Queryable as _;
|
||||
use egui_kittest::SnapshotResults;
|
||||
|
||||
#[test]
|
||||
pub fn rendering_test() {
|
||||
let mut errors = vec![];
|
||||
let mut results = SnapshotResults::new();
|
||||
for dpi in [1.0, 1.25, 1.5, 1.75, 1.6666667, 2.0] {
|
||||
let mut color_test = ColorTest::default();
|
||||
let mut harness = egui_kittest::Harness::builder()
|
||||
@@ -708,12 +709,7 @@ mod tests {
|
||||
|
||||
harness.fit_contents();
|
||||
|
||||
let result = harness.try_snapshot(&format!("rendering_test/dpi_{dpi:.2}"));
|
||||
if let Err(err) = result {
|
||||
errors.push(err);
|
||||
}
|
||||
results.add(harness.try_snapshot(&format!("rendering_test/dpi_{dpi:.2}")));
|
||||
}
|
||||
|
||||
assert!(errors.is_empty(), "Errors: {errors:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d4cfd5191dc7046a782ef2350dc8e0547d2702182badcb15b6b928ce077b76c1
|
||||
size 32154
|
||||
oid sha256:536fa3adb51f69fac91396b50e26b3b18e0aa8ff245e4a187087b02240839a90
|
||||
size 31780
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4631f841b9e23833505af39eb1c45908013d3b1e1278d477bcedf6a460c71802
|
||||
size 27163
|
||||
oid sha256:c129436a0b1dbfae999adfe0dcc6f5c4e0683c4e9b9a1e52f4b7bbb85ce3a462
|
||||
size 27162
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:96e750ebfcc6ec2c130674407388c30cce844977cde19adfebf351fd08698a4f
|
||||
size 81726
|
||||
oid sha256:322a50c522ba4ac67206332e1d251e121c8c3d5538ca7961880623b20f4933e5
|
||||
size 81732
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b03613e597631da3b922ef3d696c4ce74cec41f86a2779fc5b084a316fc9e8e8
|
||||
size 11764
|
||||
oid sha256:d4cc8e0919fed5bd1ef981658626dba728435ab95da8ee96ced1fb4838d535ff
|
||||
size 11741
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:76d77e2df39af248d004a023b238bb61ed598ab2bea3e0c6f2885f9537ec1103
|
||||
size 25988
|
||||
oid sha256:d6ba28dacacf5b6f67746fb5187b601e222fd6baf190af2248fdc98909fc17fd
|
||||
size 25921
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:003d905893b80ffc132a783155971ad3054229e9d6c046e2760c912559a66b3d
|
||||
size 20869
|
||||
oid sha256:8196e08717f16c5ad17d0f84a4e57e63bb5a51c8f2b171071bf983af18ec161d
|
||||
size 20834
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:be0f96c700b7662aab5098f8412dae3676116eeed65e70f6b295dd3375b329d0
|
||||
size 10968
|
||||
oid sha256:df029c69651ee452cc4b265828280e47ffbcafb2958d71d67a5fe38f5211afe7
|
||||
size 10788
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:99c94a09c0f6984909481189f9a1415ea90bd7c45e42b4b3763ee36f3d831a65
|
||||
size 133231
|
||||
oid sha256:0a62d309912501be8a5de7af4f1039a2a5731b1ed76fad17527f5783a5375f42
|
||||
size 133230
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e4fef5fa8661f207bae2c58381e729cdaf77aecc8b3f178caf262dc310e3a490
|
||||
size 24206
|
||||
oid sha256:9870334dd6091fa684b78f487ad9a1bb39e6e8d97f987eb74a55de2d7b764f70
|
||||
size 24345
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7b38828e423195628dcea09b0cbdd66aa4c077334ab7082efd358c7a3297009d
|
||||
size 17827
|
||||
oid sha256:bae5f410ed30ef4dba6f3b529ae20e34a26f6c15c4cafd197899cf876271f5f1
|
||||
size 17828
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c53403996451da14f7991ea61bd20b96dbc67eb67dd2041975dd6ce5015a6980
|
||||
size 22485
|
||||
oid sha256:68ded8dccceb3da2764243f2a554c2b4cf825fca09008d60dd520c7fbb2c5d3e
|
||||
size 22445
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:63673b070951246b98ca07613fa81496dbfdd10627bac3c9c4356ebff1a36b20
|
||||
size 64319
|
||||
oid sha256:d55baa6e3d4af44a35ec847639c35f968b05ad907352c45b3eb09cce6cd24280
|
||||
size 64357
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:374b4095a3c7b827b80d6ab01b945458ae0128a2974c2af8aaf6b7e9fef6b459
|
||||
size 32554
|
||||
oid sha256:98f7210fa72bdb00364e3576aefca126a6f31eff52870d116ba74c167354b13b
|
||||
size 32533
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5e756c90069803bb4d2821fff723877bffffd44b26613f5b06c8a042d6901ca4
|
||||
size 36578
|
||||
oid sha256:7868d662bd61d490dce9c049fca6c6e6b978255664fa709e959891bb40a7d434
|
||||
size 36577
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6efc59cb9908533baa1a7346b359e9e21c5faf0e373dac6fa7db5476e644233d
|
||||
size 17678
|
||||
oid sha256:fb3d031b8f658a90cf98e7a7bc5e0d7a3b601d742e2a9469cd115e7466e06524
|
||||
size 17628
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:eb631bd2608aec6c83f8815b9db0b28627bf82692fd2b1cb793119083b4f8ad1
|
||||
size 264496
|
||||
oid sha256:7ace9a6626446f8e29ec4c3f688e60cbeb86e79ad962044858aabe33a9c3d0e9
|
||||
size 264538
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d076f5365bfa87b7e61d3808b8b9b367157ea6e55ccf665720cbd2237d53793d
|
||||
size 35563
|
||||
oid sha256:7d412700c156c641f0184a239198f33bd2427a1ea998a3ee07160cf0f837df94
|
||||
size 35451
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e0870e9e1c9dc718690124a4d490f1061414f15fa40571e571d9319c3b65e74e
|
||||
size 23709
|
||||
oid sha256:89efd018caac097a5f9be37dcae15fc60b1475c72fc913ec9940540344e0b09f
|
||||
size 23622
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:532dbcbec94bb9c9fb8cc0665646790a459088e98077118b5fbb2112898e1a43
|
||||
size 183854
|
||||
oid sha256:22c89f7b9b84563d6ee7db0d9a66f6b95c9034261fdffca53ae9737d70d2b376
|
||||
size 183881
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3fd584643b68084ec4b65369e08d229e2d389551bbefa59c84ad4b58621593f7
|
||||
size 117754
|
||||
oid sha256:316c172a936f215afdcc45e7f5b32400e6acd759551adb2cc741f7121b9d83eb
|
||||
size 117790
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7e2b854d99c9b17d15ef92188cdac521d7c0635aef9ba457cd3801884a784009
|
||||
size 26159
|
||||
oid sha256:88913690a2b225ca634e38406a6a852250019a19d9bb33a4242e77c10fe88422
|
||||
size 26142
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f0b7dc029de8e669d76f3be5e0800e973c354edcf7cefa239faed07c2cd5e0d5
|
||||
size 70452
|
||||
oid sha256:43b8dae4a936bf56b92368fcef64ff2ce2518aabc534a77fa578730493034f0f
|
||||
size 70536
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:64ba40810a6480e511e8d198b0dfb39e8b220eb2f5592877e27b17ee8d47b9c3
|
||||
size 66387
|
||||
oid sha256:a307ac48abc79548c16468b3606a5df283ab2a5ac28345bd801bcc3887063414
|
||||
size 66384
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0a8151f5bd01b2cb5183c532d11f57bbb7e8cc1e77a3c4b890537d157922c961
|
||||
size 21261
|
||||
oid sha256:6311be2b850b5e41ac6dadf639b00584438b56f651a3c8d75ac8f5e06c9ad6fa
|
||||
size 21224
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:96ce36fcc334793801ab724c711f592faf086a9c98c812684e6b76010e9d1974
|
||||
size 59714
|
||||
oid sha256:c80158ac9c823f94d2830d1423236ad441dc7da31e748b6815c69663fa2a03d0
|
||||
size 59662
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8155d93b78692ced67ddee4011526ef627838ede78351871df5feef8aa512857
|
||||
size 13141
|
||||
oid sha256:3584f16229bae50cc04b31df6bf5ccf43288fd05b447b34b29f118eb7435a090
|
||||
size 13103
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf6da022ab97f9d4b862cc8e91bdfd7f9785a3ab0540aa1c2bd2646bd30a3450
|
||||
size 35115
|
||||
oid sha256:3411a4a8939b7e731c9c1a6331921b0ac905f4e3e86a51af70bdb38d9446f5e1
|
||||
size 35193
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:035b35ed053cabd5022d9a97f7d4d5a79d194c9f14594e7602ecd9ef94c24ae5
|
||||
size 48053
|
||||
oid sha256:1ac48ec9f7bde9869f1b3097e9f897b5e8df96cd6159a6ded542582dc69ab32c
|
||||
size 47913
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:80a2968e211c83639b60e84b805f1327fb37b87144cada672a403c7e92ace8a8
|
||||
size 48066
|
||||
oid sha256:795e16389b31ad719050247eb9e736782380a83fa71b5b35b50e17812c8d9bdd
|
||||
size 47886
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:86df5dc4b4ddd6f44226242b6d9b5e9f2aacd45193ae9f784fb5084a7a509e0b
|
||||
size 43987
|
||||
oid sha256:a62a286e29aa0e0f949088ddefe01137535877408ba88778f61cbfe8d50c2261
|
||||
size 43750
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1138bbc3b7e73cccd555f0fd58c27a5bda4d84484fdc1bd5223fc9802d0c5328
|
||||
size 44089
|
||||
oid sha256:9c1bc8e22aa1050a4e7d1b2abe407251e22d338c38a7e41c045a384c9139b4de
|
||||
size 43895
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3562bb87b155b2c406a72a651ffb1991305aa1e15273ce9f32cedc5766318537
|
||||
size 554922
|
||||
oid sha256:03ee62427611101758958adf2650a4a0eea4e023f07c9ec4ebc63425233e8a04
|
||||
size 554949
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ba6c0bd127ab02375f2ae5fbc3eeef33a1bdf074cbb180de2733c700b81df3e5
|
||||
size 771069
|
||||
oid sha256:82ef265f0e22649c7fcdb9556879c1a30df582bd4e97c647258b3e5acc03d112
|
||||
size 771298
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d85ab6d04059009fd2c3ad8001332b27e710c46c9300f2f1f409b882c49211dc
|
||||
size 918967
|
||||
oid sha256:cad71b486a479eb9c5339a93f4acc3df2d0b6b188ad023b9b044be7311b0ab72
|
||||
size 918775
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a64f1bdec565357fe4dee3acb46b12eeb0492b522fb3bb9697d39dadce2e8c21
|
||||
size 1039455
|
||||
oid sha256:dc9ed4d29f4227b9d38b477ee8f546ea8597acda56a6909ba4826891ebdbea01
|
||||
size 1039263
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5ca008dca03372bb334564e55fa2d1d25a36751a43df6001a1c1cf3e4db9bcd4
|
||||
size 1130930
|
||||
oid sha256:e9bf826bee811d8af345ec1281266fc9bef6d7c3782279516984a6c75130a929
|
||||
size 1130895
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7f695127e7fe6cb3b752a0acd71db85d51d2de68e45051a7afe91f4d960acf27
|
||||
size 1311641
|
||||
oid sha256:9345de28f09e2891fd01db20bb0b94176ec3c89d8c2f344a6640d33e97ab5400
|
||||
size 1311417
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:aa7d25b097911f6b18308bab56d302e3dae9f8f9916f563d5703632a26eda260
|
||||
size 72501
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5524138c3cb98aa71ef67083ad2d01813ab2394f93f9a7897f2e465ef5a1d0bc
|
||||
size 46270
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bdf06c41b69eef1eadc8b46020e6e2a7b985a54e1cf75646ca47caaaea525b95
|
||||
size 88092
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c5ca9c97cef8242ee6ff73d571479be12a8d4e9b3508b3eb6cdf93abda62f4e6
|
||||
size 120314
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f0481c97c34693b32575d96b1d4bc1238cbb0eb75a934661072f1b52ffee71cf
|
||||
size 52171
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:883fdf81e51bfe6333ddcad7998458db251f9cf513c9433179061d7d086eebe0
|
||||
size 55367
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4294949669042e009ac6825ea599dc96e33cdde25e21174b01e3ef108ad478d5
|
||||
size 55944
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:39e5d196ddcaa213b30b0655fe29881a1551c3036c2262f84af8960f66365300
|
||||
size 37207
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f9cf9d7f1921bfc0d61a2ae31e69a98d28280e4699823de5e732cdb102aee5ac
|
||||
size 37253
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a291f3a5724aefc59ba7881f48752ccc826ca5e480741c221d195061f562ccc9
|
||||
size 158220
|
||||
oid sha256:946bf96ae558ee7373b50bf11959e82b1f4d91866ec61b04b0336ae170b6f7b2
|
||||
size 158553
|
||||
|
||||
@@ -5,6 +5,12 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.31.0 - 2025-02-04
|
||||
* Animated WebP support [#5470](https://github.com/emilk/egui/pull/5470), [#5586](https://github.com/emilk/egui/pull/5586) by [@Aely0](https://github.com/Aely0)
|
||||
* Make image extension check case-insensitive [#5501](https://github.com/emilk/egui/pull/5501) by [@RyanBluth](https://github.com/RyanBluth)
|
||||
* Avoid allocations for loader cache lookup [#5584](https://github.com/emilk/egui/pull/5584) by [@mineichen](https://github.com/mineichen)
|
||||
|
||||
|
||||
## 0.30.0 - 2024-12-16
|
||||
* Use `Table::id_salt` on `ScrollArea` [#5282](https://github.com/emilk/egui/pull/5282) by [@jwhear](https://github.com/jwhear)
|
||||
* Use proper `image` crate URI and MIME support detection [#5324](https://github.com/emilk/egui/pull/5324) by [@xangelix](https://github.com/xangelix)
|
||||
|
||||
@@ -128,7 +128,7 @@ impl<'l> StripLayout<'l> {
|
||||
if flags.striped {
|
||||
self.ui.painter().rect_filled(
|
||||
gapless_rect,
|
||||
egui::Rounding::ZERO,
|
||||
egui::CornerRadius::ZERO,
|
||||
self.ui.visuals().faint_bg_color,
|
||||
);
|
||||
}
|
||||
@@ -136,7 +136,7 @@ impl<'l> StripLayout<'l> {
|
||||
if flags.selected {
|
||||
self.ui.painter().rect_filled(
|
||||
gapless_rect,
|
||||
egui::Rounding::ZERO,
|
||||
egui::CornerRadius::ZERO,
|
||||
self.ui.visuals().selection.bg_fill,
|
||||
);
|
||||
}
|
||||
@@ -144,7 +144,7 @@ impl<'l> StripLayout<'l> {
|
||||
if flags.hovered && !flags.selected && self.sense.interactive() {
|
||||
self.ui.painter().rect_filled(
|
||||
gapless_rect,
|
||||
egui::Rounding::ZERO,
|
||||
egui::CornerRadius::ZERO,
|
||||
self.ui.visuals().widgets.hovered.bg_fill,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ Changes since the last release can be found at <https://github.com/emilk/egui/co
|
||||
|
||||
|
||||
|
||||
## 0.31.0 - 2025-02-04
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.30.0 - 2024-12-16
|
||||
* Update glow to 0.16 [#5395](https://github.com/emilk/egui/pull/5395) by [@sagudev](https://github.com/sagudev)
|
||||
|
||||
|
||||
@@ -6,6 +6,18 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.31.0 - 2025-02-04
|
||||
### ⭐ Added
|
||||
* Add `Harness::new_eframe` and `TestRenderer` trait [#5539](https://github.com/emilk/egui/pull/5539) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Change `Harness::run` to run until no more repaints are requested [#5580](https://github.com/emilk/egui/pull/5580) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add `SnapshotResults` struct to `egui_kittest` [#5672](https://github.com/emilk/egui/pull/5672) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
|
||||
### 🔧 Changed
|
||||
* Extend `WgpuSetup`, `egui_kittest` now prefers software rasterizers for testing [#5506](https://github.com/emilk/egui/pull/5506) by [@Wumpf](https://github.com/Wumpf)
|
||||
* Write `.old.png` files when updating images [#5578](https://github.com/emilk/egui/pull/5578) by [@emilk](https://github.com/emilk)
|
||||
* Succeed and keep going when `UPDATE_SNAPSHOTS` is set [#5649](https://github.com/emilk/egui/pull/5649) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.30.0 - 2024-12-16 - Initial relrease
|
||||
* Support for egui 0.30.0
|
||||
* Automate clicks and text input
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# egui_kittest
|
||||
# egui_kittest
|
||||
|
||||
Ui testing library for egui, based on [kittest](https://github.com/rerun-io/kittest) (an [AccessKit](https://github.com/AccessKit/accesskit) based testing library).
|
||||
|
||||
@@ -14,16 +14,16 @@ fn main() {
|
||||
};
|
||||
|
||||
let mut harness = Harness::new_ui(app);
|
||||
|
||||
|
||||
let checkbox = harness.get_by_label("Check me!");
|
||||
assert_eq!(checkbox.toggled(), Some(Toggled::False));
|
||||
checkbox.click();
|
||||
|
||||
|
||||
harness.run();
|
||||
|
||||
let checkbox = harness.get_by_label("Check me!");
|
||||
assert_eq!(checkbox.toggled(), Some(Toggled::True));
|
||||
|
||||
|
||||
// Shrink the window size to the smallest size possible
|
||||
harness.fit_contents();
|
||||
|
||||
@@ -38,9 +38,10 @@ There is a snapshot testing feature. To create snapshot tests, enable the `snaps
|
||||
Once enabled, you can call `Harness::snapshot` to render the ui and save the image to the `tests/snapshots` directory.
|
||||
|
||||
To update the snapshots, run your tests with `UPDATE_SNAPSHOTS=true`, so e.g. `UPDATE_SNAPSHOTS=true cargo test`.
|
||||
Running with `UPDATE_SNAPSHOTS=true` will still cause the tests to fail, but on the next run, the tests should pass.
|
||||
Running with `UPDATE_SNAPSHOTS=true` will cause the tests to succeed.
|
||||
This is so that you can set `UPDATE_SNAPSHOTS=true` and update _all_ tests, without `cargo test` failing on the first failing crate.
|
||||
|
||||
If you want to have multiple snapshots in the same test, it makes sense to collect the results in a `Vec`
|
||||
If you want to have multiple snapshots in the same test, it makes sense to collect the results in a `Vec`
|
||||
([look here](https://github.com/emilk/egui/blob/70a01138b77f9c5724a35a6ef750b9ae1ab9f2dc/crates/egui_demo_lib/src/demo/demo_app_windows.rs#L388-L427) for an example).
|
||||
This way they can all be updated at the same time.
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ use std::fmt::Display;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub type SnapshotResult = Result<(), SnapshotError>;
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct SnapshotOptions {
|
||||
/// The threshold for the image comparison.
|
||||
@@ -155,8 +157,15 @@ impl Display for SnapshotError {
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is set, we update the snapshots (if different),
|
||||
/// and _succeed_ the test.
|
||||
/// This is so that you can set `UPDATE_SNAPSHOTS=true` and update _all_ tests,
|
||||
/// without `cargo test` failing on the first failing crate.
|
||||
fn should_update_snapshots() -> bool {
|
||||
std::env::var("UPDATE_SNAPSHOTS").is_ok()
|
||||
match std::env::var("UPDATE_SNAPSHOTS") {
|
||||
Ok(value) => !matches!(value.as_str(), "false" | "0" | "no" | "off"),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Image snapshot test with custom options.
|
||||
@@ -170,7 +179,7 @@ fn should_update_snapshots() -> bool {
|
||||
/// The snapshot files will be saved under [`SnapshotOptions::output_path`].
|
||||
/// The snapshot will be saved under `{output_path}/{name}.png`.
|
||||
/// The new image from the most recent test run will be saved under `{output_path}/{name}.new.png`.
|
||||
/// If new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
/// If the new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
///
|
||||
/// If the env-var `UPDATE_SNAPSHOTS` is set, then the old image will backed up under `{output_path}/{name}.old.png`.
|
||||
/// and then new image will be written to `{output_path}/{name}.png`
|
||||
@@ -182,13 +191,18 @@ pub fn try_image_snapshot_options(
|
||||
new: &image::RgbaImage,
|
||||
name: &str,
|
||||
options: &SnapshotOptions,
|
||||
) -> Result<(), SnapshotError> {
|
||||
) -> SnapshotResult {
|
||||
let SnapshotOptions {
|
||||
threshold,
|
||||
output_path,
|
||||
} = options;
|
||||
|
||||
std::fs::create_dir_all(output_path).ok();
|
||||
let parent_path = if let Some(parent) = PathBuf::from(name).parent() {
|
||||
output_path.join(parent)
|
||||
} else {
|
||||
output_path.clone()
|
||||
};
|
||||
std::fs::create_dir_all(parent_path).ok();
|
||||
|
||||
// The one that is checked in to git
|
||||
let snapshot_path = output_path.join(format!("{name}.png"));
|
||||
@@ -203,23 +217,22 @@ pub fn try_image_snapshot_options(
|
||||
std::fs::remove_file(&old_backup_path).ok();
|
||||
std::fs::remove_file(&new_path).ok();
|
||||
|
||||
let maybe_update_snapshot = || {
|
||||
if should_update_snapshots() {
|
||||
// Keep the old version so the user can compare it:
|
||||
std::fs::rename(&snapshot_path, &old_backup_path).ok();
|
||||
let update_snapshot = || {
|
||||
// Keep the old version so the user can compare it:
|
||||
std::fs::rename(&snapshot_path, &old_backup_path).ok();
|
||||
|
||||
// Write the new file to the checked in path:
|
||||
new.save(&snapshot_path)
|
||||
.map_err(|err| SnapshotError::WriteSnapshot {
|
||||
err,
|
||||
path: snapshot_path.clone(),
|
||||
})?;
|
||||
// Write the new file to the checked in path:
|
||||
new.save(&snapshot_path)
|
||||
.map_err(|err| SnapshotError::WriteSnapshot {
|
||||
err,
|
||||
path: snapshot_path.clone(),
|
||||
})?;
|
||||
|
||||
// No need for an explicit `.new` file:
|
||||
std::fs::remove_file(&new_path).ok();
|
||||
// No need for an explicit `.new` file:
|
||||
std::fs::remove_file(&new_path).ok();
|
||||
|
||||
println!("Updated snapshot: {snapshot_path:?}");
|
||||
|
||||
println!("Updated snapshot: {snapshot_path:?}");
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
@@ -234,21 +247,27 @@ pub fn try_image_snapshot_options(
|
||||
Ok(image) => image.to_rgba8(),
|
||||
Err(err) => {
|
||||
// No previous snapshot - probablye a new test.
|
||||
maybe_update_snapshot()?;
|
||||
return Err(SnapshotError::OpenSnapshot {
|
||||
path: snapshot_path.clone(),
|
||||
err,
|
||||
});
|
||||
if should_update_snapshots() {
|
||||
return update_snapshot();
|
||||
} else {
|
||||
return Err(SnapshotError::OpenSnapshot {
|
||||
path: snapshot_path.clone(),
|
||||
err,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if previous.dimensions() != new.dimensions() {
|
||||
maybe_update_snapshot()?;
|
||||
return Err(SnapshotError::SizeMismatch {
|
||||
name: name.to_owned(),
|
||||
expected: previous.dimensions(),
|
||||
actual: new.dimensions(),
|
||||
});
|
||||
if should_update_snapshots() {
|
||||
return update_snapshot();
|
||||
} else {
|
||||
return Err(SnapshotError::SizeMismatch {
|
||||
name: name.to_owned(),
|
||||
expected: previous.dimensions(),
|
||||
actual: new.dimensions(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Compare existing image to the new one:
|
||||
@@ -262,12 +281,15 @@ pub fn try_image_snapshot_options(
|
||||
path: diff_path.clone(),
|
||||
err,
|
||||
})?;
|
||||
maybe_update_snapshot()?;
|
||||
Err(SnapshotError::Diff {
|
||||
name: name.to_owned(),
|
||||
diff,
|
||||
diff_path,
|
||||
})
|
||||
if should_update_snapshots() {
|
||||
update_snapshot()
|
||||
} else {
|
||||
Err(SnapshotError::Diff {
|
||||
name: name.to_owned(),
|
||||
diff,
|
||||
diff_path,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -281,12 +303,12 @@ pub fn try_image_snapshot_options(
|
||||
/// The snapshot files will be saved under [`SnapshotOptions::output_path`].
|
||||
/// The snapshot will be saved under `{output_path}/{name}.png`.
|
||||
/// The new image from the most recent test run will be saved under `{output_path}/{name}.new.png`.
|
||||
/// If new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
/// If the new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a [`SnapshotError`] if the image does not match the snapshot or if there was an error
|
||||
/// reading or writing the snapshot.
|
||||
pub fn try_image_snapshot(current: &image::RgbaImage, name: &str) -> Result<(), SnapshotError> {
|
||||
pub fn try_image_snapshot(current: &image::RgbaImage, name: &str) -> SnapshotResult {
|
||||
try_image_snapshot_options(current, name, &SnapshotOptions::default())
|
||||
}
|
||||
|
||||
@@ -301,7 +323,7 @@ pub fn try_image_snapshot(current: &image::RgbaImage, name: &str) -> Result<(),
|
||||
/// The snapshot files will be saved under [`SnapshotOptions::output_path`].
|
||||
/// The snapshot will be saved under `{output_path}/{name}.png`.
|
||||
/// The new image from the most recent test run will be saved under `{output_path}/{name}.new.png`.
|
||||
/// If new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
/// If the new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the image does not match the snapshot or if there was an error reading or writing the
|
||||
@@ -319,7 +341,7 @@ pub fn image_snapshot_options(current: &image::RgbaImage, name: &str, options: &
|
||||
/// Image snapshot test.
|
||||
/// The snapshot will be saved under `tests/snapshots/{name}.png`.
|
||||
/// The new image from the last test run will be saved under `tests/snapshots/{name}.new.png`.
|
||||
/// If new image didn't match the snapshot, a diff image will be saved under `tests/snapshots/{name}.diff.png`.
|
||||
/// If the new image didn't match the snapshot, a diff image will be saved under `tests/snapshots/{name}.diff.png`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the image does not match the snapshot or if there was an error reading or writing the
|
||||
@@ -336,7 +358,7 @@ pub fn image_snapshot(current: &image::RgbaImage, name: &str) {
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
impl<State> Harness<'_, State> {
|
||||
/// Render a image using the setup [`crate::TestRenderer`] and compare it to the snapshot
|
||||
/// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot
|
||||
/// with custom options.
|
||||
///
|
||||
/// If you want to change the default options for your whole project, you could create an
|
||||
@@ -349,7 +371,7 @@ impl<State> Harness<'_, State> {
|
||||
/// The snapshot files will be saved under [`SnapshotOptions::output_path`].
|
||||
/// The snapshot will be saved under `{output_path}/{name}.png`.
|
||||
/// The new image from the most recent test run will be saved under `{output_path}/{name}.new.png`.
|
||||
/// If new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
/// If the new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a [`SnapshotError`] if the image does not match the snapshot, if there was an
|
||||
@@ -358,29 +380,29 @@ impl<State> Harness<'_, State> {
|
||||
&mut self,
|
||||
name: &str,
|
||||
options: &SnapshotOptions,
|
||||
) -> Result<(), SnapshotError> {
|
||||
) -> SnapshotResult {
|
||||
let image = self
|
||||
.render()
|
||||
.map_err(|err| SnapshotError::RenderError { err })?;
|
||||
try_image_snapshot_options(&image, name, options)
|
||||
}
|
||||
|
||||
/// Render a image using the setup [`crate::TestRenderer`] and compare it to the snapshot.
|
||||
/// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot.
|
||||
/// The snapshot will be saved under `tests/snapshots/{name}.png`.
|
||||
/// The new image from the last test run will be saved under `tests/snapshots/{name}.new.png`.
|
||||
/// If new image didn't match the snapshot, a diff image will be saved under `tests/snapshots/{name}.diff.png`.
|
||||
/// If the new image didn't match the snapshot, a diff image will be saved under `tests/snapshots/{name}.diff.png`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a [`SnapshotError`] if the image does not match the snapshot, if there was an
|
||||
/// error reading or writing the snapshot, if the rendering fails or if no default renderer is available.
|
||||
pub fn try_snapshot(&mut self, name: &str) -> Result<(), SnapshotError> {
|
||||
pub fn try_snapshot(&mut self, name: &str) -> SnapshotResult {
|
||||
let image = self
|
||||
.render()
|
||||
.map_err(|err| SnapshotError::RenderError { err })?;
|
||||
try_image_snapshot(&image, name)
|
||||
}
|
||||
|
||||
/// Render a image using the setup [`crate::TestRenderer`] and compare it to the snapshot
|
||||
/// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot
|
||||
/// with custom options.
|
||||
///
|
||||
/// If you want to change the default options for your whole project, you could create an
|
||||
@@ -393,7 +415,7 @@ impl<State> Harness<'_, State> {
|
||||
/// The snapshot files will be saved under [`SnapshotOptions::output_path`].
|
||||
/// The snapshot will be saved under `{output_path}/{name}.png`.
|
||||
/// The new image from the most recent test run will be saved under `{output_path}/{name}.new.png`.
|
||||
/// If new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
/// If the new image didn't match the snapshot, a diff image will be saved under `{output_path}/{name}.diff.png`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the image does not match the snapshot, if there was an error reading or writing the
|
||||
@@ -408,10 +430,10 @@ impl<State> Harness<'_, State> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a image using the setup [`crate::TestRenderer`] and compare it to the snapshot.
|
||||
/// Render an image using the setup [`crate::TestRenderer`] and compare it to the snapshot.
|
||||
/// The snapshot will be saved under `tests/snapshots/{name}.png`.
|
||||
/// The new image from the last test run will be saved under `tests/snapshots/{name}.new.png`.
|
||||
/// If new image didn't match the snapshot, a diff image will be saved under `tests/snapshots/{name}.diff.png`.
|
||||
/// If the new image didn't match the snapshot, a diff image will be saved under `tests/snapshots/{name}.diff.png`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the image does not match the snapshot, if there was an error reading or writing the
|
||||
@@ -440,7 +462,7 @@ impl<State> Harness<'_, State> {
|
||||
&mut self,
|
||||
name: &str,
|
||||
options: &SnapshotOptions,
|
||||
) -> Result<(), SnapshotError> {
|
||||
) -> SnapshotResult {
|
||||
self.try_snapshot_options(name, options)
|
||||
}
|
||||
|
||||
@@ -448,7 +470,7 @@ impl<State> Harness<'_, State> {
|
||||
since = "0.31.0",
|
||||
note = "Use `try_snapshot` instead. This function will be removed in 0.32"
|
||||
)]
|
||||
pub fn try_wgpu_snapshot(&mut self, name: &str) -> Result<(), SnapshotError> {
|
||||
pub fn try_wgpu_snapshot(&mut self, name: &str) -> SnapshotResult {
|
||||
self.try_snapshot(name)
|
||||
}
|
||||
|
||||
@@ -468,3 +490,105 @@ impl<State> Harness<'_, State> {
|
||||
self.snapshot(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility to collect snapshot errors and display them at the end of the test.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # let harness = MockHarness;
|
||||
/// # struct MockHarness;
|
||||
/// # impl MockHarness {
|
||||
/// # fn try_snapshot(&self, _: &str) -> Result<(), egui_kittest::SnapshotError> { Ok(()) }
|
||||
/// # }
|
||||
///
|
||||
/// // [...] Construct a Harness
|
||||
///
|
||||
/// let mut results = egui_kittest::SnapshotResults::new();
|
||||
///
|
||||
/// // Call add for each snapshot in your test
|
||||
/// results.add(harness.try_snapshot("my_test"));
|
||||
///
|
||||
/// // If there are any errors, SnapshotResults will panic once dropped.
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if there are any errors when dropped (this way it is impossible to forget to call `unwrap`).
|
||||
/// If you don't want to panic, you can use [`SnapshotResults::into_result`] or [`SnapshotResults::into_inner`].
|
||||
/// If you want to panic early, you can use [`SnapshotResults::unwrap`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SnapshotResults {
|
||||
errors: Vec<SnapshotError>,
|
||||
}
|
||||
|
||||
impl Display for SnapshotResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.errors.is_empty() {
|
||||
write!(f, "All snapshots passed")
|
||||
} else {
|
||||
writeln!(f, "Snapshot errors:")?;
|
||||
for error in &self.errors {
|
||||
writeln!(f, " {error}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SnapshotResults {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Check if the result is an error and add it to the list of errors.
|
||||
pub fn add(&mut self, result: SnapshotResult) {
|
||||
if let Err(err) = result {
|
||||
self.errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if there are any errors.
|
||||
pub fn has_errors(&self) -> bool {
|
||||
!self.errors.is_empty()
|
||||
}
|
||||
|
||||
/// Convert this into a `Result<(), Self>`.
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
pub fn into_result(self) -> Result<(), Self> {
|
||||
if self.has_errors() {
|
||||
Err(self)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(mut self) -> Vec<SnapshotError> {
|
||||
std::mem::take(&mut self.errors)
|
||||
}
|
||||
|
||||
/// Panics if there are any errors, displaying each.
|
||||
#[allow(clippy::unused_self)]
|
||||
#[track_caller]
|
||||
pub fn unwrap(self) {
|
||||
// Panic is handled in drop
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SnapshotResults> for Vec<SnapshotError> {
|
||||
fn from(results: SnapshotResults) -> Self {
|
||||
results.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SnapshotResults {
|
||||
#[track_caller]
|
||||
fn drop(&mut self) {
|
||||
// Don't panic if we are already panicking (the test probably failed for another reason)
|
||||
if std::thread::panicking() {
|
||||
return;
|
||||
}
|
||||
#[allow(clippy::manual_assert)]
|
||||
if self.has_errors() {
|
||||
panic!("{}", self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user