Compare commits

...

42 commits

Author SHA1 Message Date
Emil Ernerfeldt
7215fdfb7c Release eframe 0.21.3 - fix web input of the the letter P 2023-02-15 08:26:45 +01:00
Emil Ernerfeldt
e2778d9d6a
eframe: Fix inputting of the letter P on web (#2740)
* eframe: Fix inputting of the letter P on web

* Update changelog

* silence clippy
2023-02-15 08:24:52 +01:00
Emil Ernerfeldt
38849fe381 Release eframe 0.21.2 - support --no-default-features 2023-02-12 19:34:37 +01:00
Emil Ernerfeldt
df7e5bd57a
Allow compiling eframe with --no-default-features (#2728)
* Check that we can compile eframe with --no-default-features

* Allow compiling eframe with `--no-default-features`

This is useful for libraries that depend on `eframe::Frame`
but don't care what renderer eframe is using.
2023-02-12 19:29:42 +01:00
Emil Ernerfeldt
e3e781ced8 fix puffin_profiler example 2023-02-12 19:27:10 +01:00
Emil Ernerfeldt
97756bc246 Add badges to all crates' README.md 2023-02-12 18:08:13 +01:00
Emil Ernerfeldt
f71d79a0ff Release egui-winit, eframe 0.21.1 - fix crash on monitor size/dpi change
https://github.com/emilk/egui/pull/2722

The crash could happen if the monitor size or DPI changes between runs
2023-02-12 15:29:47 +01:00
Aevyrie
95247daa17
Fix window position assertion caused by negative window size (#2722) 2023-02-12 15:14:38 +01:00
Emil Ernerfeldt
530e9f667c format: add some blank lines where it was needed 2023-02-10 18:03:46 +01:00
Emil Ernerfeldt
409fb968d3 add cargo config file that sets --cfg=web_sys_unstable_apis on wasm32 2023-02-10 17:56:10 +01:00
Emil Ernerfeldt
1581f0229e examples/README.md: explain that the examples are for master
and add a link to the latest release
2023-02-08 20:34:43 +01:00
Emil Ernerfeldt
ae722ab0cf Release 0.21.0 - Deadlock fix and winit update 2023-02-08 20:11:21 +01:00
Emil Ernerfeldt
1384fa3287 Publish new web demo 2023-02-08 20:10:12 +01:00
Emil Ernerfeldt
83b5b81227 Update changelogs with recent additions 2023-02-08 19:36:33 +01:00
Emil Ernerfeldt
63fa3aec10 Update example screenshots 2023-02-08 19:04:04 +01:00
Emil Ernerfeldt
ebeb788b1f
We no longer use tts, so remove speech-related dependencies (#2698) 2023-02-08 18:45:44 +01:00
Emil Ernerfeldt
0fc25c2680 Fix: make sure always_on_top is respected on glow again 2023-02-08 18:12:52 +01:00
Emil Ernerfeldt
449dd1c23c
cargo update (#2697)
* cargo update

    Updating anyhow v1.0.68 -> v1.0.69
    Updating bindgen v0.63.0 -> v0.64.0
    Removing cocoa v0.24.1
    Updating glutin v0.30.3 -> v0.30.4
    Updating glutin_egl_sys v0.3.1 -> v0.4.0
    Updating glutin_glx_sys v0.3.0 -> v0.4.0
    Updating glutin_wgl_sys v0.3.0 -> v0.4.0
    Updating proc-macro2 v1.0.50 -> v1.0.51
    Updating rgb v0.8.34 -> v0.8.35
    Updating serde_json v1.0.91 -> v1.0.92
    Updating tiny-skia v0.8.2 -> v0.8.3
    Updating tiny-skia-path v0.8.2 -> v0.8.3
    Removing windows-sys v0.36.1
    Removing windows_aarch64_msvc v0.36.1
    Removing windows_i686_gnu v0.36.1
    Removing windows_i686_msvc v0.36.1
    Removing windows_x86_64_gnu v0.36.1
    Removing windows_x86_64_msvc v0.36.1
    Updating zbus v3.8.0 -> v3.9.0
    Updating zbus_macros v3.8.0 -> v3.9.0

* Update wasm-bindgen to 0.2.84

* cargo update

    Updating js-sys v0.3.60 -> v0.3.61
    Updating wasm-bindgen-futures v0.4.33 -> v0.4.34
    Updating web-sys v0.3.60 -> v0.3.61

* Update pollster to 0.3

* Update rfd to 0.11

* Make sure we never depend on cmake
2023-02-08 18:11:34 +01:00
Emil Ernerfeldt
636a39cbe1
Update glow to 0.12 (#2695)
* Remove three-d example due to glow incompatibility

* Update to glow 0.12

* Remove three-d from deny.toml

* Add line to changelog
2023-02-08 17:16:44 +01:00
Red Artist
be9b5a3641
polish glutin upgrade with glutin-winit crate (#2526)
* use glutin-winit for glow context creation

* added some tracing for easier debugging of glutin problems

* fmt

* add more debug logs

* more tracing

* fallback egl instead of prefer egl

* update pure glow example to use glutin_winit

* add more logging. ignore vsync option if not supported

* cranky lint

* add some logging for easier debugging

* drop window after glutin surface

* small changes based on pr review

* build fix

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2023-02-08 14:28:42 +01:00
Emil Ernerfeldt
e8b9e706ca
Fix Window::pivot causing windows to move around (#2694)
* Fix Window::pivot causing windows to move around

* Add line to changelog
2023-02-08 12:41:36 +01:00
Emil Ernerfeldt
a8d5a82a7f Lowe multisampling in examples from 8 to 4
Closes https://github.com/emilk/egui/issues/2658
2023-02-08 10:14:43 +01:00
Emil Ernerfeldt
c2d37571f7 constrain popups to the screen 2023-02-08 10:02:50 +01:00
Emil Ernerfeldt
90cd178117 Document GlyphInfo::id 2023-02-08 10:01:47 +01:00
Emil Ernerfeldt
7397be3401 Fix item spacing in CollapsingHeader 2023-02-08 10:01:37 +01:00
Emil Ernerfeldt
1edd333864 Update to winit 0.28.1 2023-02-08 10:00:03 +01:00
Harrison Gieraltowski
b40dba18c6
DragValue: when keyboard editing, only update the value on focus lost (#2688)
* test

* moved some accesskit stuff

* reverted accesskit change

* Add explanatory comment

* fmt

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2023-02-08 09:35:44 +01:00
Emil Ernerfeldt
4721a0a680
Check --all-targets in CI (#1395) 2023-02-08 09:35:01 +01:00
Emil Ernerfeldt
8d6c2580f4 Add Checkbox::without_text
Closes https://github.com/emilk/egui/pull/2508
2023-02-08 08:42:00 +01:00
lictex_
8bc88c9bf4
make dragvalue textedit style consistent with button (#2649)
* make dragvalue textedit style consistent with button

* fix comments &  fix wrong interactive cursor pos

* * apply button_padding to textedit
* support vertical align
* add same min size as button to avoid unintented height shrink
2023-02-07 10:52:48 +01:00
Andreas Reich
b52cd2052f
Support for transparent backbuffer in wgpu winit binding (#2684)
* Support for transparent backbuffer in wgpu winit binding
Choose best fitting composite alpha mode on the fly.

* Compilation fix

* Add line to eframe CHANGELOG

* Attempt to mollify CI: try different way to install apt packages

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2023-02-06 14:16:17 +01:00
Emil Ernerfeldt
b1e214bbdf Add Order::TOP 2023-02-06 08:48:12 +01:00
Emil Ernerfeldt
cef0c0b6d8 Fix typo 2023-02-05 21:58:15 +01:00
Emil Ernerfeldt
d5dcc87ace Improve custom_window_frame 2023-02-05 21:58:15 +01:00
Matt Campbell
853d492724
Update for AccessKit refactor that drastically reduces memory usage (#2678)
* Update for AccessKit refactor that drastically reduces memory usage

* changelog entry

* satisfy clippy
2023-02-05 19:10:40 +01:00
Emil Ernerfeldt
d15ce22e2e winit: Fix bug where the cursor could get stuck using the wrong icon 2023-02-05 08:48:40 +01:00
hinto-janaiyo
628c84cbee
Add trailing_fill() toggle to Slider (#2660)
* slider: add trailing_color toggle

* slider/visuals: add global option in visuals with override toggle

* slider: add to demos

* use `.unwrap_or_else()` instead of match
2023-02-05 08:17:58 +01:00
Luc (Echow) Varoqui
212656f3fc
Fix set_plot_bounds (#2653) 2023-02-04 16:17:15 +01:00
Emil Ernerfeldt
660566c499
eframe: ask if the window is minimized or maximized (#2672)
* eframe: ask if the window is minimized or maximized

* Improve note
2023-02-04 16:05:23 +01:00
Hoping White
430cbe541c
New feature to support unity vertex layout requirement (#2493)
* Update mesh.rs

adjust Vertex layout

* add unity feature

* add unity feature

* document the `unity` feature flag

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2023-02-04 15:33:41 +01:00
Sheldon M
53f8e4049f
Position persistence and sane clamping to still-available monitors for Windows (#2583)
* Attempt to fix monitor clamping on Windows so window positions can be restored between sessions.

* Missed a change.

* Renamed variables, reorganized some lines of code, and added some more comments.

* Cargo fmt run

* Updated CHANGELOG.md to briefly describe my change

* Updated CHANGELOG.md to briefly describe my change

* Applied suggested fixes from emilk
Discovered an issue where putting the monitor off a non-primary monitor to the left causes the position to be off the monitor x and y range, clamping to the primary instead of the non-primary.

* Fix for matching negative restored window positions. Should clamp if any part of the window had been visible on a remaining monitor.

* Apparently compiler attributes on statements have been marked unstable.
Rather than just wrap in blocks, I kind of prefer the more explicit if cfg! call for line 114.

CHANGELOG.md - correct a missing paren I noticed

* I was being silly, I don't need to clone inner_size_points on line 112

* Cargo fmt run

* Update crates/egui-winit/CHANGELOG.md

emilk suggested changelog formatting

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>

* Update window_settings.rs

Satisfy CI Error

* clippy

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2023-02-04 15:33:32 +01:00
SunDoge
f0718a61d3
eframe: add set_minimized and set_maximized (#2292)
* add actions for window controls

* add maximized to WindowInfo
update button text
fix clippy

* add overlap icon when maximized

* remove argument `app`

* remove WindowInfo { maximized }

* Update minimum window size

* Double-click titlebar to toggle maximized state

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2023-02-04 14:42:42 +01:00
102 changed files with 1940 additions and 1757 deletions

6
.cargo/config.toml Normal file
View file

@ -0,0 +1,6 @@
# clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
# check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility
# we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93
[target.wasm32-unknown-unknown]
rustflags = ["--cfg=web_sys_unstable_apis"]

View file

@ -16,66 +16,93 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: default
toolchain: 1.65.0
override: true
- name: Install packages (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
#uses: awalsh128/cache-apt-pkgs-action@v1.2.2
#TODO(emilk) use upstream when https://github.com/awalsh128/cache-apt-pkgs-action/pull/90 is merged
uses: rerun-io/cache-apt-pkgs-action@59534850182063abf1b2c11bb3686722a12a8397
with:
packages: libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
version: 1.0
execute_install_scripts: true
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
- name: Rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Install cargo-cranky
uses: baptiste0928/cargo-install@v1
with:
crate: cargo-cranky
- name: Check all features
- name: check --all-features
uses: actions-rs/cargo@v1
with:
command: check
args: --locked --all-features
- name: Check default features
args: --locked --all-features --all-targets
- name: check default features
uses: actions-rs/cargo@v1
with:
command: check
args: --locked
- name: Check no default features
args: --locked --all-targets
- name: check --no-default-features
uses: actions-rs/cargo@v1
with:
command: check
args: --locked --no-default-features --lib
args: --locked --no-default-features --lib --all-targets
- name: check eframe --no-default-features
uses: actions-rs/cargo@v1
with:
command: check
args: --locked --no-default-features --lib --all-targets -p eframe
- name: Test doc-tests
uses: actions-rs/cargo@v1
with:
command: test
args: --doc --all-features
- name: cargo doc --lib
uses: actions-rs/cargo@v1
with:
command: doc
args: --lib --no-deps --all-features
- name: cargo doc --document-private-items
uses: actions-rs/cargo@v1
with:
command: doc
args: --document-private-items --no-deps --all-features
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
- name: Cranky
uses: actions-rs/cargo@v1
with:
command: cranky
args: --all-targets --all-features -- -D warnings
# ---------------------------------------------------------------------------
check_wasm:
name: Check wasm32 + wasm-bindgen
runs-on: ubuntu-22.04
@ -119,7 +146,7 @@ jobs:
- name: wasm-bindgen
uses: jetli/wasm-bindgen-action@v0.1.0
with:
version: "0.2.83"
version: "0.2.84"
- run: ./sh/wasm_bindgen_check.sh --skip-setup
@ -129,6 +156,8 @@ jobs:
command: cranky
args: --target wasm32-unknown-unknown --all-features -p egui_demo_app --lib -- -D warnings
# ---------------------------------------------------------------------------
cargo-deny:
name: cargo deny
runs-on: ubuntu-22.04
@ -138,22 +167,29 @@ jobs:
with:
rust-version: "1.65.0"
# ---------------------------------------------------------------------------
android:
name: android
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.65.0
target: aarch64-linux-android
override: true
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
- run: cargo check --features wgpu --target aarch64-linux-android
working-directory: crates/eframe
# ---------------------------------------------------------------------------
windows:
name: Check Windows
runs-on: windows-latest
@ -173,4 +209,3 @@ jobs:
with:
command: check
args: --all-targets --all-features

View file

@ -5,6 +5,9 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
## Unreleased
## 0.21.0 - 2023-02-08 - Deadlock fix and style customizability
* ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)):
* `ctx.input().key_pressed(Key::A)` -> `ctx.input(|i| i.key_pressed(Key::A))`
* `ui.memory().toggle_popup(popup_id)` -> `ui.memory_mut(|mem| mem.toggle_popup(popup_id))`
@ -26,6 +29,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
* You can turn off the vertical line left of indented regions with `Visuals::indent_has_left_vline` ([#2636](https://github.com/emilk/egui/pull/2636)).
* Add `Response.highlight` to highlight a widget ([#2632](https://github.com/emilk/egui/pull/2632)).
* Add `Separator::grow` and `Separator::shrink` ([#2665](https://github.com/emilk/egui/pull/2665)).
* Add `Slider::trailing_fill` for trailing color behind the circle like a `ProgressBar` ([#2660](https://github.com/emilk/egui/pull/2660)).
### Changed 🔧
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
@ -33,14 +37,16 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
* Default `ComboBox` is now controlled with `Spacing::combo_width` ([#2621](https://github.com/emilk/egui/pull/2621)).
* `DragValue` and `Slider` now use the proportional font ([#2638](https://github.com/emilk/egui/pull/2638)).
* `ScrollArea` is less aggressive about clipping its contents ([#2665](https://github.com/emilk/egui/pull/2665)).
* Updated to be compatible with a major breaking change in AccessKit that drastically reduces memory usage when accessibility is enabled ([#2678](https://github.com/emilk/egui/pull/2678)).
* Improve `DragValue` behavior ([#2649](https://github.com/emilk/egui/pull/2649), [#2650](https://github.com/emilk/egui/pull/2650), [#2688](https://github.com/emilk/egui/pull/2688), [#2638](https://github.com/emilk/egui/pull/2638)).
### Fixed 🐛
* Trigger `PointerEvent::Released` for drags ([#2507](https://github.com/emilk/egui/pull/2507)).
* Expose `TextEdit`'s multiline flag to AccessKit ([#2448](https://github.com/emilk/egui/pull/2448)).
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
* The `button_padding` style option works closer as expected with image+text buttons now ([#2510](https://github.com/emilk/egui/pull/2510)).
* Fixed rendering of `…` (ellipsis).
* Menus are now moved to fit on the screen.
* Fix `Window::pivot` causing windows to move around ([#2694](https://github.com/emilk/egui/pull/2694)).
## 0.20.1 - 2022-12-11 - Fix key-repeat

526
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -66,11 +66,11 @@ To test the demo app locally, run `cargo run --release -p egui_demo_app`.
The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
`sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev`
`sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev`
On Fedora Rawhide you need to run:
`dnf install clang clang-devel clang-tools-extra speech-dispatcher-devel libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
`dnf install clang clang-devel clang-tools-extra libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
**NOTE**: This is just for the demo app - egui itself is completely platform agnostic!

View file

@ -3,6 +3,9 @@ All notable changes to the `ecolor` crate will be noted in this file.
## Unreleased
## 0.21.0 - 2023-02-08
* Add `Color32::gamma_multiply` ([#2437](https://github.com/emilk/egui/pull/2437)).

View file

@ -1,6 +1,6 @@
[package]
name = "ecolor"
version = "0.20.0"
version = "0.21.0"
authors = [
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
"Andreas Reich <reichandreas@gmx.de>",

View file

@ -1,5 +1,11 @@
# ecolor - egui color library
[![Latest version](https://img.shields.io/crates/v/ecolor.svg)](https://crates.io/crates/ecolor)
[![Documentation](https://docs.rs/ecolor/badge.svg)](https://docs.rs/ecolor)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
A simple color storage and conversion library.
Made for [`egui`](https://github.com/emilk/egui/).

View file

@ -5,19 +5,38 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
## Unreleased
## 0.21.3 - 2023-02-15
* Fix typing the letter 'P' on web ([#2740](https://github.com/emilk/egui/pull/2740)).
## 0.21.2 - 2023-02-12
* Allow compiling `eframe` with `--no-default-features` ([#2728](https://github.com/emilk/egui/pull/2728)).
## 0.21.1 - 2023-02-12
* Fixed crash when native window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)).
## 0.21.0 - 2023-02-08 - Update to `winit` 0.28
* ⚠️ BREAKING: `App::clear_color` now expects you to return a raw float array ([#2666](https://github.com/emilk/egui/pull/2666)).
* The `screen_reader` feature has now been renamed `web_screen_reader` and only work on web. On other platforms, use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
#### Desktop/Native:
* `eframe::run_native` now returns a `Result` ([#2433](https://github.com/emilk/egui/pull/2433)).
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
* Fix bug where the cursor could get stuck using the wrong icon.
* `NativeOptions::transparent` now works with the wgpu backend ([#2684](https://github.com/emilk/egui/pull/2684)).
* Add `Frame::set_minimized` and `set_maximized` ([#2292](https://github.com/emilk/egui/pull/2292), [#2672](https://github.com/emilk/egui/pull/2672)).
* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)).
#### Web:
* Prevent ctrl-P/cmd-P from opening the print dialog ([#2598](https://github.com/emilk/egui/pull/2598)).
## 0.20.1 - 2022-12-11
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
* Fix [docs.rs](https://docs.rs/eframe) build ([#2420](https://github.com/emilk/egui/pull/2420)).
## 0.20.0 - 2022-12-08 - AccessKit integration and `wgpu` web support

View file

@ -1,6 +1,6 @@
[package]
name = "eframe"
version = "0.20.1"
version = "0.21.3"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "egui framework - write GUI apps that compiles to web and/or natively"
edition = "2021"
@ -36,7 +36,7 @@ dark-light = ["dep:dark-light"]
default_fonts = ["egui/default_fonts"]
## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow).
glow = ["dep:glow", "dep:egui_glow", "dep:glutin"]
glow = ["dep:glow", "dep:egui_glow", "dep:glutin", "dep:glutin-winit"]
## Enable saving app state to disk.
persistence = [
@ -68,7 +68,7 @@ wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
[dependencies]
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
"bytemuck",
"tracing",
] }
@ -79,39 +79,33 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
egui_glow = { version = "0.20.0", path = "../egui_glow", optional = true, default-features = false }
glow = { version = "0.11", optional = true }
egui_glow = { version = "0.21.0", path = "../egui_glow", optional = true, default-features = false }
glow = { version = "0.12", optional = true }
ron = { version = "0.8", optional = true, features = ["integer128"] }
serde = { version = "1", optional = true, features = ["derive"] }
# -------------------------------------------
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
egui-winit = { version = "0.20.0", path = "../egui-winit", default-features = false, features = [
egui-winit = { version = "0.21.1", path = "../egui-winit", default-features = false, features = [
"clipboard",
"links",
] }
raw-window-handle = { version = "0.5.0" }
winit = "0.28"
winit = "0.28.1"
# optional native:
dark-light = { version = "1.0", optional = true }
directories-next = { version = "2", optional = true }
egui-wgpu = { version = "0.20.0", path = "../egui-wgpu", optional = true, features = [
egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true, features = [
"winit",
] } # if wgpu is used, use it with winit
pollster = { version = "0.2", optional = true } # needed for wgpu
pollster = { version = "0.3", optional = true } # needed for wgpu
# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps.
# this can be done at the same time we expose x11/wayland features of winit crate.
glutin = { version = "0.30.0", optional = true, es = [
"egl",
"glx",
"x11",
"wayland",
"wgl",
] }
glutin = { version = "0.30", optional = true }
glutin-winit = { version = "0.3.0", optional = true }
image = { version = "0.24", optional = true, default-features = false, features = [
"png",
] }
@ -124,7 +118,7 @@ wgpu = { version = "0.15.0", optional = true }
bytemuck = "1.7"
js-sys = "0.3"
percent-encoding = "2.1"
wasm-bindgen = "=0.2.83"
wasm-bindgen = "=0.2.84"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3.58", features = [
"BinaryType",
@ -170,6 +164,6 @@ web-sys = { version = "0.3.58", features = [
] }
# optional web:
egui-wgpu = { version = "0.20.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
tts = { version = "0.25", optional = true, default-features = false }
wgpu = { version = "0.15.0", optional = true, features = ["webgl"] }

View file

@ -22,7 +22,7 @@ For how to use `egui`, see [the egui docs](https://docs.rs/egui).
To use on Linux, first run:
```
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
```
You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info.

View file

@ -10,9 +10,11 @@
use std::any::Any;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub use crate::native::run::UserEvent;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub use winit::event_loop::EventLoopBuilder;
/// Hook into the building of an event loop before it is run
@ -20,6 +22,7 @@ pub use winit::event_loop::EventLoopBuilder;
/// You can configure any platform specific details required on top of the default configuration
/// done by `EFrame`.
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)>;
/// This is how your app is created.
@ -317,6 +320,7 @@ pub struct NativeOptions {
pub hardware_acceleration: HardwareAcceleration,
/// What rendering backend to use.
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub renderer: Renderer,
/// Only used if the `dark-light` feature is enabled:
@ -355,6 +359,7 @@ pub struct NativeOptions {
/// event loop before it is run.
///
/// Note: A [`NativeOptions`] clone will not include any `event_loop_builder` hook.
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub event_loop_builder: Option<EventLoopBuilderHook>,
#[cfg(feature = "glow")]
@ -381,9 +386,13 @@ impl Clone for NativeOptions {
fn clone(&self) -> Self {
Self {
icon_data: self.icon_data.clone(),
#[cfg(any(feature = "glow", feature = "wgpu"))]
event_loop_builder: None, // Skip any builder callbacks if cloning
#[cfg(feature = "wgpu")]
wgpu_options: self.wgpu_options.clone(),
..*self
}
}
@ -397,8 +406,10 @@ impl Default for NativeOptions {
maximized: false,
decorated: true,
fullscreen: false,
#[cfg(target_os = "macos")]
fullsize_content: false,
drag_and_drop_support: true,
icon_data: None,
initial_window_pos: None,
@ -413,14 +424,22 @@ impl Default for NativeOptions {
depth_buffer: 0,
stencil_buffer: 0,
hardware_acceleration: HardwareAcceleration::Preferred,
#[cfg(any(feature = "glow", feature = "wgpu"))]
renderer: Renderer::default(),
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
default_theme: Theme::Dark,
run_and_return: true,
#[cfg(any(feature = "glow", feature = "wgpu"))]
event_loop_builder: None,
#[cfg(feature = "glow")]
shader_version: None,
centered: false,
#[cfg(feature = "wgpu")]
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
}
@ -559,6 +578,7 @@ pub enum WebGlContextOption {
/// What rendering backend to use.
///
/// You need to enable the "glow" and "wgpu" features to have a choice.
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
@ -572,6 +592,7 @@ pub enum Renderer {
Wgpu,
}
#[cfg(any(feature = "glow", feature = "wgpu"))]
impl Default for Renderer {
fn default() -> Self {
#[cfg(feature = "glow")]
@ -587,6 +608,7 @@ impl Default for Renderer {
}
}
#[cfg(any(feature = "glow", feature = "wgpu"))]
impl std::fmt::Display for Renderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -599,6 +621,7 @@ impl std::fmt::Display for Renderer {
}
}
#[cfg(any(feature = "glow", feature = "wgpu"))]
impl std::str::FromStr for Renderer {
type Err = String;
@ -718,6 +741,18 @@ impl Frame {
self.output.close = true;
}
/// Minimize or unminimize window. (native only)
#[cfg(not(target_arch = "wasm32"))]
pub fn set_minimized(&mut self, minimized: bool) {
self.output.minimized = Some(minimized);
}
/// Maximize or unmaximize window. (native only)
#[cfg(not(target_arch = "wasm32"))]
pub fn set_maximized(&mut self, maximized: bool) {
self.output.maximized = Some(maximized);
}
/// Tell `eframe` to close the desktop window.
#[cfg(not(target_arch = "wasm32"))]
#[deprecated = "Renamed `close`"]
@ -799,6 +834,7 @@ impl Frame {
}
/// for integrations only: call once per frame
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub(crate) fn take_app_output(&mut self) -> backend::AppOutput {
std::mem::take(&mut self.output)
}
@ -829,6 +865,12 @@ pub struct WindowInfo {
/// Are we in fullscreen mode?
pub fullscreen: bool,
/// Are we minimized?
pub minimized: bool,
/// Are we maximized?
pub maximized: bool,
/// Window inner size in egui points (logical pixels).
pub size: egui::Vec2,
@ -1011,5 +1053,13 @@ pub(crate) mod backend {
/// Set to some bool to tell the window always on top.
#[cfg(not(target_arch = "wasm32"))]
pub always_on_top: Option<bool>,
/// Set to some bool to minimize or unminimize window.
#[cfg(not(target_arch = "wasm32"))]
pub minimized: Option<bool>,
/// Set to some bool to maximize or unmaximize window.
#[cfg(not(target_arch = "wasm32"))]
pub maximized: Option<bool>,
}
}

View file

@ -137,6 +137,7 @@ pub async fn start_web(
// When compiling natively
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
mod native;
/// This is how you start a native (desktop) app.
@ -179,6 +180,7 @@ mod native;
/// This function can fail if we fail to set up a graphics context.
#[cfg(not(target_arch = "wasm32"))]
#[allow(clippy::needless_pass_by_value)]
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub fn run_native(
app_name: &str,
native_options: NativeOptions,
@ -221,8 +223,8 @@ pub enum Error {
Glutin(#[from] glutin::error::Error),
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
#[error("Found no glutin configs matching the template: {0:?}")]
NoGlutinConfigs(glutin::config::ConfigTemplate),
#[error("Found no glutin configs matching the template: {0:?}. error: {1:?}")]
NoGlutinConfigs(glutin::config::ConfigTemplate, Box<dyn std::error::Error>),
#[cfg(feature = "wgpu")]
#[error("WGPU error: {0}")]
@ -233,24 +235,28 @@ pub type Result<T> = std::result::Result<T, Error>;
// ---------------------------------------------------------------------------
/// Profiling macro for feature "puffin"
#[cfg(not(target_arch = "wasm32"))]
macro_rules! profile_function {
#[cfg(any(feature = "glow", feature = "wgpu"))]
mod profiling_scopes {
/// Profiling macro for feature "puffin"
macro_rules! profile_function {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
puffin::profile_function!($($arg)*);
};
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use profile_function;
pub(crate) use profile_function;
/// Profiling macro for feature "puffin"
#[cfg(not(target_arch = "wasm32"))]
macro_rules! profile_scope {
/// Profiling macro for feature "puffin"
macro_rules! profile_scope {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
puffin::profile_scope!($($arg)*);
};
}
pub(crate) use profile_scope;
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use profile_scope;
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub(crate) use profiling_scopes::*;

View file

@ -12,6 +12,14 @@ use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings};
use crate::{epi, Theme, WindowInfo};
#[derive(Default)]
pub struct WindowState {
// We cannot simply call `winit::Window::is_minimized/is_maximized`
// because that deadlocks on mac.
pub minimized: bool,
pub maximized: bool,
}
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
winit::dpi::LogicalSize {
width: points.x as f64,
@ -19,7 +27,11 @@ pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
}
}
pub fn read_window_info(window: &winit::window::Window, pixels_per_point: f32) -> WindowInfo {
pub fn read_window_info(
window: &winit::window::Window,
pixels_per_point: f32,
window_state: &WindowState,
) -> WindowInfo {
let position = window
.outer_position()
.ok()
@ -38,9 +50,13 @@ pub fn read_window_info(window: &winit::window::Window, pixels_per_point: f32) -
.inner_size()
.to_logical::<f32>(pixels_per_point.into());
// NOTE: calling window.is_minimized() or window.is_maximized() deadlocks on Mac.
WindowInfo {
position,
fullscreen: window.fullscreen().is_some(),
minimized: window_state.minimized,
maximized: window_state.maximized,
size: egui::Vec2 {
x: size.width,
y: size.height,
@ -49,14 +65,13 @@ pub fn read_window_info(window: &winit::window::Window, pixels_per_point: f32) -
}
}
pub fn build_window<E>(
pub fn window_builder<E>(
event_loop: &EventLoopWindowTarget<E>,
title: &str,
native_options: &epi::NativeOptions,
window_settings: Option<WindowSettings>,
) -> Result<winit::window::Window, winit::error::OsError> {
) -> winit::window::WindowBuilder {
let epi::NativeOptions {
always_on_top,
maximized,
decorated,
fullscreen,
@ -108,6 +123,8 @@ pub fn build_window<E>(
let inner_size_points = if let Some(mut window_settings) = window_settings {
// Restore pos/size from previous session
window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop));
#[cfg(windows)]
window_settings.clamp_window_to_sane_position(&event_loop);
window_builder = window_settings.initialize_window(window_builder);
window_settings.inner_size_points()
} else {
@ -141,17 +158,19 @@ pub fn build_window<E>(
}
}
}
window_builder
}
let window = window_builder.build(event_loop)?;
pub fn apply_native_options_to_window(
window: &winit::window::Window,
native_options: &crate::NativeOptions,
) {
use winit::window::WindowLevel;
window.set_window_level(if *always_on_top {
window.set_window_level(if native_options.always_on_top {
WindowLevel::AlwaysOnTop
} else {
WindowLevel::Normal
});
Ok(window)
}
fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui::Vec2 {
@ -196,6 +215,7 @@ pub fn handle_app_output(
window: &winit::window::Window,
current_pixels_per_point: f32,
app_output: epi::backend::AppOutput,
window_state: &mut WindowState,
) {
let epi::backend::AppOutput {
close: _,
@ -207,6 +227,8 @@ pub fn handle_app_output(
window_pos,
visible: _, // handled in post_present
always_on_top,
minimized,
maximized,
} = app_output;
if let Some(decorated) = decorated {
@ -250,6 +272,16 @@ pub fn handle_app_output(
WindowLevel::Normal
});
}
if let Some(minimized) = minimized {
window.set_minimized(minimized);
window_state.minimized = minimized;
}
if let Some(maximized) = maximized {
window.set_maximized(maximized);
window_state.maximized = maximized;
}
}
// ----------------------------------------------------------------------------
@ -275,6 +307,7 @@ pub struct EpiIntegration {
/// When set, it is time to close the native window.
close: bool,
can_drag_window: bool,
window_state: WindowState,
}
impl EpiIntegration {
@ -294,12 +327,17 @@ impl EpiIntegration {
let native_pixels_per_point = window.scale_factor() as f32;
let window_state = WindowState {
minimized: window.is_minimized().unwrap_or(false),
maximized: window.is_maximized(),
};
let frame = epi::Frame {
info: epi::IntegrationInfo {
system_theme,
cpu_usage: None,
native_pixels_per_point: Some(native_pixels_per_point),
window_info: read_window_info(window, egui_ctx.pixels_per_point()),
window_info: read_window_info(window, egui_ctx.pixels_per_point(), &window_state),
},
output: epi::backend::AppOutput {
visible: Some(true),
@ -324,6 +362,7 @@ impl EpiIntegration {
pending_full_output: Default::default(),
close: false,
can_drag_window: false,
window_state,
}
}
@ -342,7 +381,7 @@ impl EpiIntegration {
egui_ctx.enable_accesskit();
// Enqueue a repaint so we'll receive a full tree update soon.
egui_ctx.request_repaint();
egui::accesskit_placeholder_tree_update()
egui_ctx.accesskit_placeholder_tree_update()
});
}
@ -405,12 +444,16 @@ impl EpiIntegration {
) -> egui::FullOutput {
let frame_start = std::time::Instant::now();
self.frame.info.window_info = read_window_info(window, self.egui_ctx.pixels_per_point());
self.frame.info.window_info =
read_window_info(window, self.egui_ctx.pixels_per_point(), &self.window_state);
let raw_input = self.egui_winit.take_egui_input(window);
// Run user code:
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
crate::profile_scope!("App::update");
app.update(egui_ctx, &mut self.frame);
});
self.pending_full_output.append(full_output);
let full_output = std::mem::take(&mut self.pending_full_output);
@ -423,7 +466,12 @@ impl EpiIntegration {
tracing::debug!("App::on_close_event returned {}", self.close);
}
self.frame.output.visible = app_output.visible; // this is handled by post_present
handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
handle_app_output(
window,
self.egui_ctx.pixels_per_point(),
app_output,
&mut self.window_state,
);
}
let frame_time = frame_start.elapsed().as_secs_f64() as f32;

View file

@ -38,6 +38,7 @@ pub use epi::NativeOptions;
#[derive(Debug)]
enum EventResult {
Wait,
/// Causes a synchronous repaint inside the event handler. This should only
/// be used in special situations if the window must be repainted while
/// handling a specific event. This occurs on Windows when handling resizes.
@ -45,20 +46,28 @@ enum EventResult {
/// `RepaintNow` creates a new frame synchronously, and should therefore
/// only be used for extremely urgent repaints.
RepaintNow,
/// Queues a repaint for once the event loop handles its next redraw. Exists
/// so that multiple input events can be handled in one frame. Does not
/// cause any delay like `RepaintNow`.
RepaintNext,
RepaintAt(Instant),
Exit,
}
trait WinitApp {
fn is_focused(&self) -> bool;
fn integration(&self) -> Option<&EpiIntegration>;
fn window(&self) -> Option<&winit::window::Window>;
fn save_and_destroy(&mut self);
fn paint(&mut self) -> EventResult;
fn on_event(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
@ -152,8 +161,8 @@ fn run_and_return(
event => match winit_app.on_event(event_loop, event) {
Ok(event_result) => event_result,
Err(err) => {
tracing::error!("Exiting because of error: {err:?} on event {event:?}");
returned_result = Err(err);
tracing::debug!("Exiting because of an error");
EventResult::Exit
}
},
@ -297,6 +306,12 @@ mod glow_integration {
use std::sync::Arc;
use egui::NumExt as _;
use glutin::{
display::GetGlDisplay,
prelude::{GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentGlContext},
surface::GlSurface,
};
use raw_window_handle::HasRawWindowHandle;
use super::*;
@ -321,132 +336,260 @@ mod glow_integration {
painter: egui_glow::Painter,
integration: epi_integration::EpiIntegration,
app: Box<dyn epi::App>,
// Conceptually this will be split out eventually so that the rest of the state
// can be persistent.
gl_window: GlutinWindowContext,
}
/// This struct will contain both persistent and temporary glutin state.
///
/// Platform Quirks:
/// * Microsoft Windows: requires that we create a window before opengl context.
/// * Android: window and surface should be destroyed when we receive a suspend event. recreate on resume event.
///
/// winit guarantees that we will get a Resumed event on startup on all platforms.
/// * Before Resumed event: `gl_config`, `gl_context` can be created at any time. on windows, a window must be created to get `gl_context`.
/// * Resumed: `gl_surface` will be created here. `window` will be re-created here for android.
/// * Suspended: on android, we drop window + surface. on other platforms, we don't get Suspended event.
///
/// The setup is divided between the `new` fn and `on_resume` fn. we can just assume that `on_resume` is a continuation of
/// `new` fn on all platforms. only on android, do we get multiple resumed events because app can be suspended.
struct GlutinWindowContext {
window: winit::window::Window,
gl_context: glutin::context::PossiblyCurrentContext,
gl_display: glutin::display::Display,
gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
builder: winit::window::WindowBuilder,
swap_interval: glutin::surface::SwapInterval,
gl_config: glutin::config::Config,
current_gl_context: Option<glutin::context::PossiblyCurrentContext>,
gl_surface: Option<glutin::surface::Surface<glutin::surface::WindowSurface>>,
not_current_gl_context: Option<glutin::context::NotCurrentContext>,
window: Option<winit::window::Window>,
}
impl GlutinWindowContext {
// refactor this function to use `glutin-winit` crate eventually.
// preferably add android support at the same time.
/// There is a lot of complexity with opengl creation, so prefer extensivve logging to get all the help we can to debug issues.
///
#[allow(unsafe_code)]
unsafe fn new(
winit_window: winit::window::Window,
winit_window_builder: winit::window::WindowBuilder,
native_options: &epi::NativeOptions,
event_loop: &EventLoopWindowTarget<UserEvent>,
) -> Result<Self> {
use glutin::prelude::*;
use raw_window_handle::*;
// convert native options to glutin options
let hardware_acceleration = match native_options.hardware_acceleration {
crate::HardwareAcceleration::Required => Some(true),
crate::HardwareAcceleration::Preferred => None,
crate::HardwareAcceleration::Off => Some(false),
};
let raw_display_handle = winit_window.raw_display_handle();
let raw_window_handle = winit_window.raw_window_handle();
// EGL is crossplatform and the official khronos way
// but sometimes platforms/drivers may not have it, so we use back up options where possible.
// TODO: check whether we can expose these options as "features", so that users can select the relevant backend they want.
// try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display.
#[cfg(target_os = "windows")]
let preference =
glutin::display::DisplayApiPreference::EglThenWgl(Some(raw_window_handle));
// try egl and fallback to x11 glx
#[cfg(target_os = "linux")]
let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
winit::platform::x11::register_xlib_error_hook,
));
#[cfg(target_os = "macos")]
let preference = glutin::display::DisplayApiPreference::Cgl;
#[cfg(target_os = "android")]
let preference = glutin::display::DisplayApiPreference::Egl;
let gl_display = glutin::display::Display::new(raw_display_handle, preference)?;
let swap_interval = if native_options.vsync {
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
} else {
glutin::surface::SwapInterval::DontWait
};
let config_template = glutin::config::ConfigTemplateBuilder::new()
/* opengl setup flow goes like this:
1. we create a configuration for opengl "Display" / "Config" creation
2. choose between special extensions like glx or egl or wgl and use them to create config/display
3. opengl context configuration
4. opengl context creation
*/
// start building config for gl display
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
.prefer_hardware_accelerated(hardware_acceleration)
.with_depth_size(native_options.depth_buffer);
.with_depth_size(native_options.depth_buffer)
.with_stencil_size(native_options.stencil_buffer)
.with_transparency(native_options.transparent);
// we don't know if multi sampling option is set. so, check if its more than 0.
let config_template = if native_options.multisampling > 0 {
config_template.with_multisampling(
let config_template_builder = if native_options.multisampling > 0 {
config_template_builder.with_multisampling(
native_options
.multisampling
.try_into()
.expect("failed to fit multisamples into u8"),
.expect("failed to fit multisamples option of native_options into u8"),
)
} else {
config_template
config_template_builder
};
let config_template = config_template
.with_stencil_size(native_options.stencil_buffer)
.with_transparency(native_options.transparent)
.compatible_with_native_window(raw_window_handle)
.build();
// finds all valid configurations supported by this display that match the config_template
// this is where we will try to get a "fallback" config if we are okay with ignoring some native
// options required by user like multi sampling, srgb, transparency etc..
// TODO: need to figure out a good fallback config template
let config = gl_display
.find_configs(config_template.clone())?
.next()
.ok_or(crate::Error::NoGlutinConfigs(config_template))?;
tracing::debug!(
"trying to create glutin Display with config: {:?}",
&config_template_builder
);
// create gl display. this may probably create a window too on most platforms. definitely on `MS windows`. never on android.
let (window, gl_config) = glutin_winit::DisplayBuilder::new()
// we might want to expose this option to users in the future. maybe using an env var or using native_options.
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
.with_window_builder(Some(winit_window_builder.clone()))
.build(
event_loop,
config_template_builder.clone(),
|mut config_iterator| {
let config = config_iterator.next().expect(
"failed to find a matching configuration for creating glutin config",
);
tracing::debug!(
"using the first config from config picker closure. config: {:?}",
&config
);
config
},
)
.map_err(|e| crate::Error::NoGlutinConfigs(config_template_builder.build(), e))?;
let gl_display = gl_config.display();
tracing::debug!(
"successfully created GL Display with version: {} and supported features: {:?}",
gl_display.version_string(),
gl_display.supported_features()
);
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
tracing::debug!(
"creating gl context using raw window handle: {:?}",
raw_window_handle
);
// create gl context. if core context cannot be created, try gl es context as fallback.
let context_attributes =
glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
// for surface creation.
let (width, height): (u32, u32) = winit_window.inner_size().into();
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
.with_context_api(glutin::context::ContextApi::Gles(None))
.build(raw_window_handle);
let gl_context = match gl_config
.display()
.create_context(&gl_config, &context_attributes)
{
Ok(it) => it,
Err(err) => {
tracing::warn!("failed to create context using default context attributes {context_attributes:?} due to error: {err}");
tracing::debug!("retrying with fallback context attributes: {fallback_context_attributes:?}");
gl_config
.display()
.create_context(&gl_config, &fallback_context_attributes)?
}
};
let not_current_gl_context = Some(gl_context);
// the fun part with opengl gl is that we never know whether there is an error. the context creation might have failed, but
// it could keep working until we try to make surface current or swap buffers or something else. future glutin improvements might
// help us start from scratch again if we fail context creation and go back to preferEgl or try with different config etc..
// https://github.com/emilk/egui/pull/2541#issuecomment-1370767582
Ok(GlutinWindowContext {
builder: winit_window_builder,
swap_interval,
gl_config,
current_gl_context: None,
window,
gl_surface: None,
not_current_gl_context,
})
}
/// This will be run after `new`. on android, it might be called multiple times over the course of the app's lifetime.
/// roughly,
/// 1. check if window already exists. otherwise, create one now.
/// 2. create attributes for surface creation.
/// 3. create surface.
/// 4. make surface and context current.
///
/// we presently assume that we will
#[allow(unsafe_code)]
fn on_resume(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) -> Result<()> {
if self.gl_surface.is_some() {
tracing::warn!(
"on_resume called even thought we already have a surface. early return"
);
return Ok(());
}
tracing::debug!("running on_resume fn.");
// make sure we have a window or create one.
let window = self.window.take().unwrap_or_else(|| {
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
glutin_winit::finalize_window(event_loop, self.builder.clone(), &self.gl_config)
.expect("failed to finalize glutin window")
});
// surface attributes
let (width, height): (u32, u32) = window.inner_size().into();
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
let surface_attributes =
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
.build(raw_window_handle, width, height);
// start creating the gl objects
let gl_context = gl_display.create_context(&config, &context_attributes)?;
.build(window.raw_window_handle(), width, height);
tracing::debug!(
"creating surface with attributes: {:?}",
&surface_attributes
);
// create surface
let gl_surface = unsafe {
self.gl_config
.display()
.create_window_surface(&self.gl_config, &surface_attributes)?
};
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
// make surface and context current.
let not_current_gl_context = self
.not_current_gl_context
.take()
.expect("failed to get not current context after resume event. impossible!");
let current_gl_context = not_current_gl_context.make_current(&gl_surface)?;
// try setting swap interval. but its not absolutely necessary, so don't panic on failure.
tracing::debug!("made context current. setting swap interval for surface");
if let Err(e) = gl_surface.set_swap_interval(&current_gl_context, self.swap_interval) {
tracing::error!("failed to set swap interval due to error: {e:?}");
}
// we will reach this point only once in most platforms except android.
// create window/surface/make context current once and just use them forever.
self.gl_surface = Some(gl_surface);
self.current_gl_context = Some(current_gl_context);
self.window = Some(window);
Ok(())
}
let gl_surface = gl_display.create_window_surface(&config, &surface_attributes)?;
let gl_context = gl_context.make_current(&gl_surface)?;
gl_surface.set_swap_interval(&gl_context, swap_interval)?;
Ok(GlutinWindowContext {
window: winit_window,
gl_context,
gl_display,
gl_surface,
})
/// only applies for android. but we basically drop surface + window and make context not current
fn on_suspend(&mut self) -> Result<()> {
tracing::debug!("received suspend event. dropping window and surface");
self.gl_surface.take();
self.window.take();
if let Some(current) = self.current_gl_context.take() {
tracing::debug!("context is current, so making it non-current");
self.not_current_gl_context = Some(current.make_not_current()?);
} else {
tracing::debug!(
"context is already not current??? could be duplicate suspend event"
);
}
Ok(())
}
fn window(&self) -> &winit::window::Window {
&self.window
self.window.as_ref().expect("winit window doesn't exist")
}
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
use glutin::surface::GlSurface;
let width = std::num::NonZeroU32::new(physical_size.width.at_least(1)).unwrap();
let height = std::num::NonZeroU32::new(physical_size.height.at_least(1)).unwrap();
self.gl_surface.resize(&self.gl_context, width, height);
self.gl_surface
.as_ref()
.expect("failed to get surface to resize")
.resize(
self.current_gl_context
.as_ref()
.expect("failed to get current context to resize surface"),
width,
height,
);
}
fn swap_buffers(&self) -> glutin::error::Result<()> {
use glutin::surface::GlSurface;
self.gl_surface.swap_buffers(&self.gl_context)
self.gl_surface
.as_ref()
.expect("failed to get surface to swap buffers")
.swap_buffers(
self.current_gl_context
.as_ref()
.expect("failed to get current context to swap buffers"),
)
}
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
use glutin::display::GlDisplay;
self.gl_display.get_proc_address(addr)
self.gl_config.display().get_proc_address(addr)
}
}
@ -494,11 +637,17 @@ mod glow_integration {
let window_settings = epi_integration::load_window_settings(storage);
let winit_window =
epi_integration::build_window(event_loop, title, native_options, window_settings)?;
// a lot of the code below has been lifted from glutin example in their repo.
let glutin_window_context =
unsafe { GlutinWindowContext::new(winit_window, native_options)? };
let winit_window_builder =
epi_integration::window_builder(event_loop, title, native_options, window_settings);
let mut glutin_window_context = unsafe {
GlutinWindowContext::new(winit_window_builder, native_options, event_loop)?
};
glutin_window_context.on_resume(event_loop)?;
if let Some(window) = &glutin_window_context.window {
epi_integration::apply_native_options_to_window(window, native_options);
}
let gl = unsafe {
glow::Context::from_loader_function(|s| {
let s = std::ffi::CString::new(s)
@ -727,26 +876,24 @@ mod glow_integration {
) -> Result<EventResult> {
Ok(match event {
winit::event::Event::Resumed => {
// first resume event.
// we can actually move this outside of event loop.
// and just run the on_resume fn of gl_window
if self.running.is_none() {
self.init_run_state(event_loop)?;
} else {
// not the first resume event. create whatever you need.
self.running
.as_mut()
.unwrap()
.gl_window
.on_resume(event_loop)?;
}
EventResult::RepaintNow
}
winit::event::Event::Suspended => {
#[cfg(target_os = "android")]
{
tracing::error!("Suspended app can't destroy Window surface state with current Egui Glow backend (undefined behaviour)");
// Instead of destroying everything which we _know_ we can't re-create
// we instead currently just try our luck with not destroying anything.
//
// When the application resumes then it will get a new `SurfaceView` but
// we have no practical way currently of creating a new EGL surface
// via the Glutin API while keeping the GL context and the rest of
// our app state. This will likely result in a black screen or
// frozen screen.
//
//self.running = None;
}
self.running.as_mut().unwrap().gl_window.on_suspend()?;
EventResult::Wait
}
@ -917,7 +1064,11 @@ mod wgpu_integration {
native_options: &NativeOptions,
) -> std::result::Result<winit::window::Window, winit::error::OsError> {
let window_settings = epi_integration::load_window_settings(storage);
epi_integration::build_window(event_loop, title, native_options, window_settings)
let window_builder =
epi_integration::window_builder(event_loop, title, native_options, window_settings);
let window = window_builder.build(event_loop)?;
epi_integration::apply_native_options_to_window(&window, native_options);
Ok(window)
}
#[allow(unsafe_code)]
@ -958,6 +1109,7 @@ mod wgpu_integration {
self.native_options.wgpu_options.clone(),
self.native_options.multisampling.max(1) as _,
self.native_options.depth_buffer,
self.native_options.transparent,
);
pollster::block_on(painter.set_window(Some(&window)))?;
painter

View file

@ -450,8 +450,6 @@ pub enum EventToUnsubscribe {
impl EventToUnsubscribe {
pub fn unsubscribe(self) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
match self {
EventToUnsubscribe::TargetEvent(handle) => {
handle.target.remove_event_listener_with_callback(
@ -468,6 +466,7 @@ impl EventToUnsubscribe {
}
}
}
pub struct AppRunnerContainer {
pub runner: AppRunnerRef,
@ -486,8 +485,6 @@ impl AppRunnerContainer {
event_name: &'static str,
mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static,
) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
// Create a JS closure based on the FnMut provided
let closure = Closure::wrap({
// Clone atomics

View file

@ -31,7 +31,6 @@ pub fn paint_and_schedule(
runner_ref: AppRunnerRef,
panicked: Arc<AtomicBool>,
) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
let window = web_sys::window().unwrap();
let closure = Closure::once(move || paint_and_schedule(&runner_ref, panicked));
window.request_animation_frame(closure.as_ref().unchecked_ref())?;
@ -95,7 +94,12 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
// egui wants to use tab to move to the next text field.
true
} else if egui_key == Some(Key::P) {
true // Prevent ctrl-P opening the print dialog. Users may want to use it for a command palette.
#[allow(clippy::needless_bool)]
if modifiers.ctrl || modifiers.command || modifiers.mac_cmd {
true // Prevent ctrl-P opening the print dialog. Users may want to use it for a command palette.
} else {
false // let normal P:s through
}
} else if egui_wants_keyboard {
matches!(
event.key().as_str(),

View file

@ -83,7 +83,6 @@ pub fn system_theme() -> Option<Theme> {
}
pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
use wasm_bindgen::JsCast;
let document = web_sys::window()?.document()?;
let canvas = document.get_element_by_id(canvas_id)?;
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()

View file

@ -10,7 +10,6 @@ use wasm_bindgen::prelude::*;
static AGENT_ID: &str = "egui_text_agent";
pub fn text_agent() -> web_sys::HtmlInputElement {
use wasm_bindgen::JsCast;
web_sys::window()
.unwrap()
.document()
@ -23,7 +22,6 @@ pub fn text_agent() -> web_sys::HtmlInputElement {
/// Text event handler,
pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().expect("document should have a body");
@ -129,7 +127,6 @@ pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(
/// Focus or blur text agent to toggle mobile keyboard.
pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
let window = web_sys::window()?;
let document = window.document()?;

View file

@ -3,10 +3,14 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
## Unreleased
* update to wgpu 0.15 ([#2629](https://github.com/emilk/egui/pull/2629))
## 0.21.0 - 2023-02-08
* Update to `wgpu` 0.15 ([#2629](https://github.com/emilk/egui/pull/2629))
* Return `Err` instead of panic if we can't find a device ([#2428](https://github.com/emilk/egui/pull/2428)).
* `winit::Painter::set_window` is now `async` ([#2434](https://github.com/emilk/egui/pull/2434)).
* `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)).
* `winit::Painter` now supports transparent backbuffer ([#2684](https://github.com/emilk/egui/pull/2684)).
## 0.20.0 - 2022-12-08 - web support

View file

@ -1,6 +1,6 @@
[package]
name = "egui-wgpu"
version = "0.20.0"
version = "0.21.0"
description = "Bindings for using egui natively using the wgpu library"
authors = [
"Nils Hasenbanck <nils@hasenbanck.de>",
@ -36,7 +36,7 @@ winit = ["dep:winit"]
[dependencies]
epaint = { version = "0.20.0", path = "../epaint", default-features = false, features = [
epaint = { version = "0.21.0", path = "../epaint", default-features = false, features = [
"bytemuck",
] }

View file

@ -105,11 +105,13 @@ pub enum WgpuError {
DeviceError(wgpu::RequestDeviceError),
SurfaceError(wgpu::CreateSurfaceError),
}
impl std::fmt::Display for WgpuError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
impl std::error::Error for WgpuError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
@ -118,11 +120,13 @@ impl std::error::Error for WgpuError {
}
}
}
impl From<wgpu::RequestDeviceError> for WgpuError {
fn from(e: wgpu::RequestDeviceError) -> Self {
Self::DeviceError(e)
}
}
impl From<wgpu::CreateSurfaceError> for WgpuError {
fn from(e: wgpu::CreateSurfaceError) -> Self {
Self::SurfaceError(e)

View file

@ -1,14 +1,14 @@
use std::sync::Arc;
use tracing::error;
use wgpu::{Adapter, Instance, Surface};
use epaint::mutex::RwLock;
use tracing::error;
use crate::{renderer, RenderState, Renderer, SurfaceErrorAction, WgpuConfiguration};
struct SurfaceState {
surface: Surface,
surface: wgpu::Surface,
alpha_mode: wgpu::CompositeAlphaMode,
width: u32,
height: u32,
}
@ -19,11 +19,12 @@ struct SurfaceState {
pub struct Painter {
configuration: WgpuConfiguration,
msaa_samples: u32,
support_transparent_backbuffer: bool,
depth_format: Option<wgpu::TextureFormat>,
depth_texture_view: Option<wgpu::TextureView>,
instance: Instance,
adapter: Option<Adapter>,
instance: wgpu::Instance,
adapter: Option<wgpu::Adapter>,
render_state: Option<RenderState>,
surface_state: Option<SurfaceState>,
}
@ -41,7 +42,12 @@ impl Painter {
/// [`set_window()`](Self::set_window) once you have
/// a [`winit::window::Window`] with a valid `.raw_window_handle()`
/// associated.
pub fn new(configuration: WgpuConfiguration, msaa_samples: u32, depth_bits: u8) -> Self {
pub fn new(
configuration: WgpuConfiguration,
msaa_samples: u32,
depth_bits: u8,
support_transparent_backbuffer: bool,
) -> Self {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: configuration.backends,
dx12_shader_compiler: Default::default(), //
@ -50,6 +56,7 @@ impl Painter {
Self {
configuration,
msaa_samples,
support_transparent_backbuffer,
depth_format: (depth_bits > 0).then_some(wgpu::TextureFormat::Depth32Float),
depth_texture_view: None,
@ -69,7 +76,7 @@ impl Painter {
async fn init_render_state(
&self,
adapter: &Adapter,
adapter: &wgpu::Adapter,
target_format: wgpu::TextureFormat,
) -> Result<RenderState, wgpu::RequestDeviceError> {
adapter
@ -94,7 +101,7 @@ impl Painter {
// will have the same format and so this render state will remain valid.
async fn ensure_render_state_for_surface(
&mut self,
surface: &Surface,
surface: &wgpu::Surface,
) -> Result<(), wgpu::RequestDeviceError> {
if self.adapter.is_none() {
self.adapter = self
@ -121,34 +128,23 @@ impl Painter {
Ok(())
}
fn configure_surface(&mut self, width_in_pixels: u32, height_in_pixels: u32) {
crate::profile_function!();
let render_state = self
.render_state
.as_ref()
.expect("Render state should exist before surface configuration");
let format = render_state.target_format;
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: width_in_pixels,
height: height_in_pixels,
present_mode: self.configuration.present_mode,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![format],
};
let surface_state = self
.surface_state
.as_mut()
.expect("Surface state should exist before surface configuration");
surface_state
.surface
.configure(&render_state.device, &config);
surface_state.width = width_in_pixels;
surface_state.height = height_in_pixels;
fn configure_surface(
surface_state: &SurfaceState,
render_state: &RenderState,
present_mode: wgpu::PresentMode,
) {
surface_state.surface.configure(
&render_state.device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_state.target_format,
width: surface_state.width,
height: surface_state.height,
present_mode,
alpha_mode: surface_state.alpha_mode,
view_formats: vec![render_state.target_format],
},
);
}
/// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`]
@ -188,15 +184,34 @@ impl Painter {
self.ensure_render_state_for_surface(&surface).await?;
let alpha_mode = if self.support_transparent_backbuffer {
let supported_alpha_modes = surface
.get_capabilities(self.adapter.as_ref().unwrap())
.alpha_modes;
// Prefer pre multiplied over post multiplied!
if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
wgpu::CompositeAlphaMode::PreMultiplied
} else if supported_alpha_modes
.contains(&wgpu::CompositeAlphaMode::PostMultiplied)
{
wgpu::CompositeAlphaMode::PostMultiplied
} else {
tracing::warn!("Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency.");
wgpu::CompositeAlphaMode::Auto
}
} else {
wgpu::CompositeAlphaMode::Auto
};
let size = window.inner_size();
let width = size.width;
let height = size.height;
self.surface_state = Some(SurfaceState {
surface,
width,
height,
width: size.width,
height: size.height,
alpha_mode,
});
self.resize_and_generate_depth_texture_view(width, height);
self.resize_and_generate_depth_texture_view(size.width, size.height);
}
None => {
self.surface_state = None;
@ -221,10 +236,17 @@ impl Painter {
width_in_pixels: u32,
height_in_pixels: u32,
) {
self.configure_surface(width_in_pixels, height_in_pixels);
let device = &self.render_state.as_ref().unwrap().device;
let render_state = self.render_state.as_ref().unwrap();
let surface_state = self.surface_state.as_mut().unwrap();
surface_state.width = width_in_pixels;
surface_state.height = height_in_pixels;
Self::configure_surface(surface_state, render_state, self.configuration.present_mode);
self.depth_texture_view = self.depth_format.map(|depth_format| {
device
render_state
.device
.create_texture(&wgpu::TextureDescriptor {
label: Some("egui_depth_texture"),
size: wgpu::Extent3d {
@ -269,7 +291,6 @@ impl Painter {
Some(rs) => rs,
None => return,
};
let (width, height) = (surface_state.width, surface_state.height);
let output_frame = {
crate::profile_scope!("get_current_texture");
@ -282,7 +303,11 @@ impl Painter {
#[allow(clippy::single_match_else)]
Err(e) => match (*self.configuration.on_surface_error)(e) {
SurfaceErrorAction::RecreateSurface => {
self.configure_surface(width, height);
Self::configure_surface(
surface_state,
render_state,
self.configuration.present_mode,
);
return;
}
SurfaceErrorAction::SkipFrame => {
@ -300,7 +325,7 @@ impl Painter {
// Upload all resources for the GPU.
let screen_descriptor = renderer::ScreenDescriptor {
size_in_pixels: [width, height],
size_in_pixels: [surface_state.width, surface_state.height],
pixels_per_point,
};

View file

@ -3,12 +3,21 @@ All notable changes to the `egui-winit` integration will be noted in this file.
## Unreleased
## 0.21.1 - 2023-02-12
* Fixed crash when window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)).
## 0.21.0 - 2023-02-08
* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)).
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
* Remove the `screen_reader` feature. Use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
* Fix bug where the cursor could get stuck using the wrong icon.
## 0.20.1 - 2022-12-11
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
* Fix [docs.rs](https://docs.rs/egui-winit) build ([#2420](https://github.com/emilk/egui/pull/2420)).
## 0.20.0 - 2022-12-08

View file

@ -1,6 +1,6 @@
[package]
name = "egui-winit"
version = "0.20.1"
version = "0.21.1"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui with winit"
edition = "2021"
@ -43,7 +43,7 @@ serde = ["egui/serde", "dep:serde"]
wayland = ["winit/wayland"]
[dependencies]
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
"tracing",
] }
instant = { version = "0.1", features = [
@ -55,7 +55,7 @@ winit = { version = "0.28", default-features = false }
#! ### Optional dependencies
# feature accesskit
accesskit_winit = { version = "0.9.0", optional = true }
accesskit_winit = { version = "0.10.0", optional = true }
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }

View file

@ -59,7 +59,8 @@ pub struct State {
egui_input: egui::RawInput,
pointer_pos_in_points: Option<egui::Pos2>,
any_pointer_button_down: bool,
current_cursor_icon: egui::CursorIcon,
current_cursor_icon: Option<egui::CursorIcon>,
/// What egui uses.
current_pixels_per_point: f32,
@ -99,7 +100,7 @@ impl State {
egui_input,
pointer_pos_in_points: None,
any_pointer_button_down: false,
current_cursor_icon: egui::CursorIcon::Default,
current_cursor_icon: None,
current_pixels_per_point: 1.0,
clipboard: clipboard::Clipboard::new(wayland_display),
@ -654,22 +655,25 @@ impl State {
}
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
// Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing.
// On other platforms: just early-out to save CPU.
if self.current_cursor_icon == cursor_icon {
if self.current_cursor_icon == Some(cursor_icon) {
// Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing.
// On other platforms: just early-out to save CPU.
return;
}
self.current_cursor_icon = cursor_icon;
if let Some(cursor_icon) = translate_cursor(cursor_icon) {
window.set_cursor_visible(true);
let is_pointer_in_window = self.pointer_pos_in_points.is_some();
if is_pointer_in_window {
self.current_cursor_icon = Some(cursor_icon);
let is_pointer_in_window = self.pointer_pos_in_points.is_some();
if is_pointer_in_window {
window.set_cursor_icon(cursor_icon);
if let Some(winit_cursor_icon) = translate_cursor(cursor_icon) {
window.set_cursor_visible(true);
window.set_cursor_icon(winit_cursor_icon);
} else {
window.set_cursor_visible(false);
}
} else {
window.set_cursor_visible(false);
// Remember to set the cursor again once the cursor returns to the screen:
self.current_cursor_icon = None;
}
}
}

View file

@ -53,16 +53,13 @@ impl WindowSettings {
// If the app last ran on two monitors and only one is now connected, then
// the given position is invalid.
// If this happens on Mac, the window is clamped into valid area.
// If this happens on Windows, the window is hidden and very difficult to find.
// So we don't restore window positions on Windows.
let try_restore_position = !cfg!(target_os = "windows");
if try_restore_position {
if let Some(pos) = self.position {
window = window.with_position(winit::dpi::PhysicalPosition {
x: pos.x as f64,
y: pos.y as f64,
});
}
// If this happens on Windows, the clamping behavior is managed by the function
// clamp_window_to_sane_position.
if let Some(pos) = self.position {
window = window.with_position(winit::dpi::PhysicalPosition {
x: pos.x as f64,
y: pos.y as f64,
});
}
if let Some(inner_size_points) = self.inner_size_points {
@ -90,4 +87,58 @@ impl WindowSettings {
*size = size.at_most(max_size);
}
}
pub fn clamp_window_to_sane_position<E>(
&mut self,
event_loop: &winit::event_loop::EventLoopWindowTarget<E>,
) {
if let (Some(position), Some(inner_size_points)) =
(&mut self.position, &self.inner_size_points)
{
let monitors = event_loop.available_monitors();
// default to primary monitor, in case the correct monitor was disconnected.
let mut active_monitor = if let Some(active_monitor) = event_loop
.primary_monitor()
.or_else(|| event_loop.available_monitors().next())
{
active_monitor
} else {
return; // no monitors 🤷
};
for monitor in monitors {
let monitor_x_range = (monitor.position().x - inner_size_points.x as i32)
..(monitor.position().x + monitor.size().width as i32);
let monitor_y_range = (monitor.position().y - inner_size_points.y as i32)
..(monitor.position().y + monitor.size().height as i32);
if monitor_x_range.contains(&(position.x as i32))
&& monitor_y_range.contains(&(position.y as i32))
{
active_monitor = monitor;
}
}
let mut inner_size_pixels = *inner_size_points * (active_monitor.scale_factor() as f32);
// Add size of title bar. This is 32 px by default in Win 10/11.
if cfg!(target_os = "windows") {
inner_size_pixels +=
egui::Vec2::new(0.0, 32.0 * active_monitor.scale_factor() as f32);
}
let monitor_position = egui::Pos2::new(
active_monitor.position().x as f32,
active_monitor.position().y as f32,
);
let monitor_size = egui::Vec2::new(
active_monitor.size().width as f32,
active_monitor.size().height as f32,
);
// Window size cannot be negative or the subsequent `clamp` will panic.
let window_size = (monitor_size - inner_size_pixels).max(egui::Vec2::ZERO);
// To get the maximum position, we get the rightmost corner of the display, then
// subtract the size of the window to get the bottom right most value window.position
// can have.
*position = position.clamp(monitor_position, monitor_position + window_size);
}
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "egui"
version = "0.20.1"
version = "0.21.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "An easy-to-use immediate mode GUI that runs on both web and native"
edition = "2021"
@ -54,8 +54,12 @@ persistence = ["serde", "epaint/serde", "ron"]
## Allow serialization using [`serde`](https://docs.rs/serde).
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
## Change Vertex layout to be compatible with unity
unity = ["epaint/unity"]
[dependencies]
epaint = { version = "0.20.0", path = "../epaint", default-features = false }
epaint = { version = "0.21.0", path = "../epaint", default-features = false }
ahash = { version = "0.8.1", default-features = false, features = [
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
@ -66,7 +70,7 @@ nohash-hasher = "0.2"
#! ### Optional dependencies
## Exposes detailed accessibility implementation required by platform
## accessibility APIs. Also requires support in the egui integration.
accesskit = { version = "0.8.1", optional = true }
accesskit = { version = "0.9.0", optional = true }
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }

View file

@ -9,6 +9,7 @@ pub(crate) struct AnimationManager {
#[derive(Clone, Debug)]
struct BoolAnim {
value: bool,
/// when did `value` last toggle?
toggle_time: f64,
}
@ -16,7 +17,9 @@ struct BoolAnim {
#[derive(Clone, Debug)]
struct ValueAnim {
from_value: f32,
to_value: f32,
/// when did `value` last toggle?
toggle_time: f64,
}

View file

@ -9,8 +9,10 @@ use crate::*;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct State {
/// Last known pos
pub pos: Pos2,
/// Last known pos of the pivot
pub pivot_pos: Pos2,
pub pivot: Align2,
/// Last know size. Used for catching clicks.
pub size: Vec2,
@ -21,8 +23,22 @@ pub(crate) struct State {
}
impl State {
pub fn left_top_pos(&self) -> Pos2 {
pos2(
self.pivot_pos.x - self.pivot.x().to_factor() * self.size.x,
self.pivot_pos.y - self.pivot.y().to_factor() * self.size.y,
)
}
pub fn set_left_top_pos(&mut self, pos: Pos2) {
self.pivot_pos = pos2(
pos.x + self.pivot.x().to_factor() * self.size.x,
pos.y + self.pivot.y().to_factor() * self.size.y,
);
}
pub fn rect(&self) -> Rect {
Rect::from_min_size(self.pos, self.size)
Rect::from_min_size(self.left_top_pos(), self.size)
}
}
@ -237,21 +253,19 @@ impl Area {
ctx.request_repaint(); // if we don't know the previous size we are likely drawing the area in the wrong place
}
let mut state = state.unwrap_or_else(|| State {
pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)),
pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)),
pivot,
size: Vec2::ZERO,
interactable,
});
state.pos = new_pos.unwrap_or(state.pos);
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
state.interactable = interactable;
if pivot != Align2::LEFT_TOP {
state.pos.x -= pivot.x().to_factor() * state.size.x;
state.pos.y -= pivot.y().to_factor() * state.size.y;
}
if let Some((anchor, offset)) = anchor {
let screen = ctx.available_rect();
state.pos = anchor.align_size_within_rect(state.size, screen).min + offset;
state.set_left_top_pos(
anchor.align_size_within_rect(state.size, screen).left_top() + offset,
);
}
// interact right away to prevent frame-delay
@ -278,12 +292,13 @@ impl Area {
// Important check - don't try to move e.g. a combobox popup!
if movable {
if move_response.dragged() {
state.pos += ctx.input(|i| i.pointer.delta());
state.pivot_pos += ctx.input(|i| i.pointer.delta());
}
state.pos = ctx
.constrain_window_rect_to_area(state.rect(), drag_bounds)
.min;
state.set_left_top_pos(
ctx.constrain_window_rect_to_area(state.rect(), drag_bounds)
.min,
);
}
if (move_response.dragged() || move_response.clicked())
@ -297,12 +312,13 @@ impl Area {
move_response
};
state.pos = ctx.round_pos_to_pixels(state.pos);
state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos()));
if constrain {
state.pos = ctx
.constrain_window_rect_to_area(state.rect(), drag_bounds)
.min;
state.set_left_top_pos(
ctx.constrain_window_rect_to_area(state.rect(), drag_bounds)
.left_top(),
);
}
Prepared {
@ -374,14 +390,16 @@ impl Prepared {
};
let max_rect = Rect::from_min_max(
self.state.pos,
bounds.max.at_least(self.state.pos + Vec2::splat(32.0)),
self.state.left_top_pos(),
bounds
.max
.at_least(self.state.left_top_pos() + Vec2::splat(32.0)),
);
let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky
let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius);
let clip_rect = Rect::from_min_max(self.state.pos, bounds.max)
let clip_rect = Rect::from_min_max(self.state.left_top_pos(), bounds.max)
.expand(clip_rect_margin)
.intersect(bounds);

View file

@ -141,9 +141,10 @@ impl CollapsingState {
add_header: impl FnOnce(&mut Ui) -> HeaderRet,
) -> HeaderResponse<'_, HeaderRet> {
let header_response = ui.horizontal(|ui| {
let prev_item_spacing = ui.spacing_mut().item_spacing;
ui.spacing_mut().item_spacing.x = 0.0; // the toggler button uses the full indent width
let collapser = self.show_default_button_indented(ui);
ui.spacing_mut().item_spacing.x = ui.spacing_mut().icon_spacing; // Restore spacing
ui.spacing_mut().item_spacing = prev_item_spacing;
(collapser, add_header(ui))
});
HeaderResponse {

View file

@ -19,11 +19,16 @@ use epaint::*;
pub struct Frame {
/// Margin within the painted frame.
pub inner_margin: Margin,
/// Margin outside the painted frame.
pub outer_margin: Margin,
pub rounding: Rounding,
pub shadow: Shadow,
pub fill: Color32,
pub stroke: Stroke,
}

View file

@ -260,8 +260,9 @@ fn show_tooltip_area_dyn<'c, R>(
Area::new(area_id)
.order(Order::Tooltip)
.fixed_pos(window_pos)
.constrain(true)
.interactable(false)
.drag_bounds(Rect::EVERYTHING) // disable clip rect
.drag_bounds(ctx.screen_rect())
.show(ctx, |ui| {
Frame::popup(&ctx.style())
.show(ui, |ui| {

View file

@ -438,9 +438,12 @@ impl<'open> Window<'open> {
content_inner
};
area.state_mut().pos = ctx
.constrain_window_rect_to_area(area.state().rect(), area.drag_bounds())
.min;
{
let pos = ctx
.constrain_window_rect_to_area(area.state().rect(), area.drag_bounds())
.left_top();
area.state_mut().set_left_top_pos(pos);
}
let full_response = area.end(ctx, area_content_ui);
@ -550,7 +553,7 @@ fn interact(
let new_rect = ctx.constrain_window_rect_to_area(new_rect, area.drag_bounds());
// TODO(emilk): add this to a Window state instead as a command "move here next frame"
area.state_mut().pos = new_rect.min;
area.state_mut().set_left_top_pos(new_rect.left_top());
if window_interaction.is_resize() {
if let Some(mut state) = resize::State::load(ctx, resize_id) {

View file

@ -65,11 +65,14 @@ struct ContextImpl {
/// Written to during the frame.
layer_rects_this_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
/// Read
layer_rects_prev_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
#[cfg(feature = "accesskit")]
is_accesskit_enabled: bool,
#[cfg(feature = "accesskit")]
accesskit_node_classes: accesskit::NodeClassSet,
}
impl ContextImpl {
@ -103,7 +106,8 @@ impl ContextImpl {
self.memory.areas.set_state(
LayerId::background(),
containers::area::State {
pos: screen_rect.min,
pivot_pos: screen_rect.left_top(),
pivot: Align2::LEFT_TOP,
size: screen_rect.size(),
interactable: true,
},
@ -113,17 +117,14 @@ impl ContextImpl {
if self.is_accesskit_enabled {
use crate::frame_state::AccessKitFrameState;
let id = crate::accesskit_root_id();
let node = Box::new(accesskit::Node {
role: accesskit::Role::Window,
transform: Some(
accesskit::kurbo::Affine::scale(self.input.pixels_per_point().into()).into(),
),
..Default::default()
});
let mut nodes = IdMap::default();
nodes.insert(id, node);
let mut builder = accesskit::NodeBuilder::new(accesskit::Role::Window);
builder.set_transform(accesskit::Affine::scale(
self.input.pixels_per_point().into(),
));
let mut node_builders = IdMap::default();
node_builders.insert(id, builder);
self.frame_state.accesskit_state = Some(AccessKitFrameState {
nodes,
node_builders,
parent_stack: vec![id],
});
}
@ -156,16 +157,16 @@ impl ContextImpl {
}
#[cfg(feature = "accesskit")]
fn accesskit_node(&mut self, id: Id) -> &mut accesskit::Node {
fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::NodeBuilder {
let state = self.frame_state.accesskit_state.as_mut().unwrap();
let nodes = &mut state.nodes;
if let std::collections::hash_map::Entry::Vacant(entry) = nodes.entry(id) {
let builders = &mut state.node_builders;
if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) {
entry.insert(Default::default());
let parent_id = state.parent_stack.last().unwrap();
let parent = nodes.get_mut(parent_id).unwrap();
parent.children.push(id.accesskit_id());
let parent_builder = builders.get_mut(parent_id).unwrap();
parent_builder.push_child(id.accesskit_id());
}
nodes.get_mut(&id).unwrap()
builders.get_mut(&id).unwrap()
}
}
@ -655,7 +656,7 @@ impl Context {
// Make sure anything that can receive focus has an AccessKit node.
// TODO(mwcampbell): For nodes that are filled from widget info,
// some information is written to the node twice.
self.accesskit_node(id, |node| response.fill_accesskit_node_common(node));
self.accesskit_node_builder(id, |builder| response.fill_accesskit_node_common(builder));
}
let clicked_elsewhere = response.clicked_elsewhere();
@ -1128,12 +1129,20 @@ impl Context {
if let Some(state) = state {
let has_focus = self.input(|i| i.raw.has_focus);
let root_id = crate::accesskit_root_id().accesskit_id();
platform_output.accesskit_update = Some(accesskit::TreeUpdate {
nodes: state
.nodes
let nodes = self.write(|ctx| {
state
.node_builders
.into_iter()
.map(|(id, node)| (id.accesskit_id(), Arc::from(node)))
.collect(),
.map(|(id, builder)| {
(
id.accesskit_id(),
builder.build(&mut ctx.accesskit_node_classes),
)
})
.collect()
});
platform_output.accesskit_update = Some(accesskit::TreeUpdate {
nodes,
tree: Some(accesskit::Tree::new(root_id)),
focus: has_focus.then(|| {
let focus_id = self.memory(|mem| mem.interaction.focus.id);
@ -1720,8 +1729,8 @@ impl Context {
}
/// If AccessKit support is active for the current frame, get or create
/// a node with the specified ID and return a mutable reference to it.
/// For newly crated nodes, the parent is the node with the ID at the top
/// a node builder with the specified ID and return a mutable reference to it.
/// For newly created nodes, the parent is the node with the ID at the top
/// of the stack managed by [`Context::with_accessibility_parent`].
///
/// The `Context` lock is held while the given closure is called!
@ -1729,16 +1738,16 @@ impl Context {
/// Returns `None` if acesskit is off.
// TODO: consider making both RO and RW versions
#[cfg(feature = "accesskit")]
pub fn accesskit_node<R>(
pub fn accesskit_node_builder<R>(
&self,
id: Id,
writer: impl FnOnce(&mut accesskit::Node) -> R,
writer: impl FnOnce(&mut accesskit::NodeBuilder) -> R,
) -> Option<R> {
self.write(|ctx| {
ctx.frame_state
.accesskit_state
.is_some()
.then(|| ctx.accesskit_node(id))
.then(|| ctx.accesskit_node_builder(id))
.map(writer)
})
}
@ -1750,12 +1759,30 @@ impl Context {
/// being called by the AccessKit adapter to provide the initial tree update,
/// then it should do so, to provide a complete AccessKit tree to the adapter
/// immediately. Otherwise, it should enqueue a repaint and use the
/// placeholder tree update from [`crate::accesskit_placeholder_tree_update`]
/// placeholder tree update from [`Context::accesskit_placeholder_tree_update`]
/// in the meantime.
#[cfg(feature = "accesskit")]
pub fn enable_accesskit(&self) {
self.write(|ctx| ctx.is_accesskit_enabled = true);
}
/// Return a tree update that the egui integration should provide to the
/// AccessKit adapter if it cannot immediately run the egui application
/// to get a full tree update after running [`Context::enable_accesskit`].
#[cfg(feature = "accesskit")]
pub fn accesskit_placeholder_tree_update(&self) -> accesskit::TreeUpdate {
use accesskit::{NodeBuilder, Role, Tree, TreeUpdate};
let root_id = crate::accesskit_root_id().accesskit_id();
self.write(|ctx| TreeUpdate {
nodes: vec![(
root_id,
NodeBuilder::new(Role::Window).build(&mut ctx.accesskit_node_classes),
)],
tree: Some(Tree::new(root_id)),
focus: None,
})
}
}
#[test]

View file

@ -12,7 +12,7 @@ pub(crate) struct TooltipFrameState {
#[cfg(feature = "accesskit")]
#[derive(Clone)]
pub(crate) struct AccessKitFrameState {
pub(crate) nodes: IdMap<Box<accesskit::Node>>,
pub(crate) node_builders: IdMap<accesskit::NodeBuilder>,
pub(crate) parent_stack: Vec<Id>,
}

View file

@ -447,8 +447,10 @@ impl InputState {
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Click {
pub pos: Pos2,
/// 1 or 2 (double-click) or 3 (triple-click)
pub count: u32,
/// Allows you to check for e.g. shift-click
pub modifiers: Modifiers,
}

View file

@ -96,10 +96,14 @@ struct GestureState {
struct DynGestureState {
/// used for proportional zooming
avg_distance: f32,
/// used for non-proportional zooming
avg_abs_distance2: Vec2,
avg_pos: Pos2,
avg_force: f32,
heading: f32,
}

View file

@ -39,6 +39,7 @@ impl Order {
Self::Tooltip,
Self::Debug,
];
pub const TOP: Self = Self::Debug;
#[inline(always)]
pub fn allow_interaction(&self) -> bool {

View file

@ -513,18 +513,30 @@ pub mod special_emojis {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum WidgetType {
Label, // TODO(emilk): emit Label events
/// e.g. a hyperlink
Link,
TextEdit,
Button,
Checkbox,
RadioButton,
SelectableLabel,
ComboBox,
Slider,
DragValue,
ColorButton,
ImageButton,
CollapsingHeader,
/// If you cannot fit any of the above slots.
@ -559,25 +571,3 @@ pub fn __run_test_ui(mut add_contents: impl FnMut(&mut Ui)) {
pub fn accesskit_root_id() -> Id {
Id::new("accesskit_root")
}
/// Return a tree update that the egui integration should provide to the
/// AccessKit adapter if it cannot immediately run the egui application
/// to get a full tree update after running [`Context::enable_accesskit`].
#[cfg(feature = "accesskit")]
pub fn accesskit_placeholder_tree_update() -> accesskit::TreeUpdate {
use accesskit::{Node, Role, Tree, TreeUpdate};
use std::sync::Arc;
let root_id = accesskit_root_id().accesskit_id();
TreeUpdate {
nodes: vec![(
root_id,
Arc::new(Node {
role: Role::Window,
..Default::default()
}),
)],
tree: Some(Tree::new(root_id)),
focus: None,
}
}

View file

@ -573,47 +573,47 @@ impl Response {
self.output_event(event);
} else {
#[cfg(feature = "accesskit")]
self.ctx.accesskit_node(self.id, |node| {
self.fill_accesskit_node_from_widget_info(node, make_info());
self.ctx.accesskit_node_builder(self.id, |builder| {
self.fill_accesskit_node_from_widget_info(builder, make_info());
});
}
}
pub fn output_event(&self, event: crate::output::OutputEvent) {
#[cfg(feature = "accesskit")]
self.ctx.accesskit_node(self.id, |node| {
self.fill_accesskit_node_from_widget_info(node, event.widget_info().clone());
self.ctx.accesskit_node_builder(self.id, |builder| {
self.fill_accesskit_node_from_widget_info(builder, event.widget_info().clone());
});
self.ctx.output_mut(|o| o.events.push(event));
}
#[cfg(feature = "accesskit")]
pub(crate) fn fill_accesskit_node_common(&self, node: &mut accesskit::Node) {
node.bounds = Some(accesskit::kurbo::Rect {
pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::NodeBuilder) {
builder.set_bounds(accesskit::Rect {
x0: self.rect.min.x.into(),
y0: self.rect.min.y.into(),
x1: self.rect.max.x.into(),
y1: self.rect.max.y.into(),
});
if self.sense.focusable {
node.focusable = true;
builder.add_action(accesskit::Action::Focus);
}
if self.sense.click && node.default_action_verb.is_none() {
node.default_action_verb = Some(accesskit::DefaultActionVerb::Click);
if self.sense.click && builder.default_action_verb().is_none() {
builder.set_default_action_verb(accesskit::DefaultActionVerb::Click);
}
}
#[cfg(feature = "accesskit")]
fn fill_accesskit_node_from_widget_info(
&self,
node: &mut accesskit::Node,
builder: &mut accesskit::NodeBuilder,
info: crate::WidgetInfo,
) {
use crate::WidgetType;
use accesskit::{CheckedState, Role};
self.fill_accesskit_node_common(node);
node.role = match info.typ {
self.fill_accesskit_node_common(builder);
builder.set_role(match info.typ {
WidgetType::Label => Role::StaticText,
WidgetType::Link => Role::Link,
WidgetType::TextEdit => Role::TextField,
@ -628,18 +628,18 @@ impl Response {
WidgetType::DragValue => Role::SpinButton,
WidgetType::ColorButton => Role::ColorWell,
WidgetType::Other => Role::Unknown,
};
});
if let Some(label) = info.label {
node.name = Some(label.into());
builder.set_name(label);
}
if let Some(value) = info.current_text_value {
node.value = Some(value.into());
builder.set_value(value);
}
if let Some(value) = info.value {
node.numeric_value = Some(value);
builder.set_numeric_value(value);
}
if let Some(selected) = info.selected {
node.checked_state = Some(if selected {
builder.set_checked_state(if selected {
CheckedState::True
} else {
CheckedState::False
@ -662,8 +662,9 @@ impl Response {
/// ```
pub fn labelled_by(self, id: Id) -> Self {
#[cfg(feature = "accesskit")]
self.ctx
.accesskit_node(self.id, |node| node.labelled_by.push(id.accesskit_id()));
self.ctx.accesskit_node_builder(self.id, |builder| {
builder.push_labelled_by(id.accesskit_id());
});
#[cfg(not(feature = "accesskit"))]
{
let _ = id;

View file

@ -305,6 +305,7 @@ pub struct Spacing {
/// Margin between contents and scroll bar.
pub scroll_bar_inner_margin: f32,
/// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar).
pub scroll_bar_outer_margin: f32,
}
@ -491,6 +492,7 @@ pub struct Visuals {
pub resize_corner_size: f32,
pub text_cursor_width: f32,
/// show where the text cursor would be if you clicked
pub text_cursor_preview: bool,
@ -509,6 +511,11 @@ pub struct Visuals {
/// Wether or not Grids and Tables should be striped by default
/// (have alternating rows differently colored).
pub striped: bool,
/// Show trailing color behind the circle of a [`Slider`]. Default is OFF.
///
/// Enabling this will affect ALL sliders, and can be enabled/disabled per slider with [`Slider::trailing_fill`].
pub slider_trailing_fill: bool,
}
impl Visuals {
@ -768,6 +775,8 @@ impl Visuals {
indent_has_left_vline: true,
striped: false,
slider_trailing_fill: false,
}
}
@ -1333,6 +1342,8 @@ impl Visuals {
indent_has_left_vline,
striped,
slider_trailing_fill,
} = self;
ui.collapsing("Background Colors", |ui| {
@ -1395,6 +1406,8 @@ impl Visuals {
ui.checkbox(striped, "By default, add stripes to grids and tables?");
ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
ui.vertical_centered(|ui| reset_button(ui, self));
}
}

View file

@ -2174,7 +2174,6 @@ impl Ui {
}
}
#[inline]
/// Create a menu button with an image that when clicked will show the given menu.
///
/// If called from within a menu this will instead create a button for a sub-menu.
@ -2198,6 +2197,7 @@ impl Ui {
/// ```
///
/// See also: [`Self::close_menu`] and [`Response::context_menu`].
#[inline]
pub fn menu_image_button<R>(
&mut self,
texture_id: TextureId,

View file

@ -269,6 +269,10 @@ impl<'a> Checkbox<'a> {
text: text.into(),
}
}
pub fn without_text(checked: &'a mut bool) -> Self {
Self::new(checked, WidgetText::default())
}
}
impl<'a> Widget for Checkbox<'a> {

View file

@ -461,23 +461,31 @@ impl<'a> Widget for DragValue<'a> {
// some clones below are redundant if AccessKit is disabled
#[allow(clippy::redundant_clone)]
let mut response = if is_kb_editing {
let button_width = ui.spacing().interact_size.x;
let mut value_text = ui
.memory_mut(|mem| mem.drag_value.edit_string.take())
.unwrap_or_else(|| value_text.clone());
let response = ui.add(
TextEdit::singleline(&mut value_text)
.clip_text(false)
.horizontal_align(ui.layout().horizontal_align())
.vertical_align(ui.layout().vertical_align())
.margin(ui.spacing().button_padding)
.min_size(ui.spacing().interact_size)
.id(id)
.desired_width(button_width)
.desired_width(ui.spacing().interact_size.x)
.font(text_style),
);
let parsed_value = match custom_parser {
Some(parser) => parser(&value_text),
None => value_text.parse().ok(),
};
if let Some(parsed_value) = parsed_value {
let parsed_value = clamp_to_range(parsed_value, clamp_range.clone());
set(&mut get_set_value, parsed_value);
// Only update the value when the user presses enter, or clicks elsewhere. NOT every frame.
// See https://github.com/emilk/egui/issues/2687
if response.lost_focus() {
let parsed_value = match custom_parser {
Some(parser) => parser(&value_text),
None => value_text.parse().ok(),
};
if let Some(parsed_value) = parsed_value {
let parsed_value = clamp_to_range(parsed_value, clamp_range.clone());
set(&mut get_set_value, parsed_value);
}
}
ui.memory_mut(|mem| mem.drag_value.edit_string = Some(value_text));
response
@ -557,28 +565,28 @@ impl<'a> Widget for DragValue<'a> {
response.widget_info(|| WidgetInfo::drag_value(value));
#[cfg(feature = "accesskit")]
ui.ctx().accesskit_node(response.id, |node| {
ui.ctx().accesskit_node_builder(response.id, |builder| {
use accesskit::Action;
// If either end of the range is unbounded, it's better
// to leave the corresponding AccessKit field set to None,
// to allow for platform-specific default behavior.
if clamp_range.start().is_finite() {
node.min_numeric_value = Some(*clamp_range.start());
builder.set_min_numeric_value(*clamp_range.start());
}
if clamp_range.end().is_finite() {
node.max_numeric_value = Some(*clamp_range.end());
builder.set_max_numeric_value(*clamp_range.end());
}
node.numeric_value_step = Some(speed);
node.actions |= Action::SetValue;
builder.set_numeric_value_step(speed);
builder.add_action(Action::SetValue);
if value < *clamp_range.end() {
node.actions |= Action::Increment;
builder.add_action(Action::Increment);
}
if value > *clamp_range.start() {
node.actions |= Action::Decrement;
builder.add_action(Action::Decrement);
}
// The name field is set to the current value by the button,
// but we don't want it set that way on this widget type.
node.name = None;
builder.clear_name();
// Always expose the value as a string. This makes the widget
// more stable to accessibility users as it switches
// between edit and button modes. This is particularly important
@ -599,7 +607,7 @@ impl<'a> Widget for DragValue<'a> {
// when in edit mode.
if !is_kb_editing {
let value_text = format!("{}{}{}", prefix, value_text, suffix);
node.value = Some(value_text.into());
builder.set_value(value_text);
}
});

View file

@ -742,7 +742,7 @@ impl Plot {
});
let PlotMemory {
mut bounds_modified,
bounds_modified,
mut hovered_entry,
mut hidden_items,
last_screen_transform,
@ -754,6 +754,7 @@ impl Plot {
items: Vec::new(),
next_auto_color_idx: 0,
last_screen_transform,
bounds_modified,
response,
ctx: ui.ctx().clone(),
};
@ -762,6 +763,7 @@ impl Plot {
mut items,
mut response,
last_screen_transform,
mut bounds_modified,
..
} = plot_ui;
@ -1042,6 +1044,7 @@ pub struct PlotUi {
items: Vec<Box<dyn PlotItem>>,
next_auto_color_idx: usize,
last_screen_transform: ScreenTransform,
bounds_modified: AxisBools,
response: Response,
ctx: Context,
}
@ -1069,11 +1072,13 @@ impl PlotUi {
/// Set the plot bounds. Can be useful for implementing alternative plot navigation methods.
pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) {
self.last_screen_transform.set_bounds(plot_bounds);
self.bounds_modified = true.into();
}
/// Move the plot bounds. Can be useful for implementing alternative plot navigation methods.
pub fn translate_bounds(&mut self, delta_pos: Vec2) {
self.last_screen_transform.translate_bounds(delta_pos);
self.bounds_modified = true.into();
}
/// Returns `true` if the plot area is currently hovered.

View file

@ -84,6 +84,7 @@ pub struct Slider<'a> {
max_decimals: Option<usize>,
custom_formatter: Option<NumFormatter<'a>>,
custom_parser: Option<NumParser<'a>>,
trailing_fill: Option<bool>,
}
impl<'a> Slider<'a> {
@ -129,6 +130,7 @@ impl<'a> Slider<'a> {
max_decimals: None,
custom_formatter: None,
custom_parser: None,
trailing_fill: None,
}
}
@ -269,6 +271,17 @@ impl<'a> Slider<'a> {
self
}
/// Display trailing color behind the slider's circle. Default is OFF.
///
/// This setting can be enabled globally for all sliders with [`Visuals::slider_trailing_fill`].
/// Toggling it here will override the above setting ONLY for this individual slider.
///
/// The fill color will be taken from `selection.bg_fill` in your [`Visuals`], the same as a [`ProgressBar`].
pub fn trailing_fill(mut self, trailing_fill: bool) -> Self {
self.trailing_fill = Some(trailing_fill);
self
}
/// Set custom formatter defining how numbers are converted into text.
///
/// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
@ -616,18 +629,40 @@ impl<'a> Slider<'a> {
let rail_radius = ui.painter().round_to_pixel(self.rail_radius_limit(rect));
let rail_rect = self.rail_rect(rect, rail_radius);
let position_1d = self.position_from_value(value, position_range);
let visuals = ui.style().interact(response);
ui.painter().add(epaint::RectShape {
rect: rail_rect,
rounding: ui.visuals().widgets.inactive.rounding,
fill: ui.visuals().widgets.inactive.bg_fill,
stroke: Default::default(),
});
let widget_visuals = &ui.visuals().widgets;
ui.painter().rect_filled(
rail_rect,
widget_visuals.inactive.rounding,
widget_visuals.inactive.bg_fill,
);
let position_1d = self.position_from_value(value, position_range);
let center = self.marker_center(position_1d, &rail_rect);
// Decide if we should add trailing fill.
let trailing_fill = self
.trailing_fill
.unwrap_or_else(|| ui.visuals().slider_trailing_fill);
// Paint trailing fill.
if trailing_fill {
let mut trailing_rail_rect = rail_rect;
// The trailing rect has to be drawn differently depending on the orientation.
match self.orientation {
SliderOrientation::Vertical => trailing_rail_rect.min.y = center.y,
SliderOrientation::Horizontal => trailing_rail_rect.max.x = center.x,
};
ui.painter().rect_filled(
trailing_rail_rect,
widget_visuals.inactive.rounding,
ui.visuals().selection.bg_fill,
);
}
ui.painter().add(epaint::CircleShape {
center,
radius: self.handle_radius(rect) + visuals.expansion,
@ -757,18 +792,20 @@ impl<'a> Slider<'a> {
response.widget_info(|| WidgetInfo::slider(value, self.text.text()));
#[cfg(feature = "accesskit")]
ui.ctx().accesskit_node(response.id, |node| {
ui.ctx().accesskit_node_builder(response.id, |builder| {
use accesskit::Action;
node.min_numeric_value = Some(*self.range.start());
node.max_numeric_value = Some(*self.range.end());
node.numeric_value_step = self.step;
node.actions |= Action::SetValue;
builder.set_min_numeric_value(*self.range.start());
builder.set_max_numeric_value(*self.range.end());
if let Some(step) = self.step {
builder.set_numeric_value_step(step);
}
builder.add_action(Action::SetValue);
let clamp_range = self.clamp_range();
if value < *clamp_range.end() {
node.actions |= Action::Increment;
builder.add_action(Action::Increment);
}
if value > *clamp_range.start() {
node.actions |= Action::Decrement;
builder.add_action(Action::Decrement);
}
});

View file

@ -67,6 +67,9 @@ pub struct TextEdit<'t> {
desired_height_rows: usize,
lock_focus: bool,
cursor_at_end: bool,
min_size: Vec2,
align: Align2,
clip_text: bool,
}
impl<'t> WidgetWithState for TextEdit<'t> {
@ -89,6 +92,7 @@ impl<'t> TextEdit<'t> {
Self {
desired_height_rows: 1,
multiline: false,
clip_text: true,
..Self::multiline(text)
}
}
@ -112,6 +116,9 @@ impl<'t> TextEdit<'t> {
desired_height_rows: 4,
lock_focus: false,
cursor_at_end: true,
min_size: Vec2::ZERO,
align: Align2::LEFT_TOP,
clip_text: false,
}
}
@ -269,6 +276,37 @@ impl<'t> TextEdit<'t> {
self.cursor_at_end = b;
self
}
/// When `true` (default), overflowing text will be clipped.
///
/// When `false`, widget width will expand to make all text visible.
///
/// This only works for singleline [`TextEdit`].
pub fn clip_text(mut self, b: bool) -> Self {
// always show everything in multiline
if !self.multiline {
self.clip_text = b;
}
self
}
/// Set the horizontal align of the inner text.
pub fn horizontal_align(mut self, align: Align) -> Self {
self.align.0[0] = align;
self
}
/// Set the vertical align of the inner text.
pub fn vertical_align(mut self, align: Align) -> Self {
self.align.0[1] = align;
self
}
/// Set the minimum size of the [`TextEdit`].
pub fn min_size(mut self, min_size: Vec2) -> Self {
self.min_size = min_size;
self
}
}
// ----------------------------------------------------------------------------
@ -364,13 +402,16 @@ impl<'t> TextEdit<'t> {
layouter,
password,
frame: _,
margin: _,
margin,
multiline,
interactive,
desired_width,
desired_height_rows,
lock_focus,
cursor_at_end,
min_size,
align,
clip_text,
} = self;
let text_color = text_color
@ -389,7 +430,7 @@ impl<'t> TextEdit<'t> {
available_width
} else {
desired_width.min(available_width)
};
} - margin.x * 2.0;
let font_id_clone = font_id.clone();
let mut default_layouter = move |ui: &Ui, text: &str, wrap_width: f32| {
@ -406,13 +447,14 @@ impl<'t> TextEdit<'t> {
let mut galley = layouter(ui, text.as_str(), wrap_width);
let desired_width = if multiline {
galley.size().x.max(wrap_width) // always show everything in multiline
let desired_width = if clip_text {
wrap_width // visual clipping with scroll in singleline input.
} else {
wrap_width // visual clipping with scroll in singleline input. TODO(emilk): opt-in/out?
galley.size().x.max(wrap_width)
};
let desired_height = (desired_height_rows.at_least(1) as f32) * row_height;
let desired_size = vec2(desired_width, galley.size().y.max(desired_height));
let desired_size = vec2(desired_width, galley.size().y.max(desired_height))
.at_least(min_size - margin * 2.0);
let (auto_id, rect) = ui.allocate_space(desired_size);
@ -547,10 +589,14 @@ impl<'t> TextEdit<'t> {
cursor_range = Some(new_cursor_range);
}
let mut text_draw_pos = response.rect.min;
let mut text_draw_pos = align
.align_size_within_rect(galley.size(), response.rect)
.intersect(response.rect) // limit pos to the response rect area
.min;
let align_offset = response.rect.left() - text_draw_pos.x;
// Visual clipping for singleline text editor with text larger than width
if !multiline {
if clip_text && align_offset == 0.0 {
let cursor_pos = match (cursor_range, ui.memory(|mem| mem.has_focus(id))) {
(Some(cursor_range), true) => galley.pos_from_cursor(&cursor_range.primary).min.x,
_ => 0.0,
@ -573,6 +619,8 @@ impl<'t> TextEdit<'t> {
state.singleline_offset = offset_x;
text_draw_pos -= vec2(offset_x, 0.0);
} else {
state.singleline_offset = align_offset;
}
let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) =
@ -666,7 +714,7 @@ impl<'t> TextEdit<'t> {
#[cfg(feature = "accesskit")]
{
let parent_id = ui.ctx().accesskit_node(response.id, |node| {
let parent_id = ui.ctx().accesskit_node_builder(response.id, |builder| {
use accesskit::{TextPosition, TextSelection};
let parent_id = response.id;
@ -674,7 +722,7 @@ impl<'t> TextEdit<'t> {
if let Some(cursor_range) = &cursor_range {
let anchor = &cursor_range.secondary.rcursor;
let focus = &cursor_range.primary.rcursor;
node.text_selection = Some(TextSelection {
builder.set_text_selection(TextSelection {
anchor: TextPosition {
node: parent_id.with(anchor.row).accesskit_id(),
character_index: anchor.column,
@ -686,8 +734,10 @@ impl<'t> TextEdit<'t> {
});
}
node.default_action_verb = Some(accesskit::DefaultActionVerb::Focus);
node.multiline = self.multiline;
builder.set_default_action_verb(accesskit::DefaultActionVerb::Focus);
if self.multiline {
builder.set_multiline();
}
parent_id
});
@ -699,16 +749,16 @@ impl<'t> TextEdit<'t> {
ui.ctx().with_accessibility_parent(parent_id, || {
for (i, row) in galley.rows.iter().enumerate() {
let id = parent_id.with(i);
ui.ctx().accesskit_node(id, |node| {
node.role = Role::InlineTextBox;
ui.ctx().accesskit_node_builder(id, |builder| {
builder.set_role(Role::InlineTextBox);
let rect = row.rect.translate(text_draw_pos.to_vec2());
node.bounds = Some(accesskit::kurbo::Rect {
builder.set_bounds(accesskit::Rect {
x0: rect.min.x.into(),
y0: rect.min.y.into(),
x1: rect.max.x.into(),
y1: rect.max.y.into(),
});
node.text_direction = Some(TextDirection::LeftToRight);
builder.set_text_direction(TextDirection::LeftToRight);
// TODO(mwcampbell): Set more node fields for the row
// once AccessKit adapters expose text formatting info.
@ -748,11 +798,11 @@ impl<'t> TextEdit<'t> {
}
word_lengths.push((character_lengths.len() - last_word_start) as _);
node.value = Some(value.into());
node.character_lengths = character_lengths.into();
node.character_positions = Some(character_positions.into());
node.character_widths = Some(character_widths.into());
node.word_lengths = word_lengths.into();
builder.set_value(value);
builder.set_character_lengths(character_lengths);
builder.set_character_positions(character_positions);
builder.set_character_widths(character_widths);
builder.set_word_lengths(word_lengths);
});
}
});

View file

@ -1,6 +1,6 @@
[package]
name = "egui_demo_app"
version = "0.20.0"
version = "0.21.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
@ -30,11 +30,11 @@ wgpu = ["eframe/wgpu", "bytemuck"]
[dependencies]
chrono = { version = "0.4", features = ["js-sys", "wasmbind"] }
eframe = { version = "0.20.0", path = "../eframe", default-features = false }
egui = { version = "0.20.0", path = "../egui", features = [
eframe = { version = "0.21.0", path = "../eframe", default-features = false }
egui = { version = "0.21.0", path = "../egui", features = [
"extra_debug_asserts",
] }
egui_demo_lib = { version = "0.20.0", path = "../egui_demo_lib", features = [
egui_demo_lib = { version = "0.21.0", path = "../egui_demo_lib", features = [
"chrono",
] }
tracing = "0.1"
@ -42,7 +42,7 @@ tracing = "0.1"
# Optional dependencies:
bytemuck = { version = "1.7.1", optional = true }
egui_extras = { version = "0.20.0", optional = true, path = "../egui_extras" }
egui_extras = { version = "0.21.0", optional = true, path = "../egui_extras" }
# feature "http":
ehttp = { version = "0.2.0", optional = true }

View file

@ -1,6 +1,6 @@
[package]
name = "egui_demo_lib"
version = "0.20.0"
version = "0.21.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Example library for egui"
edition = "2021"
@ -30,8 +30,8 @@ syntax_highlighting = ["syntect"]
[dependencies]
egui = { version = "0.20.0", path = "../egui", default-features = false }
egui_extras = { version = "0.20.0", path = "../egui_extras" }
egui = { version = "0.21.0", path = "../egui", default-features = false }
egui_extras = { version = "0.21.0", path = "../egui_extras" }
enum-map = { version = "2", features = ["serde"] }
tracing = { version = "0.1", default-features = false, features = ["std"] }
unicode_names2 = { version = "0.6.0", default-features = false }

View file

@ -16,6 +16,7 @@ pub struct Sliders {
pub integer: bool,
pub vertical: bool,
pub value: f64,
pub trailing_fill: bool,
}
impl Default for Sliders {
@ -31,6 +32,7 @@ impl Default for Sliders {
integer: false,
vertical: false,
value: 10.0,
trailing_fill: false,
}
}
}
@ -64,6 +66,7 @@ impl super::View for Sliders {
integer,
vertical,
value,
trailing_fill,
} = self;
ui.label("You can click a slider value to edit it with the keyboard.");
@ -95,7 +98,8 @@ impl super::View for Sliders {
.smart_aim(*smart_aim)
.orientation(orientation)
.text("i32 demo slider")
.step_by(istep),
.step_by(istep)
.trailing_fill(*trailing_fill),
);
*value = value_i32 as f64;
} else {
@ -106,7 +110,8 @@ impl super::View for Sliders {
.smart_aim(*smart_aim)
.orientation(orientation)
.text("f64 demo slider")
.step_by(istep),
.step_by(istep)
.trailing_fill(*trailing_fill),
);
ui.label(
@ -126,17 +131,24 @@ impl super::View for Sliders {
Slider::new(min, type_min..=type_max)
.logarithmic(true)
.smart_aim(*smart_aim)
.text("left"),
.text("left")
.trailing_fill(*trailing_fill),
);
ui.add(
Slider::new(max, type_min..=type_max)
.logarithmic(true)
.smart_aim(*smart_aim)
.text("right"),
.text("right")
.trailing_fill(*trailing_fill),
);
ui.separator();
ui.checkbox(trailing_fill, "Toggle trailing color");
ui.label("When enabled, trailing color will be painted up until the circle.");
ui.separator();
ui.checkbox(use_steps, "Use steps");
ui.label("When enabled, the minimal value change would be restricted to a given step.");
if *use_steps {

View file

@ -5,6 +5,10 @@ All notable changes to the `egui_extras` integration will be noted in this file.
## Unreleased
## 0.21.0 - 2023-02-08
* Update to egui 0.21
## 0.20.0 - 2022-12-08
* Added `RetainedImage::from_svg_bytes_with_size` to be able to specify a size for SVGs to be rasterized at.
* Lots of `Table` improvements ([#2369](https://github.com/emilk/egui/pull/2369)):

View file

@ -1,6 +1,6 @@
[package]
name = "egui_extras"
version = "0.20.0"
version = "0.21.0"
authors = [
"Dominik Rössler <dominik@freshx.de>",
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
@ -37,7 +37,7 @@ tracing = ["dep:tracing", "egui/tracing"]
[dependencies]
egui = { version = "0.20.0", path = "../egui", default-features = false }
egui = { version = "0.21.0", path = "../egui", default-features = false }
serde = { version = "1", features = ["derive"] }

View file

@ -2,6 +2,7 @@
[![Latest version](https://img.shields.io/crates/v/egui_extras.svg)](https://crates.io/crates/egui_extras)
[![Documentation](https://docs.rs/egui_extras/badge.svg)](https://docs.rs/egui_extras)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)

View file

@ -7,7 +7,7 @@ All notable changes to the `egui_glium` integration will be noted in this file.
## 0.20.1 - 2022-12-11
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
* Fix [docs.rs](https://docs.rs/egui_glium) build ([#2420](https://github.com/emilk/egui/pull/2420)).
## 0.20.0 - 2022-12-08

View file

@ -1,6 +1,6 @@
[package]
name = "egui_glium"
version = "0.20.1"
version = "0.21.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui natively using the glium library"
edition = "2021"
@ -36,10 +36,10 @@ links = ["egui-winit/links"]
[dependencies]
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
"bytemuck",
] }
egui-winit = { version = "0.20.0", path = "../egui-winit", default-features = false }
egui-winit = { version = "0.21.1", path = "../egui-winit", default-features = false }
ahash = { version = "0.8.1", default-features = false, features = [
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
@ -54,5 +54,5 @@ document-features = { version = "0.2", optional = true }
[dev-dependencies]
egui_demo_lib = { version = "0.20.0", path = "../egui_demo_lib", default-features = false }
egui_demo_lib = { version = "0.21.0", path = "../egui_demo_lib", default-features = false }
image = { version = "0.24", default-features = false, features = ["png"] }

View file

@ -11,7 +11,7 @@ This crates provides bindings between [`egui`](https://github.com/emilk/egui) an
To use on Linux, first run:
```
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
```
This crate depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).

View file

@ -3,11 +3,15 @@ All notable changes to the `egui_glow` integration will be noted in this file.
## Unreleased
## 0.21.0 - 2023-02-08
* Update to `glow` 0.12 ([#2695](https://github.com/emilk/egui/pull/2695)).
* Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)).
## 0.20.1 - 2022-12-11
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
* Fix [docs.rs](https://docs.rs/egui_glow) build ([#2420](https://github.com/emilk/egui/pull/2420)).
## 0.20.0 - 2022-12-08

View file

@ -1,6 +1,6 @@
[package]
name = "egui_glow"
version = "0.20.1"
version = "0.21.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui natively using the glow library"
edition = "2021"
@ -44,12 +44,12 @@ winit = ["egui-winit"]
[dependencies]
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
"bytemuck",
] }
bytemuck = "1.7"
glow = "0.11"
glow = "0.12"
memoffset = "0.6"
tracing = { version = "0.1", default-features = false, features = ["std"] }
@ -59,7 +59,7 @@ document-features = { version = "0.2", optional = true }
# Native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
egui-winit = { version = "0.20.0", path = "../egui-winit", optional = true, default-features = false }
egui-winit = { version = "0.21.1", path = "../egui-winit", optional = true, default-features = false }
puffin = { version = "0.14", optional = true }
# Web:
@ -69,8 +69,9 @@ wasm-bindgen = { version = "0.2" }
[dev-dependencies]
glutin = "0.30.2" # examples/pure_glow
glutin = "0.30" # examples/pure_glow
raw-window-handle = "0.5.0"
glutin-winit = "0.3.0"
[[example]]

View file

@ -14,7 +14,7 @@ To write web apps using `glow` you can use [`eframe`](https://github.com/emilk/e
To use on Linux, first run:
```
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
```
This crate optionally depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).

View file

@ -17,66 +17,91 @@ impl GlutinWindowContext {
// refactor this function to use `glutin-winit` crate eventually.
// preferably add android support at the same time.
#[allow(unsafe_code)]
unsafe fn new(winit_window: winit::window::Window) -> Self {
use glutin::prelude::*;
use raw_window_handle::*;
unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget<()>) -> Self {
use egui::NumExt;
use glutin::context::NotCurrentGlContextSurfaceAccessor;
use glutin::display::GetGlDisplay;
use glutin::display::GlDisplay;
use glutin::prelude::GlSurface;
use raw_window_handle::HasRawWindowHandle;
let winit_window_builder = winit::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(winit::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glow example") // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
.with_visible(false);
let raw_display_handle = winit_window.raw_display_handle();
let raw_window_handle = winit_window.raw_window_handle();
// EGL is crossplatform and the official khronos way
// but sometimes platforms/drivers may not have it, so we use back up options where possible.
// try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display.
#[cfg(target_os = "windows")]
let preference = glutin::display::DisplayApiPreference::EglThenWgl(Some(raw_window_handle));
// try egl and fallback to x11 glx
#[cfg(target_os = "linux")]
let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
winit::platform::x11::register_xlib_error_hook,
));
#[cfg(target_os = "macos")]
let preference = glutin::display::DisplayApiPreference::Cgl;
#[cfg(target_os = "android")]
let preference = glutin::display::DisplayApiPreference::Egl;
let gl_display = glutin::display::Display::new(raw_display_handle, preference).unwrap();
let config_template = glutin::config::ConfigTemplateBuilder::new()
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
.prefer_hardware_accelerated(None)
.with_depth_size(0)
.with_stencil_size(0)
.with_transparency(false)
.compatible_with_native_window(raw_window_handle)
.build();
.with_transparency(false);
let config = gl_display
.find_configs(config_template)
.unwrap()
.next()
.unwrap();
tracing::debug!("trying to get gl_config");
let (mut window, gl_config) =
glutin_winit::DisplayBuilder::new() // let glutin-winit helper crate handle the complex parts of opengl context creation
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
.with_window_builder(Some(winit_window_builder.clone()))
.build(
event_loop,
config_template_builder,
|mut config_iterator| {
config_iterator.next().expect(
"failed to find a matching configuration for creating glutin config",
)
},
)
.expect("failed to create gl_config");
let gl_display = gl_config.display();
tracing::debug!("found gl_config: {:?}", &gl_config);
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
tracing::debug!("raw window handle: {:?}", raw_window_handle);
let context_attributes =
glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
// for surface creation.
let (width, height): (u32, u32) = winit_window.inner_size().into();
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
// by default, glutin will try to create a core opengl context. but, if it is not available, try to create a gl-es context using this fallback attributes
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
.with_context_api(glutin::context::ContextApi::Gles(None))
.build(raw_window_handle);
let not_current_gl_context = unsafe {
gl_display
.create_context(&gl_config, &context_attributes)
.unwrap_or_else(|_| {
tracing::debug!("failed to create gl_context with attributes: {:?}. retrying with fallback context attributes: {:?}",
&context_attributes,
&fallback_context_attributes);
gl_config
.display()
.create_context(&gl_config, &fallback_context_attributes)
.expect("failed to create context even with fallback attributes")
})
};
// this is where the window is created, if it has not been created while searching for suitable gl_config
let window = window.take().unwrap_or_else(|| {
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
glutin_winit::finalize_window(event_loop, winit_window_builder.clone(), &gl_config)
.expect("failed to finalize glutin window")
});
let (width, height): (u32, u32) = window.inner_size().into();
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
let surface_attributes =
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
.build(
raw_window_handle,
std::num::NonZeroU32::new(width).unwrap(),
std::num::NonZeroU32::new(height).unwrap(),
);
// start creating the gl objects
let gl_context = gl_display
.create_context(&config, &context_attributes)
.unwrap();
let gl_surface = gl_display
.create_window_surface(&config, &surface_attributes)
.unwrap();
let gl_context = gl_context.make_current(&gl_surface).unwrap();
.build(window.raw_window_handle(), width, height);
tracing::debug!(
"creating surface with attributes: {:?}",
&surface_attributes
);
let gl_surface = unsafe {
gl_display
.create_window_surface(&gl_config, &surface_attributes)
.unwrap()
};
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
gl_surface
.set_swap_interval(
@ -86,7 +111,7 @@ impl GlutinWindowContext {
.unwrap();
GlutinWindowContext {
window: winit_window,
window,
gl_context,
gl_display,
gl_surface,
@ -216,19 +241,7 @@ fn main() {
fn create_display(
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
) -> (GlutinWindowContext, glow::Context) {
let winit_window = winit::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(winit::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glow example")
.with_visible(false) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
.build(event_loop)
.unwrap();
// a lot of the code below has been lifted from glutin example in their repo.
let glutin_window_context = unsafe { GlutinWindowContext::new(winit_window) };
let glutin_window_context = unsafe { GlutinWindowContext::new(event_loop) };
let gl = unsafe {
glow::Context::from_loader_function(|s| {
let s = std::ffi::CString::new(s)

View file

@ -106,6 +106,23 @@ impl Painter {
crate::profile_function!();
crate::check_for_gl_error_even_in_release!(&gl, "before Painter::new");
// some useful debug info. all three of them are present in gl 1.1.
unsafe {
let version = gl.get_parameter_string(glow::VERSION);
let renderer = gl.get_parameter_string(glow::RENDERER);
let vendor = gl.get_parameter_string(glow::VENDOR);
tracing::debug!(
"\nopengl version: {version}\nopengl renderer: {renderer}\nopengl vendor: {vendor}"
);
}
#[cfg(not(target_arch = "wasm32"))]
if gl.version().major < 2 {
// this checks on desktop that we are not using opengl 1.1 microsoft sw rendering context.
// ShaderVersion::get fn will segfault due to SHADING_LANGUAGE_VERSION (added in gl2.0)
return Err("egui_glow requires opengl 2.0+. ".to_owned());
}
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
let is_webgl_1 = shader_version == ShaderVersion::Es100;

View file

@ -1,6 +1,6 @@
[package]
name = "emath"
version = "0.20.0"
version = "0.21.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Minimal 2D math library for GUI work"
edition = "2021"

View file

@ -1,5 +1,11 @@
# emath - egui math library
[![Latest version](https://img.shields.io/crates/v/emath.svg)](https://crates.io/crates/emath)
[![Documentation](https://docs.rs/emath/badge.svg)](https://docs.rs/emath)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
A bare-bones 2D math library with types and functions useful for GUI building.
Made for [`egui`](https://github.com/emilk/egui/).

View file

@ -15,6 +15,7 @@ use crate::*;
pub struct Pos2 {
/// How far to the right.
pub x: f32,
/// How far down.
pub y: f32,
// implicit w = 1

View file

@ -3,6 +3,9 @@ All notable changes to the epaint crate will be documented in this file.
## Unreleased
## 0.21.0 - 2023-02-08
* Improve the look of thin white lines ([#2437](https://github.com/emilk/egui/pull/2437)).
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
* Fix bug in `Mesh::split_to_u16` ([#2459](https://github.com/emilk/egui/pull/2459)).

View file

@ -1,6 +1,6 @@
[package]
name = "epaint"
version = "0.20.0"
version = "0.21.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Minimal 2D graphics library for GUI work"
edition = "2021"
@ -63,9 +63,12 @@ mint = ["emath/mint"]
## Allow serialization using [`serde`](https://docs.rs/serde).
serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde"]
## Change Vertex layout to be compatible with unity
unity = []
[dependencies]
emath = { version = "0.20.0", path = "../emath" }
ecolor = { version = "0.20.0", path = "../ecolor" }
emath = { version = "0.21.0", path = "../emath" }
ecolor = { version = "0.21.0", path = "../ecolor" }
ab_glyph = "0.2.11"
ahash = { version = "0.8.1", default-features = false, features = [

View file

@ -1,5 +1,11 @@
# epaint - egui paint library
[![Latest version](https://img.shields.io/crates/v/epaint.svg)](https://crates.io/crates/epaint)
[![Documentation](https://docs.rs/epaint/badge.svg)](https://docs.rs/epaint)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
A bare-bones 2D graphics library for turning simple 2D shapes and text into textured triangles.
Made for [`egui`](https://github.com/emilk/egui/).

View file

@ -6,6 +6,7 @@ use emath::*;
/// Should be friendly to send to GPU as is.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg(not(feature = "unity"))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct Vertex {
@ -22,6 +23,25 @@ pub struct Vertex {
pub color: Color32, // 32 bit
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg(feature = "unity")]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct Vertex {
/// Logical pixel coordinates (points).
/// (0,0) is the top left corner of the screen.
pub pos: Pos2, // 64 bit
/// sRGBA with premultiplied alpha
pub color: Color32, // 32 bit
/// Normalized texture coordinates.
/// (0, 0) is the top left corner of the texture.
/// (1, 1) is the bottom right corner of the texture.
pub uv: Pos2, // 64 bit
}
/// Textured triangles in two dimensions.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]

View file

@ -973,12 +973,16 @@ pub struct Tessellator {
pixels_per_point: f32,
options: TessellationOptions,
font_tex_size: [usize; 2],
/// See [`TextureAtlas::prepared_discs`].
prepared_discs: Vec<PreparedDisc>,
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
feathering: f32,
/// Only used for culling
clip_rect: Rect,
scratchpad_points: Vec<Pos2>,
scratchpad_path: Path,
}

View file

@ -33,6 +33,10 @@ impl UvRect {
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GlyphInfo {
/// Used for pair-kerning.
///
/// Doesn't need to be unique.
/// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care.
pub(crate) id: ab_glyph::GlyphId,
/// Unit: points.

View file

@ -24,8 +24,9 @@ ignore = [
multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = [
{ name = "openssl" }, # prefer rustls
{ name = "cmake" }, # Lord no
{ name = "openssl-sys" }, # prefer rustls
{ name = "openssl" }, # prefer rustls
]
skip = [
@ -46,7 +47,6 @@ skip-tree = [
{ name = "darling" }, # old version via tts
{ name = "foreign-types" }, # old version from wgpu
{ name = "rfd" }, # example dependency
{ name = "three-d" }, # example dependency
]

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -4,3 +4,7 @@ All the examples in this folder uses [`eframe`](https://github.com/emilk/egui/tr
There are a lot more examples at <https://www.egui.rs>, and it has links to the source code of each example.
Also check out the official docs at <https://docs.rs/egui> and <https://docs.rs/eframe>.
Note that all the examples on `master` are for the latest `master` version of `egui`.
If you want to look for examples for a specific version of egui, go to that tag, e.g. <https://github.com/emilk/egui/tree/latest/examples>.

View file

@ -10,5 +10,5 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }

View file

@ -10,7 +10,7 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
egui_glow = { path = "../../crates/egui_glow" }
glow = "0.11"
glow = "0.12"

View file

@ -9,7 +9,7 @@ use std::sync::Arc;
fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(350.0, 380.0)),
multisampling: 8,
multisampling: 4,
renderer: eframe::Renderer::Glow,
..Default::default()
};

View file

@ -1,24 +0,0 @@
[package]
name = "custom_3d_three-d"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
] }
egui_glow = { path = "../../crates/egui_glow" }
glow = "0.11"
three-d = { version = "0.13", default-features = false }
[target.'cfg(target_arch = "wasm32")'.dependencies] # Web dependencies
console_error_panic_hook = "0.1" # For logging
wasm-bindgen = "0.2" # Core bindings
wasm-bindgen-futures = "0.4" # Core bindings

View file

@ -1,22 +0,0 @@
This demo shows how to embed 3D rendering using [`three-d`](https://github.com/asny/three-d) in `eframe`.
Any 3D library built on top of [`glow`](https://github.com/grovesNL/glow) can be used in `eframe`.
Alternatively you can render 3D stuff to a texture and display it using [`egui::Ui::image`].
If you are content of having egui sit on top of a 3D background, take a look at:
* [`bevy_egui`](https://github.com/mvlabat/bevy_egui)
* [`three-d`](https://github.com/asny/three-d)
```sh
cargo run -p custom_3d_three-d
```
```
wasm-pack build examples/custom_3d_three-d --target web
```
![](screenshot.png)

View file

@ -1,38 +0,0 @@
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<canvas id="my" style="position: absolute;top:0;bottom: 0;left: 0;right: 0;margin:auto;"></canvas>
<!-- Note the usage of `type=module` here as this is an ES6 module -->
<script type="module">
// Use ES module import syntax to import functionality from the module
// that we have compiled.
//
// Note that the `default` import is an initialization function which
// will "boot" the module and make it ready to use. Currently browsers
// don't support natively imported WebAssembly as an ES module, but
// eventually the manual initialization won't be required!
import init from './pkg/custom_3d_three_d.js';
async function run() {
// First up we need to actually load the wasm file, so we use the
// default export to inform it where the wasm file is located on the
// server, and then we wait on the returned promise to wait for the
// wasm to be loaded.
// It may look like this: `await init('./pkg/without_a_bundler_bg.wasm');`,
// but there is also a handy default inside `init` function, which uses
// `import.meta` to locate the wasm file relatively to js file
//
// Note that instead of a string here you can also pass in an instance
// of `WebAssembly.Module` which allows you to compile your own module.
// Also note that the promise, when resolved, yields the wasm module's
// exports which is the same as importing the `*_bg` module in other
// modes
await init('./pkg/custom_3d_three_d_bg.wasm');
}
run();
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View file

@ -1,21 +0,0 @@
#![allow(special_module_name)]
mod main;
// Entry point for wasm
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(start)]
pub async fn start() -> Result<(), JsValue> {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
let web_options = eframe::WebOptions::default();
eframe::start_web(
"my",
web_options,
Box::new(|cc| Box::new(main::MyApp::new(cc))),
)?;
Ok(())
}

View file

@ -1,231 +0,0 @@
#![allow(dead_code)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
#[cfg(not(target_arch = "wasm32"))]
fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(550.0, 610.0)),
multisampling: 8,
renderer: eframe::Renderer::Glow,
depth_buffer: 24,
..Default::default()
};
eframe::run_native(
"Custom 3D painting in eframe!",
options,
Box::new(|cc| Box::new(MyApp::new(cc))),
)
}
pub struct MyApp {
angle: f32,
}
impl MyApp {
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
Self { angle: 0.2 }
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
egui::widgets::global_dark_light_mode_buttons(ui);
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using ");
ui.hyperlink_to("three-d", "https://github.com/asny/three-d");
ui.label(".");
});
egui::ScrollArea::both().show(ui, |ui| {
egui::Frame::canvas(ui.style()).show(ui, |ui| {
let (rect, response) =
ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag());
self.angle += response.drag_delta().x * 0.01;
// Clone locals so we can move them into the paint callback:
let angle = self.angle;
let callback = egui::PaintCallback {
rect,
callback: std::sync::Arc::new(egui_glow::CallbackFn::new(
move |info, painter| {
with_three_d(painter.gl(), |three_d| {
three_d.frame(
FrameInput::new(&three_d.context, &info, painter),
angle,
);
});
},
)),
};
ui.painter().add(callback);
});
ui.label("Drag to rotate!");
});
});
}
}
/// We get a [`glow::Context`] from `eframe` and we want to construct a [`ThreeDApp`].
///
/// Sadly we can't just create a [`ThreeDApp`] in [`MyApp::new`] and pass it
/// to the [`egui::PaintCallback`] because [`glow::Context`] isn't `Send+Sync` on web, which
/// [`egui::PaintCallback`] needs. If you do not target web, then you can construct the [`ThreeDApp`] in [`MyApp::new`].
fn with_three_d<R>(gl: &std::sync::Arc<glow::Context>, f: impl FnOnce(&mut ThreeDApp) -> R) -> R {
use std::cell::RefCell;
thread_local! {
pub static THREE_D: RefCell<Option<ThreeDApp>> = RefCell::new(None);
}
THREE_D.with(|three_d| {
let mut three_d = three_d.borrow_mut();
let three_d = three_d.get_or_insert_with(|| ThreeDApp::new(gl.clone()));
f(three_d)
})
}
///
/// Translates from egui input to three-d input
///
pub struct FrameInput<'a> {
screen: three_d::RenderTarget<'a>,
viewport: three_d::Viewport,
scissor_box: three_d::ScissorBox,
}
impl FrameInput<'_> {
pub fn new(
context: &three_d::Context,
info: &egui::PaintCallbackInfo,
painter: &egui_glow::Painter,
) -> Self {
use three_d::*;
// Disable sRGB textures for three-d
#[cfg(not(target_arch = "wasm32"))]
#[allow(unsafe_code)]
unsafe {
use glow::HasContext as _;
context.disable(glow::FRAMEBUFFER_SRGB);
}
// Constructs a screen render target to render the final image to
let screen = painter.intermediate_fbo().map_or_else(
|| {
RenderTarget::screen(
context,
info.viewport.width() as u32,
info.viewport.height() as u32,
)
},
|fbo| {
RenderTarget::from_framebuffer(
context,
info.viewport.width() as u32,
info.viewport.height() as u32,
fbo,
)
},
);
// Set where to paint
let viewport = info.viewport_in_pixels();
let viewport = Viewport {
x: viewport.left_px.round() as _,
y: viewport.from_bottom_px.round() as _,
width: viewport.width_px.round() as _,
height: viewport.height_px.round() as _,
};
// Respect the egui clip region (e.g. if we are inside an `egui::ScrollArea`).
let clip_rect = info.clip_rect_in_pixels();
let scissor_box = ScissorBox {
x: clip_rect.left_px.round() as _,
y: clip_rect.from_bottom_px.round() as _,
width: clip_rect.width_px.round() as _,
height: clip_rect.height_px.round() as _,
};
Self {
screen,
scissor_box,
viewport,
}
}
}
///
/// Based on the `three-d` [Triangle example](https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs).
/// This is where you'll need to customize
///
use three_d::*;
pub struct ThreeDApp {
context: Context,
camera: Camera,
model: Gm<Mesh, ColorMaterial>,
}
impl ThreeDApp {
pub fn new(gl: std::sync::Arc<glow::Context>) -> Self {
let context = Context::from_gl_context(gl).unwrap();
// Create a camera
let camera = Camera::new_perspective(
Viewport::new_at_origo(1, 1),
vec3(0.0, 0.0, 2.0),
vec3(0.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
degrees(45.0),
0.1,
10.0,
);
// Create a CPU-side mesh consisting of a single colored triangle
let positions = vec![
vec3(0.5, -0.5, 0.0), // bottom right
vec3(-0.5, -0.5, 0.0), // bottom left
vec3(0.0, 0.5, 0.0), // top
];
let colors = vec![
Color::new(255, 0, 0, 255), // bottom right
Color::new(0, 255, 0, 255), // bottom left
Color::new(0, 0, 255, 255), // top
];
let cpu_mesh = CpuMesh {
positions: Positions::F32(positions),
colors: Some(colors),
..Default::default()
};
// Construct a model, with a default color material, thereby transferring the mesh data to the GPU
let model = Gm::new(Mesh::new(&context, &cpu_mesh), ColorMaterial::default());
Self {
context,
camera,
model,
}
}
pub fn frame(&mut self, frame_input: FrameInput<'_>, angle: f32) -> Option<glow::Framebuffer> {
// Ensure the viewport matches the current window viewport which changes if the window is resized
self.camera.set_viewport(frame_input.viewport);
// Set the current transformation of the triangle
self.model
.set_transformation(Mat4::from_angle_y(radians(angle)));
// Get the screen render target to be able to render something on the screen
frame_input
.screen
// Clear the color and depth of the screen render target
.clear_partially(frame_input.scissor_box, ClearState::depth(1.0))
// Render the triangle with the color material which uses the per vertex colors defined at construction
.render_partially(frame_input.scissor_box, &self.camera, &[&self.model], &[]);
frame_input.screen.into_framebuffer() // Take back the screen fbo, we will continue to use it.
}
}

View file

@ -10,5 +10,5 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }

View file

@ -10,5 +10,5 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }

View file

@ -10,5 +10,5 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -10,8 +10,8 @@ fn main() -> Result<(), eframe::Error> {
decorated: false,
// To have rounded corners we need transparency:
transparent: true,
min_window_size: Some(egui::vec2(320.0, 100.0)),
initial_window_size: Some(egui::vec2(320.0, 240.0)),
min_window_size: Some(egui::vec2(400.0, 100.0)),
initial_window_size: Some(egui::vec2(400.0, 240.0)),
..Default::default()
};
eframe::run_native(
@ -31,7 +31,7 @@ impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
custom_window_frame(ctx, frame, "egui with custom frame", |ui| {
ui.label("This is just the contents of the window");
ui.label("This is just the contents of the window.");
ui.horizontal(|ui| {
ui.label("egui theme:");
egui::widgets::global_dark_light_mode_buttons(ui);
@ -47,72 +47,118 @@ fn custom_window_frame(
add_contents: impl FnOnce(&mut egui::Ui),
) {
use egui::*;
let text_color = ctx.style().visuals.text_color();
// Height of the title bar
let height = 28.0;
let panel_frame = egui::Frame {
fill: ctx.style().visuals.window_fill(),
rounding: 10.0.into(),
stroke: ctx.style().visuals.widgets.noninteractive.fg_stroke,
outer_margin: 0.5.into(), // so the stroke is within the bounds
..Default::default()
};
CentralPanel::default()
.frame(Frame::none())
.show(ctx, |ui| {
let rect = ui.max_rect();
let painter = ui.painter();
CentralPanel::default().frame(panel_frame).show(ctx, |ui| {
let app_rect = ui.max_rect();
// Paint the frame:
painter.rect(
rect.shrink(1.0),
10.0,
ctx.style().visuals.window_fill(),
Stroke::new(1.0, text_color),
);
let title_bar_height = 32.0;
let title_bar_rect = {
let mut rect = app_rect;
rect.max.y = rect.min.y + title_bar_height;
rect
};
title_bar_ui(ui, frame, title_bar_rect, title);
// Paint the title:
painter.text(
rect.center_top() + vec2(0.0, height / 2.0),
Align2::CENTER_CENTER,
title,
FontId::proportional(height * 0.8),
text_color,
);
// Paint the line under the title:
painter.line_segment(
[
rect.left_top() + vec2(2.0, height),
rect.right_top() + vec2(-2.0, height),
],
Stroke::new(1.0, text_color),
);
// Interact with the title bar (drag to move window):
let title_bar_rect = {
let mut rect = rect;
rect.max.y = rect.min.y + height;
rect
};
let title_bar_response =
ui.interact(title_bar_rect, Id::new("title_bar"), Sense::click());
if title_bar_response.is_pointer_button_down_on() {
frame.drag_window();
}
// Add the close button:
let close_response = ui.put(
Rect::from_min_size(rect.left_top(), Vec2::splat(height)),
Button::new(RichText::new("").size(height - 4.0)).frame(false),
);
if close_response.clicked() {
frame.close();
}
// Add the contents:
let content_rect = {
let mut rect = rect;
rect.min.y = title_bar_rect.max.y;
rect
}
.shrink(4.0);
let mut content_ui = ui.child_ui(content_rect, *ui.layout());
add_contents(&mut content_ui);
});
// Add the contents:
let content_rect = {
let mut rect = app_rect;
rect.min.y = title_bar_rect.max.y;
rect
}
.shrink(4.0);
let mut content_ui = ui.child_ui(content_rect, *ui.layout());
add_contents(&mut content_ui);
});
}
fn title_bar_ui(
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
title_bar_rect: eframe::epaint::Rect,
title: &str,
) {
use egui::*;
let painter = ui.painter();
let title_bar_response = ui.interact(title_bar_rect, Id::new("title_bar"), Sense::click());
// Paint the title:
painter.text(
title_bar_rect.center(),
Align2::CENTER_CENTER,
title,
FontId::proportional(20.0),
ui.style().visuals.text_color(),
);
// Paint the line under the title:
painter.line_segment(
[
title_bar_rect.left_bottom() + vec2(1.0, 0.0),
title_bar_rect.right_bottom() + vec2(-1.0, 0.0),
],
ui.visuals().widgets.noninteractive.bg_stroke,
);
// Interact with the title bar (drag to move window):
if title_bar_response.double_clicked() {
frame.set_maximized(!frame.info().window_info.maximized);
} else if title_bar_response.is_pointer_button_down_on() {
frame.drag_window();
}
ui.allocate_ui_at_rect(title_bar_rect, |ui| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.visuals_mut().button_frame = false;
ui.add_space(8.0);
close_maximize_minimize(ui, frame);
});
});
}
/// Show some close/maximize/minimize buttons for the native window.
fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) {
use egui::{Button, RichText};
let button_height = 12.0;
let close_response = ui
.add(Button::new(RichText::new("").size(button_height)))
.on_hover_text("Close the window");
if close_response.clicked() {
frame.close();
}
if frame.info().window_info.maximized {
let maximized_response = ui
.add(Button::new(RichText::new("🗗").size(button_height)))
.on_hover_text("Restore window");
if maximized_response.clicked() {
frame.set_maximized(false);
}
} else {
let maximized_response = ui
.add(Button::new(RichText::new("🗗").size(button_height)))
.on_hover_text("Maximize window");
if maximized_response.clicked() {
frame.set_maximized(true);
}
}
let minimized_response = ui
.add(Button::new(RichText::new("🗕").size(button_height)))
.on_hover_text("Minimize the window");
if minimized_response.clicked() {
frame.set_minimized(true);
}
}

View file

@ -10,7 +10,7 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
egui_extras = { path = "../../crates/egui_extras", features = ["image"] }
ehttp = "0.2"

View file

@ -10,6 +10,6 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
rfd = "0.10"
rfd = "0.11"

View file

@ -10,6 +10,6 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
tracing-subscriber = "0.3"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View file

@ -10,6 +10,6 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
tracing-subscriber = "0.3"

View file

@ -2,6 +2,7 @@
use eframe::egui;
use egui::*;
fn main() -> Result<(), eframe::Error> {
// Log to stdout (if you run with `RUST_LOG=debug`).
tracing_subscriber::fmt::init();

View file

@ -10,7 +10,8 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"puffin",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
puffin = "0.14"
puffin_http = "0.11"

View file

@ -10,7 +10,7 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
egui_extras = { path = "../../crates/egui_extras", features = ["image"] }
image = { version = "0.24", default-features = false, features = ["png"] }

View file

@ -10,6 +10,6 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
itertools = "0.10.3"

View file

@ -10,5 +10,5 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }

View file

@ -10,6 +10,6 @@ publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
egui_extras = { path = "../../crates/egui_extras", features = ["svg"] }

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