Compare commits

...

1444 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
tlhenvironment
fb5cb3052d
Fix typo in docstring (#2643)
Tiny typo fix <3
2023-02-04 13:53:20 +01:00
itfanr
9c270448a6
Add link to tauri-egui in README.md
add a 3rd party integration: tauri-egui for tauri
2023-02-04 13:52:47 +01:00
lictex_
d15f4ef992
select all text when dragvalue gets focus (#2650) 2023-02-04 13:46:16 +01:00
Emil Ernerfeldt
21a59143a4
cargo update (#2671)
* cargo update

    Updating crates.io index
    Updating ab_glyph v0.2.18 -> v0.2.20
    Updating ab_glyph_rasterizer v0.1.7 -> v0.1.8
    Updating ahash v0.8.2 -> v0.8.3
    Updating anyhow v1.0.66 -> v1.0.68
    Updating async-broadcast v0.4.1 -> v0.5.0
    Updating async-recursion v1.0.0 -> v1.0.2
    Updating async-trait v0.1.59 -> v0.1.64
    Updating atomic_refcell v0.1.8 -> v0.1.9
    Updating bumpalo v3.11.1 -> v3.12.0
    Updating bytemuck v1.12.3 -> v1.13.0
    Updating bytemuck_derive v1.3.0 -> v1.4.0
    Updating bytes v1.3.0 -> v1.4.0
    Updating calloop v0.10.3 -> v0.10.5
    Updating cc v1.0.77 -> v1.0.79
    Removing chunked_transfer v1.4.0
    Updating clipboard-win v4.4.2 -> v4.5.0
    Updating concurrent-queue v2.0.0 -> v2.1.0
    Updating cxx v1.0.83 -> v1.0.89
    Updating cxx-build v1.0.83 -> v1.0.89
    Updating cxxbridge-flags v1.0.83 -> v1.0.89
    Updating cxxbridge-macro v1.0.83 -> v1.0.89
    Updating document-features v0.2.6 -> v0.2.7
    Updating dyn-clone v1.0.9 -> v1.0.10
    Updating either v1.8.0 -> v1.8.1
    Updating enum-map v2.4.1 -> v2.4.2
    Updating enum-map-derive v0.10.0 -> v0.11.0
    Updating futures-core v0.3.25 -> v0.3.26
    Updating futures-io v0.3.25 -> v0.3.26
    Updating futures-sink v0.3.25 -> v0.3.26
    Updating futures-task v0.3.25 -> v0.3.26
    Updating futures-util v0.3.25 -> v0.3.26
    Updating glob v0.3.0 -> v0.3.1
    Updating heck v0.4.0 -> v0.4.1
    Updating image v0.24.4 -> v0.24.5
    Updating itoa v1.0.4 -> v1.0.5
    Updating jpeg-decoder v0.2.6 -> v0.3.0
    Updating js-sys v0.3.60 -> v0.3.61
    Updating libc v0.2.138 -> v0.2.139
    Updating link-cplusplus v1.0.7 -> v1.0.8
    Updating nom v7.1.1 -> v7.1.3
      Adding nom8 v0.2.0
    Updating num_enum v0.5.7 -> v0.5.9
    Updating num_enum_derive v0.5.7 -> v0.5.9
    Updating once_cell v1.16.0 -> v1.17.0
    Updating ordered-stream v0.1.2 -> v0.1.4
    Updating owned_ttf_parser v0.17.1 -> v0.18.1
    Updating parking_lot_core v0.9.5 -> v0.9.7
    Updating paste v1.0.9 -> v1.0.11
    Updating plist v1.3.1 -> v1.4.0
    Updating polling v2.5.1 -> v2.5.2
    Updating proc-macro-crate v1.2.1 -> v1.3.0
    Updating proc-macro2 v1.0.47 -> v1.0.50
    Updating puffin v0.14.0 -> v0.14.2
    Updating puffin_http v0.11.0 -> v0.11.1
      Adding quick-xml v0.26.0
    Updating quote v1.0.21 -> v1.0.23
    Updating regex v1.7.0 -> v1.7.1
    Updating rustls v0.20.7 -> v0.20.8
    Updating ryu v1.0.11 -> v1.0.12
    Updating scratch v1.0.2 -> v1.0.3
    Updating serde v1.0.149 -> v1.0.152
    Updating serde_derive v1.0.149 -> v1.0.152
    Updating serde_json v1.0.89 -> v1.0.91
    Updating serde_repr v0.1.9 -> v0.1.10
    Updating syn v1.0.105 -> v1.0.107
    Updating termcolor v1.1.3 -> v1.2.0
    Updating thiserror v1.0.37 -> v1.0.38
    Updating thiserror-impl v1.0.37 -> v1.0.38
    Updating tinyvec_macros v0.1.0 -> v0.1.1
    Updating toml v0.5.9 -> v0.5.11
      Adding toml_datetime v0.5.1
      Adding toml_edit v0.18.1
    Updating ttf-parser v0.17.1 -> v0.18.1
    Updating typenum v1.15.0 -> v1.16.0
    Updating unicode-bidi v0.3.8 -> v0.3.10
    Updating unicode-ident v1.0.5 -> v1.0.6
    Updating ureq v2.5.0 -> v2.6.2
    Updating wasm-bindgen v0.2.83 -> v0.2.84
    Updating wasm-bindgen-backend v0.2.83 -> v0.2.84
    Updating wasm-bindgen-futures v0.4.33 -> v0.4.34
    Updating wasm-bindgen-macro v0.2.83 -> v0.2.84
    Updating wasm-bindgen-macro-support v0.2.83 -> v0.2.84
    Updating wasm-bindgen-shared v0.2.83 -> v0.2.84
    Updating wayland-sys v0.30.0 -> v0.30.1
    Updating web-sys v0.3.60 -> v0.3.61
    Updating webbrowser v0.8.6 -> v0.8.7
    Updating webpki-roots v0.22.5 -> v0.22.6
    Updating which v4.3.0 -> v4.4.0
    Updating x11-dl v2.20.1 -> v2.21.0
    Updating zbus v3.6.2 -> v3.8.0
    Updating zbus_macros v3.6.2 -> v3.8.0
    Updating zbus_names v2.4.0 -> v2.5.0
    Updating zstd-sys v2.0.4+zstd.1.5.2 -> v2.0.6+zstd.1.5.2
    Updating zvariant v3.9.0 -> v3.10.0
    Updating zvariant_derive v3.9.0 -> v3.10.0

* Remove unnecessary import of wasm_bindgen::JsCast (its now in prelude)

* egui_glow/README.md: add line on how to run the example

* revert wasm-bindgen update

* Revert "Remove unnecessary import of wasm_bindgen::JsCast (its now in prelude)"

This reverts commit 95c3076cce76577d9f0f35e48f99b4acd2dbe62e.
2023-02-04 13:41:34 +01:00
Emil Ernerfeldt
06d753c289
Update SVG crates (#2670)
* Update resvg, tiny-skia and usv to latest

* Remove default features from svg crates

Users can always opt-in to them themselves

* Update png 0.17.6 -> 0.17.7
2023-02-04 13:00:04 +01:00
Emil Ernerfeldt
8344e88f8a
Update to to winit 0.28 (#2654)
* Update to winit 0.28

Mac trackpads pinch gestures will now generate `egui::Event::Zoom`

* Update accesskit_winit

* Try to get Android CI green

* Fix wayland compilation

* Add comment about android-activity

* Update changelogs

* Fix call to register_xlib_error_hook
2023-02-04 12:43:43 +01:00
Emil Ernerfeldt
e4eaf99072
Remove native screen reader support (#2669)
* Remove native screen reader support

Use the "accesskit" feature flag to `eframe` instead.
[AccessKit](https://github.com/AccessKit/accesskit) is the future.

`tts` was a mess to compile on many platforms, so I no longer want
the native `tts` dependency.

* Update tts to 0.25

* Update changelogs

* Turn on all feature flags for package.metadata.docs.rs

* remove tts from deny.toml skip-tree

* Update web build scripts

* Update deny.toml
2023-02-04 11:47:36 +01:00
Emil Ernerfeldt
1353a5733f
Deprecate egui_glium - looking for new maintainer (#2668)
* Deprecate egui_glium - looking for new maintainer

egui_glium was the first backend of egui, and it served us well for
a long time, but we have long since moved on to glow and wgpu.

Not egui_glium is holding back an update to latest winit.

Since development on glium has long since been discontinued I will
therefore deprecate egui_glium with this PR.

The code is still there in the repository for a while longer,
but is no longer compiled.

If there is any interest in maintaining egui_glium, then fork it and
make a PR to remove the last egui_glium from this repository.
I will give you publish rights on crates.io.

* update glutin 0.30.2 -> 0.30.3

* cargo update -p backtrace

    Updating crates.io index
    Updating addr2line v0.17.0 -> v0.19.0
    Updating backtrace v0.3.66 -> v0.3.67
    Updating gimli v0.26.2 -> v0.27.1
    Removing miniz_oxide v0.5.4
    Updating object v0.29.0 -> v0.30.3

* cargo deny: allow duplicates of windows-sys, wayland-sys, and nix

* cargo-deny whitelist tiny-skia
2023-02-04 11:21:02 +01:00
Andreas Reich
8aa07e9d43
Clear color values are now explicitely sent to the rendering backend as-is. (#2666)
* Clear color values are not explicitely sent to the rendering backend as-is.
Previously, converting from Color32 to Rgba caused an srgb->linear conversion. This conversion is incorrect if the backbuffer doesn't perform automatic conversion from linear->srgb (lack of this conversion is generally what egui assumes!).

* fill in pr numbers in changelog

* Epi comment fix

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

* Color32 comment fix

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

* move changelog line

* rename fix

* use backticks in doc

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2023-02-04 11:02:15 +01:00
Emil Ernerfeldt
a6b60d5d58
Control Separator widths, and less clipping in ScrollArea (#2665)
* Add Separator::grow and Separator::shrink

* Be more conservative with the clipping in ScrollArea:s

* Add test of the growing separator

* Improve test output

* Update changelog

* Add back a little bit more clipping

* Make the minimum scroll handle length a bit longer

* More clip rect tweaks
2023-02-03 13:19:25 +01:00
John Hughes
8c59888ebd
Add simple Windows CI workflow check (#2663)
* Add simple Windows CI workflow check

* Fix Windows build in CI

* Windows fix
2023-02-03 13:19:12 +01:00
NemuiSen
5725868b57
ggez-egui was renamed to ggegui (#2662) 2023-02-03 10:59:28 +01:00
Emil Ernerfeldt
8c3d8b3ba5 Make sure the panel resize lines are visible when hovered/resizing 2023-02-03 09:31:24 +01:00
Emil Ernerfeldt
312cab5355 Export egui::Margin 2023-02-03 09:31:04 +01:00
Emil Ernerfeldt
5444ab269a Add Response::with_new_rect 2023-02-02 21:12:06 +01:00
Emil Ernerfeldt
d01e4342f0
Update accesskit_winit and dark_light (#2655)
* Update accesskit_winit to 0.8.1

* Update dark_light to 1.0
2023-01-31 10:41:27 +01:00
Emil Ernerfeldt
cc20dcb9d0
eframe: Improve window centering (#2651)
* Create the winit::Window in one unified place

* Fix wrong unit of initial_window_pos

* Improve centering
2023-01-30 15:52:30 +01:00
Emil Ernerfeldt
f222ee044e Fix for highlighting SelectableLabel 2023-01-28 00:21:53 +01:00
Emil Ernerfeldt
c72bdb77b5
Add ability to highlight any widget (#2632)
* Add ability to highlight any widget

* Add line to changelog

* Demote the demo to a test
2023-01-27 23:36:14 +01:00
Emil Ernerfeldt
e7c0547e23
DragValue and Slider text is now proportional instead of monospace (#2638)
* DragValue and Slider text is now proportional instead of monospace

Control with `Style::drag_value_text_style`

* Update changelog
2023-01-27 23:30:20 +01:00
Emil Ernerfeldt
fe7ff66266
Make the line left of indented regions optional (#2636)
* Make the line left of indented regions optional

Controlled with Visuals::indent_has_left_vline

* Add line to changelog

* Fix doclink
2023-01-27 11:12:08 +01:00
Red Artist
ce62b61e15
wgpu upgraded to 0.15. demo working on linux (#2629) 2023-01-27 09:19:47 +01:00
Emil Ernerfeldt
4a0bafbeab
Update webbrowser (#2631)
* Update webbrowser

* Update webbrowser in Cargo.toml too

Make sure all users get updated too
2023-01-26 11:31:39 +01:00
Emil Ernerfeldt
5b1cad2b72 Constrain menus to the screen 2023-01-26 11:13:21 +01:00
Emil Ernerfeldt
8ce0e1c520
Avoid deadlocks by using lambdas for context lock (#2625)
ctx.input().key_pressed(Key::A) -> ctx.input(|i| i.key_pressed(Key::A))
2023-01-25 10:24:23 +01:00
lictex_
eee4cf6a82
add functions to check which button triggered a drag start & end (#2507)
* add button release events for drags

* add utility functions

* fix CHANGELOG.md

* fix CHANGELOG.md

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2023-01-24 10:11:32 +01:00
Emil Ernerfeldt
4bd4eca2e4
Add ability to hide button backgrounds (#2621)
* Add Spacing::combo_width

* Put ComboBox arrow closer to the text

* Tweak faint_bg_color

* Make it possible to have buttons without background

…while still having background for sliders, checkboxes, etc

* Rename mandatory_bg_fill -> bg_fill

* tweak grid stripe color (again)

* Make the animated part of the ProgressBar more visible

* Add line in changelog

* Add another line in changelog

* Menu fix: use the `open` widget style for open menus

* Adjust sizes on menu buttons and regular buttons to make sure they match

* Update comment

Co-authored-by: Andreas Reich <andreas@rerun.io>

* optional_bg_fill -> weak_bg_fill

Co-authored-by: Andreas Reich <andreas@rerun.io>
2023-01-24 10:11:05 +01:00
Emil Ernerfeldt
c75e72693c Fix rendering of (ellipsis)
Broken when we introduced thing space support
2023-01-23 20:24:38 +01:00
Pâris DOUADY
518b4f447e
Allow changing ProgressBar fill color (#2618) 2023-01-23 15:33:02 +01:00
Emil Ernerfeldt
d4f9f6984d Fix red doctest 2023-01-23 15:04:35 +01:00
Weasy
356ebe55da
Add rounding fn to Button, to enable rounded buttons (#2616) 2023-01-23 12:37:39 +01:00
LEAVING
5029575ed0
Fix typo: 'Viewport width' -> 'Viewport height' (#2615) 2023-01-23 12:37:26 +01:00
Timon
30e49f1da2
Expose area interactable and movable to Window api. (#2610)
* Expose area interactable to window.

* Add movable function

* update dockstring
2023-01-23 12:37:15 +01:00
lictex_
01bbda4544
check point count before tessellating bezier (#2506) 2023-01-23 12:20:05 +01:00
Ales Tsurko
f87c6cbd7c
Derive Hash for KeyboardShortcut and Modifiers (#2563) 2023-01-23 10:06:54 +01:00
RadonCoding
ce5472633d
Fix close button not working (#2533)
* Fix close button not working

By adding the close button after the title bar drag listener the close button will sense clicks.

* Update main.rs
2023-01-23 09:55:57 +01:00
Robert Walter
0ad8aea811
Fix: button_padding when using image+text buttons (#2510)
* feat(image-button-margin): implement image button margin

- add `image_margin` field on `Button` widget
- implement setter method called `image_margin` for `Button` widget
- use margin from `image_margin` field of `Button` widget in `Widget`
  trait functions

* feat(image-button-margin): update changelog

* feat(image-button-margin): implement `map_or` clippy fix

* feat(image-button-margin): remove margin field & fix button-padding instead

* feat(image-button-margin): fix CI errors

* feat(image-button-margin): update changelog to include fix

* feat(image-button-margin): re-add changes after creating screenshots for PR
2023-01-23 09:23:57 +01:00
Fangdun Tsai
53b1d0e5e9
Add menu with an image button (#2488) 2023-01-23 08:22:43 +01:00
apoorv569
0eabd894bd
Fix typo in cargo run command. (#2582)
I think someone by mistake wrote `cargo run -p hello_world` instead of `cargo run -p keyboard_events`.
2023-01-17 10:58:38 +01:00
Emil Ernerfeldt
cd0f66b9ae
eframe web: ctrl-P and cmd-P will not open the print dialog (#2598) 2023-01-16 14:40:19 +01:00
Andreas Reich
60b4f5e3fe
changelog & doc fix for #2539 (Window::default_open) 2023-01-07 20:51:01 +01:00
joffreybesos
f7ed135192
window starting collapsed state (#2539) 2023-01-07 20:48:49 +01:00
Kornel
e70204d726
Point to caller's location when using log_or_panic (#2552) 2023-01-07 20:27:19 +01:00
Emil Ernerfeldt
34f587d1e1 Add emath::inverse_lerp 2022-12-22 12:33:06 +01:00
Emil Ernerfeldt
fa0d7f7f7f Add Response::on_hover_and_drag_cursor 2022-12-21 17:47:52 +01:00
Emil Ernerfeldt
a68c891092 Improve choice of number of decimals to show when hovering in plot 2022-12-20 15:27:01 +01:00
Emil Ernerfeldt
0e2656b77c Add ScrollArea::drag_to_scroll 2022-12-20 11:09:53 +01:00
lucasmerlin
80ea12877e
Add egui_skia integration to readme (#2483)
* Add egui_skia integration to readme

* sort

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-12-19 12:35:42 +01:00
Emil Ernerfeldt
99af63fad2
Add Plot::clamp_grid (#2480)
* Add Plot::clamp_grid

* Update changelog
2022-12-19 11:39:22 +01:00
Emil Ernerfeldt
e4e1638fc0 Fix newly introduced rendering bug for thin rectangles 2022-12-19 11:31:27 +01:00
Sven Niederberger
7e9c7dac41
Improve plot grid appearance (#2412)
* improve plot grid appearance

* update changelog

* Update crates/egui/src/widgets/plot/mod.rs

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

* address review comments

* make lines a bit weaker

* move changelog entry

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-12-19 10:57:30 +01:00
Ivan
f9728b88db
Combobox .wrap(true) width usage (#2472)
* Combobox .wrap(true) width fix

.wrap(true) does note use all the available width
this fix does not change the original .wrap(false) behaviours

* Code comment convention

Co-authored-by: IVANMK-7 <68190772+IVANMK-7@users.noreply.github.com>
2022-12-19 10:55:39 +01:00
francesco-cattoglio
2c9b130423
Add Memory::any_popup_open() (#2464)
* Added the "any_popup_open()" function

* Updated CHANGELOG.md

* add PR link to changelog

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-12-19 10:55:27 +01:00
Emil Ernerfeldt
6554fbb151 epaint: Improve rendering of very thin rectangles 2022-12-19 10:33:03 +01:00
Emil Ernerfeldt
6ae4bc486b Add Slider::drag_value_speed 2022-12-15 17:05:43 +01:00
Emil Ernerfeldt
367087f84f Annotate at_least and at_most with #[must_use] 2022-12-15 15:38:31 +01:00
Emil Ernerfeldt
ea5c9483a2
Fix bug in Mesh::split_to_u16 (#2459)
Co-authored-by: Vladislav Zhukov <vlad.zhukov@elektron.se>
2022-12-15 11:13:01 +01:00
Emil Ernerfeldt
37fd141dd1 Rename eframe::EframeError to eframe::Error 2022-12-14 17:29:54 +01:00
Emil Ernerfeldt
4e8341d35c
Don't render the \r (Carriage Return) character, because it sucks (#2452)
* Don't render the \r (Carriage Return) character, because it sucks

* Update PR links

* Fix doclink
2022-12-14 15:15:29 +01:00
Emil Ernerfeldt
df9df39f10 eframe: set_window_size, set_fullscreen and set_window_pos updates info 2022-12-14 14:14:20 +01:00
Emil Ernerfeldt
a70b173333 Toggle fullscreen in egui_demo_app with F11 2022-12-14 14:13:52 +01:00
Matt Campbell
a925511032
Expose the TextEdit multiline flag to AccessKit (#2448)
* Expose the TextEdit multiline flag to AccessKit

* Add changelog entry
2022-12-14 08:37:51 +01:00
Emil Ernerfeldt
126be51ac3 Add Visuals::striped as global default for Grids and Tables 2022-12-13 21:02:39 +01:00
Emil Ernerfeldt
51081d69fc
Maybe fix WSLg (#2439) 2022-12-13 16:06:19 +01:00
Emil Ernerfeldt
0ba2d8ca1d Add Visuals::menu_rounding 2022-12-13 15:44:27 +01:00
Emil Ernerfeldt
4a72abc8ec
Clamp eframe window size to at most the size of the largest monitor (#2445)
This can hopefully fix some reported bugs where
2022-12-13 09:09:36 +01:00
Emil Ernerfeldt
32677a54e4 egui-winit: improve clipboard logging 2022-12-13 09:09:25 +01:00
Emil Ernerfeldt
26eb002270 Remove references to old, fixed Firefox WebGL bug 2022-12-12 21:45:57 +01:00
Emil Ernerfeldt
c58ac86935
egui-wgpu: only depend on epaint (not entire egui) (#2438)
* egui-wgpu: only depend on epaint (not entire egui)

* Update changelog link

* Fix doclinks
2022-12-12 17:25:00 +01:00
Emil Ernerfeldt
e0b5bb17e5
Improve the look of thin lines, making them look weaker (#2437)
* Revert "fix all clippy lints and remove them from allow list in cranky (#2419)"

This reverts commit 930ef2db38.

* Explain the cranky lints better

* Add Color32::gamma_multiply

* Remove unused pub use

* Remove non-existing crate category

* Improve color test with more lines

* Improve the look of thin lines, making them look weaker

Before they looked were too strong for the thickness.

* Use asserts for shader compilations

* Update changelogs
2022-12-12 16:18:05 +01:00
Emil Ernerfeldt
6c4fc50fdf
Make egui_wgpu::winit::Painter::set_window async (#2434)
* Make `egui_wgpu::winit::Painter::set_window` async

* Fix changelog link
2022-12-12 15:37:55 +01:00
Emil Ernerfeldt
7a658e3ddb
Add Event::Key::repeat that is set to true if the event is a repeat (#2435)
* Add `Event::Key::repeat` that is set to `true` if the event is a repeat

* Update changelog

* Improve docstring
2022-12-12 15:37:42 +01:00
Emil Ernerfeldt
cb77458f70
eframe error handling (#2433)
* eframe::run_native: return errors instead of crashing

* Detect and handle glutin errors

* egui_demo_app: silence wgpu log spam

* Add trace logs for why eframe is shutting down

* Fix: only save App state once on Mac

* Handle Winit failure

* Log where we load app state from

* Don't panic on zero-sized window

* Clamp loaded window size to not be too tiny to see

* Simplify code: more shared code in window_builder

* Improve code readability

* Fix wasm32 build

* fix android

* Update changelog
2022-12-12 15:16:32 +01:00
Emil Ernerfeldt
1437ec8903 Tell Rust Analyzer to run build to its own target folder 2022-12-12 10:37:41 +01:00
Ryan Hileman
c8dd5d1b2d
egui-wgpu: don't panic if we can't find a device (#2427) (#2428) 2022-12-12 10:36:48 +01:00
Emil Ernerfeldt
059e6f719d egui-winit: don't call set_cursor each frame on any platform
On some platforms (WSL?) the setting of the cursor can also fail,
leading to warnings being logged by winit each frame. Not good.
2022-12-12 10:26:14 +01:00
Red Artist
930ef2db38
fix all clippy lints and remove them from allow list in cranky (#2419)
* manual range contains clippy lint removed from allow list
* removed multiple redundant allowed clippy lints
2022-12-11 19:06:09 +01:00
Emil Ernerfeldt
228f30ed46 Release 0.20.1 of eframe egui-winit egui_glium egui_glow - docs fixes 2022-12-11 17:04:23 +01:00
Emil Ernerfeldt
d7189d69f6 Release 0.20.1 - Fix key-repeat 2022-12-11 16:59:54 +01:00
Emil Ernerfeldt
2713f60f5b Update web demo 2022-12-11 16:57:20 +01:00
Emil Ernerfeldt
4e3ae098a9 Improve eframe wasm32 docs 2022-12-11 16:52:37 +01:00
Emil Ernerfeldt
0a1b85f35a
Revert key-repeat behavior (#2429)
* Revert key-repeat behavior

This fixes key-repeats everywhere in egui where it was broken,including:

- Enter in TextEdit:s
- Arrow keys for sliders and dragvalues
- …

* Update changelog

* Remove old comment
2022-12-11 16:26:57 +01:00
bilabila
e7471f1191
Fix drag_value.edit_string unexpected reset (#2421)
* Fix drag_value.edit_string unexpected reset

  solve issue #2418 #2370

* Also reset drag_value.edit_string on click

* Fix for clippy check
2022-12-10 10:05:51 +01:00
Tomoya Matsuura
aca3807e43
Fixed docs comment in web::start (#2422)
* fixed typo in docs

* fixed example code in docs (wrong return type)
2022-12-09 15:09:54 +01:00
Emil Ernerfeldt
32606c2223
Fix docs.rs build issues for eframe, egui-winit, egui_glium, egui_glow (#2420)
* Fix docs.rs build issues for eframe, egui-winit, egui_glium, egui_glow

I hope we can get rid of the `tts` crate very soon,
now that AcessKit has landed. It is only used for web atm.
Should probably be removed from all native libraries.

* Update changelogs
2022-12-09 12:24:47 +01:00
Emil Ernerfeldt
54675f8983 Update changelog for #2416 2022-12-09 09:55:16 +01:00
ItsEthra
6cc43dbdd4
Fixed backspace repeat with TextEdit (#2416) 2022-12-09 09:54:00 +01:00
Emil Ernerfeldt
85f615f21c Fix incorrect date in CHANGELOGs 2022-12-09 08:29:44 +01:00
Emil Ernerfeldt
7d91e90481 Release 0.20.0 - AccessKit, prettier text, overlapping widgets 2022-12-08 15:11:54 +01:00
Emil Ernerfeldt
124ef8ddf8 Update poll-promise and unicode_names2 2022-12-08 15:08:59 +01:00
Emil Ernerfeldt
896a299bf9 New web demo 2022-12-08 14:55:12 +01:00
Emil Ernerfeldt
4af7dc9e35 Very small doc and changelog tweaks 2022-12-08 14:49:13 +01:00
Emil Ernerfeldt
da0a178701
Style tweaks (#2406)
* Note the namechange of egui::color to egui::ecolor

* Use a solid triangle for collapsing headers and windows

* Add Shadow::NONE

* Add Visuals::panel_fill, window_fill and window_stroke

* Bug fix: ComboBox::width sets the outer width of the ComboBox

* egui_extras::Table: add functions to access the `Ui` for the header/body

* ComboBox: use solid triangle

* Tweak default menu margin

* Nudge panel separator lines so they stay visible

* Update changelogs
2022-12-08 10:55:13 +01:00
Andreas Reich
5effc68ba4
Split out ecolor crate (#2399)
* split out ecolor crate

* split up ecolor crate in lots of modules

* add changelog notes

* add readme to ecolor

* put clippy::manual_range_contains on cranky allow list

* fix hex color issues

* doc fixes

* more hex_color fixes

* Document features

* Rename hex_color module to avoid warning

* Sort the feature names

* fix link in CHANGELOG.md

* better wording

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-12-06 20:42:25 +01:00
Emil Ernerfeldt
e30ac7f91a
Only add glutin dependency if you are using the glow backend (#2402) 2022-12-06 14:29:58 +01:00
Red Artist
9145893066
fix wrong variable name (#2400)
* fix wrong variable name

* fmt
2022-12-06 13:50:58 +01:00
René Rössler
c3932f7f7f
Chrono update (#2397)
* limit day to last day of month if the month or year is changed

* update chrono to 0.4.23, switch to NaiveDate and remove use of deprecated functions.
2022-12-06 11:42:53 +01:00
Emil Ernerfeldt
9e3da91a59 Add pronunciation guide at the top of the README 2022-12-06 10:58:51 +01:00
Emil Ernerfeldt
452541d7c2 Add a "sponsored by Rerun" section at the end of the README.md 2022-12-06 10:58:51 +01:00
Luke Jones
be4a5be145
egui_glow: update example to latest glutin (#2396)
This lifts the context handling from commit hash 8eb687c as this does
all the required handling for us that the older glutin once did.
2022-12-06 10:02:20 +01:00
Matt Campbell
32144d3cb9
Update README section on accessibility (#2392) 2022-12-05 20:40:01 +01:00
Emil Ernerfeldt
b8a1670781 Change demo keyboard shortcuts to some that work in the browser 2022-12-05 17:57:11 +01:00
Emil Ernerfeldt
a86ec5a749 Remove extra separator in color test 2022-12-05 17:38:38 +01:00
Emil Ernerfeldt
8ae4d49e6e More changelog updates 2022-12-05 17:38:30 +01:00
Mike Krüger
ee2582964d
Added KeyRepeat event. (#2389)
KeyPress no longer repeats.

Co-authored-by: Mike Krueger <mkrueger@posteo.de>
2022-12-05 17:37:25 +01:00
Emil Ernerfeldt
3519358b7b Clippy fix 2022-12-05 13:41:25 +01:00
Emil Ernerfeldt
be6d23eed1 Replace Stroke::none() with Stroke::NONE 2022-12-05 12:59:02 +01:00
Emil Ernerfeldt
df01db2df1 Add show_separator_line to SidePanel and TopBottomPanel
So as not to force it onto all users since #2261
2022-12-05 12:58:26 +01:00
Emil Ernerfeldt
aa503008ae Update changelogs with recent additions 2022-12-05 12:51:00 +01:00
Emil Ernerfeldt
d7fa40ebba
cargo update (#2386)
* Better cargo deny check in check.sh

* cargo update

    Updating ab_glyph v0.2.16 -> v0.2.18
    Updating ab_glyph_rasterizer v0.1.5 -> v0.1.7
    Removing adler32 v1.2.0
    Removing ahash v0.4.7
    Removing ahash v0.8.1
      Adding ahash v0.8.2
    Updating aho-corasick v0.7.18 -> v0.7.20
    Updating android_system_properties v0.1.4 -> v0.1.5
    Removing ansi_term v0.12.1
    Updating anyhow v1.0.62 -> v1.0.66
    Updating ash v0.37.0+1.3.209 -> v0.37.1+1.3.235
    Updating async-channel v1.7.1 -> v1.8.0
    Updating async-executor v1.4.1 -> v1.5.0
    Updating async-io v1.8.0 -> v1.12.0
    Updating async-lock v2.5.0 -> v2.6.0
    Updating async-recursion v0.3.2 -> v1.0.0
    Updating async-trait v0.1.57 -> v0.1.59
    Updating base64 v0.13.0 -> v0.13.1
    Updating bindgen v0.61.0 -> v0.63.0
      Adding block-buffer v0.10.3
    Updating bumpalo v3.11.0 -> v3.11.1
    Updating bytemuck v1.12.1 -> v1.12.3
    Updating bytemuck_derive v1.2.1 -> v1.3.0
    Updating bytes v1.2.1 -> v1.3.0
    Removing cache-padded v1.2.0
    Updating calloop v0.10.1 -> v0.10.3
    Updating cc v1.0.73 -> v1.0.77
    Updating cfg-expr v0.10.3 -> v0.11.0
    Updating chrono v0.4.22 -> v0.4.23
    Updating clang-sys v1.3.3 -> v1.4.0
    Updating clap v3.2.17 -> v3.2.23
    Updating cmake v0.1.48 -> v0.1.49
    Updating cocoa v0.24.0 -> v0.24.1
    Updating concurrent-queue v1.2.4 -> v2.0.0
      Adding cpufeatures v0.2.5
    Updating crossbeam-utils v0.8.11 -> v0.8.14
    Updating crossfont v0.5.0 -> v0.5.1
      Adding crypto-common v0.1.6
      Adding cxx v1.0.83
      Adding cxx-build v1.0.83
      Adding cxxbridge-flags v1.0.83
      Adding cxxbridge-macro v1.0.83
    Updating dark-light v0.2.2 -> v0.2.3
    Removing deflate v1.0.0
      Adding digest v0.10.6
    Updating dlv-list v0.2.3 -> v0.3.0
    Updating document-features v0.2.3 -> v0.2.6
    Updating flate2 v1.0.24 -> v1.0.25
    Updating fontconfig-parser v0.5.0 -> v0.5.1
    Updating fontdb v0.9.1 -> v0.9.3
    Updating form_urlencoded v1.0.1 -> v1.1.0
    Updating futures-core v0.3.23 -> v0.3.25
    Updating futures-io v0.3.23 -> v0.3.25
    Updating futures-sink v0.3.23 -> v0.3.25
    Updating futures-task v0.3.23 -> v0.3.25
    Updating futures-util v0.3.23 -> v0.3.25
      Adding generic-array v0.14.6
    Updating getrandom v0.2.7 -> v0.2.8
    Removing hashbrown v0.9.1
    Updating iana-time-zone v0.1.46 -> v0.1.53
      Adding iana-time-zone-haiku v0.1.1
    Updating idna v0.2.3 -> v0.3.0
    Updating image v0.24.3 -> v0.24.5
    Updating indexmap v1.9.1 -> v1.9.2
    Updating itertools v0.10.3 -> v0.10.5
    Updating itoa v1.0.3 -> v1.0.4
      Adding jni v0.20.0
    Updating jobserver v0.1.24 -> v0.1.25
      Adding jpeg-decoder v0.3.0
    Updating libc v0.2.132 -> v0.2.138
    Updating libloading v0.7.3 -> v0.7.4
    Updating libm v0.2.5 -> v0.2.6
      Adding link-cplusplus v1.0.7
      Adding litrs v0.2.3
    Updating lock_api v0.4.7 -> v0.4.9
    Updating memmap2 v0.5.7 -> v0.5.8
    Removing miniz_oxide v0.5.3
      Adding miniz_oxide v0.5.4
      Adding miniz_oxide v0.6.2
    Updating mio v0.8.4 -> v0.8.5
    Removing nix v0.22.3
    Removing nix v0.23.1
    Removing nix v0.24.2
      Adding nix v0.24.3
      Adding nix v0.25.1
      Adding nu-ansi-term v0.46.0
    Removing num_threads v0.1.6
    Updating once_cell v1.13.1 -> v1.16.0
    Updating ordered-multimap v0.3.1 -> v0.4.3
    Updating ordered-stream v0.0.1 -> v0.1.2
    Updating os_str_bytes v6.3.0 -> v6.4.1
      Adding overload v0.1.1
    Updating owned_ttf_parser v0.15.1 -> v0.17.1
    Updating parking_lot_core v0.9.3 -> v0.9.5
    Updating percent-encoding v2.1.0 -> v2.2.0
    Updating pkg-config v0.3.25 -> v0.3.26
    Updating png v0.17.5 -> v0.17.7
    Updating polling v2.2.0 -> v2.5.1
    Updating ppv-lite86 v0.2.16 -> v0.2.17
    Updating proc-macro2 v1.0.43 -> v1.0.47
    Updating profiling v1.0.6 -> v1.0.7
    Updating rand_core v0.6.3 -> v0.6.4
    Updating regex v1.6.0 -> v1.7.0
    Updating regex-syntax v0.6.27 -> v0.6.28
    Updating rgb v0.8.33 -> v0.8.34
      Adding roxmltree v0.15.1
    Updating rust-ini v0.17.0 -> v0.18.0
    Updating rustls v0.20.6 -> v0.20.7
    Updating scoped-tls v1.0.0 -> v1.0.1
      Adding scratch v1.0.2
    Updating sctk-adwaita v0.4.2 -> v0.4.3
    Updating serde v1.0.143 -> v1.0.149
    Updating serde_derive v1.0.143 -> v1.0.149
    Updating serde_json v1.0.83 -> v1.0.89
    Updating sha1 v0.6.1 -> v0.10.5
    Removing sha1_smol v1.0.0
    Updating smallvec v1.9.0 -> v1.10.0
    Updating socket2 v0.4.4 -> v0.4.7
    Updating svgtypes v0.8.1 -> v0.8.2
    Updating syn v1.0.99 -> v1.0.105
    Removing synstructure v0.12.6
    Updating system-deps v6.0.2 -> v6.0.3
    Updating textwrap v0.15.0 -> v0.16.0
    Updating thiserror v1.0.32 -> v1.0.37
    Updating thiserror-impl v1.0.32 -> v1.0.37
    Removing time v0.1.44
    Removing time v0.3.13
      Adding time v0.1.45
      Adding time v0.3.17
      Adding time-core v0.1.0
      Adding time-macros v0.2.6
    Updating tracing v0.1.36 -> v0.1.37
    Updating tracing-attributes v0.1.22 -> v0.1.23
    Updating tracing-core v0.1.29 -> v0.1.30
    Updating tracing-subscriber v0.3.15 -> v0.3.16
      Adding ttf-parser v0.17.1
      Adding typenum v1.15.0
    Updating unicode-ident v1.0.3 -> v1.0.5
    Updating unicode-normalization v0.1.21 -> v0.1.22
    Updating unicode-script v0.5.4 -> v0.5.5
    Updating unicode-width v0.1.9 -> v0.1.10
    Updating unicode-xid v0.2.3 -> v0.2.4
    Updating url v2.2.2 -> v2.3.1
    Updating version-compare v0.1.0 -> v0.1.1
    Updating wayland-client v0.29.4 -> v0.29.5
    Updating wayland-commons v0.29.4 -> v0.29.5
    Updating wayland-cursor v0.29.4 -> v0.29.5
    Updating wayland-egl v0.29.4 -> v0.29.5
    Updating wayland-protocols v0.29.4 -> v0.29.5
    Updating wayland-scanner v0.29.4 -> v0.29.5
    Updating wayland-sys v0.29.4 -> v0.29.5
    Updating webbrowser v0.8.0 -> v0.8.2
    Updating webpki-roots v0.22.4 -> v0.22.5
    Updating wgpu v0.14.0 -> v0.14.2
    Updating wgpu-core v0.14.0 -> v0.14.2
    Updating wgpu-hal v0.14.0 -> v0.14.1
    Updating wgpu-types v0.14.0 -> v0.14.1
    Updating which v4.2.5 -> v4.3.0
    Updating widestring v0.5.1 -> v1.0.2
      Adding windows-sys v0.42.0
    Updating winreg v0.8.0 -> v0.10.1
    Updating x11-dl v2.20.0 -> v2.20.1
    Updating xmlparser v0.13.3 -> v0.13.5
    Updating zbus v2.3.2 -> v3.6.0
    Updating zbus_macros v2.3.2 -> v3.6.0
    Updating zbus_names v2.2.0 -> v2.4.0
    Updating zerocopy-derive v0.3.1 -> v0.3.2
    Updating zstd-sys v2.0.1+zstd.1.5.2 -> v2.0.4+zstd.1.5.2
    Updating zvariant v3.6.0 -> v3.9.0
    Updating zvariant_derive v3.6.0 -> v3.9.0

* downgrade image, webbrowser and fontconfig-parser to pass cargo deny

avoiding duplicate crate

* Remove unused dependency on egui_extras from screenshot demo

* Downgrade `chrono` to avoid having to fix egui-extras now
2022-12-05 12:12:51 +01:00
Red Artist
8eb687cf04
Glutin Upgrade (#2187)
* working. but x11 blurry

* fixed x11 blurry. was just accidentally using multisampling even when user didnt request it

* allow dbg macro temporarily

* add windows WGL fallback support when EGL fails

* fmt

* glutin features explicitly added

* extract glutin context creation into a fn

* fix warnings
2022-12-05 11:34:28 +01:00
Emil Ernerfeldt
3ba39c3022
Hide all Area:s and Window:s the first frame to hide first-frame-jitters (#2385) 2022-12-05 11:18:01 +01:00
Emil Ernerfeldt
0b2d56cff0
Grid: special-treat first frame to fix bug causing expanding grids (#2384) 2022-12-05 11:16:37 +01:00
Emil Ernerfeldt
4e59296cbb Minor code tweaks 2022-12-05 09:45:07 +01:00
Emil Ernerfeldt
5093b67e9b Enable and fix some more clippy lints 2022-12-05 09:29:59 +01:00
Matt Campbell
e1f348e4b2
Implement accessibility APIs via AccessKit (#2294)
* squash before rebase

* Update AccessKit, introducing support for editable spinners on Windows and an important fix for navigation order on macOS

* Restore support for increment and decrement actions in DragValue

* Avoid VoiceOver race condition bug

* fix clippy lint

* Tell AccessKit that the default action for a text edit (equivalent to a click) is to set the focus. This matters to some platform adapters.

* Refactor InputState functions for AccessKit actions

* Support the AccessKit SetValue for DragValue; this is the only way for a Windows AT to programmatically adjust the value

* Same for Slider

* Properly associate the slider label with both the slider and the drag value

* Lazily activate egui's AccessKit support

* fix clippy lint

* Update AccessKit

* More documentation, particularly around lazy activation

* Tweak one of the doc comments

* See if I can get AccessKit exempted from the 'missing backticks' lint

* Make PlatformOutput::accesskit_update an Option

* Refactor lazy activation

* Refactor node mutation (again)

* Eliminate the need for an explicit is_accesskit_active method, at least for now

* Fix doc comment

* More refactoring of tree construction; don't depend on Arc::get_mut

* Override a clippy lint; I seem to have no other choice

* Final planned refactor: a more flexible approach to hierarchy

* Last AccessKit update for this PR; includes an important macOS DPI fix

* Move and document the optional accesskit dependency

* Fix comment typo

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

* reformat

* More elegant code for conditionally creating a node

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

* Set step to 1.0 for all integer sliders

* Add doc example for Response::labelled_by

* Clarify a TODO comment I left for myself

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-12-04 19:17:12 +01:00
Emil Ernerfeldt
48666e1d7a
Automatically generate screenshots for all examples (#2379) 2022-12-04 17:27:40 +01:00
winne42
b774159fc8
let monospace text style be monospaced (#2201)
* let monospace text style be monospaced

* use monospace shortcut
2022-12-04 15:39:55 +01:00
Emil Ernerfeldt
ec0b2d8e9a Deprecate ui.centered
It didn't do what it advertised to do, and there is no simple way
of doing that right now.
2022-12-04 10:16:37 +01:00
Emil Ernerfeldt
c8771cd13e Add some minor improvements for debugging id clashes 2022-12-02 09:52:26 +01:00
Emil Ernerfeldt
e613315866 Improve issue templates 2022-12-02 09:51:21 +01:00
Emil Ernerfeldt
7133818c59
Make sure scroll bars are always visible (#2371)
* Nicer debug rectangles

* Move scrollbars into the clip-rect so they are always visible

* Improve table demo

* Add options for controlling inner and outer margin of the scroll bars

* Add line to changelog

* Update egui_extras changelog with recent Table improvements

* Refactor Table:s scroll options

* Add Table::auto_size

* Rename it auto_shrink
2022-11-30 22:58:00 +01:00
JP
85f8eeb9d5
Fix key pressed event (#2334)
* Fix key press event

* Add example with key presses

* Changelog line for key_press fix

* PR review improvements

* Add PR link in changelog

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-11-30 20:40:11 +01:00
Emil Ernerfeldt
2dc2a5540d
egui_extras::Table improvements (#2369)
* Use simple `ui.interact` for the resize line

* Introduce TableReizeState

* Simplify some code

* Add striped options to table demo

* Auto-size table columns by double-clicking the resize line

* Table: add option to auto-size the columns

* Table: don't let column width gets too small, unless clipping is on

* egui_extras: always use serde

Otherwise using `get_persisted` etc is impossible,
and working around that tedious.

* Avoid clipping last column in a resizable table

* Some better naming

* Table: Use new `Column` for setting column sizes and properties

Also make `clip` a per-column property

* All Table:s store state for auto-sizing purposes

* Customize each column wether or not it is resizable

* fix some auto-sizing bugs

* Fix shrinkage of adaptive column content

* Rename `scroll` to `vscroll` for clarity

* Add Table::scroll_to_row

* scroll_to_row takes alignment

* Fix bug in table sizing

* Strip: turn clipping OFF by default, because it is dangerous and sucks

* Add TableBody::mac_rect helper

* Table: add options to control the scroll area height.

* Docstring fixes

* Cleanup
2022-11-30 19:56:06 +01:00
Matt Campbell
0336816faf
Fix keyboard support in DragValue (#2342)
* Enable incrementing and decrementing `DragValue` with the keyboard

* As soon as a DragValue is focused, render it in edit mode

* Simpler, more reliable approach to managing the drag value's edit string

* Add changelog entry

* Update doc comment

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

* Add comment explaining why we don't listen for left/right arrow

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-11-29 15:27:14 +01:00
Fotis Gimian
975cbac83a
Update the glow example to avoid a white flash when the app starts. (#2345) 2022-11-29 15:04:17 +01:00
Emil Ernerfeldt
a3f1e5961f Fix bug in keyboard shortcut formatting 2022-11-27 22:03:42 +01:00
Erlend Walstad
c5e6def65e
Only update pixels_per_point when it has changed (#2352) 2022-11-27 17:22:45 +01:00
Erlend Walstad
d2f70cdcd1
Make it easier to convert strings to Id (#2350) 2022-11-27 13:15:18 +01:00
Nagy Tibor
502e1aa229
Do not emit changed responses on unchanged values in selectable_value/radio_value (#2343) 2022-11-25 22:23:56 +01:00
Lukas Hermann
f9066ff285
[WGPU] Allow for depth buffer in web target (#2335)
* Add depth stencil initialization to `Painter`

* Move depth stencil initialization into `resize_and_generate_depth_texture_view`, and call it in `set_window` and `on_window_resized`

* Allow for depth texture in WASM builds

* change `map` to `if let` statement

* use reference for render state

* Clean up descriptors and move texture generation to on resize events

* remove unused field from WebOptions
2022-11-24 20:40:53 +01:00
Emil Ernerfeldt
7d8154971b
Update ndk-sys v0.4.0 -> v0.4.1+23.1.7779620 (#2340)
Closes https://github.com/emilk/egui/issues/2336
2022-11-24 10:09:32 +01:00
Felix Zwettler
f3633534e7
add set_plot_bounds method, giving users the ability to set the plot bounds themselves. (#2320)
* add set_plot_bounds method

* call it from_min_max for consistency with Rect

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-11-22 15:05:23 +01:00
Emil Ernerfeldt
bde47c9957
epaint: cover default fonts in the license field (#2327) 2022-11-22 13:44:01 +01:00
Emil Ernerfeldt
8602326af5 Plot: round hlines, vlines, and grid lines to pixels to avoid aliasing 2022-11-22 13:42:52 +01:00
Emil Ernerfeldt
8671aa26e1 Added support for thin space
https://en.wikipedia.org/wiki/Thin_space
2022-11-21 17:33:23 +01:00
Emil Ernerfeldt
1c8cf9e3d5
Move egui::util::History to emath::History (#2329)
* Move egui::util::History to emath::History

It is a nice thing to use outside of egui,
and it is more math-related than gui-related.

* Fix doctest
2022-11-21 14:14:33 +01:00
Lukas Hermann
f4d8ab9779
[wgpu] Add depth stencil initialization to Painter (#2316) 2022-11-21 10:08:24 +01:00
Clement Rey
dfc1f2c470
add plot_secondary_clicked to plot_ui (#2318) 2022-11-17 17:07:42 +01:00
Emil Ernerfeldt
eca5e6a4d2
Update to Rust 1.65 (#2314)
* Update to Rust 1.65

Because then you can use dynamic linking on Linux

* Fix a bunch of clippy lints

* Update changelogs

* More clippy fixes
2022-11-16 19:08:03 +01:00
Clement Rey
f7019926dc
wgpu backend: do not try to render zero-sized surfaces (#2313)
* wgpu backend: do not try to render zero-sized surfaces

* reverse if

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-11-16 18:03:57 +01:00
Emil Ernerfeldt
9268f43896
eframe: make RequestRepaintEvent into an enum UserEvent (#2311)
Preparation for https://github.com/emilk/egui/pull/2294
to make that a smaller diff.
2022-11-16 12:17:41 +01:00
Robert Walter
0ff1ee3893
feat(combobox): implement text wrap for selected text (#2272)
* feat(combobox): implement text wrap for selected text

* chore(changelog): add line to changelog

* feat(combobox-text-wrap): make wrap boolean

- specifying a wrap width didn't really make sense so now it's boolean
- the selected text will now use the maximum available width while still
  respecting the spacing and icon coming after it

* feat(combobox-text-wrap): update changelog
2022-11-16 11:57:27 +01:00
Arshia Soleimani
0ba04184d5
Improve mouse selection accuracy (#2304)
* updated

* Update crates/egui/src/context.rs

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

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-11-16 11:26:02 +01:00
Matt Fellenz
690dc2d2e8
Add 'none' alias to Sense::hover (#2306) 2022-11-14 11:44:46 +01:00
Emil Ernerfeldt
ef931c406c
Add Window::pivot and position combo boxes better (#2303)
* Paint ComboBox icon differently if opening upwards

* Add Area::pivot and Window::pivot

* Add Window::contrain

* ComboBox: pop up above if it doesn't fit below

* Add line to changelog
2022-11-13 22:17:33 +01:00
LoganDark
f0f41d60e1
eframe: Don't show window until after initialization (#2279)
* Don't show window until after initialization

Shortens #1802, but does not completely solve it

* format code

* Present first frame immediately before showing window

This resolves the white flash almost completely, but is a hack. Window
visibility should be derived from the AppOutput, and the first frame
should not be painted before the event loop has processed initial
events.

Working on a better implementation.

* Integrate window showing with AppOutput

This allows an app to keep the window hidden (never shown) by calling
Frame.set_visible(false) on the first update. This includes a slightly
less nasty hack than the last commit did.

Also fixes an accidental cross-contamination of pull requests.

* fmt

* add comments

* add comments

* add comments

* add comments

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-11-13 20:30:52 +01:00
LoganDark
5bac853d9c
eframe: Repaint immediately on RepaintAsap, fixes #903 (#2280)
* eframe: Repaint immediately on RepaintAsap, fixes #903

This completely eliminates the white flickering seen on Windows when
rapidly resizing a window on the glow backend. The reason that happens
is because DWM only waits for the resize event to be delivered before
displaying the window at its new size. You must repaint synchronously
inside that iteration of the event loop or else you get flickering.

* Differentiate between RepaintAsap and RepaintNext

RepaintNext looks like it is indeed needed in at least one case instead
of RepaintAsap.

* Use RepaintNext in more situations

Starting to understand why this was the behavior. It looks like only a
few special cases should be given RepaintAsap, such as the window being
resized. All other cases should be RepaintNext, as it can wait.

Using RepaintAsap in all situations will cause things like lag when
changing a slider by keyboard with a high key repeat rate.

* Add explanatory comments

I am a total hypocrite for forgetting to add these.

* Rename RepaintAsap to RepaintNow

There is no notion of "possibility" here like there is when waiting for
RedrawEventsCleared. RepaintNow causes an immediate repaint no matter
what.

* Fix RepaintNow comment

"Delays" is ambiguous.
2022-11-13 20:30:39 +01:00
ItsEthra
f790e248e4
Fixed color edit popup going outside the screen (#2270)
* Fixed color edit popup going outside the screen

* Added option to constrain areas

* Constrain color picker area

* Constrain popups

* Updated changelog
2022-11-11 14:09:54 +01:00
Red Artist
8ff139687a
bump msrv 1.64 and tts + bindgen dep (#2274)
* bump msrv 1.64 and tts + bindgen dep

* Update crates/egui-winit/Cargo.toml

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

* Update crates/eframe/Cargo.toml

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

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-11-11 10:27:25 +01:00
Emil Ernerfeldt
e225c6b8d0 Opt-in logging of egui-wgpu using puffin 2022-11-10 16:28:08 +01:00
WesleyCh3n
b2edbe617e
Fixed Plot Line::fill does not fill last segment correctly (#2275)
* Fix missing vertex to fill the triangle

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-11-10 15:00:20 +01:00
Emil Ernerfeldt
b1e71d308f
Only show tooltips when mouse pointer is still (#2263)
* Only show tooltips when mouse pointer is still

Revert to the old behavior by setting
`style.interaction.show_tooltips_only_when_still = false`.

* Area: take `impl Into<Id>`

* refactor tooltips

* Fix was_tooltip_open_last_frame

* Bug fix

* Add some spacing between tooltips
2022-11-09 19:35:08 +01:00
Emil Ernerfeldt
51ff32797d
Give each tooltip area its own unique Id (#2264)
* Don't check for clicks on non-interactive areas

* Give each tooltip area its own unique Id
2022-11-08 11:04:54 +01:00
Emil Ernerfeldt
35213606c4 Bug fix: don't paint panel separation lines on top of everything else 2022-11-08 08:27:49 +01:00
Emil Ernerfeldt
5515d2db77 Add ui.centered 2022-11-08 01:07:51 +01:00
Emil Ernerfeldt
c3edc1b88e fix mistake added in #2261 2022-11-08 00:51:50 +01:00
Emil Ernerfeldt
4aacb4575b
Panel visual improvements (#2261)
* Remove stroke around panels and replace with separator single line

* Remove item_spacing between panels

* Update changelog
2022-11-08 00:34:31 +01:00
qthree
75a825e9a1
Fix ScrollArea::show_rows (#2258) 2022-11-07 23:26:01 +01:00
blusk
3805a3282f
feat: add a way to provide customable scaling to SVG rasterization (#2252) 2022-11-07 16:52:14 +01:00
Emil Ernerfeldt
b3ab47a594 eframe: warn if web_sys_unstable_apis was not set 2022-11-07 14:00:00 +01:00
Emil Ernerfeldt
d5eb8779cb
Allow overlapping interactive widgets (#2244)
* Turn off optimization for debug builds again

* Optimize rect_contains_pointer

* Fix for colorpicker: make popup immovable

* Area: interact first

* ScrollArea: do interaction first

* Window: shrink double-clickable area of titelbar

* Only the top-most (latest added) interactive widget gets `hovered=true`

* Add Frame::total_margin

* Update changelog

* Add debug-options to visualize what widgets cover which other widget
2022-11-07 12:44:35 +01:00
Emil Ernerfeldt
8c76b8caff
Update op puffin 0.14.0 (#2257) 2022-11-07 12:37:37 +01:00
Emil Ernerfeldt
eec18290a4
eframe: make sure to update native_pixels_per_point when dpi changes (#2256)
* eframe: make sure to update native_pixels_per_point when dpi changes

* Update changelog
2022-11-07 12:36:17 +01:00
axxop
940b896cbb
use RandomState::with_seeds replace AHasher::default (#2254)
* use `RandomState::with_seeds` replace `AHasher::default`

* remove history checkout

* update seeds to `1,2,3,4`
2022-11-07 10:53:07 +01:00
Emil Ernerfeldt
17501d7e3e
Update arboard, fixing copy-paste on X11 (#2238)
* Revert "Fix copy-paste on Windows (#2120)"

This reverts commit f61044cef7.

* Update arboard to 3.2
2022-11-07 09:54:42 +01:00
Max Wase
22a917c00a
Painter extend accepts IntoIter (#2249)
* `Painter` extend accepts `IntoIter`

* Update painter.rs
2022-11-07 09:32:28 +01:00
Emil Ernerfeldt
e48602059d
Update ahash to 0.8.1 (#2255) 2022-11-07 09:23:45 +01:00
Steven Casper
7434a7d7d5
Implement Debug for egui::Context (#2248)
* Implement Debug for Context

* Update changelog
2022-11-06 22:49:29 +01:00
Emil Ernerfeldt
a0b3f1126b
Add helpers for zooming an app using Ctrl+Plus and Ctrl+Minus (#2239)
* Using tracing-subscriber in hello_world example

* Add Key::Plus/Minus/Equals

* Warn if failing to guess OS from User-Agent

* Remove jitter when using Context::set_pixels_per_point

* Demo app: zoom in/out using ⌘+ and ⌘-

* Demo app: make backend panel GUI scale slider better

* Optimize debug builds a bit

* typo

* Update changelog

* Add helper module `egui::gui_zoom` for zooming an app

* Better names, and update changelog

* Combine Plus and Equals keys

* Last fix

* Fix docs
2022-11-05 11:18:13 +01:00
Mikhail Sheldyakov
25718f2774
fix comment for WebOptions follow_system_theme (#2233) 2022-11-05 09:20:53 +01:00
Clement Rey
fcb00723bc
wgpu: add support for user-level command buffers + viewport clarification (#2230)
* wgpu: add support for user-level command buffers
* updated wgpu demo app
2022-11-04 09:54:29 +01:00
ItsEthra
4d1e858a52
Use total_cmp for clamping drag value in order to avoid floating point ambiguities (#2213)
* Use total_cmp for clamping DragValue

* Added test for clamping

* Increase MSRV in all crates

* Increased rust version for github actions and lib.rs

* Inversed ranges are now working properply with clamp_to_range

* Added more tests
2022-11-02 19:38:39 +01:00
Emil Ernerfeldt
34e6e12f00
Specify deifferent minification and magnification filters (#2224)
* Specify deifferent minification and magnification filters

* Fixes

* Update changelogs

* Doctest fixes

* Add deprecation notice for RetainedImage::with_texture_filter
2022-11-02 17:54:06 +01:00
heretik31
8e79a5a8ae
Add egui_speedy2d link to README.md (#2211) 2022-11-01 21:24:12 +01:00
ItsEthra
76d0cf5034
Fixed datepicker not being marked as changed (#2208) 2022-10-31 21:40:49 +01:00
Antoine Marras
1fb19f08ce
Typo in egui, layout: alignmen => alignment (#2209) 2022-10-31 21:39:49 +01:00
Andreas Reich
4c82519fb8
configurable wgpu backend (#2207)
* introduce new wgpu configuration option to allow configuring wgpu renderer

* use new options with wgpu web painter

* use on_surface_error callback

* changelog update

* cleanup

* changelog and comment fixes
2022-10-31 17:57:32 +01:00
ItsEthra
f71cbc2475
Fixed menu popups going outside of the screen (#2191)
* Fixed menu popups going outside of the screen

* Made menu appear above above the button instead
2022-10-31 17:57:15 +01:00
Emil Ernerfeldt
02b9d2d082
Keyboard shortcut helpers (#2202)
* eframe web: Add WebInfo::user_agent

* Deprecate `Modifier::ALT_SHIFT`

* Add code for formatting Modifiers and Key

* Add type KeyboardShortcut

* Code cleanup

* Add Context::os/set_os to query/set what OS egui believes it is on

* Add Fonts::has_glyph(s)

* Add helper function for formatting keyboard shortcuts

* Faster code

* Add way to set a shortcut text on menu buttons

* Cleanup

* format_keyboard_shortcut -> format_shortcut

* Add TODO about supporting more keyboard sumbols

* Modifiers::plus

* Use the new keyboard shortcuts in emark editor demo

* Explain why ALT+SHIFT is a bad modifier combo

* Fix doctest
2022-10-31 12:58:26 +01:00
Mason Feurer
d97282cd92
Interact with non-Rect shapes (#2199)
* added Ui::interact_with_hovered

* fixed typo
2022-10-30 20:59:19 +01:00
Emil Ernerfeldt
2b1341095d
CI: Run cargo clippy for wasm32-unknown-unknown (#2200)
* CI: Run cargo clippy for wasm32-unknown-unknown

* wasm32 clippy fixes

* Document when AppRunner::new can fail
2022-10-30 20:55:07 +01:00
Andreas Reich
53b800502a
wgpu renderer no longer creates a sampler with every texture (#2198) 2022-10-30 15:14:45 +01:00
Emil Ernerfeldt
f7a15a34f9
Panel collapse/expansion animation (#2190)
* Add API for querying the size of a panel

* demo app: animate backend panel collapse

* Add helper function for animating panels

* More animation functions

* Add line to changelog
2022-10-28 11:51:56 +02:00
Emil Ernerfeldt
da96fcacd3 Improve panel sizing API 2022-10-28 10:16:02 +02:00
Lily M. Lyons
1fadc7396a
Add CollapsingHeader::show_unindented (#2154) 2022-10-24 17:10:13 +02:00
Emil Ernerfeldt
aebec6329a Use ⚠ instead of ‼ in "Debug build" warning
The latter is not supported by the default fonts
2022-10-24 15:00:50 +02:00
Emil Ernerfeldt
7b8c17042c eframe: Make fullsize_content mac-only 2022-10-21 08:55:56 +02:00
Paul Hazen
5e44c13b48
Allows creating a ColorImage struct without an alpha channel (#2167)
* Added functionality to image.rs that allows for creating an image from rgb instead of just rgba

* remove "unmultiplied"

* remove "unmultiplied"

* rgba -> rgb

Co-authored-by: Mingun <Alexander_Sergey@mail.ru>

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Co-authored-by: Mingun <Alexander_Sergey@mail.ru>
2022-10-19 15:01:53 +02:00
Clement Rey
3d36a20376
table: fix deadlocks caused by lock fairness (#2156) 2022-10-16 20:10:48 +02:00
Andreas Reich
944159d514
using a shared vertex & index buffer in wgpu renderer (#2148)
* using a shared vertex & index buffer in wgpu renderer
capacity each doubles when exceeded.
This change means a lot less allocation during egui's lifetime.

* changelog update

* minor code cleanup and changelog fix

* fix linter issue
2022-10-14 10:53:19 +02:00
Parth
02d1e7492a
fix typo in text_layout_types (#2150) 2022-10-14 08:03:47 +02:00
Andrew Langmeier
a1f6f68213
Allow for changing of plot bounds from PlotUI (#2145) 2022-10-13 08:00:11 +02:00
Nicolas Musset
ae5294e63c
Use WidgetText for the slider's text (#2143) 2022-10-12 17:20:32 +02:00
Andreas Reich
c414af7aa2
wgpu renderer now always requires a RenderPass being passed in, pass command encoder to prepare callback (#2136)
* wgpu renderer now always requires a RenderPass being passed in
This also implies that it no longer owns the depth buffer! (why would it anyways!)

* wgpu-renderer now passes a command encoder to prepare

* add changelog entries

* fixup changelogs, fix variable name
2022-10-12 14:27:24 +02:00
Emil Ernerfeldt
7803285221 Remove Debug trait constraint on Id sources 2022-10-10 22:34:08 +02:00
Clement Rey
367378d75d
Fix false positives wrt RwLock deadlock detection (#2121)
* add a test suite demonstrating the issue

* fix false positives in RwLock, thus passing the new test suite

* friendler output

* augmented test suite with RWlock specifics (failing as expected!)

* full support for RWLocks

* implement support for guard remappings

* Add some newlines

* join `use` statements

* pass cranky

* addressing PR comments

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-10-10 12:07:44 +02:00
Emil Ernerfeldt
f4d2aa5b4a
Only call the request_repaint_callback at most once per frame (#2126)
egui-winit adds new repaint events to the event loop on each call, and on some platforms this becomes very expensive.
2022-10-10 10:47:20 +02:00
Andrew Langmeier
0fe0c8115c
Add ability to control double click reset in plot widget (#2115)
* Add ability to control double click reset in plot widget

* improve docstring

* small optimization

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-10-10 09:57:25 +02:00
Parth
510aa1710e
fix typo in architecture.md (#2123) 2022-10-10 09:42:50 +02:00
Emil Ernerfeldt
eba3927ace
Remove .with_srgb(false) when constructing window (#2110) 2022-10-08 11:43:48 +02:00
Emil Ernerfeldt
f61044cef7
Fix copy-paste on Windows (#2120)
Closes https://github.com/emilk/egui/issues/2109

Since arboard 3.0 you must absolutely not hold onto `arboard::Clipbaord`
longer than you are using it.

See https://github.com/1Password/arboard/issues/84
2022-10-07 14:46:42 +02:00
Andreas Reich
cd74c74f6f
Update to wgpu 0.14 (#2114) 2022-10-06 15:50:46 +02:00
Emil Ernerfeldt
3ec170cc1e Tweak warning color in bright mode 2022-10-06 11:49:12 +02:00
Emil Ernerfeldt
4dd5ffa254 Fix: make ScrollAreaOutput public 2022-10-06 09:03:18 +02:00
Andreas Reich
c2a37f4bd8
eframe support for wgpu on the web (#2107)
* basic working wgpu @ webgl on websys

* fix glow compile error

* introduced WebPainter trait, provide wgpu renderstate

* WebPainterWgpu destroy implemented

* make custom3d demo work on wgpu backend

* changelog entry for wgpu support eframe wasm

* remove temporary logging hack

* stop using pollster for web
we're actually not allowed to block - this only worked because wgpu on webgl doesn't actually cause anything blocking. However, when trying webgpu this became an issue

* revert cargo update

* compile error if neither glow nor wgpu features are enabled

* code cleanup

* Error handling

* Update changelog with link

* Make sure --all-features work

* Select best framebuffer format from the available ones

* update to wasm-bindgen 0.2.83

* Fix typo

* Clean up Cargo.toml

* Log about using the wgpu painter

* fixup wgpu labels

* fix custom3d_wgpu_shader ub padding

* remove duplicated uniforms struct in wgsl shader for custom3d

* Update docs: add async/await to the web 'start' function

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-10-05 20:14:18 +02:00
Emil Ernerfeldt
c441f33ecf Cargo deny: also check wasm32-unknown-unknown 2022-10-03 10:37:50 +02:00
Emil Ernerfeldt
dd67f3631f Add Painter::image convenience function 2022-10-03 10:26:19 +02:00
Emil Ernerfeldt
77df8ca0b0
Update some crates (#2100)
* Update crate webbrowser 0.7 -> 0.8

* Update criterion 0.3 -> 0.4

* Update tts 0.20 -> 0.24

* revert tts update
2022-10-02 20:33:45 +02:00
Tobias Schmitz
981fdb3932
fix: default to changing y axis when changing data aspect (#2087) 2022-10-02 14:32:37 +02:00
Paul Rouget
aa1536f1a7
Make Layout properties public (#2096) 2022-10-02 08:51:29 +02:00
Michael Galos
9901ad4bb0
eframe: Mouse passthrough option (#2080) 2022-10-02 08:45:37 +02:00
Zoxc
53ff83737b
Plot: Add auto_bounds_x, auto_bounds_y and reset (#2029)
* Make double click reset to the initial view

* Allow forcing auto bounds

* Allow plots to be reset

* Rename `modified` to `bounds_modified`
2022-10-02 08:44:59 +02:00
Caleb Leinz (he/him)
5e91a3033d
Added ability to set color of spinner (#2088) 2022-10-01 15:18:44 +02:00
Paul Rouget
2dca01ecd5
Bug Fix: Allocate Frame outer margins (#2089) 2022-10-01 14:18:15 +02:00
Emil Ernerfeldt
2ee9ab3151 Added link to egui-glutin-gl 2022-09-27 23:08:43 +02:00
Emil Ernerfeldt
91b2d5da6f Fix typo in wgpu warning message 2022-09-25 16:46:40 +02:00
Emil Ernerfeldt
4ac1e28eae
Improve text redering and do all color operation in gamma space (#2071) 2022-09-24 17:53:11 +02:00
Emil Ernerfeldt
29fa63317e
Fix text sizes being too small (#2069)
Closes https://github.com/emilk/egui/issues/2068

Before this PR, the default font, Ubuntu-Light, was ~11% smaller
than it should have been, and the default monospace font, Hack,
was ~14% smaller. This means that setting the font size `12` in egui
would yield smaller text than using that font size in any other app.
Ooops!

The change is that this PR now takes into account the ttf properties
`units_per_em` and `height_unscaled`.

If your egui application has specified you own font sizes or text styles
you will see the text in your application grow
larger, unless you go in and compensate by dividing all font sizes by
~1.21 for Ubuntu-Light/Proportional and ~1.16 for Hack/Monospace,
and with something else if you are using a custom font!
This effects any use of `FontId`, `RichText::size`, etc.

This PR changes the default `Style::text_styles` to compensate,
so the default egui style should look the same before and after this PR.
2022-09-21 21:31:08 +02:00
Emil Ernerfeldt
990a8c8b44
Fix sRGB blending and cleanup the relevant code (#2070)
* Fix sRGBA blending on Mac

* Clean up handling of sRGB support

* Assume sRGB support if any extension has sRGB in it

* improve logging very slightly
2022-09-21 21:30:02 +02:00
setzer22
4aae638e15
Change several methods from pub(crate) to pub (#2051)
* Change several methods from pub(crate) to pub

* document next_auto_id

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-09-21 20:08:26 +02:00
Emil Ernerfeldt
12dc61ed0f
update arboard to 3.0 (#2067) 2022-09-21 09:14:55 +02:00
Emil Ernerfeldt
2b0bf82b51
Make eframe::App::as_any_mut optional to implement (#2061) 2022-09-20 13:58:55 +02:00
Chris Hamons
311eb66cae
Add spacing.menu_margin for customizing menu spacing (#2036)
- Fixes https://github.com/emilk/egui/discussions/287
2022-09-17 11:29:19 +02:00
Adia Robbie
bc6a823103
eframe: Add center to NativeOptions and monitor_size to WindowInfo (#2035)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-09-17 11:20:40 +02:00
Paul Rouget
c4117066cf
MacOS: Support fullsize content (no titlebar, but window controls) (#2049)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-09-16 14:31:21 +02:00
Sheldon Nico
0605bcfca7
Add IME support for native (#2046) 2022-09-15 17:21:43 +02:00
Emil Ernerfeldt
c5495d69fb
egui-wgpu: Fix crash on zero-sized scissor rects (#2039) 2022-09-13 09:32:05 +02:00
Zoxc
2842d390c9
Allow box zoom in any direction (#2028) 2022-09-09 08:30:14 +02:00
Stanislav
ebc4fc866d
eframe web: access app from WebHandle (#1886)
Co-authored-by: Stanislav <enomado@users.noreply.github.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-09-09 08:22:46 +02:00
Emil Ernerfeldt
e0c7533ede eframe native: Make sure we only shut down once
I don't trust winit to actually exit on ControlFlow::Exit

Perhaps this will solve https://github.com/emilk/egui/issues/2027
2022-09-08 10:50:35 +02:00
Emil Ernerfeldt
1fe08bf7e4 eframe: reduce repaint log level from debug to trace 2022-09-07 16:27:53 +02:00
Luis Wirth
4b6826575e
egui-wgpu renderer renaming (#2021)
- `RenderPass` -> `Renderer`
- `RenderPass::execute` -> `Renderer::render`
- `RenderPass::execute_with_renderpass` -> `Renderer::render_onto_renderpass`
- reexport `Renderer` in `lib.rs`
2022-09-07 14:20:21 +02:00
bigfarts
0e62c0e50b
Improve mixed CJK/Latin linebreaking. (#1986) 2022-09-06 22:20:17 +02:00
Emil Ernerfeldt
5500895845
eframe: make sure we save app state on Cmd-Q (#2013)
Cmd-Q emits a `winit::event::Event::LoopDestroyed` and then the app closes, without `run_return` returning (despite its name).
2022-09-06 16:07:58 +02:00
Adia Robbie
eeb97fa4ce
Check srgb_support on other GL | ES as well. (#2012) 2022-09-06 15:58:22 +02:00
Romet Tagobert
64aac30f36
Add depth buffer support for egui-wgpu's render pass (#2002)
* add a depth texture for wgpu callbacks

* egui-wgpu: use depth from native_options

* add wgpu caveat to depth_buffer docstring
2022-09-06 14:37:07 +02:00
ComLarsic
162210f90e
Add sdl2_egui_platform to the list of integrations (#2011) 2022-09-06 14:28:59 +02:00
Adia Robbie
b43a8626cf
Add custom shader_version on glow renderer (#1993)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-09-06 10:08:16 +02:00
Luke Jones
7fae634dc3
egui_glow: default to RGBA8 if sRGB not supported (#2007)
The Vivante GPU on many STM32MP1 based boards does not support sRGB
as an internal format.

Introduce a check for sRGB support and default to `RGBA8` internal format
if not supported.

Additionally the STM32MP1 needs to be checked for `GL_ARB_vertex_array_object`

Closes #1991
2022-09-06 10:07:49 +02:00
Emil Ernerfeldt
1d5f20b46c Improve docstrings
Closes https://github.com/emilk/egui/issues/2001
2022-09-05 12:05:29 +02:00
njust
124ed5e4ba
Fixed typo (#1990)
* Fixed typo

* fix grammar too

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-09-05 12:02:31 +02:00
RadonCoding
613a664c45
Fixed a typo (#1985)
custom_window_frame was spelled as custon_window_frame
2022-08-30 10:12:52 +02:00
Koute
be9f363c53
egui_winit: support winit with default features disabled (#1971)
* egui_winit: support winit with default features disabled

* Link to the PR in the changelog
2022-08-30 08:49:32 +02:00
Joshua Batty
153524023f
egui-wgpu: port over missing update_wgpu_texture methods from egui_wgpu_backend (#1981)
* add back missing update_egui_texture_from_wgpu_texture methods to renderer module

* clippy
2022-08-29 14:09:34 +02:00
Emil Ernerfeldt
ac4d75e9b4
egui-winit: don't repaint when just moving window (#1980)
* Bug fix: reset repaint countdown when we pass it

* eframe: debug-print what winit event caused a repaint

* egui-winit: don't repaint when only moving window

* fix docstring
2022-08-29 11:20:19 +02:00
Adam Gąsior
9b2c3d1026
Add custom parser to DragValue and Slider, with additional support for binary, octal, and hexadecimal numbers. (#1967)
* Add `custom_parser` to `DragValue`

* Add `custom_parser` to `Slider`

* Add `binary_u64`, `octal_u64`, and `hexadecimal_u64` to `DragValue`

* Add `binary_u64`, `octal_u64`, and `hexadecimal_u64` to `Slider`

* Fix formatting and errors in docs

* Update CHANGELOG.md

* Fix CI errors

* Replace manual number parsing with i64::from_str_radix. Add support for signed integers.

* Update CHANGELOG.md

* Change documentation.

* Fix documentation.

* Fix documentation.

* Remove unnecessary links.
2022-08-28 18:16:12 +02:00
Emil Ernerfeldt
3142c52b94
Minor wgpu-web releated stuff (#1977)
* egui_demo_app: remove wgpu and pollster as direct dependencies

* eframe: use same web-sys version as wgpu crate

* Make note that web_sys_unstable_apis is required by the wgpu crate

* Rename the glow web painter in eframe

* Remove trait DummyWebGLConstructor from web_glow_painter.rs

* cargo fmt

* Fix check.sh
2022-08-28 10:58:58 +02:00
njust
dbfaa4527b
egui_extras: Add Table::vertical_scroll_offset (#1946)
* egui_extras: Add Table::vertical_scroll_offset

* Added example for TableBuilder::vertical_scroll_offset

* Format code

* Add link to PR in the changelog

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-08-28 09:37:23 +02:00
MaximOsipenko
af63101fdc
eliminate some possible deadlocks (#1968) 2022-08-28 09:36:23 +02:00
Zoxc
9be060fe69
Allow cursors to be linked for plots (#1722)
* Allow cursors to be linked

* Link cursors in demo

* Refactor cursor memory to deal with removal of plots

* Refactor PlotItem::on_hover to produce a list of cursors

* Use a separate `LinkedCursorsGroup` type to link cursors.

* Refactor `Cursor`.

* Inline `push_argument_ruler` and `push_value_ruler`.

* Update documentation

* Update doc reference
2022-08-28 09:36:08 +02:00
Emil Ernerfeldt
b978b06159
Simplify, clean up and speed up CI (#1970)
Use https://github.com/Swatinem/rust-cache
2022-08-27 09:54:47 +02:00
Tomáš Král
f3f6946eb2
egui_glium correct texture filtering (#1962)
* correct texture filter in egui_glium

* update example

* cargo fmt

* rename UserTexture
2022-08-25 08:05:58 +02:00
Emil Ernerfeldt
d5933daee5 egui_glow: allow empty (zero-sized) textures
See https://github.com/emilk/egui/issues/1960
2022-08-24 14:34:34 +02:00
Robert Bragg
fb92434aac
Android support for EFrame (#1952)
* eframe: allow hooking into EventLoop building

This enables native applications to add an `event_loop_builder` callback
to the `NativeOptions` struct that lets them modify the Winit
`EventLoopBuilder` before the final `EventLoop` is built and run.

This makes it practical for applications to change platform
specific config options that Egui doesn't need to be directly aware of.

For example the `android-activity` glue crate that supports writing
Android applications in Rust requires that the Winit event loop be
passed a reference to the `AndroidApp` that is given to the
`android_main` entrypoint for the application.

Since the `AndroidApp` itself is abstracted by Winit then there's no
real need for Egui/EFrame to have a dependency on the `android-activity`
crate just for the sake of associating this state with the event loop.

Addresses: #1951

* eframe: defer graphics state initialization until app Resumed

Conceptually the Winit `Resumed` event signifies that the application is
ready to run and render and since
https://github.com/rust-windowing/winit/pull/2331 all platforms now
consistently emit a Resumed event that can be used to initialize
graphics state for an application.

On Android in particular it's important to wait until the application
has Resumed before initializing graphics state since it won't have an
associated SurfaceView while paused.

Without this change then Android applications are likely to just show
a black screen.

This updates the Wgpu+Winit and Glow+Winit integration for eframe but
it's worth noting that the Glow integration is still not able to fully
support suspend and resume on Android due to limitations with Glutin's
API that mean we can't destroy and create a Window without also
destroying the GL context, and therefore (practically) the entire
application.

There is a plan (and progress on) to improve the Glutin API here:
https://github.com/rust-windowing/glutin/pull/1435 and with that change
it should be an incremental change to enable Android suspend/resume
support with Glow later.

In the mean time the Glow changes keep the implementation consistent
with the wgpu integration and it should now at least be possible to
start an Android application - even though it won't be able to suspend
correctly.

Fixes #1951
2022-08-23 14:43:22 +02:00
Zoxc
b9bb7baaa7
Only require something GLES 3 compatible from wgpu (#1956) 2022-08-23 14:39:01 +02:00
Daniel Buch Hansen
4629f2cd12
Example: three-d downgrade depth_buffer to 24 bit (#1950)
Closes: #1949
2022-08-21 19:23:34 +02:00
Emil Ernerfeldt
8797c02ed9 Publish new web demo that works on Es100 2022-08-20 19:55:54 +02:00
Emil Ernerfeldt
bd5250f85d
Disable custom 3D painting example in the egui demo app for Es100 (#1945)
Closes https://github.com/emilk/egui/issues/1944
2022-08-20 19:54:18 +02:00
Emil Ernerfeldt
97ce103209 Release 0.19.0 - wgpu backend, repaint_after, continue-after-close 2022-08-20 16:49:58 +02:00
Emil Ernerfeldt
13f5d62b4b Commit a new web demo 2022-08-20 16:37:59 +02:00
Emil Ernerfeldt
127931ba45
eframe: rename quit/exit to "close" (#1943)
Since https://github.com/emilk/egui/pull/1919 we can continue
the application after closing the native window. It therefore makes
more sense to call `frame.close()` to close the native window,
instead of `frame.quit()`.
2022-08-20 16:08:59 +02:00
Emil Ernerfeldt
2453756782 Fix broken GitHub source links due to #1940 2022-08-20 15:18:02 +02:00
Emil Ernerfeldt
8737933101 Improve documentation and logging 2022-08-20 15:06:43 +02:00
Emil Ernerfeldt
725aa5277b Update crate rfd to 0.10 (in file_dialog example)
Also update ab_glyph from 0.2.15 to 0.2.16
2022-08-20 14:42:37 +02:00
Emil Ernerfeldt
eb10ef94f7 Build-fix for --no-default-features 2022-08-20 14:40:33 +02:00
Emil Ernerfeldt
f4cc1c5465
eframe: Don't follow system theme by default (#1941)
I have gone back and forth on this a bit, but I think the arguments
AGAINST following the system theme are many:

* `dark-light` is a big dependency with problems on Linux.
* Many people prefer the dark mode and ask how to set it as the default
  (even though they are using light mode in their OS).
* A developer may be surprised when the app changes theme when
  they run it on another computer.

So, the path of least surprise is to make this an opt-in feature
with dark mode as the default mode.

On native, you add the `dark-light` feature to enable it.
On web, you set `WebOptions::follow_system_theme`.
2022-08-20 11:11:07 +02:00
Emil Ernerfeldt
40e440b2f7 Update changelog with recent PR merges 2022-08-20 10:42:31 +02:00
Emil Ernerfeldt
041f2e64ba
Move all crates into a crates directory (#1940) 2022-08-20 10:41:49 +02:00
Emil Ernerfeldt
5c63648c02
clean up rust.yml (#1939) 2022-08-20 09:32:48 +02:00
Emil Ernerfeldt
193a434717 eframe persistence: persistence failure is now logged instead of a panic 2022-08-19 12:04:43 +02:00
Emil Ernerfeldt
277f199df4 eframe: make sure we can serialize i128/u128 with ron
Some eframe users may be relying on this.
2022-08-19 12:04:08 +02:00
Emil Ernerfeldt
5514a8afda
Update dependencies (#1933)
* Update ahash from 0.7 to 0.8

Opt to use ahash::HashMap over ahash::AHashMap

* Fix ahash compilation for web

* Update ron to 0.8

* Add note about why we cannot update tiny-skia

* cargo update

    Updating crates.io index
    Updating android_system_properties v0.1.2 -> v0.1.4
    Updating anyhow v1.0.58 -> v1.0.62
    Updating async-broadcast v0.4.0 -> v0.4.1
    Updating async-channel v1.6.1 -> v1.7.1
    Updating async-io v1.7.0 -> v1.8.0
    Updating async-task v4.2.0 -> v4.3.0
    Updating async-trait v0.1.56 -> v0.1.57
    Updating backtrace v0.3.65 -> v0.3.66
    Updating bit-set v0.5.2 -> v0.5.3
    Updating bumpalo v3.10.0 -> v3.11.0
    Updating bytemuck v1.10.0 -> v1.12.1
    Updating bytemuck_derive v1.1.0 -> v1.2.1
    Updating bytes v1.1.0 -> v1.2.1
    Updating cast v0.2.7 -> v0.3.0
    Updating chrono v0.4.19 -> v0.4.22
    Updating clap v3.2.8 -> v3.2.17
    Updating clipboard-win v4.4.1 -> v4.4.2
    Updating combine v4.6.4 -> v4.6.6
    Updating concurrent-queue v1.2.2 -> v1.2.4
    Updating criterion v0.3.5 -> v0.3.6
    Updating criterion-plot v0.4.4 -> v0.4.5
    Updating crossbeam-channel v0.5.5 -> v0.5.6
    Updating crossbeam-deque v0.8.1 -> v0.8.2
    Updating crossbeam-epoch v0.9.9 -> v0.9.10
    Updating crossbeam-utils v0.8.10 -> v0.8.11
    Updating document-features v0.2.1 -> v0.2.3
    Updating dyn-clone v1.0.6 -> v1.0.9
    Removing easy-parallel v3.2.0
    Updating either v1.7.0 -> v1.8.0
    Updating enum-map v2.1.0 -> v2.4.1
    Updating enum-map-derive v0.8.0 -> v0.10.0
    Updating event-listener v2.5.2 -> v2.5.3
    Updating fastrand v1.7.0 -> v1.8.0
    Updating futures-core v0.3.21 -> v0.3.23
    Updating futures-io v0.3.21 -> v0.3.23
    Updating futures-sink v0.3.21 -> v0.3.23
    Updating futures-task v0.3.21 -> v0.3.23
    Updating futures-util v0.3.21 -> v0.3.23
    Updating gimli v0.26.1 -> v0.26.2
    Updating gpu-descriptor v0.2.2 -> v0.2.3
    Removing hashbrown v0.11.2
    Removing hashbrown v0.12.1
      Adding hashbrown v0.12.3
      Adding iana-time-zone v0.1.46
    Updating image v0.24.2 -> v0.24.3
    Updating inplace_it v0.3.3 -> v0.3.4
    Updating itoa v1.0.2 -> v1.0.3
    Updating js-sys v0.3.58 -> v0.3.59
    Updating libc v0.2.126 -> v0.2.132
    Updating libm v0.2.2 -> v0.2.5
    Removing memmap2 v0.3.1
    Removing memmap2 v0.5.4
      Adding memmap2 v0.5.7
    Removing num-iter v0.1.43
    Updating object v0.28.4 -> v0.29.0
    Updating once_cell v1.13.0 -> v1.13.1
    Updating os_str_bytes v6.1.0 -> v6.3.0
    Updating owned_ttf_parser v0.15.0 -> v0.15.1
    Removing parking_lot v0.11.2
    Removing parking_lot_core v0.8.5
    Updating plotters v0.3.1 -> v0.3.3
    Updating plotters-backend v0.3.2 -> v0.3.4
    Updating plotters-svg v0.3.1 -> v0.3.3
    Updating proc-macro-crate v1.1.3 -> v1.2.1
    Updating proc-macro2 v1.0.40 -> v1.0.43
    Updating quote v1.0.20 -> v1.0.21
    Updating redox_syscall v0.2.13 -> v0.2.16
    Updating regex v1.5.6 -> v1.6.0
    Updating regex-syntax v0.6.26 -> v0.6.27
    Updating rfd v0.8.0 -> v0.8.4
    Removing rustc_version v0.4.0
    Updating ryu v1.0.10 -> v1.0.11
    Updating sctk-adwaita v0.4.1 -> v0.4.2
    Removing semver v1.0.12
    Updating serde v1.0.138 -> v1.0.143
    Updating serde_derive v1.0.138 -> v1.0.143
    Updating serde_json v1.0.82 -> v1.0.83
    Updating serde_repr v0.1.8 -> v0.1.9
    Updating slab v0.4.6 -> v0.4.7
    Removing smithay-client-toolkit v0.15.4
    Updating smithay-clipboard v0.6.5 -> v0.6.6
    Updating syn v1.0.98 -> v1.0.99
    Updating thiserror v1.0.31 -> v1.0.32
    Updating thiserror-impl v1.0.31 -> v1.0.32
    Updating time v0.3.11 -> v0.3.13
      Adding tiny-skia v0.7.0
      Adding tiny-skia-path v0.7.0
    Updating tracing v0.1.35 -> v0.1.36
    Updating tracing-core v0.1.28 -> v0.1.29
    Updating tracing-subscriber v0.3.14 -> v0.3.15
    Updating unicode-ident v1.0.1 -> v1.0.3
    Updating unicode_names2 v0.5.0 -> v0.5.1
    Updating ureq v2.4.0 -> v2.5.0
    Updating wasm-bindgen-futures v0.4.31 -> v0.4.32
    Updating web-sys v0.3.58 -> v0.3.59
    Updating webpki-roots v0.22.3 -> v0.22.4
    Updating weezl v0.1.6 -> v0.1.7
    Updating wgpu-core v0.13.1 -> v0.13.2
    Updating wgpu-hal v0.13.1 -> v0.13.2
    Updating wgpu-types v0.13.0 -> v0.13.2
    Updating windows v0.32.0 -> v0.37.0
    Updating windows_aarch64_msvc v0.32.0 -> v0.37.0
    Updating windows_i686_gnu v0.32.0 -> v0.37.0
    Updating windows_i686_msvc v0.32.0 -> v0.37.0
    Updating windows_x86_64_gnu v0.32.0 -> v0.37.0
    Updating windows_x86_64_msvc v0.32.0 -> v0.37.0
    Updating x11-dl v2.19.1 -> v2.20.0
    Updating zbus_names v2.1.0 -> v2.2.0
    Updating zvariant v3.4.1 -> v3.6.0
    Updating zvariant_derive v3.4.1 -> v3.6.0

* Add "Unicode-DFS-2016" to deny.toml whitelist
2022-08-19 11:46:38 +02:00
Asger Nyman Christiansen
eeeb4b7de2
Improve custom_3d_three-d example (#1923)
* Use correct FBO to output

* custom_3d_three-d web

* Update .gitignore

* Do not free the FBO

* Use three-d 0.13

* ThreeDApp

* Only construct model and camera once

* Clean-up and docs

* Web build instructions

* Remove unused dependencies

* Update Cargo.lock

* Fix build

* More fixes

* omg
2022-08-17 21:33:34 +02:00
Emil Ernerfeldt
39b63f6163 Warn if using an TextShape from before a change to pixels_per_point
Closes https://github.com/emilk/egui/issues/1915
2022-08-16 22:47:23 +02:00
Emil Ernerfeldt
9c58f12a6c
eframe: several windows in series (#1919)
* Add example of opening several eframe windows in series

* Reuse the same winit event loop

* Ignore events to the wrong window

* Run run_return again
2022-08-15 16:31:03 +02:00
Zoxc
48e7f219a3
Add CI for android (#1900)
* Add CI for android

* Don't use arboard on Android

* Fix Android support for eframe
2022-08-15 10:19:59 +02:00
Emil Ernerfeldt
923b67ef9c
Update to winit 0.27.2, glutin 0.29.0, glium 0.32 (#1914) 2022-08-14 16:23:46 +02:00
eranfu
38a67f8646
Add PointerState::button_double_clicked() and PointerState::button_triple_clicked(). (#1907)
Co-authored-by: eranfu <eranfu@tencent.com>
2022-08-14 16:10:38 +02:00
Emil Ernerfeldt
7c25a9238e Remove get_ prefix from functions to better follow Rust API Guidelines 2022-08-08 12:21:53 +02:00
Emil Ernerfeldt
e38955fbac Fix: remember to call integration.post_rendering on wgpu path 2022-08-08 12:15:51 +02:00
Emil Ernerfeldt
9e41fa021a eframe: rename render_state to wgpu_render_state for added clarity 2022-08-08 12:15:31 +02:00
Emil Ernerfeldt
66c601f775
Continue execution after closing native eframe window (#1889)
This adds `NativeOptions::run_and_return` with default value `true`.

If `true`, execution will continue after the eframe window is closed. This is a new behavior introduced in this PR.
If `false`, the app will close once the eframe window is closed. The is the old behavior.

This is `true` by default, and the `false` option is only there so we can revert if we find any bugs.

When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`](https://docs.rs/winit/latest/winit/platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return) is used. The winit docs warns of its usage, recommending `EventLoop::run`, but 🤷 
When `false`, [`winit::event_loop::EventLoop::run`](https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html#method.run) is used.

This is a really useful feature. You can now use `eframe` to quickly open up a window and show some data or options, and then continue your program after the `eframe` window is closed

My previous attempt at this caused some problems, but my new attempt seems to be working much better, at least on my Mac.
2022-08-05 08:20:31 +02:00
Lorren Biffin
e3f993d7b4
Fixed bug in custom window example (#1750) 2022-08-04 12:32:27 +02:00
Barugon
2f3c2a360b
Add with_main_align method (#1891)
Co-authored-by: Barugon <barugon@dungeonbox.net>
2022-08-04 12:07:45 +02:00
Emil Ernerfeldt
a827c3e033 Add glow::Painter::intermediate_fbo()
This allow callbacks to restore to the correct framebuffer
after using their own temporary FBO.

See discussion in https://github.com/emilk/egui/issues/1744
2022-08-03 09:38:46 +02:00
Emil Ernerfeldt
53249d36df Remove superflous #[cfg(test)] 2022-08-03 09:31:43 +02:00
Dylan Ancel
1af446b9e8
Make egui_wgpu::RenderPass Send and Sync (#1883)
* Make RenderPass Send and Sync

* Add change to CHANGELOG

* Make CHANGELOG formatting match egui

* Add test
2022-08-03 09:26:16 +02:00
Emil Ernerfeldt
cb0d5a58ab Fix incorrect documentation for Response::context_menu
Closes https://github.com/emilk/egui/issues/1882
2022-08-03 08:26:42 +02:00
Asger Nyman Christiansen
bcc01c8b1c
Add egui/default_fonts feature to pure_glow example (#1881)
It seems to me like the `pure_glow` example was broken sometime in april because of changes to feature flags. The text simply didn't show up which is due to missing fonts unless you figured out that you needed the `egui/default_fonts` feature flag. This change enforces the use of the `egui/default_fonts` feature flag in this example.
2022-08-02 23:04:59 +02:00
Nicolas Musset
e288ca86fd
Remove dependency on AsRef trait for TextBuffer (#1824) 2022-08-02 20:35:37 +02:00
YgorSouza
e39410c37f
Make EasyMark numbered lists allow more than 2 digits (#1826)
Co-authored-by: Ygor Souza <ygor.souza@protonmail.com>
2022-08-02 20:35:25 +02:00
Emil Ernerfeldt
06adb09fa3 Make stick_to_bottom take a bool argument 2022-08-02 20:34:50 +02:00
Emil Ernerfeldt
263c9bd601 Make use of AppRunnerRef
Follow-up to #1650
2022-08-02 17:44:27 +02:00
Stanislav
64496cacb9
Graceful exit from web (#1650)
Return a handle that can be used to stop a running egui instance.
2022-08-02 17:42:55 +02:00
Asger Nyman Christiansen
3eccd341ad
Add depth buffer in native 3D example (#1878)
The `custom_3d_three-d` example does not enable a depth buffer since it is only rendering a triangle. However, if it is used as a starting point for other projects, it is highly likely that a depth buffer is actually needed, therefore I propose to enable it by default.
Also see [this](https://github.com/asny/three-d/issues/268) issue for full context.
2022-08-02 17:31:15 +02:00
Emil Ernerfeldt
10788ccc92
More newlines for improved readability (#1880)
* Add blank lines above all `fn`, `impl`, `struct`, etc
* Even newlines between docstringed struct and enum fields
* Improve some documentation
2022-08-02 17:26:33 +02:00
Emil Ernerfeldt
5d8ef5326b Change build_demo_web.sh option from --fast to --optimize 2022-08-02 09:56:38 +02:00
Emil Ernerfeldt
2500a60062 Code cleanup and improved docs 2022-07-30 18:40:49 +02:00
Kubik
5fb4efa768
Improve documentation about panel order (#1869) 2022-07-30 17:11:56 +02:00
Emil Ernerfeldt
d659e5d24f Add Shape::hline and Shape::vline 2022-07-30 15:34:24 +02:00
Emil Ernerfeldt
c62f3409bd Fix misnamed variable (content_is_too_small -> content_is_too_large)
See https://github.com/emilk/egui/issues/1376
2022-07-30 15:33:08 +02:00
quietvoid
8997519eb2
Fix valign typo (#1870) 2022-07-30 14:55:34 +02:00
Emil Ernerfeldt
235d77713d Improve README.md files 2022-07-29 16:07:48 +02:00
Emil Ernerfeldt
6de9d89b65 Add emath::exponential_smooth_factor 2022-07-29 16:07:35 +02:00
Emil Ernerfeldt
4e8a6e3370 misc code cleanup 2022-07-29 16:07:26 +02:00
Emil Ernerfeldt
b0fa0c65cc fix typo 2022-07-29 15:56:04 +02:00
Emil Ernerfeldt
2612dd1064 Add Visuals::error_fg_color and Visuals::warn_fg_color 2022-07-29 15:32:32 +02:00
Emil Ernerfeldt
8c09804abd
eframe: selectively expose parts of the API based on compile target (#1867)
A lot of the `eframe` API is native-only or web-only. With this PR, only the parts that are implemented for each platform is exposed.

This means you'll need to add `#[cfg(target_arch = "wasm32")]` around code that uses the web-parts of the eframe API, and add `#[cfg(not(target_arch = "wasm32"))]` around the parts that are for native/desktop.
2022-07-29 14:37:23 +02:00
Emil Ernerfeldt
51052c08e9 code cleanup: Pos2::new -> pos2, Vec2::new -> vec2 2022-07-29 14:34:26 +02:00
Emil Ernerfeldt
105cb4b8f2
eframe: add function to set, query and toggle fullscreen mode (#1866)
Closes https://github.com/emilk/egui/pull/674

Adds `NativeOptions:fullscreen`, `Frame::set_fullscreen` and `WindowInfo::fullscreen`.
2022-07-29 14:21:23 +02:00
Matt Campbell
c6c6d2dc5d
Track the global focus state of the UI (#1859)
* Track the global focus state of the UI

* Fix changelog entries

* Document the new difference between `Response::has_focus` and `Memory::has_focus`
2022-07-29 13:15:26 +02:00
zapp88
36a49ffba9
Add dry run feature for anchor calculation. (#1) (#1856)
* Add dry run feature for anchor calculation. (#1)

This PR resolves issue: emilk#1852
We introduce dry_run flag which makes component invisible until we do second pass of rendering - which allows us to properly calculate position for anchor. (This removes rapid flicker when new window is drawn for the first time).

* Change naming convention and add description
2022-07-29 13:14:28 +02:00
Emil Ernerfeldt
278db1c94b
Fix plot auto bounds (#1865)
* Better estimate the plot bounds for generator functions

Avoid infinities, and sample more densely

* Optimize and improve plot auto-bounds logic

* Fix cropping out of the top/bottom of plots during auto-bounds
2022-07-29 12:32:47 +02:00
Emil Ernerfeldt
97880e18d7 Make egui_demo_app native window size larger by default 2022-07-29 11:14:37 +02:00
Matt Campbell
b3ab31953e
Fix focus behavior when pressing Tab with no focus (#1861) 2022-07-29 10:42:09 +02:00
Emil Ernerfeldt
09d636b089 egui-wgpu: correctly handle viewport rectangle for callbacks
This is important for when a callback shape is inside a ScrollArea.
2022-07-29 00:06:08 +02:00
Connor Fitzgerald
0571bf67e2
Reset the scissor rect after rendering onto a renderpass (#1858) 2022-07-27 01:09:19 +02:00
Matt Campbell
7a46a23db5
Update MSRV to Rust 1.61.0 (#1846) 2022-07-26 16:50:53 +02:00
Emil Ernerfeldt
2278128e66 Add bacon.toml for https://github.com/Canop/bacon 2022-07-26 11:21:57 +02:00
Emil Ernerfeldt
8e2de26e4e Enable more clippy lints 2022-07-26 11:18:21 +02:00
Emil Ernerfeldt
a7012cf8a6
Use pinned version of wasm-bindgen-cli (#1855)
* Use pinned version of wasm-bindgen-cli

* Update wasm-bindgen to 0.2.82

* Call setup_web.sh from scripts that depends on it
2022-07-25 23:02:10 +02:00
Adam Gąsior
36cdae98df
Add methods for custom number formatting in DragValue and Slider (#1851) 2022-07-25 22:38:24 +02:00
Emil Ernerfeldt
c02fbfe973 Make it easier to disable tts
This is a small step in mitigating https://github.com/emilk/egui/issues/1125
2022-07-25 22:17:52 +02:00
Nolan Darilek
0913c77f3d
Make sliders correctly generate events on change. (#1854) 2022-07-25 22:05:05 +02:00
wayne
74ccde566d
egui_extras: Add Table::stick_to_bottom (#1849) 2022-07-25 15:42:24 +02:00
Sven Niederberger
0bf9fc9428
Improve plot item UX (#1816)
* initial work

* changelog entry

* fix CI

* Update egui/src/widgets/plot/items/values.rs

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

* Update egui/src/widgets/plot/items/values.rs

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

* derive 'FromIterator'

* remove `bytemuck` dependency again and remove borrowing plot points for now

* update doctest

* update documentation

* remove unnecessary numeric cast

* cargo fmt

* Update egui/src/widgets/plot/items/values.rs

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

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-07-24 17:13:12 +02:00
Emil Ernerfeldt
cf591da1a0 Use past tense in all changelogs (for consistency) 2022-07-23 23:55:21 +02:00
Emil Ernerfeldt
8b3d218f4b Update changelogs with all changes from all PR:s since 0.18 release 2022-07-23 23:54:13 +02:00
Emil Ernerfeldt
d76d055d49 eframe: only enable puffin feature on egui_glow if glow is enabled 2022-07-22 11:09:27 +02:00
Emil Ernerfeldt
41f31116ce Layout::left_to_right/right_to_left now takes the valign as argument
Previous default was `Align::Center`.

Closes https://github.com/emilk/egui/issues/1040
Closes https://github.com/emilk/egui/issues/1836
Closes https://github.com/emilk/egui/pull/1837
2022-07-22 11:02:26 +02:00
Emil Ernerfeldt
9f1f0a9038 Add documentation for Layout
Related to https://github.com/emilk/egui/issues/1836
2022-07-22 10:41:14 +02:00
Emil Ernerfeldt
fdc2d1cd6d epaint tessellator: don't emit empty meshes
Closes https://github.com/emilk/egui/issues/1772
2022-07-22 10:25:20 +02:00
Emil Ernerfeldt
77b4bacdf4 Make widget_text module public
Closes https://github.com/emilk/egui/issues/1756
2022-07-21 20:52:50 +02:00
Emil Ernerfeldt
0337d78eaa Fix inpuit requiring ALT key
Common on Mac, maybe other platforms too.

Closes https://github.com/emilk/egui/issues/1795

Bug introduced in https://github.com/emilk/egui/pull/1697
2022-07-21 20:25:53 +02:00
Julian
0338843950
(eframe) add Frame.set_visible (#1808) 2022-07-21 20:16:56 +02:00
Milo Moisson
cbe22a0371
Update the link to the nannou egui integration. (#1827)
The project has now moved to the official repository. The old is an archived repo.
Thanks
2022-07-21 19:43:42 +02:00
Sahil Singh
48d48096eb
Add support for ctrl+h as backspace (#1812) 2022-07-21 19:23:24 +02:00
Emil Ernerfeldt
bf15bb6e19
Use cargo cranky instead of cargo clippy (#1820)
* Use cargo cranky instead of cargo clippy

cargo cranky (https://github.com/ericseppanen/cargo-cranky)
is a new tool that passes lints specified in a Cranky.toml
to cargo clippy.

This is a possible solution to
https://github.com/rust-lang/cargo/issues/5034

* Remove `-W clippy::all` from `check.sh` (rely on `Cranky.toml` instead)
2022-07-20 12:34:19 +02:00
Emil Ernerfeldt
898f4804b7 Enable and fix a bunch more lints 2022-07-11 23:08:48 +02:00
Aiden
e76c919c7e
Added WebGlContextOption for eframe::WebOptions (#1803)
* Added WebGlContextOption for eframe::WebOptions

* Fix doclink

* Fix minor doc issue

Co-authored-by: xxvvii <xuwei@aecg.com.cn>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-07-08 10:40:44 +02:00
Emil Ernerfeldt
c062bca6ee faster builds: remove tracing-attributes feature from tracing 2022-07-04 00:11:08 +02:00
Emil Ernerfeldt
0f0031ebbb
cargo update (#1794)
* cargo update

    Updating crates.io index
    Updating anyhow v1.0.57 -> v1.0.58
    Updating arboard v2.1.0 -> v2.1.1
    Updating async-broadcast v0.3.4 -> v0.4.0
    Updating async-io v1.6.0 -> v1.7.0
    Updating async-trait v0.1.53 -> v0.1.56
    Updating bindgen v0.59.2 -> v0.60.1
    Updating bumpalo v3.9.1 -> v3.10.0
    Updating bytemuck v1.9.1 -> v1.10.0
    Updating cfg-expr v0.10.2 -> v0.10.3
    Updating clang-sys v1.3.1 -> v1.3.3
      Adding clap v3.2.8
      Adding clap_lex v0.2.4
    Updating crossbeam-channel v0.5.4 -> v0.5.5
    Updating crossbeam-epoch v0.9.8 -> v0.9.9
    Updating crossbeam-utils v0.8.8 -> v0.8.10
    Updating dyn-clone v1.0.5 -> v1.0.6
    Updating either v1.6.1 -> v1.7.0
    Updating enum-map v2.1.0 -> v2.4.0
    Updating enum-map-derive v0.8.0 -> v0.9.0
    Updating flate2 v1.0.23 -> v1.0.24
    Updating getrandom v0.2.6 -> v0.2.7
    Updating gif v0.11.3 -> v0.11.4
      Adding hashbrown v0.12.1
    Updating indexmap v1.8.1 -> v1.9.1
    Updating itoa v1.0.1 -> v1.0.2
    Updating jpeg-decoder v0.2.4 -> v0.2.6
    Updating linked-hash-map v0.5.4 -> v0.5.6
    Updating log v0.4.16 -> v0.4.17
    Updating memmap2 v0.5.3 -> v0.5.4
    Updating miniz_oxide v0.5.1 -> v0.5.3
    Updating mio v0.8.2 -> v0.8.4
    Removing miow v0.3.7
      Adding nix v0.24.1
    Removing ntapi v0.3.7
    Updating num-rational v0.4.0 -> v0.4.1
    Updating num-traits v0.2.14 -> v0.2.15
    Updating num_threads v0.1.5 -> v0.1.6
    Updating object v0.28.3 -> v0.28.4
    Updating once_cell v1.10.0 -> v1.12.0
      Adding os_str_bytes v6.1.0
    Updating parking_lot v0.12.0 -> v0.12.1
    Updating parking_lot_core v0.9.1 -> v0.9.3
    Updating proc-macro2 v1.0.37 -> v1.0.40
    Updating profiling v1.0.5 -> v1.0.6
    Updating puffin v0.13.1 -> v0.13.3
    Updating quote v1.0.18 -> v1.0.20
    Updating rayon v1.5.2 -> v1.5.3
    Updating rayon-core v1.9.2 -> v1.9.3
    Updating regex v1.5.5 -> v1.5.6
    Updating regex-syntax v0.6.25 -> v0.6.26
      Adding remove_dir_all v0.5.3
    Updating rfd v0.8.0 -> v0.8.4
    Updating rgb v0.8.32 -> v0.8.33
    Updating ron v0.7.0 -> v0.7.1
    Updating rustls v0.20.4 -> v0.20.6
    Updating rustybuzz v0.5.0 -> v0.5.1
    Updating ryu v1.0.9 -> v1.0.10
    Updating semver v1.0.7 -> v1.0.12
    Updating serde v1.0.136 -> v1.0.138
    Updating serde_derive v1.0.136 -> v1.0.138
    Updating serde_json v1.0.79 -> v1.0.82
    Updating serde_repr v0.1.7 -> v0.1.8
    Updating smallvec v1.8.0 -> v1.9.0
      Adding smithay-client-toolkit v0.16.0
    Updating smithay-clipboard v0.6.5 -> v0.6.6
    Updating str-buf v1.0.5 -> v1.0.6
    Removing strsim v0.8.0
    Updating svgtypes v0.8.0 -> v0.8.1
    Updating syn v1.0.92 -> v1.0.98
      Adding tempfile v3.3.0
      Adding textwrap v0.15.0
    Updating thiserror v1.0.30 -> v1.0.31
    Updating thiserror-impl v1.0.30 -> v1.0.31
    Removing time v0.1.43
    Removing time v0.3.9
      Adding time v0.1.44
      Adding time v0.3.11
    Updating tiny-skia v0.6.3 -> v0.6.6
    Updating tracing v0.1.34 -> v0.1.35
    Updating tracing-attributes v0.1.21 -> v0.1.22
    Updating tracing-core v0.1.26 -> v0.1.28
    Updating tracing-subscriber v0.3.11 -> v0.3.14
    Updating ttf-parser v0.15.0 -> v0.15.2
    Updating tts v0.20.3 -> v0.20.4
    Updating twox-hash v1.6.2 -> v1.6.3
      Adding uds_windows v1.0.2
      Adding unicode-ident v1.0.1
    Updating unicode-normalization v0.1.19 -> v0.1.21
    Removing vec_map v0.8.2
    Updating wasi v0.10.2+wasi-snapshot-preview1 -> v0.10.0+wasi-snapshot-preview1
    Updating wgpu v0.13.0 -> v0.13.1
    Updating wgpu-core v0.13.0 -> v0.13.1
    Updating wgpu-hal v0.13.0 -> v0.13.1
    Removing windows v0.32.0
      Adding windows v0.33.0
      Adding windows v0.37.0
    Updating windows-sys v0.32.0 -> v0.36.1
    Removing windows_aarch64_msvc v0.32.0
      Adding windows_aarch64_msvc v0.33.0
      Adding windows_aarch64_msvc v0.36.1
      Adding windows_aarch64_msvc v0.37.0
    Removing windows_i686_gnu v0.32.0
      Adding windows_i686_gnu v0.33.0
      Adding windows_i686_gnu v0.36.1
      Adding windows_i686_gnu v0.37.0
    Removing windows_i686_msvc v0.32.0
      Adding windows_i686_msvc v0.33.0
      Adding windows_i686_msvc v0.36.1
      Adding windows_i686_msvc v0.37.0
    Removing windows_x86_64_gnu v0.32.0
      Adding windows_x86_64_gnu v0.33.0
      Adding windows_x86_64_gnu v0.36.1
      Adding windows_x86_64_gnu v0.37.0
    Removing windows_x86_64_msvc v0.32.0
      Adding windows_x86_64_msvc v0.33.0
      Adding windows_x86_64_msvc v0.36.1
      Adding windows_x86_64_msvc v0.37.0
    Updating zbus v2.1.1 -> v2.3.2
    Updating zbus_macros v2.1.1 -> v2.3.2
    Updating zstd v0.10.0+zstd.1.5.2 -> v0.11.2+zstd.1.5.2
    Updating zstd-safe v4.1.4+zstd.1.5.2 -> v5.0.2+zstd.1.5.2
    Updating zstd-sys v1.6.3+zstd.1.5.2 -> v2.0.1+zstd.1.5.2
    Updating zvariant v3.1.2 -> v3.4.1
    Updating zvariant_derive v3.1.2 -> v3.4.1

❯ cargo update -p smithay-clipboard --precise 0.6.5
    Updating crates.io index
    Removing nix v0.24.1
    Removing smithay-client-toolkit v0.16.0
    Updating smithay-clipboard v0.6.6 -> v0.6.5

* Downgrade enum-map so we can build with rust 1.60

* update syntect

* Update usvg and resvg

* Fix syntect update

* Update tts to 0.22

* Make egui_demo_app compile for wasm with wgpu feature

This broke in https://github.com/emilk/egui/pull/1781

* Ignore rfd tree in deny.toml

* Revert "Update tts to 0.22"

This reverts commit 2e1280b61ef9422c76491ab718ad8da105657097.

* Explain why tts is stuck on an old version

* Downgrade `rfd` to avoid problems with duplicate `windows` crate
2022-07-03 20:12:57 +02:00
Emil Ernerfeldt
bd2cab2f0e
Update three d (#1793)
* Update three-d to 0.12

* Fix Z fighting in three-d example

Closes https://github.com/emilk/egui/issues/1744

* cargo deny: ignore three-d (only used in examples)
2022-07-03 18:29:12 +02:00
Ashley
9739009f20
Update wgpu to 0.13 (#1670)
* Update the wgsl syntax used in egui-wgpu

* Updates for the latest version of wgpu

* Update the wgpu version

* get_preffered_format -> get_supported_formats

* Just use an array access for compatible formats

* Use the naga cli to validate the egui demo app custom wgpu shader

* Run cargo check on the custom3d wgpu app

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-07-03 15:43:39 +02:00
Barugon
433719717a
Don't call scroll if TextEdit is fully in view (#1779)
* Don't call scroll if TextEdit is fully in view

* Explain why the new logic was added

* cargo fmt

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-07-03 15:43:22 +02:00
Thomas Hansen
cb9bc8698d
Browser Hotkey Conflicts (#1697)
* code hotkey to N, move superscript hotkey to Y

ctrl A S D F G H are all taken, CTRL Q is traditionally to remove formatting and should be reserved for that. CTRL W E R T are also all taken. CTRL Z X C V are taken so all of the first 4/5 keys of each row except Q are inaccessible.

* strike through conflict, update text

* fixed underline command

* added ALTSHIFT, browser documentation

* underline ALTSHIFT Q

it leaves the Q character which is considered a bug but before this pull underline was not working entirely so this is progress

* update text

* ALTSHIFT is treated as a command

* added eighth command, ALTSHIFT+W adds two spaces

* CTRL+Y to toggle case on text_edit demo

* better code

* Revised Menu

* fix dead link

* Update lib.rs

* Update easy_mark_editor.rs

* Update egui/src/data/input.rs

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

* update

* reverted variables used for debugging

* fixed labels hotkey conflict

* comments

* fmt

* cargo fmt

* Nice hotkey menu

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-07-03 15:25:35 +02:00
Yuan Chang
14ae4b24a7
Support rich text convert from cow-string. (#1774) 2022-07-03 15:16:05 +02:00
Sahil Singh
5f364795cc
Add doc example for hint_text persistence (#1776) 2022-07-03 15:15:55 +02:00
Zeenobit
0c65a9df41
Add body_unindented to HeaderResponse (#1731) 2022-07-03 14:57:42 +02:00
Ashley
980a06b95e
Don't force the webgl backend in egui-wgpu (#1781) 2022-07-03 14:37:21 +02:00
Emil Ernerfeldt
406703568e Minor clippy fixes (clippy::format_push_string) 2022-07-03 11:58:53 +02:00
Emil Ernerfeldt
eeae485629
Implement repaint_after for eframe web (#1760)
* Implement repaint_after for eframe web

Follow-up to #1694

* cargo fmt

* Simplify demo UI for "repaint_after"
2022-06-22 14:25:42 +02:00
Red Artist
935913b1ec
Add Context::request_repaint_after (#1694) 2022-06-22 13:19:13 +02:00
Michael Birdhouse
1a89cb35e1
Fix uneven table striping (#1680)
* Fix uneven table striping

* simplify the code

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-06-22 12:33:51 +02:00
wucke13
bd5f553c3a
Fix multiple partial updates of the same texture (#1338)
Co-authored-by: Wanja Zaeske <wanja.zaeske@dlr.de>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-06-19 22:49:06 +02:00
Barugon
f5b2363fff
Fix persistence of window position (#1745)
* Fix window position

* Better comment

* Add doc link
2022-06-19 16:13:10 +02:00
Barugon
5b3bff22e2
Make Windows specific code Windows specific (#1747) 2022-06-19 16:12:58 +02:00
Nazarí González
9c26093fea
Added notan_egui as 3rd party integration (#1741) 2022-06-18 16:10:48 +02:00
mitchmindtree
7eeb292adf
Refactor common code in egui-wgpu shader entry points (#1730)
Creates `unpack_color` and `position_from_screen` functions to share
common logic between the alternative vertex shader entry points.
2022-06-11 13:52:06 +02:00
Emil Ernerfeldt
8c7c4c764b
Don't load fonts in doctests (#1711)
I was hoping this would speed up the doctests, but it doesn't really
2022-06-10 14:33:16 +02:00
Emil Ernerfeldt
317436c057
Use dark-light on Mac and Windows (#1726)
* Use dark-light on Mac and Windows

dark-light has a nasty problem on Linux: https://github.com/frewsxcv/rust-dark-light/issues/17

So we made dark-light opt-in in https://github.com/emilk/egui/pull/1437

This PR makes dark-light a default dependency again,
but only use it on Max and Windows.

This is controlled with the new NativeOptions::follow_system_theme.
If this isn't enabled, then NativeOptions::default_theme is used.

* Add eframe::WebOptions
2022-06-09 17:41:59 +02:00
Emil Ernerfeldt
29973e5d02 Make document-features an opt-in dependency 2022-06-09 17:41:37 +02:00
Charles Ray
4525cad7af
Constrain date picker to screen (#1699)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-06-09 16:05:04 +02:00
Zicklag
62a00c4597
Fix bug in egui-wgpu (#1716) 2022-06-09 15:59:43 +02:00
Cisco
8446665b02
Fix on-hover typo in plot module (#1714) 2022-06-09 15:28:18 +02:00
Emil Ernerfeldt
3169ce62d5
Document feature flags using crate document-features (#1725) 2022-06-09 15:27:22 +02:00
Zicklag
218d4d4eea
WGPU PaintCallback Fixes (#1704)
* Expose egui WGPU Textures and Limit Exposed API

This allows paint callbacks to access textures allocated by egui, and
also hides the functions on the `RenderPass` that users should not need
to call.

* Fix WGPU Rendering Bug When Using Paint Callbacks

Depending on the order custom paint callbacks were rendered, some of the
egui meshes would previously not be rendered at all in a seemingly random
fashion.

* Make egui_wgpu::Renderer Functions Public Again
2022-06-02 22:45:54 +02:00
Emil Ernerfeldt
083e20474b
deny warnings on CI and check.sh with -D warnings (#1710) 2022-06-02 21:06:12 +02:00
John Wells
f8ce51e5aa
Add screen-13-egui link to README.md (#1709) 2022-06-02 20:55:02 +02:00
follower
e2bfdbe8b0
egui-wgpu: Ensure docs for winit feature are generated. (#1700)
per https://github.com/emilk/egui/issues/381#issuecomment-839707073
2022-05-31 12:24:12 +02:00
Emil Ernerfeldt
e6cfa5028e Add link to chinese translation of egui docs 2022-05-29 21:16:32 +02:00
Emil Ernerfeldt
ac9faaf1c1 epaint_assert that partial texture update is within texture size 2022-05-29 21:00:49 +02:00
Emil Ernerfeldt
42edb92232 Fix compilation of screenshot example (merge race) 2022-05-29 20:59:39 +02:00
Erdal Acar
7987920f7e
Fix exit handling (#1696) 2022-05-29 20:37:31 +02:00
Emil Ernerfeldt
20c8ee302c
Allow software rendering OpenGL by default (#1693)
Follow-up to https://github.com/emilk/egui/pull/1681
2022-05-29 20:37:19 +02:00
René Rössler
4a7a2d6430
eframe::App::post_rendering (#1591) 2022-05-29 20:33:04 +02:00
Ashley
abff2dcae2
Add some padding to the egui-wgpu uniform buffer for WebGL (#1671)
Required on Chromium
2022-05-28 18:41:01 +02:00
Emil Ernerfeldt
2771c8c3e5 Fix doclinks 2022-05-28 18:09:08 +02:00
Benedikt Terhechte
72e38370fe
Add flag to disable hardware acceleration (#1681)
This is a fix for the behaviour on macOS platforms where any egui app would use the dedicated GPU and consume more power than needed. Not all apps might have dedicated GPU requirements.
2022-05-28 17:53:05 +02:00
Zicklag
1d9524cc59
Re-implement PaintCallbacks With Support for WGPU (#1684)
* Re-implement PaintCallbacks With Support for WGPU

This makes breaking changes to the PaintCallback system, but makes it
flexible enough to support both the WGPU and glow backends with custom
rendering.

Also adds a WGPU equivalent to the glow demo for custom painting.
2022-05-28 17:52:36 +02:00
Emil Ernerfeldt
8173093c67 run cargo check --locked on CI
This ensures the Cargo.lock is up-to-date in PR:s
2022-05-28 17:32:59 +02:00
sy1ntexx
f5cca2a288
Added support for 20 fn keys (#1665) 2022-05-23 17:49:48 +02:00
Emil Ernerfeldt
f2dcdfc22c Fix some broken doc links 2022-05-23 17:25:31 +02:00
Robert Bragg
a5076d4cc4
egui_winit/wgpu: enable Android support (#1634)
* egui-winit: don't assume window available at init

On Android in particular we can only initialize render state once we
have a native window, after a 'Resumed' lifecycle event. It's still
practical to be able to initialize an egui_winit::State early on
so this adds setters for the max_texture_side and pixels_per_point
that can be called once we have a valid Window and have initialized
a graphics context.

On Wayland, where we need to access the Display for clipboard handling
we now get the Display from the event loop instead of a window.

* egui-wgpu: lazily initialize render + surface state

Enable the renderer and surface state initialization to be deferred
until we know that any winit window we created has a valid native window
and enable the surface state to be updated in case the native window
changes.

In particular these changes help with running on Android where winit
windows will only have a valid native window associated with them
between Resumed and Paused lifecycle events, and so surface creation
(and render state initialization) needs to wait until the first
Resumed event, and the surface needs to be dropped/recreated based on
Paused/Resumed events.
2022-05-22 20:24:41 +02:00
Emil Ernerfeldt
fff2008255 Move three-d patch into example Cargo.tom 2022-05-22 18:14:00 +02:00
Sebastian Urban
b2510676b9
Take Glow context using Arc. (#1640)
This allows usage with a Glow context that is passed between threads.
2022-05-22 17:43:30 +02:00
Emil Ernerfeldt
a29154233b Add support for texture filters in WGPU backend 2022-05-22 17:40:56 +02:00
Florian Diebold
5d15e3d367
egui-wgpu: Add ability to register user textures (#1660) 2022-05-22 17:32:54 +02:00
Emil Ernerfeldt
cc148ca895 Add texture filters to changelogs and improve docs 2022-05-22 17:32:38 +02:00
Zicklag
1a9a0d7ec8
Add the Ability to Specify Egui Texture Filters (#1636)
Only works for egui_glow
2022-05-22 16:56:51 +02:00
Emil Ernerfeldt
f3e305a646 Replace TODO: with TODO(emilk): and update code guidelines 2022-05-21 16:53:25 +02:00
Emil Ernerfeldt
3d5e203d86
egui_glow: clear the post-processing render target (#1658) 2022-05-21 16:53:05 +02:00
Emil Ernerfeldt
32ad9c29be Use better names in glow::Painter 2022-05-21 16:03:26 +02:00
Emil Ernerfeldt
aef8a7901f Fix broken link
Closes https://github.com/emilk/egui/issues/1643
2022-05-21 15:42:43 +02:00
Emil Ernerfeldt
d6fd5dec3b
Add single-threaded deadlock detection to RwMutex (#1619) 2022-05-21 14:08:19 +02:00
Emil Ernerfeldt
810b609a80 clippy fixes 2022-05-21 13:04:58 +02:00
Emil Ernerfeldt
934fcd7e99 Add hex_color clipboard fixes to changelogs 2022-05-21 13:04:58 +02:00
Sebastian Urban
5687aa6b50
Request GLSL version 3.30. (#1639) 2022-05-21 12:52:20 +02:00
griffi-gh
27cca111c8
Fix wrong name in egui-wgpu CHANGELOG.md (#1645) 2022-05-21 12:49:37 +02:00
Emil Ernerfeldt
f807a290a4 Release egui-wgpu 0.18.0 2022-05-16 16:39:26 +02:00
Erlend Walstad
f27f67b76b
Make color-hex optional (#1632) 2022-05-16 16:38:14 +02:00
Antti Keränen
b8a5924295
Fix clipboard on Wayland (#1613)
arboard advertises that it works with Wayland, but in reality it only
works with Wayland terminal applications. To make the clipboard work
with applications that draw Wayland surfaces, arboard isn't going to
work.

Copypasta does support Wayland's graphical clipboard, but the usage
isn't documented. However, for the reasons mentioned in #1474 the move
from Copypasta to arboard makes sense.

To resolve the issue, this commit brings in an optional dependency
smithay-clipboard, that is a crate that correctly handles the Wayland
clipboard in graphical applications. It is used by default if a Wayland
window handle is found. If for some reason the handle to the Wayland
window handle cannot be fetched, arboard is used as a backup.
2022-05-16 16:37:41 +02:00
Stanislav
b008b147e5
Fix Plot auto_bounds when LinkedAxisGroup one axis (#1599)
Co-authored-by: Stanislav <enomado@users.noreply.github.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-05-15 19:27:30 +02:00
Emil Ernerfeldt
0389ce68e2
CI: Run cargo check --all-features on Windows and Mac too (#1631) 2022-05-15 19:25:24 +02:00
Luke Newcomb
d2decfa338
Add ability to convert from a hex string to Color32 using macros (#1596) 2022-05-15 17:07:30 +02:00
TicClick
3c685d7bf6
eframe: read native window position and size (#1617)
Position and dimensions are available via `eframe::Frame::info().window_info`
2022-05-13 10:15:43 +02:00
Emil Ernerfeldt
aef5942d0f Make egui_demo_app the default binary, so cargo run just works 2022-05-12 13:30:35 +02:00
Emil Ernerfeldt
2e4138d050
Add InputState::stable_dt (#1625)
This provides a better estimate of a typical frametime in reactive mode.

From the docstring of `stable_dt`:

Time since last frame (in seconds), but gracefully handles the first frame after sleeping in reactive mode.

In reactive mode (available in e.g. `eframe`), `egui` only updates when there is new input or something animating.
This can lead to large gaps of time (sleep), leading to large [`Self::unstable_dt`].

If `egui` requested a repaint the previous frame, then `egui` will use
`stable_dt = unstable_dt;`, but if `egui` did not not request a repaint last frame,
then `egui` will assume `unstable_dt` is too large, and will use
`stable_dt = predicted_dt;`.

This means that for the first frame after a sleep,
`stable_dt` will be a prediction of the delta-time until the next frame,
and in all other situations this will be an accurate measurement of time passed
since the previous frame.

Note that a frame can still stall for various reasons, so `stable_dt` can
still be unusually large in some situations.

When animating something, it is recommended that you use something like
`stable_dt.min(0.1)` - this will give you smooth animations when the framerate is good
(even in reactive mode), but will avoid large jumps when framerate is bad,
and will effectively slow down the animation when FPS drops below 10.
2022-05-12 11:23:53 +02:00
Emil Ernerfeldt
931e716b97
Add egui_wgpu crate (#1564)
Based on https://github.com/hasenbanck/egui_wgpu_backend

`egui-wgpu` is now an official backend for `eframe` (opt-in).

Use the `wgpu` feature flag on `eframe` and the `NativeOptions::renderer` settings to pick it.

Co-authored-by: Nils Hasenbanck <nils@hasenbanck.de>
Co-authored-by: Sven Niederberger <niederberger@embotech.com>
Co-authored-by: Sven Niederberger <73159570+s-nie@users.noreply.github.com>
2022-05-12 09:02:28 +02:00
4JX
3d52cc8867
Fix docstring in area.rs (#1620) 2022-05-11 22:44:29 +02:00
sy1ntexx
d850b47f9e
Added Extra1 & Extra2 pointer buttons (#1603)
Extra1 is usually extra back button on most mice & Extra2 is usually extra forward button.
2022-05-11 22:44:00 +02:00
Emil Ernerfeldt
9624de6c41
Fix dead-lock when alt-tabbing while also showing a tooltip (#1618)
Closes https://github.com/emilk/egui/issues/1609
2022-05-11 20:56:57 +02:00
4JX
c47e20c733
Allow manually painting the CollapsingState icon (#1592)
* Allow manually painting the CollapsingState icon

* Update egui/src/containers/collapsing_header.rs

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

* Oops

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-05-11 19:02:37 +02:00
Luke Newcomb
e44c9b8e54
Fix image button padding on hover (#1595) 2022-05-11 11:58:15 +02:00
Emil Ernerfeldt
7b18fab7a4
Optimize tessellation of filled circles (#1616)
When painting a scatter plot we sometimes want to paint hundreds of thousands of points (filled circles) on screen every frame.

In this PR the font texture atlas is pre-populated with some filled circled of various radii. These are then used when painting (small) filled circled, which means A LOT less triangles and vertices are generated for them.

In a new benchmark we can see a 10x speedup in circle tessellation, but the the real benefit comes in the painting of these circles: since we generate a lot less vertices, the backend painter has less to do.

In a real-life scenario with a lot of things being painted (including around 100k points) I saw tessellation go from 35ms -> 7ms and painting go from 45ms -> 1ms. This means the total frame time went from 80ms to 8ms, or a 10x speedup.
2022-05-10 19:31:19 +02:00
Emil Ernerfeldt
28efc0e1c8 emath: Implement NumExt on all integer types 2022-05-09 12:44:28 +02:00
Emil Ernerfeldt
d69ce546fa Release egui_glow 0.18.1 - remove unnecessary calls to gl.get_error 2022-05-09 12:43:37 +02:00
Nigecat
87ca29173d
Fix typo in ui.rs (waker -> weaker) (#1586) 2022-05-06 10:36:49 +02:00
Alejandro Perea
fe6e1ce28f
Add *_released & *_clicked PointerState methods (#1582) 2022-05-05 23:26:15 +02:00
Emil Ernerfeldt
e82b87ca73
Remove calls to gl.getError in release builds (#1583)
This slows down the web version a lot, especially on some browsers

Publish new web demo
2022-05-05 23:17:33 +02:00
carrotflakes
cb2298e98b
Fix CircleShape::visual_bounding_rect() (#1575) 2022-05-05 09:16:00 +02:00
Reilly Moore
1dd014cbed
mark the response of a toggle_value as changed on click (#1573) 2022-05-04 21:45:14 +02:00
René Rössler
d3af3a6de1
egui_extras date picker: fix Feb 29 crash (#1571) 2022-05-04 19:30:41 +02:00
Emil Ernerfeldt
32b4781da2
Improve web demo for mobile (#1556)
`egui_demo_app/lib`: add "About egui" window, and improve mobile layout

This makes the app responsive, removing the side bars on mobile and turning them into drop-down menus instead.
2022-05-02 13:13:35 +02:00
Emil Ernerfeldt
078be52ff8 README.md: Fix broken links to toggle_switch.rs example code 2022-05-01 15:27:12 +02:00
Emil Ernerfeldt
b30224471c
Release 0.18.1 of egui & epaint - Change Shape::Callback to &mut dyn Any (#1552) 2022-05-01 11:35:29 +02:00
Emil Ernerfeldt
dc26890a80 Release 0.18.0 - Shape::Callback, Table, and better text contrast 2022-04-30 20:27:27 +02:00
Emil Ernerfeldt
9394e7ea22 Publish new web demo 2022-04-30 20:23:40 +02:00
Emil Ernerfeldt
f0e6332b42
Fix cargo deny (#1549)
* Make the strip demo less ugly

* revert duplicated windows dependency in Cargo.lock

* cargo update

* Remove duplicated windows dependency by reverting rfd

* And again for parking_lot_core
2022-04-30 20:18:59 +02:00
Emil Ernerfeldt
d24599f3cc Revert tts version to 0.20 2022-04-30 19:23:44 +02:00
Emil Ernerfeldt
45b0a071c8 Clean up changelogs and add contributors section 2022-04-30 19:21:40 +02:00
Emil Ernerfeldt
b41fb585fe Add doclinks to egui_extras::RetainedImage 2022-04-30 18:36:24 +02:00
Emil Ernerfeldt
ec57683c1a Update some crates 2022-04-30 18:05:58 +02:00
Emil Ernerfeldt
00471f2887 Update cint 0.2 -> 0.3 2022-04-30 18:01:24 +02:00
Emil Ernerfeldt
ed4fe43e7a Update tts 0.20 -> 0.21 2022-04-30 17:59:30 +02:00
Emil Ernerfeldt
30aeb2a94f Update webbrowser 0.6 -> 0.7 2022-04-30 17:58:12 +02:00
Emil Ernerfeldt
11e8ffa079 Reduce Plot::show code bloat by adding Plot::show_dyn 2022-04-30 17:51:07 +02:00
Emil Ernerfeldt
003dc4aac9 egui_demo_app: add link to my twitter 2022-04-30 17:50:39 +02:00
Emil Ernerfeldt
051deb293f mute warning when building for wasm 2022-04-30 17:47:30 +02:00
Emil Ernerfeldt
51f6cd9c58 Simplify plot demo code 2022-04-30 17:45:39 +02:00
Emil Ernerfeldt
e5ccfedd61 Escape some strings in build_demo_web.sh 2022-04-30 17:18:31 +02:00
Emil Ernerfeldt
462ad5a7f6 egui_demo_app: fall back to dark mode if there is no system preference 2022-04-30 17:18:31 +02:00
Emil Ernerfeldt
3ace620f37 Code cleanup in eframe web backend 2022-04-30 17:18:31 +02:00
Emil Ernerfeldt
9b9c5005d3 CI and check.sh: run cargo doc for all crates 2022-04-30 15:51:46 +02:00
Emil Ernerfeldt
603e5bc5fb Add Rect::lerp 2022-04-30 15:50:12 +02:00
Emil Ernerfeldt
b137f16472 Make members of Response public, but hide documentation
Discourage their direct use, but allow it
2022-04-30 15:49:50 +02:00
Emil Ernerfeldt
c869f67066 Simplify file_dialog example 2022-04-30 15:49:22 +02:00
Emil Ernerfeldt
8a2470b45e example hello_world: remove frame.set_window_size
it doesn't work very well
2022-04-30 15:47:55 +02:00
Emil Ernerfeldt
afd27ca664 Simplify eframe web panic handler code 2022-04-30 15:47:34 +02:00
Emil Ernerfeldt
1983d19614 eframe: fix wrong clear color on native 2022-04-30 15:42:35 +02:00
Emil Ernerfeldt
da2ce58127 egui_demo_app: don't do a transparent native window
It doesn't work great on Mac
2022-04-30 15:42:18 +02:00
Emil Ernerfeldt
e1bcaeebe5 App::clear_color: give egui::Visuals as argument 2022-04-30 15:41:43 +02:00
Emil Ernerfeldt
8303523ccf
Precompute rounded vertices (#1547)
Co-authored-by: Daniel Buch Hansen <boogiewasthere@gmail.com>
2022-04-30 13:14:33 +02:00
Emil Ernerfeldt
3a83a600bb
Add a custom 3D demo using glow to egui_demo_app (#1546) 2022-04-30 12:58:29 +02:00
Emil Ernerfeldt
bb421c7e8a
Remove egui_web and epi (#1545)
* Remove integration name (it is always eframe)

* Remove egui_web crate

* Move egui_web/CHANGELOG.md into eframe/CHANGELOG.md

* Remove all mentions of egui_web

* Remove epi crate and absorb into eframe

* egui_glow: only use puffin on native

* Remove WASM doc from CI (we don't generate it anyways!)

* Remove eframe::epi and improve eframe docs
2022-04-30 10:44:35 +02:00
Emil Ernerfeldt
18d529203a Publish new web demo to fix source links following PR #1540
https://github.com/emilk/egui/pull/1540
2022-04-29 08:23:56 +02:00
Emil Ernerfeldt
ed002acc68
Refactor: move things into eframe (#1542)
* Move all epi-related code from egui_glow into eframe

* Move epi stuff from egui-winit into eframe

* Remove mention of epi in egui

* Remove mention of epi in egui_glium

* Remove trait epi::NativeTexture

* Remove confusing mentions of epi

* Refactor egui_web: break up into smaller files

* Clean up feature flags further, and update changelogs

* Clean up check.sh

* Small cleanup of egui_web/Cargo.toml

* Fix dependencies for pure_glow example

* Fix clippy false positive
2022-04-29 08:17:49 +02:00
Nihaal Sangha
5ea51c3f0b
Fix typo in README (#1533) 2022-04-29 00:36:04 +02:00
Emil Ernerfeldt
881f9b4161 Deprecate `CollapsingHeader::selectable/selected
Better to use the more powerful CollapsingState::show_header
2022-04-28 20:06:55 +02:00
Emil Ernerfeldt
355d70d2b9
Move code from egui_demo_lib to egui_demo_app (#1540)
Also clean up feature names and dependencies
2022-04-28 11:23:34 +02:00
Emil Ernerfeldt
39917bec26
Collapsing header with custom header (#1538)
* Returns openness in CollapsingResponse
* Make CollapsingState a building block for custom collapsing headers
* Add a demo of the custom collapsing header
* Revert to much simpler tree demo
* Add CollapsingState::is_open and CollapsingState::set_open
2022-04-28 11:09:44 +02:00
Emil Ernerfeldt
8e266760e2 Silence warnings about unused profiling macros in egui-winit 2022-04-27 10:15:32 +02:00
Emil Ernerfeldt
1133f3a42b Improve look of color button and show_color slightly 2022-04-27 10:06:42 +02:00
Emil Ernerfeldt
06802cb0a0 Add Rounding::at_least and Rounding::at_most 2022-04-27 10:05:09 +02:00
Emil Ernerfeldt
e3b77e320a Fix broken doclink 2022-04-27 09:59:05 +02:00
Emil Ernerfeldt
2fd20308e5 Add Ui::toggle_value 2022-04-26 21:36:22 +02:00
Emil Ernerfeldt
4d2eb5b71e Don't export macros that should only be pub(crate) 2022-04-25 22:01:32 +02:00
Emil Ernerfeldt
95c0174331 Change canvas frame margin two points 2022-04-25 16:58:24 +02:00
Emil Ernerfeldt
0862712595 Add Vec2::dot 2022-04-25 11:26:54 +02:00
trevyn
888cd9c3eb
Don't paint insertion point if text is immutable (#1523) 2022-04-21 09:19:40 +02:00
Emil Ernerfeldt
c70c72ef61 eframe: make sure we wait for FileStorage to complete the save
Closes https://github.com/emilk/egui/pull/1520
2022-04-20 21:54:31 +02:00
Emil Ernerfeldt
f789159a4a Don't persist Grid sizes
A) there are potentially a lot of Grids, using up a lot of space
(and therefore serialization time).

B) if the code changes, the grid should also change,
and not remember old sizes
2022-04-19 21:28:45 +02:00
Emil Ernerfeldt
558891c146 eframe native: persist app state in background thread
Gives smoother frame rate
2022-04-19 21:26:35 +02:00
Emil Ernerfeldt
1dee439ab1 Added CursorIcons for resizing columns, rows and 8 cardinal directions 2022-04-19 16:56:27 +02:00
trevyn
2932c36238
Add triple-click support (#1512) 2022-04-19 15:14:55 +02:00
Emil Ernerfeldt
4231a5303b Clean up custom plot axis demo 2022-04-19 11:43:22 +02:00
Emil Ernerfeldt
e82ea0c4b4 Plot: estimate bounds for generator functions 2022-04-19 11:43:04 +02:00
Emil Ernerfeldt
cde5f95fc9 code cleanup: replace some for_each with normal for-loops 2022-04-19 11:42:05 +02:00
Jan Haller
e22f6d9a7e
Customize grid spacing in plots (#1180) 2022-04-19 11:35:05 +02:00
Emil Ernerfeldt
676ff047e9 Add Ui::scroll_with_delta, and improve scroll docs 2022-04-19 10:46:24 +02:00
Emil Ernerfeldt
5414e8a7fb Rename Painter::sub_region to Painter::with_clip_rect 2022-04-19 10:00:34 +02:00
Emil Ernerfeldt
2d2022fb72
Add Link widget (#1506)
This looks like a Hyperlink, but doesn't do anything when clicked.
Or rather: it lets the user decide what happens on click.

Closes https://github.com/emilk/egui/issues/1152
2022-04-16 22:55:15 +02:00
Boby
96335d5f45
added epi::Frame::set_window_pos (#1505)
this allows setting the position of window at runtime
2022-04-16 22:27:22 +02:00
Emil Ernerfeldt
2b861f86e8 egui_web: Ignore input of "GroupNext"
Closes https://github.com/emilk/egui/issues/510
2022-04-16 10:38:25 +02:00
bigfarts
2ca72e0bae
Add line breaking rules for Japanese text. Fixes #1497. (#1498)
This allows line breaks after any kana, unless they are immediately followed by a gyōtō kinsoku character, in which case the line may not break at that point.

Also pedantically renamed is_chinese to is_cjk_ideograph as is_chinese will also cause line breaks on Japanese kanji.
2022-04-16 10:36:16 +02:00
Zachary Kohnen
abf340c62a
egui_web: Fix misplaced resize and other event handlers (#1503)
Fix copy-paste error introduced in #1306
2022-04-16 10:08:46 +02:00
Emil Ernerfeldt
2355828d41 Remove epaint::mutex::Arc type alias 2022-04-15 15:18:21 +02:00
Emil Ernerfeldt
5a78213421
Enable depth buffer in WebGL backend (#1499)
This is useful when embedding 3D into eframe using egui::PaintCallback
2022-04-15 12:41:42 +02:00
Emil Ernerfeldt
f3e31391e0 Pass clip rectangle to PaintCallback 2022-04-15 10:31:33 +02:00
Stanisław Jelnicki
2745699bd6
Add Ui::spinner shortcut method (#1494) 2022-04-15 07:39:08 +02:00
tami5
b738418243
New example 'custom_font_style' + improve docs (#1476) 2022-04-14 07:54:01 +02:00
Emil Ernerfeldt
a7b6334784 Misc doc improvements 2022-04-13 22:14:34 +02:00
Emil Ernerfeldt
2ae93c40ab
Move examples out of eframe/examples into examples/ (#1486)
* Move examples out of eframe/examples into examples/

Give each example a `Cargo.toml` and `src/main.rs`.
This makes it easier for people to use as templates.

* Update README.md with more deps needed on vanilla Ubuntu
* Install libgtk-3-dev on CI, hoping that will fix something
2022-04-13 16:13:24 +02:00
Emil Ernerfeldt
170b21b63e
Add opt-in support for the 'puffin' profiler in eframe (#1483) 2022-04-13 11:06:13 +02:00
Emil Ernerfeldt
973c3f22d1
Revert "Update tts to 0.21.1" (#1482)
This reverts commit de038b9546.
2022-04-12 21:40:57 +02:00
Emil Ernerfeldt
08b208586a egui-winit: if clipboard fails to start, fall back to local clipboard 2022-04-12 11:39:35 +02:00
Emil Ernerfeldt
d364dfac66 Add Rect::distance_to_pos 2022-04-12 11:33:48 +02:00
Emil Ernerfeldt
038b3cf2e2 Add Painter::hline and Painter::vline 2022-04-12 10:54:38 +02:00
Emil Ernerfeldt
701ae3cb46 egui_extras::Table: fix bugs in the virtual scrolling 2022-04-11 17:54:57 +02:00
Emil Ernerfeldt
56b127f209 Simplify table demo 2022-04-11 17:25:44 +02:00
Emil Ernerfeldt
de038b9546 Update tts to 0.21.1 2022-04-11 16:36:25 +02:00
Emil Ernerfeldt
efc0b992e0 egui_extras: fix bug when restoring persisted table widths 2022-04-11 14:27:32 +02:00
Emil Ernerfeldt
e97241861e egui_extras Table/Strip: less aggressive cell clipping 2022-04-11 10:55:52 +02:00
Emil Ernerfeldt
917f9e1768 Refactor and simplify feature flags 2022-04-11 10:53:16 +02:00
Emil Ernerfeldt
426b933d2f egui_extras: add cell_layout option to set the layout of all cells 2022-04-11 10:29:34 +02:00
Emil Ernerfeldt
0e0eedfdda Add ui.horizontal_centered 2022-04-11 10:28:45 +02:00
Emil Ernerfeldt
772ef84242 egui_extras Table/Strip: fix some spacing bugs 2022-04-11 10:19:07 +02:00
Emil Ernerfeldt
c88e1f8b29 egui_extras: improve Table/Strip docs, and only panic in debug builds 2022-04-11 10:17:36 +02:00
Emil Ernerfeldt
65d16695ae
Replace copypasta with arboard (#1475)
* Replace copypasta with arboard

Closes https://github.com/emilk/egui/issues/1474

* Clean up deny.toml
2022-04-10 16:41:07 +02:00
Alexis Sellier
b036257729
Make FontId functions constant (#1463) 2022-04-10 16:40:42 +02:00
Emil Ernerfeldt
9b37c82d46 Update three-d to 0.11 2022-04-10 10:00:57 +02:00
Emil Ernerfeldt
29d214912f Improve explanation of immediate mode
Closes https://github.com/emilk/egui/issues/1465
2022-04-10 09:43:23 +02:00
Emil Ernerfeldt
c2ab0404b7
Fix crash in text layout (#1468)
Fix text layout bug added in https://github.com/emilk/egui/pull/1291
2022-04-09 22:43:40 +02:00
Emil Ernerfeldt
cf0338d48f
Rust 1.60.0 + refactor cargo features (#1467)
* Update to rust 1.60.0
* Rename the feature `convert_bytemuck` to `bytemuck`
* Rename the feature `serialize` to `serde`.
* Make use of the "weak dependency" cargo feature
* Set rust-version = "1.60" for all crates
* egui_glow: clipboard, links, persistence & winit are now opt-in features
2022-04-09 13:54:47 +02:00
Emil Ernerfeldt
bdfc512b01 Add docstring example for TableBody::rows 2022-04-09 13:34:55 +02:00
Emil Ernerfeldt
cd0fb1f3d9 Simplify table demo 2022-04-09 13:18:57 +02:00
wayne
0c87e02f55
egui_extras: enable virtual scroll for heterogenous rows (#1444)
Introduce `TableBody::heterogenous_rows` for "virtual scrolling" over rows with differing heights.
2022-04-09 13:18:33 +02:00
Friz64
dd58d5175f
Replace top_most_layer with more flexible layer_ids (#1266) 2022-04-08 09:06:04 +02:00
Emil Ernerfeldt
8cf196a34b Use proper shebang for all bash scripts: #!/usr/bin/env bash 2022-04-07 17:34:34 +02:00
Emil Ernerfeldt
7cd285ecbc misc clippy fixes from 1.60.0 2022-04-07 17:03:19 +02:00
Zachary Kohnen
dffab1c737
Add Shape::galley_with_color (#1461) 2022-04-06 18:47:46 +02:00
Emil Ernerfeldt
68d5806b41 Add a text blending test to the color test 2022-04-05 14:23:30 +02:00
Emil Ernerfeldt
a9ae8c3e2c
Smaller checkboxes and radiobutton without a label (#1456)
Co-authored-by: Michael Völkl <michaelvoelkl@zoho.com>
2022-04-05 09:16:58 +02:00
Emil Ernerfeldt
bd25526a4f check.sh: fail on rustdoc failure
Same as https://github.com/emilk/egui/pull/1454
2022-04-05 08:07:57 +02:00
Nicklas Møller
f8e833ad8b
Fail docs build when warnings are generated (#1454) 2022-04-05 08:06:41 +02:00
Emil Ernerfeldt
c3b6d1bab9
Warn about Id clashes for Grid, Plot, ScrollArea, Table (#1452)
Id clashes can cause subtle bugs.

egui already warns when the same Id is used to interact with different
parts of the screen.

This adds warnings about id clashes for some widgets that store state:
Grid, Plot, ScrollArea, Table.

The PR also adds `Context::check_for_id_clash` so users who create
their own widgets can add the same type of check.
2022-04-04 13:13:34 +02:00
Emil Ernerfeldt
2d30bd751c emath: add Rect::signed_distance_to_pos and Vec2::abs 2022-04-04 11:24:08 +02:00
Emil Ernerfeldt
5d19f381f9 Fix TexEdit clipping half the cursor when in the first column
Closes https://github.com/emilk/egui/issues/1449
2022-04-04 09:20:19 +02:00
Emil Ernerfeldt
dfd6a91cb0 Fix doclinks 2022-04-03 20:38:51 +02:00
awaken1ng
901b7c7994
[epaint] Add more text wrapping options (#1291) 2022-04-03 20:28:47 +02:00
Emil Ernerfeldt
d09fa63d9c Fix Ui::add_visible sometimes leaving the Ui in a disabled state
Closes https://github.com/emilk/egui/issues/1436
2022-04-03 19:38:34 +02:00
4JX
6091370962
Add more doc-links in docstrings (#1419) 2022-04-03 18:18:35 +02:00
Emil Ernerfeldt
861b0e11ba
Fix sh/check.sh (#1446)
* Don't bother serializing date in widget gallery
* Make egui_extras non-optional dependency of egui_demo_lib
2022-04-03 18:14:40 +02:00
Emil Ernerfeldt
10f30a0c52
Fix tessellation of Shape::Vec of heterogenous TextureId:s (#1445)
Closes https://github.com/emilk/egui/issues/1443
2022-04-03 18:14:27 +02:00
Lucas Kent
c2039920de
Add Plot::allow_scroll (#1382) 2022-04-03 10:43:55 +02:00
Emil Ernerfeldt
95efbbc03e
Remember to update glow window size when DPI changes (#1441) 2022-04-03 10:20:49 +02:00
Emil Ernerfeldt
aa6a2bb73f Update PR template to suggests users to run ./sh/check.sh 2022-04-03 09:52:25 +02:00
Emil Ernerfeldt
5dff1e42c6
More table improvements (#1440)
* Clip by default
* Fix some spacing bugs
* datepicker: look nicer in light mode
* datepicker: show month names
* Table: don't allow resize of last column if it is Size::Remainder
2022-04-01 15:27:42 +02:00
Emil Ernerfeldt
c029f25c13 Table: don't allow resize of last column if it is Size::Remainder 2022-04-01 14:44:25 +02:00
Emil Ernerfeldt
51cc9c9a9a Table: fix bug with item spacing 2022-04-01 14:43:43 +02:00
Emil Ernerfeldt
21c32a18d8
Table resize (#1438)
* Let 1D strips fill up parent width/height
* Add Strip + Table + DatePicker to egui_extras changelog
* Expose some dragging- and pointer related context/memory methods
* Make tables resizable
2022-04-01 12:01:00 +02:00
Collin Kemper
a52bbade45
Place dark-light behind a feature gate in eframe and egui_glow (#1437) 2022-04-01 11:10:47 +02:00
René Rössler
1d32670cf3
Dynamic sized strips, tables, and date picker (#963) 2022-03-31 21:13:25 +02:00
follower
95ff7ec000
Typo fixes in egui_web README (#1433) 2022-03-30 10:19:42 +02:00
Luis Wirth
c3ac340e25
egui-winit: re-export egui (#1429) 2022-03-28 13:23:07 +02:00
Emil Ernerfeldt
8f178fa4e0
Add glow::Context to epi::Frame (#1425)
This can be used, for instance, to:

* Render things to offscreen buffers.
* Read the pixel buffer from the previous frame (glow::Context::read_pixels).
* Render things behind the egui windows.
2022-03-27 15:20:45 +02:00
Emil Ernerfeldt
b7ebe16cfb
Storage and frame refactor (#1418)
The purpose of this is to expose `frame.storage()` and `frame.storage_mut()` so users can save/load app state from the `App::update` function, without having to add another parameter to that function.

Changes:
* Added `Frame::storage()` and `Frame::storage_mut()`
* `App::update` now takes a `&mut Frame` rather than just `&Frame`
* `Frame` is no longer `Clone` or `Sync` (doesn't have to be since https://github.com/emilk/egui/pull/1366)
2022-03-25 21:19:31 +01:00
Anna Clemens
bc0fdefceb
Expose ui.cursor and ui.next_widget_position (#1342) 2022-03-25 19:34:03 +01:00
4JX
dd9699099f
Add ComboBox::icon() (#1405) 2022-03-23 21:56:46 +01:00
Emil Ernerfeldt
8272b08742
Improve text contrast in bright mode (#1412)
* Rename AlphaImage to FontImage to discourage any other use for it
* Encode FontImage as f32 and postpone the alpha correction
* Interpret alpha coverage in a new, making dark text darker, improving contrast in bright mode
2022-03-23 16:49:49 +01:00
Zachary Kohnen
d3c002a4e5
Remove FAQs for resolved issues (#1413)
It was noted that the problems with Firefox on Linux/MacOS have been resolved in #1377 and the limitation on the canvas size was lifted in commit 465c96122c
2022-03-23 15:37:50 +01:00
Emil Ernerfeldt
bcddafb505 Add a some fine lines to the color test to test anti-aliasing 2022-03-23 13:31:38 +01:00
Emil Ernerfeldt
3e41da7187 Revert accidentally setting srgb option on glutin window based on vsync
Introduced in a9fd03709e
2022-03-23 13:04:12 +01:00
Emil Ernerfeldt
1387d6e9d6
Refactor TessellationOptions to expose slider for feathering size (#1408)
The epaint tessellator uses "feathering" to accomplish anti-aliasing. This PS allows you to control the feathering size, i.e. how blurry the edges of epaint shapes are.

This changes the interface of Tessellator slightly, and renames some options in TessellationOptions.
2022-03-23 11:41:38 +01:00
Emil Ernerfeldt
a9fd03709e Add new NativeOptions: vsync multisampling depth_buffer stencil_buffer
These are useful when embedding 3D into eframe.
2022-03-23 11:13:57 +01:00
Emil Ernerfeldt
c63bdeab67
Add an example of showing 3D using three-d (#1407) 2022-03-23 11:06:33 +01:00
Emil Ernerfeldt
85e3ec5027 Log supported OpenGL extensions if VAO is in doubt. 2022-03-23 10:10:12 +01:00
Emil Ernerfeldt
ea9393aa9b
glow painter improvements (#1406)
* Add viewport info to PaintCallback
* glow: be more explicit with more state
* glow: Refactor VAO
2022-03-22 23:11:27 +01:00
Emil Ernerfeldt
6f10e2e725
Improve glow error reporting (#1403)
* Improve glow error reporting
* Add more check_for_gl_error calls
* Remove clippy lint list from egui_glow lib.rs
  - Forgotten in https://github.com/emilk/egui/pull/1394
* egui_glow: move vao code to own file
* Cleanup: `use glow::HasContext as _;`

Co-authored-by: Zachary Kohnen <me@dusterthefirst.com>
2022-03-22 16:04:06 +01:00
Emil Ernerfeldt
41b178b6ec
Use atomic_refcell instead of parking_lot for wasm32 targets (#1404)
Closes https://github.com/emilk/egui/issues/1401
2022-03-22 15:34:21 +01:00
Edgeworth
e5aeb1618f
Export the PlotBounds type. (#1392) 2022-03-22 08:59:13 +01:00
Hunter522
0a400a5bcc
Add Image::rotate and Mesh::rotate (#1371)
Co-authored-by: Hunter Morgan <hmorgan@bellflight.com>
2022-03-22 08:44:23 +01:00
Emil Ernerfeldt
805539b50d
Add example of custom window frame for native window using eframe (#1396) 2022-03-21 22:20:58 +01:00
Emil Ernerfeldt
15254f8235
Remove the single_threaded/multi_threaded feature flags (#1390)
Always use parking_lot for mutexes, i.e. always be multi-threaded.

Closes #1379
2022-03-21 22:20:37 +01:00
Emil Ernerfeldt
5c68edbb15 Clippy fixes 2022-03-21 22:14:25 +01:00
Emil Ernerfeldt
339b28b470 Add Frame::outer_margin, and rename Frame::margin to Frame::inner_margin 2022-03-21 21:44:52 +01:00
Emil Ernerfeldt
fda8189cba
Move lints list to .carg/config.toml (#1394)
That way they apply to all crates equally.

See https://github.com/EmbarkStudios/rust-ecosystem/issues/22 for why.
2022-03-21 16:54:29 +01:00
Emil Ernerfeldt
ccbddcfe95 Add example of how to move text cursor in a TextEdit 2022-03-20 23:08:19 +01:00
Emil Ernerfeldt
fde9c232b3 Improve the introspection paint stats 2022-03-20 23:05:16 +01:00
Emil Ernerfeldt
e369626d3d glow: move where FRAMEBUFFER_SRGB is enabled 2022-03-20 23:04:44 +01:00
Emil Ernerfeldt
d20be45c4c Add egui_assert to ensure texture size is <= max_texture_side 2022-03-20 22:49:44 +01:00
Emil Ernerfeldt
861e129ace Add Shape::image convenience method 2022-03-20 20:39:48 +01:00
jean-airoldie
734d4c57ad
Expose more epaint tessellator methods (#1384)
* Expose more tessellator method

* Make public the Tessellator methods to tessellate a circle, a
    mesh, a rectangle, a line, a path, a quadratic and cubic
    bezier curve.
* Add doc to tessellate_text.
* Add Mesh::append_ref method.

* Make tessellate_text take a reference

* Fix breaking change in benchmark
2022-03-20 20:38:48 +01:00
Zachary Kohnen
8bb381d50b
Fix code that could lead to a possible deadlock. (#1380)
* Fix code that could lead to a possible deadlock.

Drop implementations are not called until the end of a statement. The statement changed in this commit therefore took 4 read locks on a RwLock which can lead to problems if a write lock is requested between any of these read locks. The code looks like it would only hold one lock at a time but it does not drop any of the locks until after the arithmatic operations complete, which leads to this situation. See https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=996079046184329f3a9df1cd19c87da8 to see this in action. The fix is to just take one lock and share it between the three calls to num_presses, letting it drop at the end of the scope.

* Fix code that may cause a deadlock in `MenuRoot::stationary_interaction`

The issue here is related to that in 9673b8f2a08302c10ffcfd063f2dbdec4301d3e2 in that the lock is not dropped when it is expected.  Since the `RwLockReadGuard` produced by `ctx.input()` has a reference taken from it (one into `pointer`), the lock cannot be dropped until that reference is no longre valid, which is the end of the scope (in this case this function).  The following `ctx.input()` then attempts to aquire a second lock on the `RwLock`, creating the same situation that was found in the referenced commit.

This has been resolved by holding one lock on the input for the whole function.

* Reference this PR from comments in the code for future maintainers

* Add the change to the changelog

* Use full link to PR

* Use full link to PR

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-03-20 20:30:38 +01:00
Emil Ernerfeldt
465c96122c
egui_web: by default, use full web browser size (#1378)
* egui_web: by default, use full web browser size

Closes https://github.com/emilk/egui/issues/1377

* Remove max_size_points from demo app
2022-03-19 13:47:30 +01:00
Emil Ernerfeldt
6ce8594351 README.md: add links to license files
Closes https://github.com/emilk/egui/issues/1367
2022-03-19 13:34:08 +01:00
Emil Ernerfeldt
12c31e980b
Add Ui::push_id (#1374) 2022-03-19 13:30:43 +01:00
Emil Ernerfeldt
f6af7bda27 Better error message when trying to upload too large texture
Closes https://github.com/emilk/egui/issues/1370
2022-03-19 13:30:29 +01:00
jean-airoldie
0d47e57775
Add Tessellator::set_clip_rect (#1369)
This allows the user to set the outer rectangle used for culling,
which is required to be able to implement its own tessellate_shapes.
2022-03-19 13:01:33 +01:00
zam-5
cecb48af03
Added plot_ui::plot_clicked() (#1372) 2022-03-19 13:00:18 +01:00
Emil Ernerfeldt
c69f39e869
Box the app creator (#1373) 2022-03-18 14:23:07 +01:00
Emil Ernerfeldt
c8f6cae362
eframe app creation refactor (#1363)
* Change how eframe apps are created
* eframe: re-export epi::* so users don't need to care about what epi is
2022-03-16 15:39:48 +01:00
Emil Ernerfeldt
c768d1d48e
Context::request_repaint will wake up the UI thread (#1366)
This adds a callback (set by `Context::set_request_repaint_callback`)
which integration can use to wake up the UI thread.

eframe (egui_web and egui_glow) will use this, replacing
`epi::Frame::request_repaint`.

Existing code calling `epi::Frame::request_repaint` should be changed
to instead call `egui::Context::request_repaint`.

This is the first callback added to the egui API, which otherwise is
completely driven by data.

The purpose of this is to remove the confusion between the two
`request_repaint` methods (by removing one). Furthermore, it makes
`epi::Frame` a lot simpler, allowing future simplifications to it
(perhaps no longer having it be `Send+Sync+Clone`).
2022-03-15 17:21:52 +01:00
Emil Ernerfeldt
6aee4997d4
Add Shape::Callback to do custom rendering inside of an egui UI (#1351)
* Add Shape::Callback to do custom rendering inside of an egui UI
* Use Rc<glow::Context> everywhere
* Remove trait WebPainter
* Add glow::Context to epi::App::setup
2022-03-14 13:25:11 +01:00
Emil Ernerfeldt
002158050b
Add Frame::canvas - bright in bright mode, dark in dark mode (#1362)
and use it in the demo app
2022-03-14 12:33:17 +01:00
Emil Ernerfeldt
29c52e8eb6
Remove epi backend from egui_glow (#1361) 2022-03-13 22:49:24 +01:00
Emil Ernerfeldt
50539bd31a
egui_web: always use the glow painter, and remove the old WebGL code. (#1356)
* egui_web: always use the glow painter, and remove the old WebGL code.
* Clean up the WebPainter trait
* Clarify WebGL1 warning text in color test

The glow painter became standard in egui 0.17, and I've heard no complaints! So let's simplify and go all in on glow.

Part of https://github.com/emilk/egui/issues/1198
2022-03-11 19:15:06 +01:00
Emil Ernerfeldt
52b4ab4e18
Remove egui_glium as a backend for eframe (#1357)
eframe will now always use egui_glow as a native backend.

Part of https://github.com/emilk/egui/issues/1198
2022-03-11 19:14:27 +01:00
Emil Ernerfeldt
510cef02ca Run a formatter on all toml files 2022-03-10 14:25:33 +01:00
Simon Gardling
c19a7ff34f
remove unnecessary to_string in docs (#1345) 2022-03-10 08:23:00 +01:00
mbillingr
cd555e07b8
Fix typo: Highligher -> Highlighter (#1346) 2022-03-10 08:14:06 +01:00
Zachary Kohnen
5d950e1c15
[egui_web] Prevent event handlers from running if code has panicked (#1306)
Closes: #1290

Fix panic reported by @Titaniumtown
See https://github.com/emilk/egui/pull/1306#issuecomment-1060775376
2022-03-10 08:13:32 +01:00
Emil Ernerfeldt
30399bf6ff Update regex crate v1.5.4 -> v1.5.5 2022-03-10 08:08:46 +01:00
Simon Gardling
0e7a4fdbfd
Add method to set a Plot's margin (#1308) 2022-03-08 15:50:53 +01:00
Emil Ernerfeldt
d8300037ad
Fix cargo run -p egui_demo_app (#1343)
Broken by https://github.com/emilk/egui/pull/1303
2022-03-08 08:26:43 +01:00
Jake Shadle
37c9f116bf
Fix egui_glow when targeting wasm32-unknown-unknown (#1303)
* Gate winit/glow and epi correctly

* Add check to CI

* Fix epi cfg
2022-03-07 10:48:40 +01:00
Juan Campa
e3d1fa22d1
Fix combo box misalignment on rtl layout (#1304) 2022-03-07 10:48:12 +01:00
Zachary Kohnen
27e179268b
Remove warning about cpal drag and drop (#1329)
* Remove warning about cpal drag and drop

Given that the issue https://github.com/rust-windowing/winit/issues/1255 was closed by https://github.com/rust-windowing/winit/pull/1524, it would make sense to remove the warning about the issue from NativeOptions

* Change `NativeOptions::drag_and_drop_support` default to true
2022-03-07 10:33:59 +01:00
Colin Terry
d4bbea3967
egui_extras README grammar fixes (#1313) 2022-03-07 10:14:00 +01:00
Emil Ernerfeldt
833829e3d8 cargo deny: ignore duplicate of hashbrown crate 2022-02-22 19:44:25 +01:00
Emil Ernerfeldt
a05520b9d3 Release 0.17.0 - Improved font selection and image handling 2022-02-22 19:32:30 +01:00
Emil Ernerfeldt
83225f46ad Make Bézier demo more compact 2022-02-22 19:12:21 +01:00
Emil Ernerfeldt
31d324932c
Introduce egui::FullOutput, returned from Context::run (#1292)
* Introduce `egui::FullOutput`, returned from `Context::run`
* Rename `Output` to `PlatformOutput`
2022-02-22 17:13:53 +01:00
Emil Ernerfeldt
c5a9421dbd
Run wasm-bindgen in CI and update parking_lot (#1293)
* Run wasm-bindgen in CI
* Update parking_lot 0.11 -> 0.12
2022-02-22 16:30:42 +01:00
Emil Ernerfeldt
2e1a4cf08a impl std::fmt::Debug for TexturesDelta 2022-02-22 13:21:41 +01:00
Emil Ernerfeldt
745f209c61 Make RawInput.max_texture_side an Option 2022-02-22 13:21:28 +01:00
Emil Ernerfeldt
76f564428b Implement Serialize/Deserialize for TextShape 2022-02-22 13:20:46 +01:00
Emil Ernerfeldt
a90379ac8d Ignore characters that are wrong in emoji-icon-font.ttf
Closes https://github.com/emilk/egui/issues/1284
2022-02-22 09:47:31 +01:00
Emil Ernerfeldt
2af1dda4c3 Add TexturesDelta::clear 2022-02-21 21:51:21 +01:00
Emil Ernerfeldt
0a46634c13 Simplify backends by adding fn paint_and_update_textures helper 2022-02-21 21:49:52 +01:00
Emil Ernerfeldt
8f887e2ebd Add Shape::visual_bounding_rect() 2022-02-21 21:46:30 +01:00
Emil Ernerfeldt
fd3fb726c1 Fix bugs in consume_key and improve Modifiers API
Improvements and fixes following https://github.com/emilk/egui/pull/1212
2022-02-21 16:53:41 +01:00
Emil Ernerfeldt
476a3057b0 egui_demo_lib: make egui_extras an optional dependency 2022-02-21 16:10:16 +01:00
Emil Ernerfeldt
6d68838821 Clean up changelogs 2022-02-21 16:10:05 +01:00
Emil Ernerfeldt
ddf914b517
Update crates (#1283)
* Update rfd 0.8 -> 0.8

* Update webbrowser 0.5 -> 0.6

* Update unicode_names2 0.4 -> 0.5

* cargo update

    Updating crates.io index
      Adding arrayvec v0.7.2
    Updating async-lock v2.4.0 -> v2.5.0
    Updating autocfg v1.0.1 -> v1.1.0
    Updating cc v1.0.72 -> v1.0.73
    Updating cfg-expr v0.9.1 -> v0.10.1
    Updating core-foundation v0.9.2 -> v0.9.3
    Updating crc32fast v1.3.1 -> v1.3.2
    Updating crossbeam-epoch v0.9.6 -> v0.9.7
    Updating crossbeam-utils v0.8.6 -> v0.8.7
    Updating deflate v0.9.1 -> v1.0.0
    Removing encoding v0.2.33
    Removing encoding-index-japanese v1.20141219.5
    Removing encoding-index-korean v1.20141219.5
    Removing encoding-index-simpchinese v1.20141219.5
    Removing encoding-index-singlebyte v1.20141219.5
    Removing encoding-index-tradchinese v1.20141219.5
    Removing encoding_index_tests v0.1.4
    Updating enum-map v2.0.1 -> v2.0.2
    Updating futures-core v0.3.19 -> v0.3.21
    Updating futures-io v0.3.19 -> v0.3.21
    Updating futures-sink v0.3.19 -> v0.3.21
    Updating futures-task v0.3.19 -> v0.3.21
    Updating futures-util v0.3.19 -> v0.3.21
    Updating gio-sys v0.15.4 -> v0.15.6
    Updating glib-sys v0.15.4 -> v0.15.6
    Updating gobject-sys v0.15.1 -> v0.15.5
    Updating image v0.24.0 -> v0.24.1
    Updating kurbo v0.8.0 -> v0.8.3
    Updating libc v0.2.117 -> v0.2.119
    Updating memmap2 v0.5.2 -> v0.5.3
      Adding miniz_oxide v0.5.1
      Adding ndk-context v0.1.0
    Removing ndk-glue v0.5.0
    Removing ndk-glue v0.6.0
      Adding ndk-glue v0.5.1
      Adding ndk-glue v0.6.1
    Updating ntapi v0.3.6 -> v0.3.7
    Updating png v0.17.2 -> v0.17.3
    Updating proc-macro-crate v1.1.0 -> v1.1.2
    Updating rand v0.8.4 -> v0.8.5
    Removing rand_hc v0.3.1
    Updating rustls v0.20.2 -> v0.20.4
    Updating semver v1.0.4 -> v1.0.5
    Updating serde_json v1.0.78 -> v1.0.79
    Updating system-deps v6.0.1 -> v6.0.2
    Updating tracing v0.1.30 -> v0.1.31
    Updating tracing-subscriber v0.3.7 -> v0.3.9
    Updating tts v0.20.2 -> v0.20.3
    Removing windows v0.30.0
    Removing windows_aarch64_msvc v0.30.0
    Removing windows_i686_gnu v0.30.0
    Removing windows_i686_msvc v0.30.0
    Removing windows_x86_64_gnu v0.30.0
    Removing windows_x86_64_msvc v0.30.0
2022-02-21 15:40:25 +01:00
Emil Ernerfeldt
b8f427ddd6 Add a Code of Conduct, based on the Contributor Covenant
Taken verbatim (with an added mention of the egui Discord) from
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
2022-02-21 15:30:53 +01:00
Emil Ernerfeldt
b360dffdbf Demo app: handle the case of wrong selected anchor 2022-02-21 15:29:18 +01:00
Emil Ernerfeldt
c3fc8997d6
Introduce egui_extras with RetainedImage for loading svg,png,jpeg,… (#1282) 2022-02-21 15:26:26 +01:00
Emil Ernerfeldt
713917e481 refactor egui_web (break up lib.rs) 2022-02-21 09:28:12 +01:00
Emil Ernerfeldt
89d19860b8 Misc code cleanup, docs fixes, etc 2022-02-19 20:58:28 +01:00
Emil Ernerfeldt
e49245fae5 Improve the anti-aliasing of filled paths with sharp corners
This comes at the cost of these corners sometimes becoming badly
extruded instead. The sharper the corner, the bigger the problem.

A proper fix will have to wait for later.

Part of https://github.com/emilk/egui/issues/1226
2022-02-19 20:46:44 +01:00
Emil Ernerfeldt
b8fbbf7d62 Fix anti-aliasing of filled paths with counter-clockwise winding order
Part of https://github.com/emilk/egui/issues/1226
2022-02-19 20:46:44 +01:00
Emil Ernerfeldt
10634fc344 Improve the Bézier demo: drag control points and simplify code
Follow-up to https://github.com/emilk/egui/pull/1178
2022-02-19 20:46:44 +01:00
Emil Ernerfeldt
3a5ec4733f Fix bug when debug-painting clip rectangles 2022-02-19 20:46:40 +01:00
Emil Ernerfeldt
aa53522179
Slider event fixes (#1270)
* Slider: return drag_started,dragged,drag_released if value was dragged

Closes https://github.com/emilk/egui/issues/1269

* Fix: DragValue correctly reports gained_focus

* Make `Slider` report gained_focus,has_focus,lost_focus from DragValue

Closes https://github.com/emilk/egui/issues/1268
2022-02-19 19:56:12 +01:00
Emil Ernerfeldt
3ed68274b0 Make Slider report gained_focus,has_focus,lost_focus from DragValue
Closes https://github.com/emilk/egui/issues/1268
2022-02-19 11:12:09 +01:00
Emil Ernerfeldt
0d21405855 Fix: DragValue correctly reports gained_focus 2022-02-19 11:05:02 +01:00
Emil Ernerfeldt
989a6d202f Slider: return drag_started,dragged,drag_released if value was dragged
Closes https://github.com/emilk/egui/issues/1269
2022-02-19 10:58:07 +01:00
Benedikt Terhechte
8e62b382fd
Add some macOS emacs keybindings (#1243)
Move cursor left: ^B
    Move cursor right: ^F
    Beginning of line: ^A
    End of line: ^E
    Line up: ^P
    Line down: ^N
2022-02-19 10:42:43 +01:00
Tulio Leao
e746e3a58b
Light edits on README.md (#1246) 2022-02-19 10:41:31 +01:00
Urho Laukkarinen
6abdde0334
Add more source code links to demos (#1260) 2022-02-17 17:08:13 +01:00
Emil Ernerfeldt
3d754e3a16
Prevent ScrollArea:s from becoming tiny (#1255)
Closes https://github.com/emilk/egui/issues/1097
2022-02-17 17:08:05 +01:00
Emil Ernerfeldt
b5c8f034e7
Add web location info to egui_web/epi (#1258)
This adds all parts of the web "location" (URL) to frame.info().web_info, included a HashMap of the query parameters, percent-decoded and ready to go.

This lets you easily pass key-value pairs to your eframe web app.
2022-02-17 16:46:43 +01:00
Emil Ernerfeldt
4e316d32e5 Fix scroll_to with align
Broke in https://github.com/emilk/egui/pull/1252
2022-02-17 16:45:42 +01:00
Emil Ernerfeldt
4af3cae26d
Scroll so that text cursor remains visible (#1252)
Closes https://github.com/emilk/egui/issues/165
2022-02-16 21:34:47 +01:00
cat-state
c1569ed0d7
Add Ui.input_mut & InputState.ignore_key (#1212) 2022-02-15 17:14:24 +01:00
Friz64
c8c871fcd1
Add move_to_top and top_most_layer (#1242) 2022-02-15 17:13:08 +01:00
Sven Niederberger
8f8eb5d4a9
Customize Plot label and cursor texts (#1235)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-02-15 17:12:29 +01:00
BctfN0HUK7Yg
cfad28936d
Color picker now always pops up next to the button (#1237) 2022-02-15 16:55:43 +01:00
Juan Campa
635c65773d
Allow scroll into view without specifying an alignment (#1247)
* Allow scroll into view without specifying an alignment
* Handle case of UI being too big to fit in the scroll view
2022-02-15 16:52:29 +01:00
Emil Ernerfeldt
c1cd47e3a7 README: Add section to FAQ about how to work with async code 2022-02-13 23:04:08 +01:00
Emil Ernerfeldt
f92df7db56 README: Add screenshot to demonstrate the skin-ability of egui 2022-02-13 23:04:08 +01:00
Alexander
62504fface
Slider: Add step parameter (#1225)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-02-13 21:55:51 +01:00
Juan Campa
c4528beb72
Make non interactable layers not interact (#1240)
* Make non interactable layers not interact
* Make menus interactable
* Fix area interactable not being updated each frame
2022-02-13 21:09:25 +01:00
4JX
14e985a894
Expose FontTweak through egui::FontTweak (#1244) 2022-02-13 10:10:39 +01:00
Emil Ernerfeldt
69626296f1
Make v-align and scale of user fonts tweakable (#1241)
* Add ability to "tweak" the scale and y-offsets of individual fonts
* Change default font tweak

This help vertically center large text.
2022-02-12 18:22:42 +01:00
Emil Ernerfeldt
3f8ba3a542
Improve the contrast of the default light mode style (#1238)
Makes backgrounds brighter and foregrounds darker.
2022-02-11 13:58:50 +01:00
Emil Ernerfeldt
815598a842 .vscode/settings.json: insertFinalNewline and trimTrailingWhitespace
For when people make PR:s touching .toml, .md files
2022-02-10 19:34:07 +01:00
Yusuf Bera Ertan
1ebb5ccabf
Don't install console panic hook and tracing wasm subscriber in egui_web (#1230)
Do so in egui_demo_app instead
2022-02-10 15:44:41 +01:00
4JX
2f042ababd
Make Margin pub and move to style.rs (#1236) 2022-02-10 15:26:57 +01:00
Emil Ernerfeldt
b2323bd13e Add VSCode settings with {"editor.formatOnSave": true}
Hopefully this will increase the number of PR:s with correct formatting.
2022-02-09 18:14:55 +01:00
Emil Ernerfeldt
4cede42748 Add ui.weak(text) 2022-02-09 15:08:01 +01:00
Emil Ernerfeldt
defc400c21
Add an example of how to display an SVG image in egui (#1228) 2022-02-09 08:12:12 +01:00
Erlend Walstad
2802e03526
Per-side margins with new struct Margin (#1219) 2022-02-07 11:29:16 +01:00
Emil Ernerfeldt
603ec82a5e
cargo deny: clarify MPL-2.0 license and use un-patched ureq again (#1222)
See https://github.com/algesten/ureq/issues/478#issuecomment-1031029647
2022-02-07 10:38:08 +01:00
4JX
c9098288f5
Fix a typo: tolerence -> tolerance (#1214) 2022-02-06 16:30:34 +01:00
Emil Ernerfeldt
5459ab29b7 Fix: allow calling set_pixels_per_point before first frame
Closes https://github.com/emilk/egui/issues/1153
2022-02-06 00:16:59 +01:00
Emil Ernerfeldt
55067f54ce Demo app fix: respect native pixels_per_point on startup 2022-02-05 23:54:44 +01:00
Emil Ernerfeldt
cbc53fbe2e eframe: Add epi::NativeOptions::initial_window_pos 2022-02-05 19:12:03 +01:00
Manuel Innerhofer
7d41551913
Fixed typos: wether -> whether (#1210)
Co-authored-by: mir <mir@wisdomtag.com>
2022-02-05 18:14:16 +01:00
Emil Ernerfeldt
9ed96155e9 Rename corner_radius to rounding
Also update changelogs and clean up other aspects of
https://github.com/emilk/egui/pull/1206
2022-02-05 18:13:46 +01:00
Emil Ernerfeldt
ace2ac00da Update to tts 0.20.2 which resolves copy-left license issue
See https://github.com/ndarilek/tts-rs/pull/21
2022-02-05 17:53:37 +01:00
4JX
7e7b9e1919
Per-corner rounding of rectangles (#1206)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-02-05 17:48:55 +01:00
Emil Ernerfeldt
0fa4bb9c64 Clean up all Cargo.toml: put features higher, and document them better 2022-02-05 11:11:15 +01:00
Juan Campa
32a9b4abcc
Add on_hover_text_at_pointer (#1179) 2022-02-05 10:51:16 +01:00
Juan Campa
ea28485bdd
Remove prevent_default for mousedown events (#1209)
This fixes the issue of tabs not focusing when egui is used inside vscode
2022-02-05 08:56:53 +01:00
Emil Ernerfeldt
712d8c9a2b
Run cargo deny in CI (#1205)
* Run cargo deny in CI
* Use patched fork of ureq to avoid copy-left license
* Use patched fork of tts to avoid LGPL license

More:
* https://github.com/ndarilek/tts-rs/pull/21
* https://github.com/algesten/ureq/pull/479
2022-02-04 22:08:15 +01:00
Alan Race
3cd3cceefd
Add method to enable generation of a ComboBox with both id and label (#1204) 2022-02-04 14:11:08 +01:00
Emil Ernerfeldt
1b623fdd34 Update rfd 0.6 -> 0.7 2022-02-04 13:31:06 +01:00
Emil Ernerfeldt
47038c631e Update image 0.23 -> 0.24 2022-02-04 13:31:06 +01:00
Emil Ernerfeldt
8dfa6ce2f0 Update tts 0.19 -> 0.20 2022-02-04 13:18:03 +01:00
Emil Ernerfeldt
3999e5b373 cargo update
Updating crates.io index
    Removing anyhow v1.0.52
    Updating atk-sys v0.14.0 -> v0.15.1
    Updating backtrace v0.3.63 -> v0.3.64
    Updating cairo-sys-rs v0.14.9 -> v0.15.1
    Updating cfg-expr v0.8.1 -> v0.9.1
    Updating clang-sys v1.3.0 -> v1.3.1
    Updating crc32fast v1.3.0 -> v1.3.1
    Updating enum-map v2.0.0 -> v2.0.1
    Updating enum-map-derive v0.7.0 -> v0.8.0
    Updating gdk-pixbuf-sys v0.14.0 -> v0.15.1
    Updating gdk-sys v0.14.0 -> v0.15.1
    Updating gio-sys v0.14.0 -> v0.15.4
    Updating glib-sys v0.14.0 -> v0.15.4
    Updating gobject-sys v0.14.0 -> v0.15.1
    Updating gtk-sys v0.14.0 -> v0.15.3
    Updating heck v0.3.3 -> v0.4.0
    Updating js-sys v0.3.55 -> v0.3.56
    Updating libc v0.2.116 -> v0.2.117
    Updating lock_api v0.4.5 -> v0.4.6
    Updating mint v0.5.8 -> v0.5.9
    Updating nix v0.22.0 -> v0.22.3
      Adding num_threads v0.1.3
    Updating pango-sys v0.14.0 -> v0.15.1
    Updating quote v1.0.14 -> v1.0.15
    Updating rfd v0.6.3 -> v0.6.4
    Updating serde v1.0.133 -> v1.0.136
    Updating serde_derive v1.0.133 -> v1.0.136
    Updating serde_json v1.0.75 -> v1.0.78
    Removing strum v0.21.0
    Removing strum_macros v0.21.1
    Updating syn v1.0.85 -> v1.0.86
    Updating system-deps v3.2.0 -> v6.0.1
    Updating time v0.3.5 -> v0.3.7
    Updating tracing v0.1.29 -> v0.1.30
    Updating tracing-attributes v0.1.18 -> v0.1.19
    Updating tracing-core v0.1.21 -> v0.1.22
    Removing unicode-segmentation v1.8.0
      Adding valuable v0.1.0
    Updating version-compare v0.0.11 -> v0.1.0
    Updating wasm-bindgen-futures v0.4.28 -> v0.4.29
    Updating web-sys v0.3.55 -> v0.3.56
    Updating which v4.2.2 -> v4.2.4
      Adding windows v0.30.0
      Adding windows_aarch64_msvc v0.30.0
      Adding windows_i686_gnu v0.30.0
      Adding windows_i686_msvc v0.30.0
      Adding windows_x86_64_gnu v0.30.0
      Adding windows_x86_64_msvc v0.30.0
    Updating zbus v2.1.0 -> v2.1.1
    Updating zbus_macros v2.1.0 -> v2.1.1
2022-02-04 13:17:58 +01:00
Emil Ernerfeldt
fd267c542e Add a link to smithay-egui 2022-02-04 10:55:14 +01:00
Emil Ernerfeldt
c3be566574
egui-winit: Automatically detect and apply dark or light mode (#1045) 2022-02-02 17:09:36 +01:00
Unknown
270c08a030
min and max window size (#1171) 2022-02-02 16:47:27 +01:00
nongiach
869d556335
Plot boxed zoom with secondary mouse button (#1188) 2022-02-02 16:32:46 +01:00
Emil Ernerfeldt
c6ac1827f6
Use tracing crate for logging (#1192)
* egui_web: use tracing crate
* egui_glow: use tracing crate
* Log at the debug level
* egui_demo_app: enable tracing to log to stdout
* Use tracing in egui-winit
* Add opt-in tracing support to egui
2022-02-01 12:27:39 +01:00
Xu Desheng
1f03f53dc0
Add Bezier Shapes #1120 (#1178) 2022-01-31 20:26:31 +01:00
Sven Niederberger
4e99d8f409
Plot: Linked axis support (#1184) 2022-01-31 20:18:10 +01:00
Emil Ernerfeldt
b5aaa5fa6f Improve some feature flags 2022-01-31 19:24:56 +01:00
Emil Ernerfeldt
2183e9fdea Add early egui history to changelog 2022-01-31 19:17:26 +01:00
Emil Ernerfeldt
7eddd20a01 index.html: catch and display errors 2022-01-31 19:16:50 +01:00
Emil Ernerfeldt
871a200ecf egui_web glow painter: return errors instead of panic 2022-01-31 19:15:48 +01:00
Emil Ernerfeldt
ef81a52951 egui_web: log panics using console.error 2022-01-31 19:14:50 +01:00
Emil Ernerfeldt
532990d3dd emilk.github.io -> www.egui.rs 2022-01-30 22:01:33 +01:00
Emil Ernerfeldt
b4be94df35
Create CNAME 2022-01-30 21:26:26 +01:00
Emil Ernerfeldt
25c03e49eb
Delete CNAME 2022-01-30 19:50:53 +01:00
Emil Ernerfeldt
7ccc7caffd
Create CNAME 2022-01-30 19:50:23 +01:00
Emil Ernerfeldt
ea04ce9d58
Delete CNAME 2022-01-30 19:46:38 +01:00
Emil Ernerfeldt
f6b1feddcd
Create CNAME 2022-01-30 19:40:34 +01:00
Emil Ernerfeldt
4db6984e74
Delete CNAME 2022-01-30 16:21:38 +01:00
Emil Ernerfeldt
785c6f4c85
Create CNAME 2022-01-30 16:20:19 +01:00
Emil Ernerfeldt
b618636425
Add ui.data(), ctx.data(), ctx.options() and ctx.tessellation_options() (#1175)
Helpful access deeper into Memory
2022-01-29 17:53:41 +01:00
Emil Ernerfeldt
3333d63b91
Return more info from ScrollArea::show (#1166) 2022-01-26 22:19:32 +01:00
Emil Ernerfeldt
1134258441 Documentation improvements 2022-01-26 22:09:19 +01:00
AlexxxRu
0d00185d9f
Bugfix/drag window (#1108)
Call .drag_window() only after Left mouse btn clicked

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-01-26 22:04:24 +01:00
Emil Ernerfeldt
26be0ace1d Tidy up README.md 2022-01-26 17:25:59 +01:00
Emil Ernerfeldt
04c3efd01a Minor optimization: avoid reallocating set of visible ID:s 2022-01-26 14:44:10 +01:00
Emil Ernerfeldt
417bb3123a Fonts: only pre-render glyphs for fonts in the current egui styles 2022-01-26 14:42:44 +01:00
Emil Ernerfeldt
1e4d8ae943 Remove warm-up of demo app
Since https://github.com/emilk/egui/pull/1149 we only update the
region of the font atlas that has changes, so loading new glyphs is much
cheaper. This means warm-up is much less needed.

There is now a small delay when opening the font book,
but not when opening anything else.
2022-01-25 01:08:10 +01:00
Emil Ernerfeldt
7ed0880b8f
Lower default font atlas size (#1160)
In https://github.com/emilk/egui/pull/1154 the default was set to
16k x 512, which is way excessive, as most of it will be blank.

This PR changes it to 8k x 64, which will make egui
use less RAM and VRAM, and load quicker again.

This also decreases the max size from 16k² to 8k².
That should be enough.
2022-01-25 01:04:28 +01:00
Jan Haller
366f544655
Formatter for plot axis labels (#1130)
* Fix Orientation not exposed, although there are public fields with its type

* Implement formatters for X/Y axis labels

* Use array instead of separate X/Y formatters

* Swap axis formatters if charts are horizontal

* Review suggestions
2022-01-24 22:34:00 +01:00
Sven Niederberger
3418eb5d35
Make Plot::highlight take a boolean (#1159) 2022-01-24 20:50:22 +01:00
triangle drawer
9d596967b4
Reducing glutin dependency in egui_glow (#1151)
Use winit wherever possible
2022-01-24 17:08:27 +01:00
Emil Ernerfeldt
fa43d16c41
Choose your own font and size (#1154)
* Refactor text layout: don't need &Fonts in all functions
* Replace indexing in Fonts with member function
* Wrap Fonts in a Mutex
* Remove mutex for Font::glyph_info_cache
* Remove RwLock around Font::characters
* Put FontsImpl and GalleyCache behind the same Mutex
* Round font sizes to whole pixels before deduplicating them
* Make TextStyle !Copy
* Implement user-named TextStyle:s
* round font size earlier
* Cache fonts based on family and size
* Move TextStyle into egui and Style
* Remove body_text_style
* Query graphics about max texture size and use that as font atlas size
* Recreate texture atlas when it is getting full
2022-01-24 14:32:36 +01:00
Emil Ernerfeldt
bb407e9b00 Plot text can now contain rich text 2022-01-23 12:01:34 +01:00
bthehacker
dba949240f
Fix eframe changelog links to other changelogs (#1146)
Links to the changelogs of egui_web, egui-winit, and egui_glium were broken and led to a 404, fixed them by adding "../"
2022-01-22 11:33:39 +01:00
Emil Ernerfeldt
462f181db3
Partial font texture update (#1149) 2022-01-22 11:23:12 +01:00
Juan Campa
343f7da564
Add ability to override collapsing icons (#1147) 2022-01-22 10:32:02 +01:00
Emil Ernerfeldt
e957674467 glow on web: simplify the webgl2 vs webgl1 selection 2022-01-22 09:07:41 +01:00
Emil Ernerfeldt
199bbef77b Add misc documentation 2022-01-22 09:07:41 +01:00
Emil Ernerfeldt
8138a073e7
glow: reuse the same GL texture when possible (#1142) 2022-01-21 19:51:52 +01:00
awaken1ng
30f9700f6c
egui-web: Update web_location_hash when hash in URL changes (#1140) 2022-01-21 19:41:18 +01:00
Emil Ernerfeldt
a689b623a6
Make shift-scroll do horizontal scrolling on all platforms (#1136)
Closes https://github.com/emilk/egui/issues/1135
2022-01-21 10:48:44 +01:00
Dusty DeWeese
e4aa1e6e1a
Make color_picker_hsva_2d public. (#1091)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-01-19 11:28:26 +01:00
Emil Ernerfeldt
76ac41f9b5 WidgetGallery: hide spinner widget to save CPU in reactive mode 2022-01-17 19:57:32 +01:00
Emil Ernerfeldt
9df0c68a38 Small improvements to check.sh 2022-01-17 19:57:32 +01:00
Emil Ernerfeldt
39dd6d7644 clippy fixes 2022-01-17 19:57:32 +01:00
Erlend Walstad
ab77099781
eframe: allow aborting an exit event (#1038) 2022-01-17 18:45:09 +01:00
BctfN0HUK7Yg
87ac7446da
Add new function to animate f32 values (#1039)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-01-17 14:57:09 +01:00
BctfN0HUK7Yg
d6b32b7956
Add set margin method to TextEdit (#1104) 2022-01-17 14:56:27 +01:00
Jay Oster
ffa1b6bd43
egui-winit: re-enable window transparency (#1126)
`winit` 0.26.1 fixes the crash on Windows 11
2022-01-17 14:32:16 +01:00
Max Wase
5bedf73566
Add a link to the source code of a fractal_clock example (#1029) 2022-01-17 14:30:57 +01:00
Mingun
87ae77aab0
Add some improvements in the documentation (#1056)
* Better documentation for align.rs
* Document some painting methods
* Fix broken intra-doc links
* Add some internal documentation for Window TitleBar
2022-01-16 22:06:28 +01:00
Juan Campa
624e2fcfc1
Follow style for corner radius for SelectableLabel 2022-01-16 22:04:11 +01:00
Emil Ernerfeldt
c713fd98dd Fix horizontal scrolling direction on Linux
Closes https://github.com/emilk/egui/issues/356

Work-around until https://github.com/rust-windowing/winit/pull/2105 is merged and released
2022-01-16 09:49:22 +01:00
Emil Ernerfeldt
ad5418777d
Fix dead-lock in TextEdit on touch-screens (#1118)
Introduced in https://github.com/emilk/egui/pull/1035

Fixes https://github.com/emilk/egui/issues/1116
2022-01-15 22:40:53 +01:00
Emil Ernerfeldt
b2c8cd0867 Simplify http demo and add new download_image eframe demo 2022-01-15 20:34:03 +01:00
Emil Ernerfeldt
66d80e2519
Texture loading in egui (#1110)
* Move texture allocation into epaint/egui proper
* Add TextureHandle
* egui_glow: cast using bytemuck instead of unsafe code
* Optimize glium painter
* Optimize WebGL
* Add example of loading an image from file
2022-01-15 13:59:52 +01:00
dvec
6c616a1b69
egui_glow: Reduce memory allocations in Painter::set_texture (#1096)
Use bytemuck::cast_slice
2022-01-12 11:33:06 +01:00
dvec
d9cfeff72c
egui_glow: Optimize Painter::set_texture (#1093) 2022-01-11 09:58:51 +01:00
Emil Ernerfeldt
205e04aa18
Fix bugs in glow on web (#1092)
* Re-add check of WEBGL_debug_renderer_info to avoid OpenGL error

I removed this check in https://github.com/emilk/egui/pull/1020
because it produced a warning on Firefox. Better a warning
than an OpenGL error though.

* Bug fix: don't ask for webgl context and then later for webgl2 context

The browser will only allow the first thing we check, so this will
prevent webgl2 from working.
2022-01-10 23:19:30 +01:00
Emil Ernerfeldt
d5673412dd
Put everything in Context behind the same Mutex (#1050)
* Move all interior mutability from Context to CtxRef and make it a handle
* Rename `CtxRef` to `Context`
* The old `Context` is now `ContextImpl` and is non-pub
* Add benchmark Painter::rect

Co-authored-by: Daniel Keller <dklr433@gmail.com>
2022-01-10 23:13:10 +01:00
Simon Werner
225d2b506d
Split Event::Text into Text and Paste (#1058)
* Split `Event::Text` into `Text` and `Paste`

* Added explicit Event::Paste change

See #1043

* Link to PR in changelog (not the issue)

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-01-10 23:12:30 +01:00
Emil Ernerfeldt
650057dd4a Improve OpenGL error detection and reporting in egui_glow
May help to diagnose https://github.com/emilk/egui/issues/1087
2022-01-09 23:04:00 +01:00
Lampsitter
611eaa52e8
Revert "Don't constrain immovable egui windows to native window (#1049)" (#1054)
This reverts commit 7b641be7b0.
It accidentally disabled constraining for all windows
2022-01-09 15:32:09 +01:00
Emil Ernerfeldt
342737e2f0 Improve a couple of docstrings 2022-01-08 11:15:15 +01:00
Lampsitter
7b641be7b0
Don't constrain immovable egui windows to native window (#1049) 2022-01-08 10:07:02 +01:00
Emil Ernerfeldt
1f93c7b0b6 Implement Clone & PartialEq on RichText WidgetTextJob WidgetTextGalley 2022-01-07 15:22:21 +01:00
Justin Jaffray
4fe5fa6c59
egui_glow: Add function to set the texture filter (#1041)
* Allow setting the scale filter for `glow` backend

This commit adds a `set_scale_filter` method to the `glow` painter so that
textures can be set to scale using nearest-neighbour scaling rather than
linear. This is useful for pixel art.

I wasn't entirely sure what kind of API you want for this kind of change so I
went with what seemed least intrusive, I don't mind doing something more
holistic if this isn't what you had in mind.

* Rename scale_filter -> texture_filter

* Store the TextureFilter directly

* PR link in changelog

* Use inter-doc links

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-01-06 17:09:53 +01:00
Lampsitter
b0ea4dc0b5
Fix context menu styling (#1043) 2022-01-06 12:17:12 +01:00
Emil Ernerfeldt
7863f44111 Add demo of advanced TextEdit usage 2022-01-06 11:53:23 +01:00
Lampsitter
d31f7d6522
Faster dashed line generation (#1027)
* Faster dashed lines generation

* Add dashed_line benchmark
2022-01-06 10:44:53 +01:00
Emil Ernerfeldt
79cfdaa1f9 Better docs and spelling fixes 2022-01-03 23:58:06 +01:00
Niklas Korz
01015ac94c
Use instant crate in egui-winit for WebAssembly support (#1023)
* Replace `std::time::Instant` with wasm-compatible `instant::Instant`

* Change version requirement for instant to be compatible to winit

* Enable wasm-bindgen feature for instant

* Update lockfile

* Update changelog

* sort dependencies

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2022-01-03 22:13:53 +01:00
Emil Ernerfeldt
b1fd6a44e8
Switch to using glow as the default renderer both on native and the web (#1020)
* Switch to using glow as the default renderer both on native and the web
* Simplify code to find WebGL context for glow
* egui_web: make webgl an opt-in feature
* Stop using deprecated WEBGL_debug_renderer_info
2021-12-31 15:17:55 +01:00
LordMZTE
8da592c6ab
Add Spinner widget (#1013)
* add spinner widget

* implement requested changes to spinner

- removed enabled field
- cleaned up math
- improved docs

* improve spinner docs & make spinner in demo always active
2021-12-31 14:32:48 +01:00
Emil Ernerfeldt
b5c119ef19
0.16.1 patch release: Add back CtxRef::begin_frame/end_frame (#1019)
`begin_frame`, `end_frame` is more convenient when using egui in a game engine. In particular, 0.16.0 was incompatible with https://github.com/mvlabat/bevy_egui>.
2021-12-31 11:45:57 +01:00
Emil Ernerfeldt
b00edfe97f Remove keyboard focus from a widget if something else is dragged
Closes https://github.com/emilk/egui/issues/618
2021-12-30 23:24:29 +01:00
Emil Ernerfeldt
753d709d3d
Hide console on windows for all examples in release builds (#1008) 2021-12-30 22:43:53 +01:00
Emil Ernerfeldt
4ef21380a2 Add Ui::add_visible and Ui::add_visible_ui
Also rename `Ui::visible` to `Ui::is_visible`.
2021-12-30 22:39:41 +01:00
Emil Ernerfeldt
db110b1690 Remove deprecated functions 2021-12-30 22:37:58 +01:00
Timo
21fe9316d5
Adding missing package for fedora (#1018)
libxcb-devel
2021-12-30 21:52:05 +01:00
Emil Ernerfeldt
0960f38552 Add inter-linking between different forms of documentations and examples 2021-12-29 21:44:48 +01:00
Emil Ernerfeldt
f779e8a346 Add an eframe example of how to install a custom font 2021-12-29 21:43:37 +01:00
Emil Ernerfeldt
0146c7e7fc Release 0.16.0 - Context menus and rich text 2021-12-29 12:07:05 +01:00
Emil Ernerfeldt
77869c21ea Publish new web demo 2021-12-29 12:06:25 +01:00
Emil Ernerfeldt
037ca5f9cd update tts 0.17 -> 0.19 2021-12-29 10:56:16 +01:00
Emil Ernerfeldt
ffd28252ab cargo update 2021-12-29 10:49:40 +01:00
Emil Ernerfeldt
84399cde83 Small tweaks to documentation and demo 2021-12-29 10:49:22 +01:00
Emil Ernerfeldt
d0a47bf8e8 setup_web.sh: don't force-install wasm-bindgen-cli 2021-12-28 21:30:36 +01:00
Emil Ernerfeldt
190c85a40f Rename Texture to FontImage 2021-12-28 21:19:24 +01:00
Emil Ernerfeldt
d775eb3733 Fix wrong label on the feature request PR template 2021-12-28 17:34:27 +01:00
Emil Ernerfeldt
68fca09df9 Improve documentation 2021-12-28 17:34:27 +01:00
Emil Ernerfeldt
00f965de87 Add Context::animate_bool_with_time
Closes https://github.com/emilk/egui/discussions/984
2021-12-28 17:34:27 +01:00
Rasmus Brönnegård
811b974003
Fix typo "the close" -> "this close" in style.rs (#887) 2021-12-28 16:42:22 +01:00
Emil Ernerfeldt
5b9a349c26 Clean up glow painter destroy code 2021-12-28 14:24:59 +01:00
Emil Ernerfeldt
beb2ea2ef6 Add favicon.ico - a single "e" for "egui", using the Ubuntu-Light font 2021-12-28 14:14:49 +01:00
Emil Ernerfeldt
c412fd4a9c Add loading animation while waiting for wasm to load
Closes https://github.com/emilk/egui/issues/1009
2021-12-28 14:13:51 +01:00
Emil Ernerfeldt
cda4c8ba13 Minor cleanup following https://github.com/emilk/egui/pull/999 2021-12-28 13:22:01 +01:00
Emil Ernerfeldt
f919b0cc05 Clean up context menu documentation 2021-12-28 13:09:14 +01:00
Emil Ernerfeldt
369ce95bbd
Add CollapsingHeader::open to control if it is open or collapsed (#1006)
Closes https://github.com/emilk/egui/issues/978
2021-12-28 10:45:32 +01:00
Emil Ernerfeldt
2684929a5d Make Memory::lock_focus public
closes https://github.com/emilk/egui/issues/993
2021-12-28 10:08:28 +01:00
Emil Ernerfeldt
c63996179b README.md: add link to discussion of begin/end calls vs closures 2021-12-28 10:05:19 +01:00
Emil Ernerfeldt
b88bec9ca3 Document that CtxRef is generational and shouldn't be stored
Related to https://github.com/emilk/egui/issues/1005
2021-12-28 10:05:10 +01:00
Emil Ernerfeldt
b7441eeee7
Make epi::Frame cloneable so you can allocate textures in other threads (#999)
Closes https://github.com/emilk/egui/issues/673

Also adds `epi::Image`
2021-12-26 21:21:28 +01:00
Emil Ernerfeldt
647e020824 file_dialogs example: remove macos exemption
this got fixed in the latest winit upgrade
2021-12-26 18:13:29 +01:00
Emil Ernerfeldt
d5efa4bbca 1.56 fix: missing_crate_level_docs -> rusdoc::missing_crate_level_docs 2021-12-26 09:56:14 +01:00
Emil Ernerfeldt
c60d17b91b
Set MSRV to 1.56.0 and use rust edition 2021 (#998) 2021-12-25 19:32:25 +01:00
Emil Ernerfeldt
ee00214511 Add a badge and link to the official discord server
Closes https://github.com/emilk/egui/pull/990
2021-12-25 16:56:24 +01:00
Ivgeni "Iv" Segal
26885c20d0
Add ability to customize the display of hover plot labels (#934)
* Add ability to customize the display of hover plot labels

* Ergonomic enhancement to plot hover label function

* Use Option instead of empty string for custom hover label name arg

* Revert "Use Option instead of empty string for custom hover label name arg"

This reverts commit 296caebb74b7ee993fbff97187791180d16708af.

Co-authored-by: Ivgeni Segal <ivgeni.segal@tovutiteam.com>
2021-12-25 16:29:29 +01:00
Victor Sergienko
d05379902c
#972: ScrollArea::stick_to_bottom() has no effect if ScrollArea is not initialized. (#973) 2021-12-25 16:29:11 +01:00
Aevyrie
c69fe941af
Reverse horizontal scrolling on windows (#966)
* Reverse horizontal scrolling on windows

* Add link to winit issue

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-12-20 23:00:38 +01:00
Jay Oster
dd99f68e82
Update winit to 0.26 (#930)
Also update glium, glutin and rfd
2021-12-20 22:44:53 +01:00
kud1ing
214d2b5d4f
Changlog: mention menu::menu(ui, ...) => ui.menu_button(...) (#956) 2021-12-19 21:17:02 +01:00
Jan Haller
5ec14867c8
OrderedFloat refactor (#918)
* Move egui/util/float_ord.rs -> epaint/util/ordered_float.rs

* Implement Hash on OrderedFloat

* Generic OrderedFloat<T>; impl Hash; documentation
2021-12-11 13:52:23 +01:00
Emil Ernerfeldt
c85eca6eaa Update changelog 2021-11-29 18:43:52 +01:00
Jan Haller
1088d950e9
Add bar charts and box plots (#863)
Changes:
* New `BarChart` and `BoxPlot` diagrams
* New `FloatOrd` trait for total ordering of float types
* Refactoring of existing plot items

Co-authored-by: niladic <git@nil.choron.cc>
2021-11-29 18:39:58 +01:00
Alexander Chaplin Braz
224d4d6d26
eframe: don't prettify presisted ron strings (#902)
What ultimately ends up stored into a file is the
HashMap<String, String>, which when prettified only nets three
(depending on the settings) lines of "string": "long prettified
string that's really hard to read because of the extra indentation and
literal \n characters that are all just on one single line".

Not prettifying the values in the first place makes it somewhat easier
to read and also saves a bit of space.
2021-11-28 16:37:40 +01:00
Sven Niederberger
9d56bce592
Improved plot interaction methods (#892)
ctx plot_bounds plot_hovered screen_from_plot plot_from_screen etc
2021-11-27 23:59:32 +01:00
Henrique Penteado Kujawski Périgo
6b5c4b9aec
Disable glow scissor test after painting (#905) 2021-11-27 11:44:23 +01:00
Emil Ernerfeldt
5fee6b7bc5
Anti-alias path ends (#893)
Closes https://github.com/emilk/egui/issues/876
2021-11-14 17:23:51 +01:00
Emil Ernerfeldt
a0b635dc21 Add Button::image_and_text to changelog
following https://github.com/emilk/egui/pull/832
2021-11-13 13:19:13 +01:00
d10sfan
90757ca221
Add Button::image_and_text (#832)
* Support image button with text
* Add example (`cargo run --example native_texture`)
2021-11-13 13:17:02 +01:00
Emil Ernerfeldt
8896243146 Fix egui_web integration link in demo app 2021-11-13 13:14:39 +01:00
Emil Ernerfeldt
89700dfbbb Improve egui_glow debug prints 2021-11-13 13:14:10 +01:00
Emil Ernerfeldt
105cb57050 Clean up glsl code 2021-11-13 13:09:08 +01:00
Emil Ernerfeldt
b5cb2b2c0d Some code cleanup following https://github.com/emilk/egui/pull/888 2021-11-13 12:55:48 +01:00
triangle drawer
008a971e73
Fix wrong gamma in WebkitGTK (#888)
Closes https://github.com/emilk/egui/issues/794

Also refactor and improve VAO support detection

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-11-13 12:32:01 +01:00
Emil Ernerfeldt
4d4c75c6f1 Fix vertical slider up/down keys and add a line in the changelog
Follow-up to https://github.com/emilk/egui/pull/875
2021-11-13 12:30:13 +01:00
Bruce Reif (Buswolley)
491739b580
implement vertical slider orientation (#875) 2021-11-13 12:03:10 +01:00
Sven Niederberger
0bad1d0c99
Plot interaction methods (#766)
* move to a basic plot builder with callback
* add some interaction methods
* move interaction demo to its own panel
2021-11-13 11:56:22 +01:00
Emil Ernerfeldt
6018c0e194
Area: automatically request a repaint when showing up for the first time (#866) 2021-11-07 21:14:13 +01:00
Emil Ernerfeldt
951ee4e142
Improve color picker cache (#886)
* colorpicker: try to maintain hue even when saturation goes to zero
* More consistent arguments to color types
* implement `Hash` for `Rgba`.
2021-11-07 21:11:42 +01:00
Emil Ernerfeldt
ddd5f6f4f6
winit: don't explicitly handle Cmd-Q and Alt-F4 (#881)
Closes https://github.com/emilk/egui/issues/877

Still a problem: https://github.com/rust-windowing/winit/issues/1998
2021-11-07 20:58:02 +01:00
Emil Ernerfeldt
19d24bbebe
Don't clip popups to the parent cliprect (#885)
Closes https://github.com/emilk/egui/issues/825
2021-11-07 19:48:38 +01:00
Emil Ernerfeldt
878eddd546
When using a custom font you can now specify a font index (#873)
When using a custom font you can now specify a font index

Closes https://github.com/emilk/egui/issues/853
2021-11-07 19:47:52 +01:00
sumibi-yakitori
10c8ffa543
Fix to limit X position of text agent to client width (#870) 2021-11-06 10:33:43 +01:00
Emil Ernerfeldt
83e490fb6a Remove egui::math & egui::paint, replace with egui::emath & egui::epaint 2021-11-06 09:47:23 +01:00
Emil Ernerfeldt
0ab93576da Remove references to the old begin_frame 2021-11-03 23:00:00 +01:00
Emil Ernerfeldt
6d33beabb1 Replace scroll_delta and zoom_delta in RawInput with events
Part of https://github.com/emilk/egui/issues/843
2021-11-03 22:57:13 +01:00
Emil Ernerfeldt
49e43885ff
Replace Context::begin_frame/end_frame with fn run taking a closure (#872)
* Replace Context begin_frame/end_frame with `fn run` taking a closure
* Create `egui::__run_test_ui` to replace `Ui::__test`
* Add helper `egui::__run_test_ctx` for doctests
2021-11-03 20:11:25 +01:00
Emil Ernerfeldt
e54106e950 Extend contributor list 2021-11-03 19:37:38 +01:00
Emil Ernerfeldt
c71090473b glow-vs-web cleanup following https://github.com/emilk/egui/pull/868 2021-11-03 19:35:20 +01:00
triangle drawer
804722a1ba
Make egui_glow painter to work on web (#868)
Add WebGL1 and WebGL2 support to glow painter.
Add "glow" feature to egui_web to use the glow painter there.
Make winit an optional part of egui_glow
2021-11-03 19:17:07 +01:00
Emil Ernerfeldt
1dbe608e73
Refactor integrations (#871)
* Unify code in egui_glium and egui_glow into egui_winit::EpiIntegration
* Simplify `EguiGlium` interface
* Simplify `EguiGlow` interface
* egui_web refactor: merge `WebBackend` into `AppRunner`
2021-11-03 13:45:51 +01:00
sumibi-yakitori
b1716be745
egui_web: constrain the IME text agent to the canvas (#830)
limit the position of the text agent to the height of the client area
2021-11-02 19:46:42 +01:00
Emil Ernerfeldt
eda1d91654 Add culling of the painting for most widgets
This is a good early-out for widgets in `ScrollAreas`, but
also prepares for speeding up the first pass of a possible two-pass
version of egui: https://github.com/emilk/egui/issues/843
2021-11-01 22:08:23 +01:00
5225225
461f380a24
egui-winit: Fix shift key getting stuck with shift:both_capslock X11 option set (#849)
Listen for modifiers using ModifiersChanged
2021-11-01 21:34:06 +01:00
Emil Ernerfeldt
09b8269326
Rich text for all widgets (#855)
Introduce `RichText` and `WidgetText`
2021-11-01 21:30:10 +01:00
Emil Ernerfeldt
9378cd5c6e ScrollArea: add methods to control horizontal scroll offset
Closes https://github.com/emilk/egui/issues/864
2021-11-01 19:43:27 +01:00
Linus Behrbohm
b9368aba13
Fix Response::context_menu return type (#857) 2021-11-01 19:27:51 +01:00
Emil Ernerfeldt
78dfde40b2 Remove deprecated functions 2021-10-30 09:46:06 +02:00
Emil Ernerfeldt
b551dfec81 fix broken docstring link 2021-10-29 21:10:53 +02:00
Emil Ernerfeldt
081485bcaf Make it cheaper to clone Memory if there are a lot of persisted values 2021-10-27 16:49:08 +02:00
Emil Ernerfeldt
3819ec6fc7 Update "password" demo (storing widget state) after #836 refactor
https://github.com/emilk/egui/pull/836
2021-10-27 16:40:21 +02:00
Emil Ernerfeldt
c7638ca7f5
Add read/write of TextEdit cursor state (#848)
* Rename `CursorPair` to `CursorRange`
* Easymark editor: add keyboard shortcuts to toggle bold, italics etc
* Split up TextEdit into separate files
* Add TextEdit::show that returns a rich TextEditOutput object with response, galley and cursor
* Rename text_edit::State to TextEditState
2021-10-27 16:30:14 +02:00
Emil Ernerfeldt
ddd52f47c5 Add tessellation option to toggle rounding text to pixel coordinates
On by default and only there for testing
2021-10-27 08:56:06 +02:00
Emil Ernerfeldt
8e4fd942a9
Refactor memory data (#836)
This refactors the widget state storage introduced by @optozorax in https://github.com/emilk/egui/pull/257

* Unify the four buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `data`.
  * Less complexity, and also less chance of error (storing in one bucket, reading from another).
* Store data by `Id` and `TypeId`.
  * Users can thus reuse the same `Id` to store many types.
  * Uses a simple xor of id and typeid, which is fast and good since both id and typeid are already high-entropy hashes.
* Use different suffixes on the functions to pick if you want the data persisted or not (`get_temp`, `insert_persisted`, etc).
  * Writing with one suffix and reading with the other works.
* To store state not bound to a specific `Id` (i.e. only based on type), use the new `Id::null` as the key.
2021-10-27 08:51:34 +02:00
Emil Ernerfeldt
bbe0f6089c Context-menu: fix right-click edge-case
response.hovered() checks a larger rect which includes the item spacing,
and it always matches the hover effect.
2021-10-26 20:40:17 +02:00
Emil Ernerfeldt
41f77ba7d7 Clean up some of the new context menus
Follow-up to https://github.com/emilk/egui/pull/543

* Add entry to CHANGELOG.md
* Add entry to contributors in README.md
* Improve documentation
* Simplify demo
2021-10-26 20:16:46 +02:00
Linus Behrbohm
46fb9ff09b
Context menus (#543)
Main usage: `response.context_menu(…)` and `ui.menu_button`
2021-10-26 19:55:42 +02:00
Emil Ernerfeldt
b31ca7efc9 PR template: remind people to run cargo fmt and cargo clippy 2021-10-25 23:21:39 +02:00
Emil Ernerfeldt
445488755f Point contributors to the new egui Discord 2021-10-25 23:20:42 +02:00
Emil Ernerfeldt
c335c56de1 dancing_string demo: handle large input.time
casting time to f32 is a bad idea if the time is seconds since epoch
as it is on some integrations.
2021-10-24 18:38:44 +02:00
Emil Ernerfeldt
c090497727 Add 0.15.0 release code editor gif used in CHANGELOG.md 2021-10-24 17:15:23 +02:00
Emil Ernerfeldt
08cc07bb2d Release 0.15.0 - Syntax highlighting, hscroll, egui-winit and egui_glow 2021-10-24 16:15:07 +02:00
Emil Ernerfeldt
c3f61b67fe New web demo 2021-10-24 16:00:56 +02:00
Emil Ernerfeldt
316202c33a egui_web: improve text input on mobile and for IME 2021-10-24 15:57:36 +02:00
Emil Ernerfeldt
9a9b1b8746 On touch screens, prioritize drag-to-scroll over drag-to-select text
On mobile, click first, then edit the text.
Otherwise a lot of areas become difficult to scroll on mobile.
2021-10-24 15:26:40 +02:00
Emil Ernerfeldt
17f0ae22c9 Code example demo: remove extra ScrollArea 2021-10-23 15:36:25 +02:00
Emil Ernerfeldt
da09a5c69c Fix compilation with "serialize" feature without "persistence" feature 2021-10-23 15:12:39 +02:00
Emil Ernerfeldt
4ace85b780 Fix: '\t' always take up the width of four spaces 2021-10-23 14:38:26 +02:00
Emil Ernerfeldt
96b642a7f5 Improve issue template instructions 2021-10-23 13:57:58 +02:00
Emil Ernerfeldt
7c82111234 Update changelog with credits
Thanks to:

* [AlexApps99](https://github.com/AlexApps99)
* [baysmith](https://github.com/baysmith)
* [bpostlethwaite](https://github.com/bpostlethwaite)
* [cwfitzgerald](https://github.com/cwfitzgerald)
* [DrOptix](https://github.com/DrOptix)
* [JerzySpendel](https://github.com/JerzySpendel)
* [NiceneNerd](https://github.com/NiceneNerd)
* [parasyte](https://github.com/parasyte)
* [spersson](https://github.com/spersson)
* [Stock84-dev](https://github.com/Stock84-dev)
* [sumibi-yakitori](https://github.com/sumibi-yakitori)
* [t18b219k](https://github.com/t18b219k)
* [TobTobXX](https://github.com/TobTobXX)
* [zu1k](https://github.com/zu1k)
2021-10-23 06:14:08 +02:00
Emil Ernerfeldt
39c6c7e5c9 Small code cleanup 2021-10-23 06:03:45 +02:00
Emil Ernerfeldt
a3ba85dbb3 Improve docs 2021-10-23 06:03:45 +02:00
Emil Ernerfeldt
4194a83a5e Update ron 0.6 -> 0.7 2021-10-23 05:53:58 +02:00
Emil Ernerfeldt
5301043a77 cargo update 2021-10-23 05:51:56 +02:00
Emil Ernerfeldt
6185ee8ce4 Demo: add button to reset the demo 2021-10-23 05:50:42 +02:00
Emil Ernerfeldt
5d463b2af7 Remove serde_json from egui dev-dependencies 2021-10-23 04:47:17 +02:00
Caleb Smith
79d1ede496
Add Frame::set_window_title() (#828)
* Add `Frame::set_window_title()`

* Changelog and fmt for `Frame::set_window_title()`

Co-authored-by: Caleb Smith <caleb@myrvmail.com>
2021-10-23 00:03:17 +02:00
Emil Ernerfeldt
26c6cea117 Credit notable contributors
@n2 @optozorax @quadruple-output @EmbersArc @AsmPrgmC3 @AlexApps99

Chosen for their substantial contributions
2021-10-23 00:02:21 +02:00
sumibi-yakitori
19766bfe4c
Fix when a string containing CRLF is pasted from the clipboard (#826) 2021-10-20 22:26:26 +02:00
Emil Ernerfeldt
2a9037cd90 Clean up epaint mutex code 2021-10-20 22:24:34 +02:00
Emil Ernerfeldt
dd50cba9a7 Optimize debug builds 2021-10-20 21:58:35 +02:00
Emil Ernerfeldt
d97a369c44 End statements with semicolon (clippy::semicolon_if_nothing_returned) 2021-10-20 16:46:57 +02:00
Emil Ernerfeldt
ab3be8aca3 Use for-loop instead of Iterator::for_each 2021-10-20 16:46:57 +02:00
Emil Ernerfeldt
40445c450c use map_or and map_or_else 2021-10-20 16:46:57 +02:00
Emil Ernerfeldt
a0cd41755e Add more clippy lints 2021-10-20 16:30:39 +02:00
Emil Ernerfeldt
087c6695bb Handle having no fonts (missing "default_fonts" feature) without a crash 2021-10-20 15:40:06 +02:00
Emil Ernerfeldt
19a2a57f80 Rename and clean-up the "pure" glium and glow examples 2021-10-20 15:05:41 +02:00
Emil Ernerfeldt
dd5d41d04e Improve backend panel in demo 2021-10-20 13:54:56 +02:00
Emil Ernerfeldt
72d483ac22 repaint when pixels_per_point changes 2021-10-20 13:54:36 +02:00
Emil Ernerfeldt
3ba406c0fe egui-winit: restore window position accurately
previous code had mixed up inner and outer coordinates
2021-10-20 13:54:08 +02:00
Emil Ernerfeldt
f025513998
Code example demo (#823) 2021-10-20 12:34:27 +02:00
Emil Ernerfeldt
8accfd9a8f Clean up egui_glow painter code 2021-10-20 12:29:35 +02:00
Emil Ernerfeldt
da74687be5 egui-winit: fix windows compilation with use WindowBuilderExtWindows
Closes https://github.com/emilk/egui/pull/821
2021-10-20 09:51:21 +02:00
Emil Ernerfeldt
8178d23d19
Deduplicate code found in both egui_glium and egui_glow (#819)
* Move window building to egui-winit

* Move icon loading to egui-winit

* `use glow::HasContext;` -> `use glow::HasContext as _;`

* Move FileStorage into epi behind a feature flag

* De-duplicate screen_size_in_pixels and native_pixels_per_point

* Move creation of FileStorage to epi

* Handle epi app output (window size changes etc) in egui-winit

* Move app and memory persistence and autosave logic to egui-winit

* fix check.sh

* Make the epi backend opt-in for egui_glium and egui_glow

* Fix persistence

* Add integration name to epi::IntegrationInfo and the demo

* Clean up Cargo.toml files and fix making egui_glium optional

* fix typo

* Make egui_glium compile without the `epi` feature
2021-10-19 21:40:55 +02:00
Emil Ernerfeldt
844dd9d7a4 Remove "seconds_since_midnight" from epi/eframe. Use chrono instead
chrono works both natively and on web.

Related: https://github.com/emilk/egui/issues/212
2021-10-19 15:37:20 +02:00
Emil Ernerfeldt
cdd4dccf5f
Add egui_glow as an opt-in backend to eframe (#817)
* Make egui_glow and opt-in backend for eframe

* Add egui_glow to Cargo.toml and to CI

* Reference egui_glow where egui_glium is mentioned

* Remove path-patches from root Cargo.toml

* Add instructions on how to enable the glow backend of eframe
2021-10-19 15:32:23 +02:00
Emil Ernerfeldt
cf273e3519 Fix glow window resize bug 2021-10-18 23:39:33 +02:00
AlexApps99
877e89f2ec
Add egui_glow backend as alternative to egui_glium (#685) 2021-10-18 23:13:32 +02:00
Emil Ernerfeldt
df3aeab434 Update egui_glium and egui_web changelogs 2021-10-18 23:13:13 +02:00
Connor Fitzgerald
74494cd713
Add clear to webgl1 and webgl2 backends (#816)
Signed-off-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com>
2021-10-18 23:10:30 +02:00
Emil Ernerfeldt
9f1a5dcb33 ui.label now take impl ToString as argument, not impl Into<Label> 2021-10-18 21:09:44 +02:00
Emil Ernerfeldt
ebd2c859ac Make multi-touch demo a bit nicer looking 2021-10-18 21:00:25 +02:00
Emil Ernerfeldt
96be848e42 rename egui_template to eframe_template 2021-10-18 20:19:43 +02:00
Emil Ernerfeldt
2af2e8bad5 Add ui.add_enabled and ui.add_enabled_ui, and remove Button::enabled 2021-10-17 22:17:50 +02:00
Emil Ernerfeldt
8a47019c1a Rename TextEdit::enabled to TextEdit::interactive. 2021-10-17 22:13:15 +02:00
Emil Ernerfeldt
fe76382141 Expand eframe/README.md 2021-10-17 21:18:25 +02:00
Emil Ernerfeldt
7e0bb18152 More #[inline] 2021-10-17 20:56:37 +02:00
Emil Ernerfeldt
1f5cd482f6
Replace ProggyClean.ttf with Hack-Regular.ttf as default monospace font (#802)
https://github.com/source-foundry/Hack, MIT license
2021-10-17 20:49:28 +02:00
TobTobXX
ca632c5e5d
egui-winit: Emulate mouse from touches (#791)
Additionally to emmiting egui::Event::Touch events, on_touch() now also
emits egui::Event::PointerButton, egui::event::PointerMoved and
egui::event::PointerGone facilitating UI usage on touchscreen devices.

If there are multiple touches, only the first one will be translated
into pointer events. This prevents situations with two pointers.
2021-10-17 20:30:22 +02:00
Emil Ernerfeldt
bd06dec653
Add sudo apt-get update to CI (#801) 2021-10-16 15:45:35 +02:00
Emil Ernerfeldt
ef218e8ba8 Panels: make Side and TopBottomSide public
Closes https://github.com/emilk/egui/issues/799
2021-10-16 15:35:07 +02:00
Emil Ernerfeldt
6f699b3391 Replace every ... with … like some sort of crazy person
Also fix some small typos

Closes https://github.com/emilk/egui/pull/736
2021-10-13 11:52:34 +02:00
Emil Ernerfeldt
85941033e8 Simplify and improve http demo app
Move demo of POST to the ehttp crate instead
2021-10-13 11:47:57 +02:00
Emil Ernerfeldt
1ab61ce9bb Implement bytemuck for epaint::Rgba
Closes https://github.com/emilk/egui/pull/784
2021-10-13 08:56:34 +02:00
Emil Ernerfeldt
e43cfeac17
egui-winit: fix AltGr characters on windows/linux (#790)
Closes https://github.com/emilk/egui/issues/351
Closes https://github.com/emilk/egui/pull/785
2021-10-13 08:55:00 +02:00
Emil Ernerfeldt
fe2094af14 Fix visual clipping of singleline TextEdit 2021-10-12 22:35:31 +02:00
Emil Ernerfeldt
c42afd3941 Fix missing spaces between labels in horizontal layouts 2021-10-12 22:23:53 +02:00
Cristian Dinu
1c4237097f
Slider, by default clamp to range (#789)
* Slider, by default clamp to range

* Update change log.
2021-10-12 21:15:32 +02:00
Emil Ernerfeldt
fc45b6fdf2 Fix scroll area size clipping to screen rect 2021-10-10 17:14:22 +02:00
Emil Ernerfeldt
e547b149ca Improve docs, especially of epaint, and add epaint/CHANGELOG.md 2021-10-10 15:35:13 +02:00
Emil Ernerfeldt
88d087b462 Fix bad docstring 2021-10-09 15:55:18 +02:00
Emil Ernerfeldt
bc54a49413 Optimization: use IdHasher for AnyMap 2021-10-09 15:55:18 +02:00
Emil Ernerfeldt
cca11ea9cc Optimization: less HashMap, more AHashMap 2021-10-09 15:22:36 +02:00
Emil Ernerfeldt
22a3a75eb5 Optimization: replace HashSet uses with AHashSet 2021-10-09 14:15:45 +02:00
Emil Ernerfeldt
4dcdd014d6 Optimization: introduce IdMap 2021-10-09 14:10:38 +02:00
Ben Postlethwaite
5799758c2b
implement stick-to-end scroll (#765)
* implement stick-to-end scroll

* improve comment grammar

* accept emilk suggestion for demo text tweak

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

* request repaint on each frame to show incoming scroll demo rows

* simplify pub api + doc strings

* disable scroll_stuck_to_end when wheel-scrolling or dragging

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-10-09 12:59:42 +02:00
Emil Ernerfeldt
1dfc399d98 code cleanup: use Trait as _; 2021-10-09 12:04:10 +02:00
Emil Ernerfeldt
45ab9a2450 demo: add syntax_highlighting module with theme editor 2021-10-09 12:04:10 +02:00
Emil Ernerfeldt
0cb1b18a6a fix shrinking scroll area in http demo 2021-10-09 12:04:10 +02:00
Stock84
cd2227814f
Implement bytemuck behind a feature (#775) 2021-10-07 22:30:15 +02:00
Emil Ernerfeldt
ce8d863249 auto-shrink ScrollArea to content size
Unless the user disables it with `ScollArea::auto_shrink([false; 2])`.
2021-10-06 23:51:03 +02:00
Emil Ernerfeldt
a1bf5aff47 Add Memory::caches for caching things from one frame to the next 2021-10-06 17:54:45 +02:00
Emil Ernerfeldt
613c0b29f6 TextEdit: replace monomorphization with &mut dyn TextBuffer 2021-10-02 21:50:24 +02:00
Emil Ernerfeldt
8d854391df TextEdit can now show immutable text 2021-10-02 21:43:17 +02:00
Emil Ernerfeldt
8ce7fadc9f TextEdit: make sure it is wide enough when wrap_width isn't respected 2021-10-02 21:33:07 +02:00
Emil Ernerfeldt
1537171205 AnyMap: print deserialization errors to stderr 2021-10-02 21:30:52 +02:00
Emil Ernerfeldt
f59abd9684 Clean up some macro syntax 2021-10-02 21:08:00 +02:00
Emil Ernerfeldt
f0868c2f07 Code editor demo: nice syntax highlighting with customizable theme 2021-10-01 21:29:33 +02:00
Emil Ernerfeldt
96557a4fa6 Add egui::widgets::global_dark_light_mode_buttons 2021-10-01 21:28:12 +02:00
Emil Ernerfeldt
4c78f61a96 egui any maps: add get_or + #[inline] 2021-10-01 21:08:17 +02:00
Emil Ernerfeldt
863e6cb07d expose egui::TextFormat 2021-10-01 19:25:15 +02:00
Emil Ernerfeldt
981b812f68 Smaller and nicer color picker 2021-10-01 19:24:36 +02:00
Emil Ernerfeldt
76cdbe2cf8 Add better documentation for epi::NativeOptions::drag_and_drop_support
Closes https://github.com/emilk/egui/issues/747
2021-09-30 19:40:21 +02:00
Emil Ernerfeldt
e2bdd40985 Move WindowSettings from egui_glium to egui-winit 2021-09-30 19:18:51 +02:00
Simon Persson
7df2408482
Fix disabled textedit blocking focus shift. (#748)
* Fix disabled textedit blocking focus shift.

Fixes #732. Ui::interact was being called twice for the frame rect
regardless of enabled status which was causing problems for kb focus.
Now the interact function is called zero or one time.

* Apply clippy suggestion

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

Co-authored-by: Persson <Simon.Persson@nov.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-09-30 18:54:19 +02:00
Emil Ernerfeldt
3e1db880dc
Revert change to winit event loop in egui_glium (#756)
* Revert change to winit event loop in egui_glium

This reverts https://github.com/emilk/egui/pull/631

Fixes https://github.com/emilk/egui/issues/755

* Add example of file dialogs and file drag-and-drop

* fix ci
2021-09-30 18:53:41 +02:00
Emil Ernerfeldt
5539dbe620
Add separate serialize feature and better persitence control (#753)
* Rename epaint feature "persistence" to "serialize"

* Add separate "serialize" feature to egui

* egui_demo_lib: separate serialize and persistence features

* Add App::persist_native_window and App::persist_egui_memory

Controls what gets persisted
2021-09-29 08:45:13 +02:00
Emil Ernerfeldt
f2dd3dfdd9 Fix popups sometimes getting clipped by panels 2021-09-28 22:19:03 +02:00
Emil Ernerfeldt
711defddb8 Show tooltips above widgets on touch screens 2021-09-28 22:18:22 +02:00
Emil Ernerfeldt
c8bb4cf6e5 Fix multiline pasting in egui_web
Closes https://github.com/emilk/egui/issues/738
2021-09-28 20:47:02 +02:00
Emil Ernerfeldt
e2fa13ed2a cargo update 2021-09-28 20:46:28 +02:00
Emil Ernerfeldt
64b0478993 Fix the issue templates 2021-09-28 20:29:11 +02:00
Emil Ernerfeldt
d9db768180 Optimize tessellator: pass options by reference 2021-09-28 18:00:01 +02:00
Emil Ernerfeldt
2e83e36146 Use ahash for Id and other things that need hashing 2021-09-28 17:56:24 +02:00
zu1k
f6fb4d942a
epi: drag native window (#728)
* feat: drag window

* Update epi/src/lib.rs

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

* Update egui_demo_lib/src/backend_panel.rs

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

* cargo fmt

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-09-28 17:34:58 +02:00
Emil Ernerfeldt
1b36863248
Split out new crate egui-winit from egui_glium (#735) 2021-09-28 17:33:28 +02:00
Emil Ernerfeldt
ba0e3780a1 Add epaint::util::hash function for hashing a value 2021-09-26 03:16:12 +02:00
Emil Ernerfeldt
976260c2bd Better error if neither single_threaded or multi_threaded features is on 2021-09-25 05:26:45 +02:00
Emil Ernerfeldt
52e49bd4ae README: Point to better example in the "how to render 3D" FAQ section 2021-09-20 22:58:26 +02:00
Emil Ernerfeldt
cf17cb2065 egui_glium: allow sharing a native glium texture using Rc 2021-09-20 22:52:29 +02:00
Emil Ernerfeldt
cfb6b31914 Improve native_texture.rs demo 2021-09-20 22:39:44 +02:00
Emil Ernerfeldt
a5a5d6176d Improve the frame around ImageButton
Helps https://github.com/emilk/egui/issues/721
2021-09-20 22:37:21 +02:00
Emil Ernerfeldt
e7cfda4941
Shape refactor (#705)
* More introspection stats about vertices/indices etc

* more serde derive

* #[inline] to Shape constructors

* Introduce RectShape

* Introduce CircleShape

* Introduce PathShape

* More serde derive

* impl Copy for RectShape and CircleShape

* Simplify some code

* More serde derive

* Add helpers for appending more input or output

* Serde derives for RawInput

* Rename Fonts::from_definitions to Fonts::new

* Add Output::take

* refactor EguiGlium slightly

* Derive PartialEq for RawInput

* Improve egui::util::History interface

* tweaks

* Improve History filter: add minimum length

* Calculate galley bounding rect

* tessellator: cull line segments and paths

* tessellator: cull meshes

* Fix bug in History bandwidth estimator
2021-09-20 21:36:56 +02:00
Emil Ernerfeldt
93c2fde1fc Extend section about rendering 3D inside of egui 2021-09-19 20:16:50 +02:00
Emil Ernerfeldt
67bf716b0e
Hide DragValue tooltips unless user set Style::explanation_tooltips (#708)
* Hide DragValue tooltips unless user set Style::explanation_tooltips

Closes https://github.com/emilk/egui/issues/548
Closes https://github.com/emilk/egui/pull/704

* Silence drag_angle_tau tooltip too
2021-09-12 23:05:23 +02:00
Emil Ernerfeldt
f2b6edd6db Replace egui_winit_ash_vk_mem with egui-winit-ash-integration in README.md
Closes https://github.com/emilk/egui/issues/707
2021-09-11 09:34:00 +02:00
Emil Ernerfeldt
f37180f7dc
Bump MSRV (Minimum Supported Rust Version) to 1.54.0 (#703)
1.51.0 clippy has been giving me trouble (not reporting all problems),
and so I take the easy way out and just bump MSRV.

We will upgrade to 1.56.0 once it comes around anyway
to get access to that sweet disjoint capture in closures
(https://doc.rust-lang.org/nightly/edition-guide/rust-2021/disjoint-capture-in-closures.html)
2021-09-09 00:16:06 +02:00
Emil Ernerfeldt
ae6418edf3 clippy fix 2021-09-08 23:40:44 +02:00
Emil Ernerfeldt
a76b81647c Hide tooltips while dragging a widget
Also: don't register as click if the
pointer has been pressed for too long.
2021-09-08 23:26:21 +02:00
zu1k
19eed94499
feat: Set whether to show decorations (#672)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-09-07 21:42:14 +02:00
Emil Ernerfeldt
241667b078 Reduce monomorphization, reducing wasm size by around 3% 2021-09-07 21:33:10 +02:00
Emil Ernerfeldt
bb034e2e6c Improve documentation of different ui layout functions 2021-09-07 20:48:30 +02:00
Emil Ernerfeldt
6331bfed90 Revert change of scrollspeed in egui_web
https://github.com/emilk/egui/pull/702
2021-09-07 20:47:53 +02:00
Emil Ernerfeldt
acb5501fe4 Add justified and/or center- and right-aligned text
Label text will now be centered, right-aligned and/or
justified based on the layout.

Galleys are no longer always pivoted in the left top corner,
so now have a Rect rather than just a size.
2021-09-07 20:37:50 +02:00
Emil Ernerfeldt
cbafd10ee4 Clippy fix 2021-09-07 19:59:14 +02:00
Emil Ernerfeldt
d23982d83e Window bounds fix: handle infinite drag_bounds 2021-09-07 19:55:37 +02:00
Emil Ernerfeldt
249876523d Increase scroll-speed on glium from 8 to 50 points per scroll tick
Closes https://github.com/emilk/egui/issues/461
Closes https://github.com/emilk/egui/pull/702
2021-09-07 19:45:13 +02:00
Emil Ernerfeldt
aef23753ca Fix clip rectangle of windows that don't fit the central area. 2021-09-07 19:34:34 +02:00
Emil Ernerfeldt
5e3c522b6c clippy fixes 2021-09-06 21:23:57 +02:00
Emil Ernerfeldt
7a9805dfb3 demo: highlight easymark editor field with different fonts and colors 2021-09-06 08:14:47 +02:00
Emil Ernerfeldt
5592124ad5 TextEdit: paint cursor on top of text
This is so that we see the cursor even when the text galley has
background colors, like `code` section in easymark do.
2021-09-05 18:59:35 +02:00
Emil Ernerfeldt
f9afdfa143 TextEdit: left/right arrows move cursor to start/end of selection
Closes https://github.com/emilk/egui/issues/611
2021-09-05 11:11:31 +02:00
Emil Ernerfeldt
2382425198 Revert "Silence nightly clippy"
This reverts commit acf9d0114d.
2021-09-05 11:00:56 +02:00
triangle drawer
203d571c8b
Add api for accessing backend texture via epi (#695)
* Define NativeTexture trait for offscreen rendering
add demo for NativeTexture trait

* write changelog

* add comment for native texture example

* formatting

* add license of Rust logo

* NativeTexture trait method rename
remove duplicate function with native texture
remove rust logo

* deprecated notice for register_glium_texture,register_webgl_texture

* collect deprecated notice
2021-09-05 11:00:45 +02:00
Emil Ernerfeldt
acf9d0114d Silence nightly clippy 2021-09-05 10:19:28 +02:00
Emil Ernerfeldt
391bec1170 FAQ: add "How do I render 3D stuff in an egui area?" 2021-09-05 10:18:49 +02:00
Emil Ernerfeldt
92503ea9e1 clippy fix 2021-09-05 10:11:52 +02:00
Emil Ernerfeldt
d9f3596475 docs: explain auto-shrinking panels/windows and how to work around it
related to https://github.com/emilk/egui/issues/696
2021-09-05 09:40:25 +02:00
Emil Ernerfeldt
14c989fdfa Implement rotating text
Closes https://github.com/emilk/egui/issues/428
2021-09-05 09:06:53 +02:00
Emil Ernerfeldt
6902151a96 Add example of loading and showing an image with eframe/egui
Closes https://github.com/emilk/egui/pull/700
2021-09-04 17:44:01 +02:00
Emil Ernerfeldt
5f88d89f74
Faster galley cache (#699)
* Speed up galley cache by only using the hash as key

This hashes the job but doesn't compare them with Eq,
which speeds up demo_with_tessellate__realistic by 5-6%,
winning back all the performance lost in
https://github.com/emilk/egui/pull/682

* Remove custom Eq/PartialEq code for LayoutJob and friends

* Silence clippy

* Unrelated clippy fixes
2021-09-04 10:19:58 +02:00
Emil Ernerfeldt
3b75a84d3b Point crate repository & homepage urls to their subfolders 2021-09-03 21:12:44 +02:00
Emil Ernerfeldt
71d18ba3e7 Spelling: tesselate -> tessellate 2021-09-03 21:07:25 +02:00
Emil Ernerfeldt
9598596bdc
Replace all http code in epi/eframe/egui_glium/egui_web with ehttp (#697)
I've extracted all the http request code and turned it
into its own crate at <https://github.com/emilk/ehttp>.

There was never a reason for the HTTP request library to be part of
`eframe`. Much better to have it as its own crate!
2021-09-03 21:04:43 +02:00
Emil Ernerfeldt
de1a1ba9b2
New text layout (#682)
This PR introduces a completely rewritten text layout engine which is simpler and more powerful. It allows mixing different text styles (heading, body, etc) and formats (color, underlining, strikethrough, …) in the same layout pass, and baked into the same `Galley`.

This opens up the door to having a syntax-highlighed code editor, or a WYSIWYG markdown editor.

One major change is the color is now baked in at layout time. However, many widgets changes text color on hovered. But we need to do the text layout before we know if it is hovered. Therefor the painter has an option to override the text color of a galley.


## Performance
Text layout alone is about 20% slower, but a lot of that is because more tessellation is done upfront. Text tessellation is now a lot faster, but text layout + tessellation still lands at a net loss of 5-10% in performance. There are however a few tricks to speed it up (like using `smallvec`) which I am saving for later. Text layout is also cached, meaning that in most cases (when all text isn't changing each frame) text tessellation is actually more important (and that's more than 2x faster!).

Sadly, the actual text cache lookup is significantly slower (300ns -> 600ns). That's because the `TextLayoutJob` is a lot bigger (it has more options, like underlining, fonts etc), so it is slower to hash and compare. I have an idea how to speed this up, but I need to do some other work before I can implement that.

All in all, the performance impact on `demo_with_tesselate__realistic` is about 5-6% in the red. Not great; not terrible. The benefits are worth it, but I also think with some work I can get that down significantly, hopefully down to the old levels.
2021-09-03 18:18:00 +02:00
Emil Ernerfeldt
36cffd7b84 Fix wrongly sized multiline TextEdit in justified layouts 2021-09-03 17:49:23 +02:00
Mohammed Alyousef
d9fd806e94
add fltk-egui as a 3rd party backend crate (#694) 2021-09-03 15:20:34 +02:00
Emil Ernerfeldt
4f8dcf9a27 Augment list of fedora dependencies
Taken from https://github.com/emilk/egui_template/pull/24
2021-09-02 21:25:38 +02:00
Bradley Smith
931c3a9b6a
TextEdit option cursor_at_end to control initial position of cursor at beginning or end. (#687) 2021-09-02 19:37:55 +02:00
JerzySpendel
56502fbb3c
Make paint function accept anything implementing Surface trait (#681)
* Make paint function accept anything implementing Surface trait

* Remove unused imports
2021-09-02 14:44:50 +02:00
Emil Ernerfeldt
9def6ef6df All Ui:s must now have a finite max_rect
Deprecated `max_rect_finite`, `available_size_before_wrap_finite`
and `available_rect_before_wrap_finite`.
2021-08-28 16:02:16 +02:00
Emil Ernerfeldt
105b999cb6
Horizontal scrolling (#663)
* First pass (missing rendering the bar)

* Render horizontal bars, and change Window scroll API

* emath: add impl Index + IndexMut for Align2

* Scrolling: fix subtle sizing bugs

* Add horizontal scrolling to color test

* try to wrap content before showing scrollbars, + add auto-shrink option

* Add hscroll to the misc demo window

* Fix for putting wrapping labels in an infinitely wide layout

* Add a egui_asserts to protect against nans in the layout engine

* Add line about horizontal scrolling to changelog

* Add example to docs of ScrollArea

* code cleanup
2021-08-28 13:18:21 +02:00
Emil Ernerfeldt
e98ae2ea7a
Revert "feat: Set whether to show decorations (#660)" (#671)
This reverts commit 0db74f3000.
2021-08-28 12:29:19 +02:00
Emil Ernerfeldt
776770cdcd
Publish 0.14.2 (#670)
* Fix window resize bug introduced in `0.14.1`.

* tweak plot demo layout to allow more narrow window

* Release 0.14.2 - Window resize fix
2021-08-28 12:19:35 +02:00
Emil Ernerfeldt
1fbce6b2c3 Remove everything marked deprecated 2021-08-28 11:46:30 +02:00
zu1k
0db74f3000
feat: Set whether to show decorations (#660)
* feat: Set whether to show decorations

* cargo fmt

* Update comment and changelog
2021-08-28 11:18:36 +02:00
Emil Ernerfeldt
2ce99f3a12 Release 0.14.1 - Bug fixes and better web blending 2021-08-28 11:04:39 +02:00
Emil Ernerfeldt
fe2b3e26aa egui_glium: add bool to simulate touch screens to test egui 2021-08-28 10:34:05 +02:00
Emil Ernerfeldt
1a177f7ecd Improve area introspection panel 2021-08-28 10:28:50 +02:00
Emil Ernerfeldt
56913a9ae9 Fix sometimes not being able to click inside a combo box or popup menu 2021-08-28 10:28:05 +02:00
Emil Ernerfeldt
9bc95289cc
egui_web: Fix use of egui on devices with both touch and mouse (#654) 2021-08-28 08:37:07 +02:00
Emil Ernerfeldt
c6a0db8602 Docs: add link to on_disabled_hover_ui/text from on_hover_ui/text 2021-08-27 20:23:58 +02:00
Emil Ernerfeldt
7c67066190 demo: add expandable top panel to panels-in-window demo 2021-08-27 20:08:01 +02:00
Emil Ernerfeldt
52193c2273 demo: demonstrate how to prevent auto-shrinking windows 2021-08-27 19:59:31 +02:00
Emil Ernerfeldt
906a798003 Add Ui::horizontal_top
Closes https://github.com/emilk/egui/issues/601
2021-08-26 21:32:11 +02:00
Emil Ernerfeldt
a9467fc5df epaint stats: correctly count the impact of Shape::Text 2021-08-26 20:44:42 +02:00
Emil Ernerfeldt
7e9c3291bd Tiny demo tweaks 2021-08-26 18:54:38 +02:00
Emil Ernerfeldt
693426d9c6
Layout fixes (#651)
* Fix incorrect max_width/height of panels

* Fix set_width/set_min_width/set_height/set_min_height

Closes https://github.com/emilk/egui/issues/647

Broke in https://github.com/emilk/egui/pull/629

* Fix expand_to_include_x/expand_to_include_y

* Make minimum grid column width propagate properly

* Expand cursor when max_rect expands

* Add ui.expand_to_include_y

* Only expand cursor in advance

* demo: clean up font_book code

* Fix: Make sure `TextEdit` contents expand to fill width if applicable

* ProgressBar: minimum width and fix for having it in an infinite layout

* clippy fix
2021-08-26 18:50:30 +02:00
AsmPrgmC3
3a634ff46e
Reintroduce color fixes to WebGL1 backend, but only if sRGB textures are supported (#652)
Also removed the 300es shaders, as they'd be identical to the 100es ones.
2021-08-26 18:40:35 +02:00
Emil Ernerfeldt
c510899019 Revert "Fix set_width/set_min_width/set_height/set_min_height"
This reverts commit 01d3f53113.
2021-08-25 22:02:54 +02:00
Emil Ernerfeldt
c2d1034af3 Make Options::tessellation_options public
Closes https://github.com/emilk/egui/issues/644
2021-08-25 21:39:00 +02:00
AsmPrgmC3
31a1882997
Fix alpha blending in WebGL2 backend (#650)
Add a render-to-texture step with an sRGBA8 texture
2021-08-25 21:28:42 +02:00
Vladislav Izbash
a256337856
Add maximized property to NativeOptions (#649) 2021-08-25 18:20:53 +02:00
Emil Ernerfeldt
01d3f53113 Fix set_width/set_min_width/set_height/set_min_height
Closes https://github.com/emilk/egui/issues/647

Broke in https://github.com/emilk/egui/pull/629
2021-08-25 17:28:22 +02:00
Emil Ernerfeldt
cb566fc295 Release 0.14.0 - Ui panels and bug fixes 2021-08-24 16:47:10 +02:00
Emil Ernerfeldt
a6799b1278 Minor demo improvements 2021-08-24 16:38:03 +02:00
Emil Ernerfeldt
a2082f226f Change an assert into an egui_assert 2021-08-24 16:00:55 +02:00
Emil Ernerfeldt
f11f9bd1fd Bug fix: fix horizontal layouts inside infinitely wide Ui:s. 2021-08-24 16:00:39 +02:00
Emil Ernerfeldt
d865852d24 Add a minimum width to TextEdit to prevent them from being tiny 2021-08-24 15:59:31 +02:00
Emil Ernerfeldt
f940ae9edf Fix alignment corner case (infinite size in infinite range) 2021-08-24 15:58:57 +02:00
Emil Ernerfeldt
246b1e084b Improve panel demo 2021-08-23 22:28:42 +02:00
Emil Ernerfeldt
ce19e10258 REAMDE: add link to egui docs about immediate mode 2021-08-23 21:48:38 +02:00
Emil Ernerfeldt
ffbd094f53 Improve docs 2021-08-23 21:47:00 +02:00
Emil Ernerfeldt
5d0e348777 Clean up demo slightly 2021-08-23 21:28:06 +02:00
Emil Ernerfeldt
f8a3042258 Fix for dragging widgets with non-primary mouse button
Broken in e31312cf7a
2021-08-22 21:16:54 +02:00
Emil Ernerfeldt
605762364a Add recent additions to CHANGELOG.md 2021-08-21 22:11:38 +02:00
Emil Ernerfeldt
5a63419aa3 Bug fix: clicking the edge of a menu button would flicker the menu
There was a very annoying bug where clicking the edge of a menu button
would open the menu and immediately close it.
2021-08-21 21:59:25 +02:00
Emil Ernerfeldt
0e457c4b06 build_demo_web.sh: add --fast flag to skip optimization step 2021-08-21 21:33:51 +02:00
Emil Ernerfeldt
91bdf9ba6e
egui_web: make text thicker and less pixelated (#640)
Closes https://github.com/emilk/egui/issues/516
2021-08-21 21:18:00 +02:00
sigaloid
12fd4906de
Update dependencies and pass all clippy's (#636)
* Cargo update

https://pastebin.com/raw/KWNuJD9u

* Cargo clippy +nightly

* Remove deprecated clippy

* Fix unbalanced backtick (now passes cargo {+nightly,} clippy)
2021-08-20 22:31:20 +02:00
Emil Ernerfeldt
a256ca115b
Drag and drop files into egui_glium and egui_web (#637)
* Implement file drag-and-drop for egui_glium

* Implement file drag-and-drop into egui_web

* Cleanup
2021-08-20 22:20:45 +02:00
mitchmindtree
488b1f2462
Add methods for optionally displaying the background/axes of a Plot (#562)
* Add methods for optionally displaying the background/axes of a `Plot`

These are particularly useful when using the `Plot` widget as an overlay
over an existing grid or some other content.

* Allow for showing each axis of a `Plot` individually
2021-08-20 19:04:44 +02:00
gents83
68ed22ab6f
Add option to select collapsing headers (#623)
* Add collapsing header select as selectable label

* Modified Tree demo adding selectable example

* Update egui/src/containers/collapsing_header.rs

Selected is not linked to selectable

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

* Update egui/src/containers/collapsing_header.rs

Description example

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

* Changing example without name clashing

* Fixing merge issue (ah I miss P4 sometimes)

* Fixing doctest example

* Add possibility to show background to a single one

* Fixing clippy test

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-08-20 19:04:13 +02:00
Emil Ernerfeldt
04b3921923
egui_glium: run app code outside event loop to fix file dialogs (#631)
Previously app code was run from within the event loop
which lead to file dialogs (e.g. using nfd2) to hang
(see https://github.com/rust-windowing/winit/issues/1779)

Now egui_glium polls for events and then runs the app code.
2021-08-20 18:59:32 +02:00
Emil Ernerfeldt
661f0d71a7
Add libssl-dev to apt-get install path (#635) 2021-08-20 14:37:14 +02:00
Emil Ernerfeldt
3e2746a288
Make it easy to panels inside of Ui:s (#629)
* Allow using the layout cursor to restrict available area

* Avoid id clashes when putting panels inside a Ui

* Panels: Propagate height/width range to inner Ui

* Allow easy placement of panels inside of Ui:s

* demo: simplify Windows with Panels demo
2021-08-20 00:10:06 +02:00
Emil Ernerfeldt
ee50cca696 Run egui_demo_app with extra_debug_asserts 2021-08-18 22:51:29 +02:00
Emil Ernerfeldt
62808b2bb9 Silence warning 2021-08-18 22:51:16 +02:00
Emil Ernerfeldt
9bc732328f eframe: Don't restore window position on Windows
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 impossible
to bring to get at. So we no longer restore window positions on Windows.
2021-08-18 22:51:04 +02:00
Emil Ernerfeldt
734ec9dc5a Add link to amethyst_egui 2021-08-18 12:46:59 +02:00
zu1k
1fc2510b3b
Fix blocking when using custom large font files (#594)
* Fix blocking when using custom large font files

* Add docstring explaining laziness

* Put characters behind a epaint::RwLock

* cargo fmt font.rs

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-08-16 22:17:31 +02:00
gents83
ff8c4c0d38
Add possibility of panels inside UI (i.e. windows) (#624)
* Adding possibility to have panels inside UI

* Adding window with panels demo
2021-08-16 21:32:44 +02:00
Emil Ernerfeldt
d6299bcd91 Expand egui_web/README.md with list of shortcomings 2021-08-16 21:17:15 +02:00
Emil Ernerfeldt
96c45716be Expand and clean up changelogs 2021-08-15 18:31:50 +02:00
Emil Ernerfeldt
2f46b0eb06 Fix lost_focus for TextEdit widgets
Closes https://github.com/emilk/egui/issues/565
2021-08-15 17:52:53 +02:00
Emil Ernerfeldt
e31312cf7a Only move/resize windows with primary mouse button
Closes #578
Closes #579
2021-08-15 17:26:48 +02:00
Emil Ernerfeldt
07196158c9 eframe/epi: Default drag_and_drop_support to false
Closes https://github.com/emilk/egui/issues/598
2021-08-15 17:17:16 +02:00
Emil Ernerfeldt
22a13c75bb Add Ui::available_height
Closes #553
2021-08-15 17:04:24 +02:00
Emil Ernerfeldt
a48c69d809 Improve UI docs, pointing to Ui::available_size
Closes #541
2021-08-15 17:03:24 +02:00
Emil Ernerfeldt
9bbcfd5996 Fix typo
Closes #567 #617
2021-08-15 17:02:28 +02:00
skuzins
6a8a93e120
Generalize http fetch (#488)
* Generalize http fetch

- allow bytes as request body
- expose request and response headers in API
- update http example to show response headers and allow POST requests

* clippy fixes

* add missing comment, pub

* doc comment fix

* fix: missing argument when feature syntect not enabled

* formatting fixes

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

* remove commented out code

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

* formatting fixes

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

* cargo fmt

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-08-15 16:56:46 +02:00
mental
eefc56c213
fix and pin web-sys breakage in Navigator::clipboard (#608) 2021-08-15 16:55:33 +02:00
Linus Behrbohm
34a11fefd2
Only add resize radius for interactable areas (#577)
Fixes #576
2021-08-15 16:48:15 +02:00
Linus Behrbohm
6c18332424
Add helpers <pointerbutton>_down() in PointerState (#583) 2021-08-15 16:41:33 +02:00
Linus Behrbohm
f4af22efb5
Allow unnecessary mut (#584)
needed for feature persistence, but an error in release build without
explicit allow
2021-08-15 16:39:26 +02:00
Norbert Pozar
934dc42e58
Fix tooltips overlapping widgets (#566) (#568)
Tooltips are now shown above the widget if they do not fit under it.
2021-08-15 16:34:43 +02:00
mitchmindtree
7c1c775020
Fix case where Plot's min_auto_bounds can be ignored after first instantiation (#563)
* Fix case where `Plot`'s `min_auto_bounds` can be ignored after first

I ran into an issue using `Plot` within my timeline widget where if I
zoom in and out of the timeline (not the plot), the `Plot` instances
would ignore the necessary changes to the `include_x` calls and in turn
would become skewed and misaligned with the timeline below.

This changes the `Plot` to check whether or not `min_auto_bounds` have
changed and, if so, reset the memory and recalculate the bounds.

See #562 for an image of my current use case.

* Carry hidden_items when updating plot for changed bounds
2021-08-15 16:34:12 +02:00
Emil Ernerfeldt
784bac53f1 Improve error message on bad texture allocation
Fixes https://github.com/emilk/egui/issues/592
2021-07-29 22:20:22 +02:00
Luca
a1c5ce05f7
readme: add egui_glfw_gl (#586) 2021-07-29 22:14:17 +02:00
Emil Ernerfeldt
4feee59f84 Improve README files for all crates 2021-07-29 22:04:20 +02:00
Emil Ernerfeldt
326da7a0d7 README: Clean up the integration section 2021-07-29 21:58:50 +02:00
Emil Ernerfeldt
9b1a8c9e00 Add link to egui-tetra 2021-07-29 01:33:55 +02:00
Emil Ernerfeldt
94937a33e8 Add link to ggez-egui 2021-07-28 17:52:05 +02:00
Emil Ernerfeldt
52d187ab5f Add godot-egui to README.md 2021-07-23 11:04:55 +02:00
Ezra Barrow
224af23fd1
Pass more inner return values (#557)
* add Window.show_with_return

* Fixed all missed opportunities to pass an inner return value
2021-07-21 11:43:02 +02:00
Emil Ernerfeldt
06fc9afb1d Add Frame setters for rounding, margin and shadow 2021-07-20 14:33:36 +02:00
Emil Ernerfeldt
12334addda Add Vec2::to_pos2 2021-07-20 14:32:12 +02:00
Jay Oster
a9c004d16b
Fix custom font definitions getting replaced when pixels_per_point is changed (#564)
- This bug is most noticable when default fonts are disabled.
2021-07-20 14:06:27 +02:00
Sven Niederberger
7c5a2d60c5
Plot: Line styles (#482)
* added new line styles

* update changelog

* fix #524

Add missing functions to `HLine` and `VLine`

* add functions for creating points and dashes from a line

* apply suggestions

* clippy fix

* address comments
2021-07-06 20:15:04 +02:00
Emil Ernerfeldt
d8b2b50780
Add libxkbcommon-dev to list of linux deps (#549)
Closes https://github.com/emilk/egui/issues/545
2021-07-06 19:33:37 +02:00
lucaspoffo
a6c3daff6f
TextEdit: Add visual clipping for singleline inputs when text is large. (#531)
* TextEdit: Add visual clipping for singleline inputs when text is large.

* TextEdit: Add reviewer suggestions.
2021-07-06 18:59:52 +02:00
Emil Ernerfeldt
faf104220b Grid::num_columns: allow the last column to take up the rest of the space
This allows for resizaeable grids, where the last column will be given the remainder of the width.
To demonstrate, the widget gallery window is now resizeable.
2021-07-02 09:55:57 +02:00
Simon Persson
9603bb4f85
Grid fixes (#473)
* Fix margin for grid layout nested inside grid

* Minor fix for grid layout

At time of end_row, current state is finished updating row_height.
Might as well use that instead of previous state.

* Fix horizontal advancing for nested layouts in grid

* Add back horizontal layout

* Add test for nested layouts in grids

* make test table striped

* Improve table test case with slider for dynamic text
2021-07-02 09:25:53 +02:00
Sven Niederberger
89cea7aca7
Progress bar (#519)
* add progress bar

* update changelog

* apply suggestions

* disable animation by default and tweak colors

* allow toggling the animation by clicking

* Update egui/src/widgets/progress_bar.rs

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

* Update egui/src/widgets/progress_bar.rs

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

* Update egui/src/widgets/progress_bar.rs

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

* address review comments

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-07-01 22:50:41 +02:00
Emil Ernerfeldt
52e3663958 Release egui 0.13.1: Plot fixes 2021-06-28 11:05:27 +02:00
Emil Ernerfeldt
cba840ec49
Small plot-relates fixes (#526)
* plot: take any id source as argument instead of ToString

* plot: allow user to set stroke on HLine/VLine

* Update changelog
2021-06-28 10:51:06 +02:00
Jay Oster
3a14f5e8e2
Fix a bug on Windows where minimizing adjusts all of the egui window positions. (#522)
- Closes #518
- This bug is caused by an issue in winit where minimized windows will
  be given 0 width and height on Windows.
- See: https://github.com/rust-windowing/winit/issues/208
- See also: https://github.com/hasenbanck/egui_winit_platform/pull/19
2021-06-28 10:27:32 +02:00
Emil Ernerfeldt
ccecad88b1 egui_glium and eframe 0.13.1: fix http feature and docs 2021-06-24 20:36:36 +02:00
Emil Ernerfeldt
4f6dac27e6 Clippy fix 2021-06-24 20:13:34 +02:00
Emil Ernerfeldt
d807451348 Release 0.13.0 - Better panels, plots and new visual style 2021-06-24 20:00:06 +02:00
Emil Ernerfeldt
8abd232854 Bug fix: accidentally resizing an area through a window 2021-06-24 17:52:15 +02:00
Emil Ernerfeldt
eb6c293774 Update crates glium, tts, and run cargo update 2021-06-24 17:51:39 +02:00
Emil Ernerfeldt
1363ac4a24 demo: in font book, ask font what characters are supported
This replaces manual lists with a call to ab_glypgh::Font::codepoint_ids
plus names from the unicode_names2 crate.
2021-06-24 17:35:56 +02:00
Emil Ernerfeldt
749c5cbdc8 egui_glium: don't take control of the control_flow
Closes https://github.com/emilk/egui/issues/434
2021-06-24 16:24:05 +02:00
Emil Ernerfeldt
182eb32b95 Tidy up plot demo 2021-06-24 15:20:31 +02:00
Emil Ernerfeldt
6e3604ee4b Bug fix: ui.scope(…) is now equivalent to ui.allocate_space(…) WRT IDs
Before a disabled and enabled button would leave the parent ui
in different states, which lead to a bug where a slider drag could be
aborted if it caused a button before it to switch between enabled
and disabled.

Repro: dragging slider in "Manual Layout Test"
2021-06-24 15:08:16 +02:00
Emil Ernerfeldt
9007890440 EguiGlium::on_event: take event by reference
Closes https://github.com/emilk/egui/issues/500
2021-06-24 12:35:54 +02:00
Sven Niederberger
147e7a47aa
More plot items (#471)
* Added plot items:

* Arrows, also called "Quiver plots" in matplotlib etc.
* Convex polygons
* Text
* Images

Other changes:

* Make HLine/VLine into PlotItems as well.
* Add a "fill" property to Line so that we can fill/shade the area between a line and a horizontal reference line.
* Add stems to Points, which are lines between the points and a horizontal reference line.
* Allow using .. when specifying ranges for values generated by explicit callback functions, as an alias for f64::NEG_INFINITY..f64::INFINITY
* Allow using ranges with exclusive end bounds for values generated by parametric callback functions to generate values where the first and last value are not the same.

* update changelog

* add legend background
2021-06-24 12:29:51 +02:00
Benjamin Bouvier
e22c242d17
Use ab_glyph instead of rusttype for font rendering (#490)
* Use ab_glyph instead of rusttype for font rendering

* address review feedback
2021-06-24 12:13:57 +02:00
Emil Ernerfeldt
63bddb67f8 CollapsingHeader: only fill full width if it has a frame 2021-06-24 12:12:20 +02:00
Emil Ernerfeldt
c03caa663b Fix bug where clicking a TextEdit frame would not give it focus
Closes https://github.com/emilk/egui/issues/506
2021-06-23 16:49:07 +02:00
Emil Ernerfeldt
6e7e88ba80
Use old 1.51 toolchain instead of bleeding edge (#505)
* Use old 1.51 toolchain instead of bleeding edge

1.52 and 1.53 has problems with incremental compilation,
so some people chose to stay on 1.51 for now.

So let's make sure egui supports 1.51 for a while!

* Update to cint 0.2.2 to get rust 1.51.0 compatability
2021-06-23 09:16:39 +02:00
Emil Ernerfeldt
269a4538d9 clippy fixes for rust 1.53 2021-06-22 23:38:34 +02:00
follower
60fd70921d
Typo fixes: "an"->"and" & "tex" -> "text" (#432) 2021-06-12 22:17:57 +02:00
Emil Ernerfeldt
ef36cac422 Improve misc docs 2021-06-12 22:12:45 +02:00
Zenithsiz
7f1123a54c
Expanded TextBuffer interface to allow borrowed values. (#444)
* Expanded `TextBuffer` interface to allow borrowed values.

* Removed superfluous `PartialEq` requirement on `TextBuffer`.

* Removed `std::fmt::Display` requirement for `TextBuffer`.
Now uses the `AsRef<str>` impl to format it where applicable.

Co-authored-by: Filipe Rodrigues <filipejacintorodrigues1@gmail.com>
2021-06-12 22:12:32 +02:00
Jay Oster
00575e158f
Fix an issue where losing focus could prevent the event loop from receiving events for releasing modifier keys (#479)
- This issue was made apparent on macOS since 67c6002578
- Repro:
  1. Cmd+Tab away from the window (this will keep the Cmd modifier state `true` until it is pressed again)
  2. Cmd+Tab back to the window
  3. Try to scroll with the trackpad or mouse wheel ... it won't work until you press and release the Cmd key!
  4. Also the plot widget will be stuck in "zoom mode" while the Cmd modifier state is true.
- I was not able to reproduce the issue with `egui_web`
2021-06-12 15:55:08 +02:00
Emil Ernerfeldt
778bcc1ef7
Style tweaks (#450)
* Tweak style

More compact, less round, less noisy

* Button text is now same size as body text
* The rounder corners are now less rounded
* Collapsing headers no longer have a frame around them
* Combo-boxes looks better when opened
* Slightly more muted colors
* Remove extra line spacing after `\n` (i.e. between paragraphs)

* Thinner scrollbars

* Tweak light mode

* Tweak shadows

* Fix broken doc link

* Add style tweak to CHANGELOG
2021-06-12 15:53:56 +02:00
Emil Ernerfeldt
a50ddc2703 TextEdit: Clean up password masking
Follow-up to https://github.com/emilk/egui/pull/412
2021-06-12 15:18:14 +02:00
Emil Ernerfeldt
f4a95b1e5f TextEdit: don't set response.changed() when cursor changes 2021-06-12 15:02:33 +02:00
Nolan Darilek
508f6d9bf5
Additional accessibility support (#412)
* Expose getter for currently focused widget.

* Remove a level of indirection, exposing the widget event on the top level.

* Align widget descriptions more closely with common screen reader conventions.

Note that this work isn't complete--I'll correct more cases as I add more widgets and become familiar with their structures.

* Add support for click and double-click events.

* Add `ValueChanged` events, with initial support for text.

* Add support for reporting cursor selection changes.

* Track enabled/disabled status.

* Move `prev_text` off of the widget struct.

* Get rid of `has_widget_info` and push events directly where it makes sense.

* Fix typo.

* s/text_value/current_text_value/

* Use a `RangeInclusive` for text selection.

* Invert parameters.

* Various fixes.

* Only dispatch `SelectionChanged` if the selection actually changes.
* Fix missing focus events.

* If values for `current_text` and `prev_text` are unchanged, filter out the previous value.

* No need to pass in `&mut prev_text` everywhere

* Appease Clippy.

* Mask password fields in generated events.

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-06-12 14:54:01 +02:00
Sven Niederberger
2b4d3fa5b1
apply suggested fix (#481) 2021-06-12 14:36:39 +02:00
Emil Ernerfeldt
998e07d865
Make sure the scroll bar is never outside the screen rectangle (#475)
* Make sure the scroll bar is never outside the screen rectangle

This is an alternative attempt to fix the bug mentioned in
https://github.com/emilk/egui/pull/392

egui expects that the container can always be made wider,
which is true for all egui Ui:s, but not true for the outer
frame/chrome that egui ultimately needs to sit within.

* Clamp scroll to screen rect rather than available rect

* Fix scrollbar spacing when shrinking too small

* Update changelog
2021-06-12 14:30:42 +02:00
edko99
e007afc3c3
Freeze scroll area (#472)
* added ScrollArea::enable_scrolling

* also freeze dragging and scroll-bar

* fixed styling of inactive scrollbar

* fixed docs (backtick-quoted TextEdit)

Co-authored-by: edko99 <edko@jouzz.com>
2021-06-12 14:30:14 +02:00
Sven Niederberger
02db9ee583
Plot: Legend improvements (#410)
* initial work on markers

* clippy fix

* simplify marker

* use option for color

* prepare for more demo plots

* more improvements for markers

* some small adjustments

* better highlighting

* don't draw transparent lines

* use transparent color instead of option

* don't brighten curves when highlighting

* Initial changes to lengend:
* Font options
* Position options
* Internal cleanup

* draw legend on top of curves

* update changelog

* fix legend checkboxes

* simplify legend

* remove unnecessary derives

* remove config from legend entries

* avoid allocations and use line_segment

* compare against transparent color

* create new Points primitive

* fix doctest

* some cleanup and fix hover

* common interface for lines and points

* clippy fixes

* reduce visibilities

* update legend

* clippy fix

* change instances of "curve" to "item"

* change visibility

* Update egui/src/widgets/plot/mod.rs

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

* Update egui/src/widgets/plot/mod.rs

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

* Update egui_demo_lib/src/apps/demo/plot_demo.rs

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

* Update egui_demo_lib/src/apps/demo/plot_demo.rs

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

* changes based on review

* add legend to demo

* fix test

* move highlighted items to front

* dynamic plot size

* add legend again

* remove height

* clippy fix

* update changelog

* minor changes

* Update egui/src/widgets/plot/legend.rs

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

* Update egui/src/widgets/plot/legend.rs

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

* Update egui/src/widgets/plot/legend.rs

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

* changes based on review

* add functions to mutate legend config

* use horizontal_align

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-06-07 22:36:13 +02:00
Emil Ernerfeldt
ece25ee7f3 Add Ui::set_visible as a way to hide widgets
Closes https://github.com/emilk/egui/issues/460
2021-06-07 22:12:49 +02:00
Emil Ernerfeldt
3c603c55b8 Make Layouts:: horizontal/vertial align/justify pub 2021-06-07 21:06:29 +02:00
Emil Ernerfeldt
62f58a3b05 egui_web: default to light mode unless prefers-color-scheme: dark 2021-06-07 20:56:18 +02:00
Emil Ernerfeldt
44b573f6a6 epi: merge App::load into App::setup, and provide Frame argument
This gives users more control over the order of load/setup.

It also allows users to load textures in setup.
2021-06-07 20:53:33 +02:00
Kayo Phoenix
31769d400f
Fixed GLSL ES 1.0 version string (#470) 2021-06-07 20:18:42 +02:00
Emil Ernerfeldt
effd3c7440 egui_web: Vastly improve WebGL alpha blending
This finally fixes the rough edges on text, especially in light mode
2021-06-04 23:03:48 +02:00
Emil Ernerfeldt
2af86cd2c7
Revert "Fix scroll bar position (#392)" (#458)
This reverts commit 2932ff0e53.
2021-06-04 00:11:35 +02:00
Emil Ernerfeldt
ec6268e4ac egui_web: document how to fill the full width of the browser 2021-06-03 18:56:37 +02:00
Jay Oster
2932ff0e53
Fix scroll bar position (#392)
* Fix scroll bar position

* Always position scroll bar on the right side of the ScrollArea inner rect

* Fix the clipping rect
2021-06-03 18:54:34 +02:00
Emil Ernerfeldt
6468b2b84e
Implement efficent scrolling of large content (#457) 2021-06-03 18:48:45 +02:00
Tiago Ferreira
2cdd90b111
Allow alternate shortcuts on Windows (#456) 2021-06-03 17:39:55 +02:00
Emil Ernerfeldt
4964d762a7 Move WidgetType from output.rs to lib.rs 2021-05-30 22:53:10 +02:00
Emil Ernerfeldt
e6fe6a6f6a Fix occasional jittery vertical positioning of glyphs 2021-05-28 19:51:10 +02:00
Emil Ernerfeldt
4d56d0328b demo: Move egui settings/inspection windows to backend panel 2021-05-28 00:40:36 +02:00
Violeta Hernández
66095b69a7
Disable default features of ord-float (#440)
* Might fix #439

* `cargo update`

If the page builds, then this did fix it.
2021-05-27 20:25:30 +02:00
Emil Ernerfeldt
3b807e1ad6 Add Context::set_debug_on_hover and egui::trace!(ui) 2021-05-27 19:30:08 +02:00
Sven Niederberger
8623909d82
Plotting: Add line markers (#363)
* initial work on markers

* clippy fix

* simplify marker

* use option for color

* prepare for more demo plots

* more improvements for markers

* some small adjustments

* better highlighting

* don't draw transparent lines

* use transparent color instead of option

* don't brighten curves when highlighting

* update changelog

* avoid allocations and use line_segment

* compare against transparent color

* create new Points primitive

* fix doctest

* some cleanup and fix hover

* common interface for lines and points

* clippy fixes

* reduce visibilities

* Update egui/src/widgets/plot/mod.rs

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

* Update egui/src/widgets/plot/mod.rs

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

* Update egui_demo_lib/src/apps/demo/plot_demo.rs

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

* Update egui_demo_lib/src/apps/demo/plot_demo.rs

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

* changes based on review

* fix test

* dynamic plot size

* remove height

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-27 18:40:20 +02:00
St John Giddy
e320ef6c64
Detect single-threaded mutex reentry in debug mode (#433)
* fix(plot): expose VLine and HLine

* feat(mutex): detect mutex lock reetry

* chore(mutex): fix lints in tests
2021-05-26 22:13:24 +02:00
Emil Ernerfeldt
c9766f8a7b
Resizable panels + right and bottom panels (#438)
* Side panel resizing and add right panels

* Add resizable top/bottom panels

* Deprecate TopPanel

* Final tweaks and update CHANGELOG.md
2021-05-26 22:06:10 +02:00
Emil Ernerfeldt
196ddff499 Rename Shape::polygon to Shape::convex_polygon
epaint only supports filling convex polygons (for now)
2021-05-20 22:14:08 +02:00
Emil Ernerfeldt
085233f907 Improve various documentation 2021-05-20 22:12:17 +02:00
Emil Ernerfeldt
5b462197fa Improve instructions in PR template 2021-05-20 22:07:02 +02:00
Emil Ernerfeldt
2dea4d8db2 Color picker: fix rare infinite loop in very weird situtations 2021-05-20 22:05:44 +02:00
Emil Ernerfeldt
f4ddc21740 Simplify IME cursor pos handling in TextEdit 2021-05-20 22:01:59 +02:00
Emil Ernerfeldt
6a576f4c34 Rename Output::text_cursor to text_cursor_pos 2021-05-20 21:58:44 +02:00
Emil Ernerfeldt
8cce09687f Expose text layout functions in Painter 2021-05-20 21:53:39 +02:00
Emil Ernerfeldt
53d5d32a80 GitHub issue templates: add reminder to check for duplicates 2021-05-20 21:47:17 +02:00
Emil Ernerfeldt
94479317b3 Add #[must_use] to container types: remind to call show() 2021-05-20 21:45:00 +02:00
Emil Ernerfeldt
a892519297 Add Style::override_text_style
Add Style::override_text_style to easily change the text style
of everything in a `Ui` (or globally).

You can now change `TextStyle` on checkboxes,
radio buttons and `SelectableLabel`.

Closes https://github.com/emilk/egui/issues/406
Closes https://github.com/emilk/egui/pull/407
2021-05-20 21:31:34 +02:00
Zenithsiz
57981d49ee
Add support for buffers other than a String in TextEdit (#399)
* Initial design for `TextBuffer` trait, to allow `TextEdit` to edit types other than `String`.

* Moved `insert_text` implementation into `TextBuffer`.
This allows the user to implement text inserting depedent on their type instead of using a `String` and converting back to `S`, which may be a lossless convertion.

* Moved part of `delete_selected_ccursor_range` implementation into `TextBuffer::delete_range`.

* `TextBuffer::insert_text` not returns how many characters were inserted into the buffer.
This allows implementations to "saturate" the buffer, only allowing for a limited length of characters to be inserted.

* Now using `byte_index_from_char_index` instead of custom implementation.

* `decrease_identation` impl now modified the string in-place.
Removed `From<String>` bound for `TextBuffer`.

* Added changes to changelog.

* Moved updated changelog to .

* Updated documentation on `TextBuffer`.

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

* Renamed `TextBuffer::delete_text_range` to `delete_char_range`.

Co-authored-by: Filipe Rodrigues <filipejacintorodrigues1@gmail.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-20 21:00:50 +02:00
Wojciech Kępka
d292b831a1
Add an option to overwrite frame of SidePanel and TopPanel (#418)
* Add an option to overwrite frame of SidePanel and TopPanel

* Update CHANGELOG
2021-05-20 20:10:30 +02:00
Gray Olson
c0929014bf
Add support for cint (conversions to and from color types) (#393)
* implement cint for color types under feature flag

* upgrade to cint 0.2, remove from default features

* upgrade to cint 0.2.1, add a couple more from/into implementations

* changelog entry

* fix typo in changelog

* sort dependency

* fmt
2021-05-20 20:09:52 +02:00
St John Giddy
ac82cc7be3
fix(plot): expose VLine and HLine (#422) 2021-05-20 19:56:45 +02:00
Ivo Vollrath
67c6002578
Fix mac scroll modifier (#402)
* fix modifier key for mouse wheel zoom

* update CHANGELOG

* fix zoom modifier for web backend

* improve instructions in plot demo window

* accept emilk's proposed change

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

* update UI instructions for Plot demo

* improve UI instructions for plot demo

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-20 19:56:33 +02:00
Emil Ernerfeldt
5bc53ce069 Improve panel docs
Related to https://github.com/emilk/egui/issues/421
2021-05-20 19:48:34 +02:00
Violeta Hernández
de02f7d042
Value of DragValue correctly clamped (#405)
* Update drag_value.rs

* `value` → `old_value`
2021-05-18 20:03:04 +02:00
Emil Ernerfeldt
dd4ac43b13 Make sure egui can handle zero-sized screen rect
This would previously hit a debug assert

Fixes https://github.com/emilk/egui/issues/395
2021-05-17 22:53:52 +02:00
Emil Ernerfeldt
6e5b52e3bc Add features extra_asserts and extra_debug_asserts for more asserts
This replaces all debug_asserts with these opt-in asserts

Related: https://github.com/emilk/egui/issues/395
2021-05-17 22:38:39 +02:00
Emil Ernerfeldt
bd5a85808a egui_demo_lib: fix persistence feature flag 2021-05-17 22:37:36 +02:00
Sven Niederberger
bfabb70cba
Fix BottomUp layout (#386) 2021-05-15 09:37:33 +02:00
Emil Ernerfeldt
2f46c975a5 Fix wrong vertical align in default layout
Only seen when having unusually high buttons
2021-05-13 23:15:44 +02:00
Emil Ernerfeldt
442b953964 Tell docs.rs to use the --all-features flag when generating docs
Closes https://github.com/emilk/egui/issues/381
2021-05-12 20:02:25 +02:00
Emil Ernerfeldt
9c475204da Fix uneven text kerning for non-integral dpi scales
Closes https://github.com/emilk/egui/issues/382
2021-05-12 19:41:45 +02:00
Emil Ernerfeldt
50a2ed0a14 Update wasm-bindgen to 0.2.74 2021-05-12 19:40:15 +02:00
ilya sheprut
9dd23b44e0
readme: add nannou_egui (#379) 2021-05-12 13:29:37 +02:00
Emil Ernerfeldt
934bb7f5e9 egui_web: fix double-paste bug 2021-05-11 19:25:31 +02:00
Emil Ernerfeldt
7b0f991b20 Improve docs concerning custom fonts, themes and accessibility
Closes https://github.com/emilk/egui/pull/370
Closes https://github.com/emilk/egui/issues/372
2021-05-11 14:56:27 +02:00
Emil Ernerfeldt
8f8ba16696 Release 0.12.0 - Multitouch, user memory, window pivots, and improved plots 2021-05-10 18:27:39 +02:00
Emil Ernerfeldt
3e8723c8ac Publish new web demo 2021-05-10 18:16:50 +02:00
Emil Ernerfeldt
1423bac3aa Smooth fading of plot grid and tick labels 2021-05-10 18:13:22 +02:00
Emil Ernerfeldt
cf3d12669c Fix misc typos
Closes https://github.com/emilk/egui/pull/366
Closes https://github.com/emilk/egui/pull/365
2021-05-10 10:52:49 +02:00
Emil Ernerfeldt
7747a1f171 Update tts 0.15 -> 0.16 2021-05-09 14:42:14 +02:00
Emil Ernerfeldt
c476ddb57c cargo update 2021-05-09 14:37:56 +02:00
Emil Ernerfeldt
b1559963bf Enable a bunch more clippy lints 2021-05-09 14:13:09 +02:00
Emil Ernerfeldt
4022b84ae7 group rustdoc lints under rustdoc:: prefix 2021-05-09 14:01:57 +02:00
Emil Ernerfeldt
fb5176c133 #![allow(clippy::float_cmp)] everywhere
it has always been an annoyance, never a help
2021-05-09 14:00:53 +02:00
Emil Ernerfeldt
6ad6f56cb1 Move shell scripts into sh/ subfolder 2021-05-09 13:28:24 +02:00
Emil Ernerfeldt
38186fe23e Add a TODO 2021-05-09 13:20:27 +02:00
Emil Ernerfeldt
5a21bc78a6 minor code simplification 2021-05-09 13:19:09 +02:00
Emil Ernerfeldt
a8c3deaf08 Rename ui.wrap to ui.scope 2021-05-09 13:17:32 +02:00
Emil Ernerfeldt
9dc092b778 Clean up demos 2021-05-09 13:07:56 +02:00
Emil Ernerfeldt
aa3c40c49f bug fix: don't lock focus by default 2021-05-09 11:17:32 +02:00
Emil Ernerfeldt
0b52813f62 Sort demo windows (multi-touch demo was out of order) 2021-05-09 10:09:05 +02:00
Emil Ernerfeldt
4bb79a7047 Move normalize_angle to emath 2021-05-08 23:42:17 +02:00
Emil Ernerfeldt
dd6980bacb Implement non-proportional multitouch pinch zooming 2021-05-08 23:31:31 +02:00
Emil Ernerfeldt
04d9ce227b Tidy up multitouch code: remove double spaces after full stop
Weirdest thing I've seen in my life.
2021-05-08 22:49:40 +02:00
Emil Ernerfeldt
268ddca161 demo_app: Remove screen_reader from default features
closes https://github.com/emilk/egui/issues/338
2021-05-08 16:27:41 +02:00
follower
a5e41f275c
Typo: CollpasingHeaders -> CollapsingHeaders (#359) 2021-05-08 16:24:22 +02:00
Emil Ernerfeldt
a7a36bd313 eframe: add always_on_top option (native) 2021-05-08 10:19:47 +02:00
Emil Ernerfeldt
7374ed9d00 epi/eframe: move options out of trait App into new NativeOptions 2021-05-08 10:14:56 +02:00
Emil Ernerfeldt
5e46bd404c Some documentation improvements 2021-05-08 09:25:52 +02:00
Emil Ernerfeldt
cb797a489a Expand PR template with reminder to update CHANGELOG.md 2021-05-08 08:23:26 +02:00
Emil Ernerfeldt
12c0199d1b Mention mint in changelog 2021-05-08 08:18:37 +02:00
Luis Wirth
87bc26fb5a
implement mint conversions (#352)
* Implement mint conversions

Implement conversions for [mint](https://docs.rs/mint) (math interoperability standard types).

- `impl {From, Into}<mint::Point2> for Pos2`
- `impl {From, Into}<mint::Vector2> for Vec2`

* Forward `mint` feature: egui -> epaint -> emath
2021-05-08 08:17:01 +02:00
Emil Ernerfeldt
2cb94b98ef Add plot legends and on_disabled_hover_text to CHANGELOG.md 2021-05-07 10:35:55 +02:00
Sven Niederberger
838f3e4ff2
Add plot legends (#349)
* add plot legends

* don't show crosshairs when hovering over legend

* add a toggle for the legend

* changes based on review

* improve legend behavior when curves share names
2021-05-07 10:32:17 +02:00
Emil Ernerfeldt
d862ff66ac Add Image::sense to let an image respond to clicks and drags 2021-05-06 22:10:05 +02:00
Ivo Vollrath
03721dbfd8
Basic multi touch support (issue #279) (#306)
* translate touch events from glium to egui

Unfortunately, winit does not seem to create _Touch_ events for the touch pad
on my mac.  Only _TouchpadPressure_ events are sent.

Found some issues (like
[this](https://github.com/rust-windowing/winit/issues/54)), but I am not sure
what they exactly mean:  Sometimes, touch events are mixed with
touch-to-pointer translation in the discussions.

* translate touch events from web_sys to egui

The are a few open topics:
- egui_web currently translates touch events into pointer events.
  I guess this should change, such that egui itself performs this kind of
  conversion.
- `pub fn egui_web::pos_from_touch_event` is a public function, but I
  would like to change the return type to an `Option`.  Shouldn't this
  function be private, anyway?

* introduce `TouchState` and `Gesture`

InputState.touch was introduced with type `TouchState`, just as
InputState.pointer is of type `Pointer`.

The TouchState internally relies on a collection of `Gesture`s.  This commit
provides the first rudimentary implementation of a Gesture, but has no
functionality, yet.

* add method InputState::zoom()

So far, the method always returns `None`, but it should work as soon as the
`Zoom` gesture is implemented.

* manage one `TouchState` per individual device

Although quite unlikely, it is still possible to connect more than one touch
device. (I have three touch pads connected to my MacBook in total, but
unfortunately `winit` sends touch events for none of them.)

We do not want to mix-up the touches from different devices.

* implement control loop for gesture detection

The basic idea is that each gesture can focus on detection logic and does not
have to care (too much) about managing touch state in general.

* streamline `Gesture` trait, simplifying impl's

* implement first version of Zoom gesture

* fix failing doctest

a simple `TODO` should be enough

* get rid of `Gesture`s

* Provide a Zoom/Rotate window in the demo app

For now, it works for two fingers only.  The third finger interrupts the
gesture.

Bugs:
- Pinching in the demo window also moves the window -> Pointer events must be
  ignored when touch is active
- Pinching also works when doing it outside the demo window -> it would be nice
  to return the touch info in the `Response` of the painter allocation

* fix comments and non-idiomatic code

* update touch state *each frame*

* change egui_demo to use *relative* touch data

* support more than two fingers

This commit includes an improved Demo Window for egui_demo, and a complete
re-write of the gesture detection.  The PR should be ready for review, soon.

* cleanup code and comments for review

* minor code simplifications

* oops – forgot the changelog

* resolve comment fee8ed83db (r623226656)

* accept suggestion https://github.com/emilk/egui/pull/306#discussion_r623229228

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

* fix syntax error (dough!)

* remove `dbg!` (why didnt clippy see this?)

* apply suggested diffs from review

* fix conversion of physical location to Pos2

* remove redundanct type `TouchAverages`

* remove trailing space

* avoid initial translation jump in plot demo

* extend the demo so it shows off translation

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-06 21:01:10 +02:00
Emil Ernerfeldt
0d71017ad4 clippy fixes for rust 1.52 2021-05-06 20:49:22 +02:00
Emil Ernerfeldt
29668b5128 Minor documentation improvements 2021-05-02 22:02:26 +02:00
Emil Ernerfeldt
1394205f52 Document and demonstrate how to expand a TextEdit to fill a Ui 2021-05-02 21:57:51 +02:00
Emil Ernerfeldt
66122e4c9a Decrease indentation with shift-tab 2021-05-02 20:09:11 +02:00
Emil Ernerfeldt
bf8ce774cc Don't draw bidi control characters
Closes https://github.com/emilk/egui/issues/336
2021-05-02 19:50:06 +02:00
Emil Ernerfeldt
7da9928548 Clean up new code editor code 2021-05-02 19:28:02 +02:00
Emil Ernerfeldt
10b60d5361
Run clippy on all targets and all features (#347)
* Run clippy on all targets and all features

* check.sh: print each step

* impl ToString -related clippy fix
2021-05-02 19:26:57 +02:00
Cristian Dinu
35c7b09285
Tab identation for multiline text edit (#246)
Lock focus on multiline text edit, and insert tabs on tab char

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-05-02 18:56:28 +02:00
ilya sheprut
101eed0d67
memory: add Send + Sync reqirement, fix #337 (#341)
* memory: add `Send + Sync` reqirement, fix #337

* Update egui/src/memory.rs

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>

Co-authored-by: Lucien Greathouse <me@lpghatguy.com>
2021-04-30 10:03:45 +02:00
David Pedersen
02a62d1986
Replace impl Into<String> with impl ToString (#302)
* Replace `impl Into<String>` with `impl ToString`

This is something I ran into today. Types that implement
`std::fmt::Display` cannot be passed to functions that take `impl
Into<String>`. You have to call `display_thing.to_string()`. Its a small
thing but would be fixed by instead taking `impl ToString`.

Afaik `impl ToString` is a superset of `impl Into<String>`, unless users
manually implement `Into<String> for T` (or `From<T> for String`) for
their own types. However I think its more common to implement `Display`
as that works with `println` and friends. The main difference is that
`Display::fmt` can return errors but thats also quite rare in my
experience.

I did some testing in a [playground] and seems to work.

[playground]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1111e071f6ae416ae2688d58d2e9b575

* Silence warnings
2021-04-29 19:49:49 +02:00
crumblingstatue
e991a1c310
Fix typo in README.md (#340) 2021-04-29 19:31:24 +02:00
Emil Ernerfeldt
6f01577d5f Tab only selects labels if the screen_reader option is turned on. 2021-04-25 18:20:10 +02:00
Emil Ernerfeldt
c2744a1437
Implement trackpad pinch-to-zoom for plots in egui_web (#333)
This adds a new `zoom_delta` to input.
This is hooked up to ctrl-scroll on egui_web and egui_glium.

Browsers convert trackpad pinch gestures to ctrl-scroll,
so this means you can not pinch-to-zoom plots (on trackpad).

In the future we can support multitouch pinch-to-zoom via the same
`InputState::zoom_factor()` function
2021-04-25 17:04:34 +02:00
Emil Ernerfeldt
7f0689e566 Refactor egui_glium with new EguiGlium wrapper and standalone example 2021-04-25 17:02:27 +02:00
Sven Niederberger
a505d01090
Plot refactor (#331)
* drag and zoom support for plots

* update doctest

* use impl ToString

* revert back to Into<String> until #302 is solved

* Apply suggestions from code review

Co-authored-by: ilya sheprut <optitel223@gmail.com>

* use persistence feature for PlotMemory

* * split plot into multiple files
* add curve from function
* move more functionality into ScreenTransform struct

* changes from code review in base branch

* let user specify a range for generated functions

* rename file

* minor changes

* improve generator functionality

* improve callback and add parametric callback

* minor changes

* add documentation

* fix merge issues

* changes based on review

* rename folder

* make plot.rs the mod.rs file

* remove mod.rs

* rename file

* namespace changes

* fix doctest

* Update egui/src/widgets/plot/items.rs

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

Co-authored-by: ilya sheprut <optitel223@gmail.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-04-24 14:26:54 +02:00
Emil Ernerfeldt
b69bc2c06a egui_web: Scroll faster when scrolling with mouse wheel
Closes https://github.com/emilk/egui/issues/159
2021-04-24 11:08:29 +02:00
Emil Ernerfeldt
0f112db550 Reduce binary size with more inlining and less monomorphization
5%=150kB savings on egui_demo_app wasm
2021-04-24 09:54:11 +02:00
Emil Ernerfeldt
f38f68318d build_demo_web.sh: add --open as an option 2021-04-24 09:41:57 +02:00
Emil Ernerfeldt
6b24dbc997 Move easy_mark from egui deo egui_demo_lib 2021-04-24 01:18:08 +02:00
Emil Ernerfeldt
641e9c2d26 egui_glium: sleep a bit when not focused
This is to stop using all of the CPU when in minimized in
continious mode.

Fixes https://github.com/emilk/egui/issues/325
2021-04-22 20:12:49 +02:00
Emil Ernerfeldt
fb6f49024f Add Response::on_disabled_hover_text to show tooltip for disabled widgets
Closes https://github.com/emilk/egui/issues/323
2021-04-22 19:48:05 +02:00
Emil Ernerfeldt
07da54ad08 Refactor Ui::next_auto_id_source 2021-04-21 22:19:51 +02:00
Emil Ernerfeldt
a59c8ac2c3 DragValue: only show extra decimal with shift down if currently dragged 2021-04-21 22:14:30 +02:00
Emil Ernerfeldt
d2b669e1cd Changelog: Add line about storing state in Memory 2021-04-21 22:03:05 +02:00
Emil Ernerfeldt
6e9abfc9c0 Changelog: Add plot improvements 2021-04-21 22:02:51 +02:00
Sven Niederberger
012542d066
Drag and zoom support for plots (#317)
* drag and zoom support for plots

* update doctest

* use impl ToString

* revert back to Into<String> until #302 is solved

* Apply suggestions from code review

Co-authored-by: ilya sheprut <optitel223@gmail.com>

* use persistence feature for PlotMemory

* rename shift -> translate

* remove automatic bounds

* removed unused methods

* Into<String> -> ToString

* Apply suggestions from code review

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

* avoid potential invalid bounds bug

* use new is_valid method

* improve auto bounds behavior as suggested

* use NOTHING to initialize min_auto_bounds

Co-authored-by: ilya sheprut <optitel223@gmail.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-04-21 21:50:27 +02:00
Emil Ernerfeldt
cb14e8571f Add Vec2::LEFT,RIGHT,UP,DOWN and Vec2::angle 2021-04-20 23:04:04 +02:00
Emil Ernerfeldt
58ebb217dc Tesselator: ignore zero-sized clip rects
Improves https://github.com/emilk/egui/issues/328
2021-04-20 10:05:47 +02:00
Emil Ernerfeldt
1681769329 Add #[inline(always)] here and there 2021-04-19 23:11:42 +02:00
Emil Ernerfeldt
0f1df90d90 Tesselator: ignore non-positive clip rectangles
Closes https://github.com/emilk/egui/issues/328
2021-04-19 23:00:30 +02:00
Joel Nises
72d0d71d66
made drag and drop support selectable on Windows for the eframe glium integration (#324)
* made drag and drop support selectable on windows

to avoid issues with crates that use multi-threaded COM apis

* fixed formatting and clippy issues
2021-04-19 22:49:28 +02:00
Emil Ernerfeldt
23d292a974 faster debug builds on mac 2021-04-18 23:15:07 +02:00
Emil Ernerfeldt
fbd0e35017 Clippy fixes for benchmark 2021-04-18 23:14:35 +02:00
Emil Ernerfeldt
0351662763 Fix bug with the layout of wide DragValue:s 2021-04-18 23:10:12 +02:00
Emil Ernerfeldt
c07f439b28 Add benchmark of ui.label
This is to help evaluate the impact of
https://github.com/emilk/egui/pull/302
2021-04-18 10:24:31 +02:00
ilya sheprut
c69ecfe421
Memory usage example in the widget gallery (#307)
* init example

* add comments

* fix grammar in comments

* fix CI

* change example from view_edit to password

* rename file

* fix CI
2021-04-18 10:13:08 +02:00
Emil Ernerfeldt
580d27e0d3
Add anchors to windows and areas (#310)
This is so that you can put a window in e.g. the top right corner
or the center of the screen.
2021-04-18 10:01:41 +02:00
Emil Ernerfeldt
5667e7eb51 Add docstring to ui.with_layout 2021-04-15 22:23:15 +02:00
Emil Ernerfeldt
f07bdf0433 Add more instructions to PR template 2021-04-15 22:21:48 +02:00
Emil Ernerfeldt
231c075867 Add #[inline(always)] to a few things 2021-04-15 10:37:31 +02:00
Emil Ernerfeldt
76d5229821
CI: run cargo doc (#309)
* Deny doc errors

* Add intentional bad intradoc link to test CI

* Add cargo doc to CI

* Fix carg doc web (add wasm32 target)

* Fix intentionally broken doc-link
2021-04-15 10:35:15 +02:00
Emil Ernerfeldt
b187d1c576 Make Memory::has_focus public (again) 2021-04-15 10:31:24 +02:00
ilya sheprut
d6de19b507
Mention macroquad backend in the readme (#308) 2021-04-15 10:20:04 +02:00
Emil Ernerfeldt
2aa05f00e1 check.sh: cargo doc all features 2021-04-15 09:48:30 +02:00
Emil Ernerfeldt
e1adb9c091 Fix broken markdown in docstring 2021-04-15 09:48:06 +02:00
Emil Ernerfeldt
96a2732735 Add Response::request_focus and surrender_focus 2021-04-15 09:45:12 +02:00
Emil Ernerfeldt
daf2e13238 demo: Use new id_data to store the show/hide password toggle 2021-04-12 22:37:39 +02:00
ilya sheprut
186362a4b8
Arbitrary data in Memory using Any, fix #255 (#257)
* init work

* implement deferred deserialization

* many improvements
* rename `DataElement` -> `AnyMapElement`
* make `data` in `Memory` as public field of type with public interface
* make interface more rich
* transform most unwraps to proper error handling
* make `AnyMap` store by `TypeId`, so now individual type can be counted and reset

* improve storing TypeId between different rust versions

* rewrite system widgets to use AnyMap

* refactor everything
* replace `serde_json` -> `ron`
* move `any_map` to module
* separate `AnyMap` into `AnyMapId` and `serializable::AnyMapId` in order to not require `serde` traits in methods
* add `AnyMap` and `serializable::AnyMap` that stores elements just by type, without `Id`
* write documentation
* change tooltips and color picker to use `Memory::data_temp`

* fix bugs and docs

* Apply suggestions from code review

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

* rename `AnyMap` → `TypeMap`

* rename `AnyMapId` → `AnyMap`, add generic <Key> to it

* rename files `id_map` → `any_map`

* move out usages from `serializable` mod

* rename `ToDeserialize` → `Serialized`

* fix bug with counting

* add tests, and...
* rename `reset` → `remove`
* add function `remove_by_type`
* format code

* improve code
* make identical interface for serialized and simple maps
* make serialized maps serialized fully, without features by moving this into `Memory` struct with `#[cfg(feature = "persistence")]` under fields
* move `serialized::TypeId` into `AnyMapElement` struct

* fix pipeline and add one more test

* update docs

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-04-12 21:53:32 +02:00
Emil Ernerfeldt
4ecf304335 Fix touch screen defocus bug
Fixes https://github.com/emilk/egui/issues/288
2021-04-12 21:41:07 +02:00
Emil Ernerfeldt
5d50fa1350 Add Label::sense so you can make clickable labels
relates to https://github.com/emilk/egui/issues/292
2021-04-12 21:26:13 +02:00
follower
3c0c729af8
Add links to the changelogs for other crates. (#293)
(Tested via the GH web interface.)
2021-04-12 20:57:49 +02:00
Lin Han
20bf09560e
IME: Handle composition events to show suggestion on web (#278)
* Handle composition message to show suggestion.

* CI check

* Apply suggestions from code review

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

* Some minor changes

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-04-12 20:57:14 +02:00
follower
0353f40dd5
Typo fix: "occational" -> "occasional" (#286) 2021-04-08 22:22:58 +02:00
Cristian Dinu
50a56d41ee
DragValue improvements (#274)
* Display ResizeHorizontal cursor on hover

* Adapt the URL open command to the platform

With this new version we use $OSTYPE bash env var to query what OS
are we running on.

- On Linux, ex: Fedora, we use `xdg-open`
- On Windows, ex: msys, we use `start`
- For other other variants we try to use `open`

We should update this script when we notice that `open` is not
available on a particular platform.

(cherry picked from commit b3aa4982f683d961c21fd18e9ffc1fdf5fba0783)

* Add slow speed mode for `DragValue`

The slow speed mode is disabled by default. It can be activated
or deactivated using `DragValue::slow_speed()`.

When active the `Shift` key can be used to have a better control
over the value, `DragValue::speed` / 10.0 increments.

* Display `ResizeHorizontal` cursor while dragging too

* Apply review suggestion

Shorter and in line with the rest of the code base

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

* Add `Modifiers::shift_only` for better code clarity

* Make slow speed always enabled

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-04-08 22:20:52 +02:00
Emil Ernerfeldt
b30cb3313a
Try to allow turning off default_fonts for all libs (#266) 2021-04-07 20:14:44 +02:00
Emil Ernerfeldt
d544c3dd8b Release 0.11.0 - Optimization, screen reader & new layout logic 2021-04-05 14:49:06 +02:00
Emil Ernerfeldt
c96819e95e build_demo_web.sh: code cleanup 2021-04-05 14:45:32 +02:00
Emil Ernerfeldt
370d269cba Publish new web demo 2021-04-05 14:43:38 +02:00
Emil Ernerfeldt
49ef94d322 build_demo_web.sh: fix opening link on windows and linux
Co-authored with @DrOptix with code from
https://github.com/emilk/egui/pull/246
2021-04-05 14:23:42 +02:00
Emil Ernerfeldt
3803d0f3d1 Minor cleanup of the demo code 2021-04-05 14:23:42 +02:00
Emil Ernerfeldt
ab4819ca99 PR template: encourage linking to the relevant issue 2021-04-05 14:23:42 +02:00
Emil Ernerfeldt
77014c7c75 cargo update 2021-04-05 14:23:42 +02:00
Emil Ernerfeldt
7792ee0422 Update tts from 0.14 -> 0.15 (screen reader feature) 2021-04-05 14:23:42 +02:00
Emil Ernerfeldt
aba2108159 Replace JSON with RON for persistence (epi/eframe/glium/web) 2021-04-05 14:23:42 +02:00
Emil Ernerfeldt
4fc3c6d375 Remove unnecessary copy of mesh indices in glium painter 2021-04-05 14:23:42 +02:00
pixeljoelson
b028e708e0
fixed typo in README.md (#275) 2021-04-05 09:56:58 +02:00
Emil Ernerfeldt
ebb08f87f1 Fix: custom Ui:s now allocate minimal space in parent ui.
This makes nested horizontal/vertical layouts work better
2021-04-02 22:39:08 +02:00
Emil Ernerfeldt
4ac5b37702 Turn off text wrapping for DragValue:s 2021-04-02 13:42:46 +02:00
Emil Ernerfeldt
4b9db0cc55 Rename ui.advance_cursor to ui.add_space 2021-04-02 10:13:06 +02:00
Emil Ernerfeldt
d848b2a664 Add TextEdit::password to hide input characters 2021-04-02 09:58:55 +02:00
Emil Ernerfeldt
33a4058381 Add comment about Firefox CPU usage in FAQ 2021-04-02 09:24:34 +02:00
Emil Ernerfeldt
1c955e56fe Some code cleanup 2021-04-01 23:07:58 +02:00
Emil Ernerfeldt
facb01a7c2 Use wasm-opt when compiling for web 2021-04-01 23:07:43 +02:00
Emil Ernerfeldt
fe0d31204e Small optimization of tesselate_text 2021-04-01 23:07:15 +02:00
Emil Ernerfeldt
1068750bbc Clean up egui_web code a bit 2021-04-01 23:07:15 +02:00
Emil Ernerfeldt
d7f9e2246c Add a bunch on inline annotations 2021-04-01 23:07:15 +02:00
Emil Ernerfeldt
d702e3078a Reduce amount of data being cloned in begin_frame 2021-04-01 22:09:58 +02:00
Emil Ernerfeldt
44869a6718 Clean up benchmarks 2021-04-01 21:42:45 +02:00
Emil Ernerfeldt
f6770f0183 Various spelling fixes, docs improvements and code cleanup 2021-03-31 23:12:42 +02:00
Emil Ernerfeldt
1f965d16a2 Try the new cargo dependency resolver added in rust 1.51 2021-03-31 23:07:37 +02:00
Emil Ernerfeldt
f79f24c83e Clean up dependencies and features flags 2021-03-31 22:53:54 +02:00
Emil Ernerfeldt
0d7c84c327 Make each DragValue at least the size of interact_size 2021-03-31 22:18:45 +02:00
Emil Ernerfeldt
b8a0f5be15 Improve Ui::add_sized (simplify and fix bugs) 2021-03-31 22:18:08 +02:00
Emil Ernerfeldt
1090de67fd Refactor: move debug options out of Visuals 2021-03-31 22:00:04 +02:00
Emil Ernerfeldt
b393bdcb74 Improve documentation of ui.add_sized 2021-03-31 21:49:24 +02:00
Emil Ernerfeldt
3450168e94 egui_glium: add support for transparent windows
Also support non-decorated windows (without border)
2021-03-31 20:53:13 +02:00
Emil Ernerfeldt
0a21b01c31 Fix nightly build: remove deprecated missing_crate_level_docs lint
Closes https://github.com/emilk/egui/issues/250 and
https://github.com/emilk/egui/pull/251
2021-03-31 20:00:22 +02:00
Thorbjørn Lindeijer
f563ff77c3
Fixed some spelling in documentation (#262) 2021-03-31 19:52:23 +02:00
Emil Ernerfeldt
c583f94ef0 Correct the documentation of where app state is saved
Replaces https://github.com/emilk/egui/pull/263
2021-03-31 19:51:19 +02:00
Emil Ernerfeldt
4808da44a2 Optimize: more inlining and more use of AHashMap
No real gains, but it didn't hurt either
2021-03-31 17:06:12 +02:00
Emil Ernerfeldt
4984d51f99 Optimize: store a reference to the target PaintList in Painter
Saves us a hash lookup for each paint call, giving us 5% perf gain
2021-03-31 17:03:20 +02:00
Emil Ernerfeldt
17983e1bbc Optimize tessellator by around 9% 2021-03-30 22:36:45 +02:00
Emil Ernerfeldt
8c4bb0d1d9 refactor: no need to pass Fonts structure to tessellator
This makes the tesselator take only data as argument,
which is a lot nicer.
2021-03-30 21:41:39 +02:00
Emil Ernerfeldt
0802a9d9c0 Optimize: get glyph uv rects during layouts instead of in tesselation
This allows them to be cached, saving around 20% total CPU.

It also makes the code more nicely structured
2021-03-30 21:07:19 +02:00
Lin Han
22cd1a8e10
Move IME candidate window following text cursor (#258)
* egui_web: enable IME support on web.

* Move candidate window following text cursor.

* Preclude too frequent agent movement.

* IME candidate window move on native app.
2021-03-30 08:48:55 +02:00
Emil Ernerfeldt
1c60dc8d66 Pass around Arc<Galley> to avoid copying a lot of data 2021-03-29 22:49:54 +02:00
Emil Ernerfeldt
94baf98eab Add a galley cache to Fonts to avoid doing the same layout each frame 2021-03-29 22:49:48 +02:00
Emil Ernerfeldt
f9c4be33a7 Add a realistic end-to-end benchmark 2021-03-29 22:49:40 +02:00
Emil Ernerfeldt
d4e5133da2 refactor fonts: put TextStyle in Galley instead of in Shape::Text 2021-03-29 21:24:09 +02:00
Emil Ernerfeldt
ade41403b5 refactor fonts: do all text layout via the Fonts struct 2021-03-29 21:12:11 +02:00
Emil Ernerfeldt
46425f1e38 Optimize: add #[inline(always)] to various low-level things
saves up to 20% (text tesselation), and at least 5% overall
2021-03-28 23:16:19 +02:00
Emil Ernerfeldt
ccc501f302 Spelling: tesselate -> tessellate 2021-03-28 22:44:03 +02:00
Emil Ernerfeldt
aeaa54aab1 optimization: don't compare font data each frame 2021-03-28 22:13:41 +02:00
Emil Ernerfeldt
def09c2455 Don't close colopicker and other popups when clicking inside of them 2021-03-27 16:50:35 +01:00
Emil Ernerfeldt
6fbb59de1f Add new Slider/DragValue constructors to changelog 2021-03-27 16:10:51 +01:00
Emil Ernerfeldt
5011623744 Deprecate old DragValue constructors in favor of DragValue::new 2021-03-27 16:09:09 +01:00
Emil Ernerfeldt
de439b6e21 Deprecate old Slider constructors in favor of Slider::new 2021-03-27 16:07:18 +01:00
Emil Ernerfeldt
5481aa8d98 Add DragValue::new and Slider::new 2021-03-27 16:03:11 +01:00
Emil Ernerfeldt
fd80a64cdb DragValue: Lower default speed for integers
to make it easier to hit the values
2021-03-27 15:47:53 +01:00
Emil Ernerfeldt
246ab55a7f Fix: integer DragValue could sometimes not reach all values
Fixes https://github.com/emilk/egui/issues/256
2021-03-27 15:41:44 +01:00
Emil Ernerfeldt
1bb100d766 Updated changelogs 2021-03-27 10:37:31 +01:00
Emil Ernerfeldt
41c9de2734 Add new ComboBox builder to replace the combo_box_with_label function 2021-03-27 10:35:40 +01:00
Lin Han
795282edc0
egui_web: enable IME support on web. (#253) 2021-03-26 13:56:26 +01:00
Emil Ernerfeldt
fe92a9826b
ci: install libspeechd-dev and fix new clippy lints (#252)
* ci: install libspeechd-dev

* Fix new clippy lints for rust 1.51.0
2021-03-25 22:18:03 +01:00
Emil Ernerfeldt
8e7d4ff4fd Upgrade wasm-bindgen 0.2.71 -> 0.2.72 2021-03-25 16:30:09 +01:00
Emil Ernerfeldt
70c6f4596a Add checkbox in demo app to turn screen reader on/off 2021-03-24 21:35:29 +01:00
Emil Ernerfeldt
cbe6faa83b Render tab character (\t) as four spaces 2021-03-23 20:06:52 +01:00
Emil Ernerfeldt
fd3444473f Add some clippy lints 2021-03-23 19:53:31 +01:00
Emil Ernerfeldt
25c5e9d94e Return InnerResponse from Frame, Grid and ui.group() 2021-03-21 22:04:41 +01:00
Emil Ernerfeldt
05308e8d37 layout: don't return negative availability rectangles 2021-03-21 19:57:38 +01:00
Emil Ernerfeldt
f5c372910c Replace emath::clamp with f32::clamp (new in rustc 1.50) 2021-03-21 17:47:03 +01:00
Emil Ernerfeldt
cdab9d777f Add years and email in LICENSE-MIT 2021-03-21 17:16:12 +01:00
Emil Ernerfeldt
474b02d4e8 Add instruction to not add .js/.wasm files in pull requests 2021-03-21 17:15:46 +01:00
Emil Ernerfeldt
ed0d406698 Improve misc documentation
Closes https://github.com/emilk/egui/issues/229
2021-03-21 17:13:58 +01:00
Emil Ernerfeldt
c1d5bda143 ColorPicker: always show hue slider at full saturation and lightness
Closes https://github.com/emilk/egui/issues/230
2021-03-21 16:15:13 +01:00
Emil Ernerfeldt
405ae3bcee Add link to https://github.com/hakolao/egui_winit_vulkano 2021-03-21 16:09:44 +01:00
Emil Ernerfeldt
7e302ad905 Document the need for latest rustc
Closes https://github.com/emilk/egui/issues/240
2021-03-21 16:00:24 +01:00
Emil Ernerfeldt
24b3cd021f Add demo app instructions for Defora Rawhide
Closes https://github.com/emilk/egui/issues/235
2021-03-21 15:28:00 +01:00
Emil Ernerfeldt
65a86b2d15 Middle-click links to open in new tab
Closes https://github.com/emilk/egui/issues/231
2021-03-21 15:20:53 +01:00
Emil Ernerfeldt
f77ab26828 [EasyMark] Add support for small and raised text 2021-03-21 15:11:12 +01:00
Emil Ernerfeldt
eaa1ed96ee Depcrecate ui.horizontal_for_text and ui.horizontal_wrapped_for_text
They just add unnecessary complexity at this point
2021-03-21 14:48:16 +01:00
Emil Ernerfeldt
953d2bb39b Add ui.set_row_height 2021-03-21 14:48:16 +01:00
Emil Ernerfeldt
e20e3baa98 Bug-fixes related to recent layout rewrite 2021-03-21 14:48:12 +01:00
Emil Ernerfeldt
e232264b53 Add Visuals::debug_widgets to debug layouting by hovering widgets 2021-03-21 10:33:10 +01:00
Emil Ernerfeldt
ec9f374d8c Fix: centered horizontal layouts should never overflow upwards 2021-03-21 10:31:18 +01:00
Emil Ernerfeldt
cc5ad1505c Fix bug that would allocate the full width of non-centered layouts 2021-03-20 22:21:14 +01:00
Emil Ernerfeldt
589bae1211
Refactor layout (#241)
* Fix https://github.com/emilk/egui/issues/222

* Rewrite layout logic

Cursor is now a Rect.

Closes https://github.com/emilk/egui/issues/179
2021-03-20 21:47:19 +01:00
Emil Ernerfeldt
5621a46b4b emath: add any_nan to Vec2, Pos2 and Rect 2021-03-20 16:18:04 +01:00
Emil Ernerfeldt
4e852727c0 Add helper functions to Rect 2021-03-20 16:17:58 +01:00
Emil Ernerfeldt
0c9b4858f0 refactor: simplify CollapsingHeader enable/disable code 2021-03-20 15:40:40 +01:00
Emil Ernerfeldt
7ac91970bd bug fix: false id clash error for wrapping text 2021-03-20 09:29:36 +01:00
Emil Ernerfeldt
36d9f8a7c7 Add sanity check on pixels_per_point range 2021-03-17 08:44:00 +01:00
Emil Ernerfeldt
ec2aab3a72 Improve github templates with <!-- comments --> 2021-03-13 13:58:52 +01:00
Emil Ernerfeldt
30885b85f9 Derive PartialEq for Vertex, Mesh, Row, Galley and Shape 2021-03-13 12:58:17 +01:00
Emil Ernerfeldt
6442d254a6 Fix hover-to-zoom of font texture in demo
Closes https://github.com/emilk/egui/issues/220
2021-03-13 12:55:29 +01:00
triangle drawer
b1883d5d48
Add functions to register textures in egui_web and egui_glium (#226)
* add texture registering function

* fmt

* Revert "add texture registering function"

This reverts commit f9b4db12

* make get_texture public to get render target owned by Painter .

* revert egui_web painter.rs change
2021-03-13 12:41:51 +01:00
Emil Ernerfeldt
958aea922f Add a lot more CursorIcon's 2021-03-13 12:38:03 +01:00
Norbert Nemec
ee1fcf1ead
egui_glium: prevent cursor icon flickering at frame boundary on Windows (#217) (#218)
* egui_glium: prevent cursor icon flickering at frame boundary (Windows)

* fix compiler warning

* cargo fmt
2021-03-13 12:27:05 +01:00
Jay Oster
269bcdfce3
Replace clipboard with copypasta (#221)
- Fixes #146
2021-03-10 11:16:45 +01:00
Emil Ernerfeldt
017d602fe5 Rename "kb_focus" to just "focus" everywhere 2021-03-09 20:55:24 +01:00
Emil Ernerfeldt
3fbc07659c Labels can now be focused so they can be read by screen-reader 2021-03-09 19:58:41 +01:00
Emil Ernerfeldt
33d2f16041 Bug fix: you can now use text edits again 2021-03-09 19:51:23 +01:00
Emil Ernerfeldt
a8320881ba remove native screen reader spam 2021-03-09 19:47:41 +01:00
Bradley Smith
bd34cfd43e
Configurable label text style for CollapsingHeader (#200)
CollapsingHeader: change label text style & enable options
2021-03-09 19:13:21 +01:00
Norbert Nemec
6fb4e19e9e
DragValue: fix crash for speed==0.0 (#216) 2021-03-09 19:06:04 +01:00
Tomáš Vojtášek
f07f9bf5bd
Typo previus -> previous (#214) 2021-03-09 18:57:45 +01:00
Emil Ernerfeldt
d2d074395a
Install speech-dispatcher in CI for ttl (#219) 2021-03-09 18:57:28 +01:00
Emil Ernerfeldt
4c45ca113e Make sure to include license files in the next release 2021-03-09 18:35:13 +01:00
Emil Ernerfeldt
07a17dc6cf Sort 3rd party integrations and add link to egui_vulkano 2021-03-08 22:57:45 +01:00
Emil Ernerfeldt
2428763756 Fix bug in Response::clicked_elsewhere
Fixes https://github.com/emilk/egui/issues/205
2021-03-08 22:22:44 +01:00
Emil Ernerfeldt
8f3a25d749 clippy fix 2021-03-08 22:16:35 +01:00
Ridan Vandenbergh
f1c6d2b59c
feat: Derive (de)serialize for Key (#213) 2021-03-08 22:09:30 +01:00
Emil Ernerfeldt
44cd304cdf Add experimental screen_reader feature
Part of https://github.com/emilk/egui/issues/167
2021-03-08 20:58:01 +01:00
Emil Ernerfeldt
cb7ef6faeb bug fix: open links in same tab by default 2021-03-08 20:25:43 +01:00
Emil Ernerfeldt
ea248d66b5 Improve widget info output for potential screen readers
Part of https://github.com/emilk/egui/issues/167
2021-03-08 18:36:32 +01:00
Emil Ernerfeldt
1c06622dbc Hold down a modifier key when clicking a link to open it in a new tab 2021-03-08 17:48:23 +01:00
aakamenov
c1ef81628b
Add optional drag bounds to Area and Window
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-03-07 20:42:16 +01:00
Emil Ernerfeldt
c212d4512e Clarify behavior of window resizing
closes https://github.com/emilk/egui/issues/206
2021-03-07 20:15:42 +01:00
Emil Ernerfeldt
d6233de9dc Small improvements to the demo (add source code links etc) 2021-03-07 19:51:07 +01:00
Emil Ernerfeldt
25d4a7e11e Fix gained kb focus event on shift-tab 2021-03-07 19:45:28 +01:00
Emil Ernerfeldt
eba4d3d7b1 refactor: move kb-focus logic to own struct 2021-03-07 19:45:28 +01:00
Emil Ernerfeldt
cd4c07e09a Output events when widgets gain keyboard focus
Part of https://github.com/emilk/egui/issues/167
2021-03-07 19:45:20 +01:00
Emil Ernerfeldt
a370339db7 Give focus to any clickable widget with tab/shift-tab
Use space or enter to click the selected widget.
Use arrow keys to adjust sliders and `DragValue`s.

Closes https://github.com/emilk/egui/issues/31
2021-03-07 18:15:57 +01:00
Emil Ernerfeldt
6fd7c422ab Refactor: remove has_kb_focus/lost_kb_focus bools from Reponse
Just forward the queries to Memory
2021-03-07 13:06:57 +01:00
Ridan Vandenbergh
4df8418e41
fix: Modern browsers encode Space as ' ' (#208) 2021-03-07 10:20:11 +01:00
Emil Ernerfeldt
b72184dc7e clippy fix 2021-03-06 11:19:32 +01:00
Emil Ernerfeldt
5d0c71350d Add fn Memory::gained_kb_focus() -> bool query 2021-03-06 11:17:24 +01:00
Emil Ernerfeldt
007f9f3cb9 Fix secondary-click to open a menu
Fixes https://github.com/emilk/egui/issues/198
2021-03-06 11:11:07 +01:00
Emil Ernerfeldt
fa4752f315 Add the syntect feature to egui_demo_app 2021-03-06 11:04:05 +01:00
Emil Ernerfeldt
9c8439d053 Add some helper functions to Response 2021-03-06 10:48:39 +01:00
Emil Ernerfeldt
fb2db4940e Release 0.10.0 - Plot and polish 2021-02-28 20:11:37 +01:00
Emil Ernerfeldt
f793ac7f3e [demo] Remove dead code 2021-02-28 20:11:37 +01:00
Emil Ernerfeldt
8fb1472560 Add glsl files to egui_web and egui_glium includes 2021-02-28 20:11:37 +01:00
Emil Ernerfeldt
8047a0f87f Publish new web demo 2021-02-28 19:51:22 +01:00
Emil Ernerfeldt
1fb3b43dfc [demo] Improve painting demo slightly 2021-02-28 19:50:47 +01:00
Emil Ernerfeldt
e417fac52e Fix web shaders
Got broken in https://github.com/emilk/egui/pull/187
2021-02-28 19:47:01 +01:00
Emil Ernerfeldt
a9a49f19ca cargo update 2021-02-28 19:13:05 +01:00
Emil Ernerfeldt
24d68cc55e Merge epi/CHANGELOG.md into eframe/CHANGELOG.md 2021-02-28 19:11:02 +01:00
Emil Ernerfeldt
84cc227f11 eframe: always provide a texture allocator 2021-02-28 19:09:48 +01:00
Emil Ernerfeldt
fdb1aa6bec improve documentation 2021-02-28 18:59:46 +01:00
Emil Ernerfeldt
8be37b3d6c Add Response::has_kb_focus()
Closes https://github.com/emilk/egui/issues/196
2021-02-28 18:19:33 +01:00
Emil Ernerfeldt
834078a476 Turn off the cursor preview when hovering a TextEdit 2021-02-28 17:27:39 +01:00
Emil Ernerfeldt
d3fd51d6a4 Add Response::changed(): see if e.g. text was entered or slider dragged 2021-02-28 17:24:07 +01:00
Patrik Höglund
a859b2a26e
Add icon support to eframe (#193)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2021-02-26 15:59:30 +01:00
Emil Ernerfeldt
bdbc59455c Improve documentation for the most common widgets 2021-02-23 22:18:13 +01:00
Emil Ernerfeldt
814f8c0dd8 Add support for all integers in DragValue and Slider (except 128-bit) 2021-02-23 20:40:14 +01:00
Emil Ernerfeldt
02a65132e4 Add epi::App::initial_window_size to control initial native window size 2021-02-23 20:28:55 +01:00
Emil Ernerfeldt
049a7b0382 Make DragValue::from_get_set public
Closes https://github.com/emilk/egui/issues/188
2021-02-23 20:16:43 +01:00
Emil Ernerfeldt
5f6a468812 Add Context::set_pixels_per_point to control the scale of the UI 2021-02-21 11:23:33 +01:00
Emil Ernerfeldt
c601db5956 refactor: move tooltip state handling out of memory 2021-02-21 10:31:28 +01:00
Emil Ernerfeldt
67623919d7 refactor: move DragValue state 2021-02-21 10:30:31 +01:00
Emil Ernerfeldt
24a1c3136c Add a pull_request_template.md 2021-02-21 10:12:23 +01:00
Emil Ernerfeldt
82350a2f1e Improve documentation 2021-02-21 10:12:08 +01:00
Kayo Phoenix
c9919daa11
Added shaders on GLSL 1.2 (#187)
* Added shaders on GLSL 1.2

- Used `glium::program` to create shaders
- Moved shaders code to its own sources and include it as str
- Added shaders implementation on GLSL which allows run egui on old hardware
  (Raspberry Pi 1/zero in game again)

* Moved webgl shaders code to sources in `shader` subdir

* Added GLSL ES shaders to glium backend to support OpenGL ES

* Described changes related to GLSL versions support
2021-02-20 19:48:02 +01:00
Emil Ernerfeldt
ebc2486d22 Slider: use a DragValue for the value, and implement suffix/prefix 2021-02-20 18:29:09 +01:00
Emil Ernerfeldt
32f35c6251 Implement "Smart Aim" for DragValue 2021-02-20 17:43:35 +01:00
Emil Ernerfeldt
7ac26b84b1 DragValue: handle slowly dragging a value with limited precision 2021-02-20 17:27:55 +01:00
Emil Ernerfeldt
9a546ff97a SlidSlider will now show the value display by default.
Turn off with slider.show_value(false)

Closes https://github.com/emilk/egui/issues/183
2021-02-20 16:28:39 +01:00
Emil Ernerfeldt
4e041185f1 Add module special_emojis with apple, linux, windows & github logos 2021-02-20 16:09:00 +01:00
Emil Ernerfeldt
9b58d5d4d9 color picker: just copy the r,g,b,a values without a "rgba(…)" wrapper 2021-02-20 15:49:09 +01:00
Emil Ernerfeldt
ac356e2bd8 Add a link from the crate-level docs to the online egui web demo 2021-02-20 12:43:53 +01:00
Emil Ernerfeldt
6354709fe1 [demo] Link to the egui docs from the widget gallery 2021-02-20 12:07:15 +01:00
Emil Ernerfeldt
040553da78 impl<F> Widget for F where F: FnOnce(&mut Ui) -> Response
This enables functions that return `impl Widget`, so that you can
create a widget by just returning a lambda from a function.

For instance: `ui.add(toggle(bool))` (instead of `toggle(ui, bool)`)
2021-02-20 12:02:38 +01:00
Emil Ernerfeldt
6fe70e685b Simplify and unify colors of selectable widgets 2021-02-20 11:28:00 +01:00
Emil Ernerfeldt
741f0bfe8a Only show tooltips if mouse is still. 2021-02-20 10:45:19 +01:00
Emil Ernerfeldt
4354f7582f Improve the positioning of tooltips 2021-02-20 10:33:33 +01:00
Emil Ernerfeldt
d5bb85b245 Add InputState::is_still to compliment InputState::is_moving 2021-02-20 09:26:58 +01:00
Emil Ernerfeldt
9c3b7d719b Bug fix: child painters now inherit color tint (e.g. grayed out) 2021-02-20 09:26:58 +01:00
Emil Ernerfeldt
21c99e1130 Add Ui::hyperlink_to 2021-02-20 09:26:58 +01:00
Emil Ernerfeldt
9e38674d13 [demo] Alwyas start with widget gallery 2021-02-20 09:19:40 +01:00
Emil Ernerfeldt
0f13fff24b Assign default colors to plot lines if not explicitly set 2021-02-18 18:59:59 +01:00
Emil Ernerfeldt
a19140ec67 A simple 2D plot library 2021-02-17 22:54:10 +01:00
Emil Ernerfeldt
7dad76b913 Use explicit epaint over paint alias (re-export)
egui reexports the `epaint` crate both under its original name
and under the alias `paint` (for historical reasons)
2021-02-14 10:53:39 +01:00
Emil Ernerfeldt
6d255cd179 Use explicit emath to math alias
egui exports `emath` under its original name AND under the alias `math`
(for historical reasons).
2021-02-14 10:44:46 +01:00
Emil Ernerfeldt
c376d0bb7e [emath] RectTransform: transforms Pos2 from one Rect to another
Very useful for transforming coordinate systems, e.g. for painting
2021-02-14 10:33:44 +01:00
Emil Ernerfeldt
dbc6a620cd Control the maximum egui web canvas size with App::max_size_points 2021-02-12 17:58:02 +01:00
Emil Ernerfeldt
be8d7b4eef Slider: add largest_finite for log-sliders that include infinity 2021-02-12 17:45:27 +01:00
Emil Ernerfeldt
5906bf7a87 Fix clippy 1.50 lints 2021-02-12 17:40:53 +01:00
Emil Ernerfeldt
fc8b27807c Add discussion about the pros and cons of immediate mode GUIs 2021-02-11 19:29:05 +01:00
Emil Ernerfeldt
e5233d2268 Add link to egui_winit_ash_vk_mem crate 2021-02-09 18:38:37 +01:00
Emil Ernerfeldt
fc0bec44ba Remove Fonts::default() implementation 2021-02-08 23:05:48 +01:00
Emil Ernerfeldt
156d68d266 Refactor: move FrameState to own file 2021-02-08 23:03:41 +01:00
Emil Ernerfeldt
1c415bd8fe Add benchmark for text layout and tesselation 2021-02-08 22:53:31 +01:00
474 changed files with 75825 additions and 30156 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

@ -7,32 +7,44 @@ assignees: ''
---
<!--
First look if there is already a similar bug report. If there is, upvote the issue with 👍
Please also check if the bug is still present in latest master! Do so by adding the following lines to your Cargo.toml:
[patch.crates-io]
egui = { git = "https://github.com/emilk/egui", branch = "master" }
# if you're using eframe:
eframe = { git = "https://github.com/emilk/egui", branch = "master" }
-->
**Describe the bug**
A clear and concise description of what the bug is.
<!-- A clear and concise description of what the bug is. An image is good, a gif or movie is better! -->
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
1. <!-- Go to '…' -->
2. <!-- Click on '…' -->
3. <!-- Scroll down to '…' -->
4. <!-- See error -->
**Expected behavior**
A clear and concise description of what you expected to happen.
<!-- A clear and concise description of what you expected to happen. -->
**Screenshots**
If applicable, add screenshots to help explain your problem.
<!-- If applicable, add screenshots to help explain your problem. -->
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- OS: <!-- e.g. iOS -->
- Browser <!-- e.g. chrome, safari -->
- Version <!-- e.g. 22 -->
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
- Device: <!-- e.g. iPhone6 -->
- OS: <!-- e.g. iOS8.1 -->
- Browser <!-- e.g. stock browser, safari -->
- Version <!-- e.g. 22 -->
**Additional context**
Add any other context about the problem here.
<!-- Add any other context about the problem here. -->

View file

@ -2,19 +2,24 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
labels: feature-request
assignees: ''
---
<!--
First look if there is already a similar feature request. If there is, upvote the issue with 👍
-->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when […] -->
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
Add any other context or screenshots about the feature request here.
<!-- Add any other context or screenshots about the feature request here. -->

15
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,15 @@
<!--
Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) before opening a Pull Request!
* Keep your PR:s small and focused.
* If applicable, add a screenshot or gif.
* Unless this is a trivial change, add a line to the relevant `CHANGELOG.md` under "Unreleased".
* If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run `./sh/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review you PR, but my time is limited!
-->
Closes <https://github.com/emilk/egui/issues/THE_RELEVANT_ISSUE>.

View file

@ -3,117 +3,209 @@ on: [push, pull_request]
name: CI
env:
# This is required to enable the web_sys clipboard API which egui_web uses
# web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses,
# as well as by the wasm32-backend of the wgpu crate.
# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
RUSTFLAGS: --cfg=web_sys_unstable_apis
RUSTFLAGS: --cfg=web_sys_unstable_apis -D warnings
RUSTDOCFLAGS: -D warnings
jobs:
check_default:
name: cargo check (default features)
runs-on: ubuntu-latest
fmt-crank-check-test:
name: Format + check + test
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: check
check_all_features:
name: cargo check --all-features
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
profile: default
toolchain: 1.65.0
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features
check_web_default:
name: cargo check web (default features)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- name: Install packages (Linux)
if: runner.os == 'Linux'
#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:
profile: minimal
toolchain: stable
override: true
- run: rustup target add wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: check
args: -p egui_demo_app --lib --target wasm32-unknown-unknown
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
check_web_all_features:
name: cargo check web --all-features
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup target add wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: check
args: -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
test:
name: cargo test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
- uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
fmt:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
- name: Rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: cargo clippy
runs-on: ubuntu-latest
- name: Install cargo-cranky
uses: baptiste0928/cargo-install@v1
with:
crate: cargo-cranky
- name: check --all-features
uses: actions-rs/cargo@v1
with:
command: check
args: --locked --all-features --all-targets
- name: check default features
uses: actions-rs/cargo@v1
with:
command: check
args: --locked --all-targets
- name: check --no-default-features
uses: actions-rs/cargo@v1
with:
command: check
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
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
toolchain: 1.65.0
target: wasm32-unknown-unknown
override: true
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
- name: Install cargo-cranky
uses: baptiste0928/cargo-install@v1
with:
command: clippy
args: -- -D warnings
crate: cargo-cranky
- name: Check wasm32 egui_demo_app
uses: actions-rs/cargo@v1
with:
command: check
args: -p egui_demo_app --lib --target wasm32-unknown-unknown
- name: Check wasm32 egui_demo_app --all-features
uses: actions-rs/cargo@v1
with:
command: check
args: -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features
- name: Check wasm32 eframe
uses: actions-rs/cargo@v1
with:
command: check
args: -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown
- name: wasm-bindgen
uses: jetli/wasm-bindgen-action@v0.1.0
with:
version: "0.2.84"
- run: ./sh/wasm_bindgen_check.sh --skip-setup
- name: Cranky wasm32
uses: actions-rs/cargo@v1
with:
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
steps:
- uses: actions/checkout@v2
- uses: EmbarkStudios/cargo-deny-action@v1
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
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.65.0
override: true
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
- name: Check
uses: actions-rs/cargo@v1
with:
command: check
args: --all-targets --all-features

3
.gitignore vendored
View file

@ -1,4 +1,7 @@
.DS_Store
**/target
**/target_ra
**/target_wasm
/.*.json
/.vscode
/media/*

32
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,32 @@
{
"files.insertFinalNewline": true,
"editor.formatOnSave": true,
"files.trimTrailingWhitespace": true,
"editor.semanticTokenColorCustomizations": {
"rules": {
"*.unsafe:rust": "#eb5046"
}
},
"files.exclude": {
"target/**": true,
"target_ra/**": true,
},
// Tell Rust Analyzer to use its own target directory, so we don't need to wait for it to finish wen we want to `cargo run`
"rust-analyzer.checkOnSave.overrideCommand": [
"cargo",
"cranky",
"--target-dir=target_ra",
"--workspace",
"--message-format=json",
"--all-targets"
],
"rust-analyzer.cargo.buildScripts.overrideCommand": [
"cargo",
"check",
"--quiet",
"--target-dir=target_ra",
"--workspace",
"--message-format=json",
"--all-targets"
],
}

53
ARCHITECTURE.md Normal file
View file

@ -0,0 +1,53 @@
# Architecture
This document describes how the crates that make up egui are all connected.
Also see [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) for what to do before opening a PR.
## Crate overview
The crates in this repository are: `egui, emath, epaint, egui_extras, egui-winit, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`.
### `egui`: The main GUI library.
Example code: `if ui.button("Click me").clicked() { … }`
This is the crate where the bulk of the code is at. `egui` depends only on `emath` and `epaint`.
### `emath`: minimal 2D math library
Examples: `Vec2, Pos2, Rect, lerp, remap`
### `epaint`
2d shapes and text that can be turned into textured triangles.
Example: `Shape::Circle { center, radius, fill, stroke }`
Depends on `emath`.
### `egui_extras`
This adds additional features on top of `egui`.
### `egui-winit`
This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [winit](https://crates.io/crates/winit).
The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc.
### `egui_glium`
Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glium](https://github.com/glium/glium).
### `egui_glow`
Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glow](https://github.com/grovesNL/glow).
### `eframe`
`eframe` is the official `egui` framework, built so you can compile the same app for either web or native.
The demo that you can see at <https://www.egui.rs> is using `eframe` to host the `egui`. The demo code is found in:
### `egui_demo_lib`
Depends on `egui`.
This contains a bunch of uses of `egui` and looks like the ui code you would write for an `egui` app.
### `egui_demo_app`
Thin wrapper around `egui_demo_lib` so we can compile it to a web site or a native app executable.
Depends on `egui_demo_lib` + `eframe`.
### Other integrations
There are also many great integrations for game engines such as `bevy` and `miniquad` which you can find at <https://github.com/emilk/egui#integrations>.

View file

@ -1,50 +1,657 @@
# egui changelog
All notable changes to the `egui` crate will be documented in this file.
All notable changes to the egui crate will be documented in this file.
NOTE: `epi`, `eframe`, `egui_web` and `egui_glium` has their own changelogs!
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glium`](crates/egui_glium/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs!
## Unreleased
## 0.9.0 - 2021-02-07 - Light Mode and much more
## 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))`
### Added ⭐
* Add `Response::drag_started_by` and `Response::drag_released_by` for convenience, similar to `dragged` and `dragged_by` ([#2507](https://github.com/emilk/egui/pull/2507)).
* Add `PointerState::*_pressed` to check if the given button was pressed in this frame ([#2507](https://github.com/emilk/egui/pull/2507)).
* `Event::Key` now has a `repeat` field that is set to `true` if the event was the result of a key-repeat ([#2435](https://github.com/emilk/egui/pull/2435)).
* Add `Slider::drag_value_speed`, which lets you ask for finer precision when dragging the slider value rather than the actual slider.
* Add `Memory::any_popup_open`, which returns true if any popup is currently open ([#2464](https://github.com/emilk/egui/pull/2464)).
* Add `Plot::clamp_grid` to only show grid where there is data ([#2480](https://github.com/emilk/egui/pull/2480)).
* Add `ScrollArea::drag_to_scroll` if you want to turn off that feature.
* Add `Response::on_hover_and_drag_cursor`.
* Add `Window::default_open` ([#2539](https://github.com/emilk/egui/pull/2539)).
* Add `ProgressBar::fill` if you want to set the fill color manually. ([#2618](https://github.com/emilk/egui/pull/2618)).
* Add `Button::rounding` to enable round buttons ([#2616](https://github.com/emilk/egui/pull/2616)).
* Add `WidgetVisuals::optional_bg_color` - set it to `Color32::TRANSPARENT` to hide button backgrounds ([#2621](https://github.com/emilk/egui/pull/2621)).
* Add `Context::screen_rect` and `Context::set_cursor_icon` ([#2625](https://github.com/emilk/egui/pull/2625)).
* 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)).
* Improved the algorithm for picking the number of decimals to show when hovering values in the `Plot`.
* 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)).
* 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
### Changed 🔧
* `InputState`: all press functions again include key repeats (like in egui 0.19) ([#2429](https://github.com/emilk/egui/pull/2429)).
* Improve the look of thin white lines ([#2437](https://github.com/emilk/egui/pull/2437)).
### Fixed 🐛
* Fix key-repeats for `TextEdit`, `Slider`s, etc ([#2429](https://github.com/emilk/egui/pull/2429)).
## 0.20.0 - 2022-12-08 - AccessKit, prettier text, overlapping widgets
* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)).
* ⚠️ BREAKING: egui now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)).
* ⚠️ BREAKING: if you have overlapping interactive widgets, only the top widget (last added) will be interactive ([#2244](https://github.com/emilk/egui/pull/2244)).
### Added ⭐
* Added helper functions for animating panels that collapse/expand ([#2190](https://github.com/emilk/egui/pull/2190)).
* Added `Context::os/Context::set_os` to query/set what operating system egui believes it is running on ([#2202](https://github.com/emilk/egui/pull/2202)).
* Added `Button::shortcut_text` for showing keyboard shortcuts in menu buttons ([#2202](https://github.com/emilk/egui/pull/2202)).
* Added `egui::KeyboardShortcut` for showing keyboard shortcuts in menu buttons ([#2202](https://github.com/emilk/egui/pull/2202)).
* Texture loading now takes a `TexureOptions` with minification and magnification filters ([#2224](https://github.com/emilk/egui/pull/2224)).
* Added `Key::Minus` and `Key::Equals` ([#2239](https://github.com/emilk/egui/pull/2239)).
* Added `egui::gui_zoom` module with helpers for scaling the whole GUI of an app ([#2239](https://github.com/emilk/egui/pull/2239)).
* You can now put one interactive widget on top of another, and only one will get interaction at a time ([#2244](https://github.com/emilk/egui/pull/2244)).
* Added `spacing.menu_margin` for customizing menu spacing ([#2036](https://github.com/emilk/egui/pull/2036))
* Added possibility to enable text wrap for the selected text of `egui::ComboBox` ([#2272](https://github.com/emilk/egui/pull/2272))
* Added `Area::constrain` and `Window::constrain` which constrains area to the screen bounds ([#2270](https://github.com/emilk/egui/pull/2270)).
* Added `Area::pivot` and `Window::pivot` which controls what part of the window to position ([#2303](https://github.com/emilk/egui/pull/2303)).
* Added support for [thin space](https://en.wikipedia.org/wiki/Thin_space).
* Added optional integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)).
* Added `panel_fill`, `window_fill` and `window_stroke` to `Visuals` for your theming pleasure ([#2406](https://github.com/emilk/egui/pull/2406)).
* Plots:
* Allow linking plot cursors ([#1722](https://github.com/emilk/egui/pull/1722)).
* Added `Plot::auto_bounds_x/y` and `Plot::reset` ([#2029](https://github.com/emilk/egui/pull/2029)).
* Added `PlotUi::translate_bounds` ([#2145](https://github.com/emilk/egui/pull/2145)).
* Added `PlotUi::set_plot_bounds` ([#2320](https://github.com/emilk/egui/pull/2320)).
* Added `PlotUi::plot_secondary_clicked` ([#2318](https://github.com/emilk/egui/pull/2318)).
### Changed 🔧
* Panels always have a separator line, but no stroke on other sides. Their spacing has also changed slightly ([#2261](https://github.com/emilk/egui/pull/2261)).
* Tooltips are only shown when mouse pointer is still ([#2263](https://github.com/emilk/egui/pull/2263)).
* Make it slightly easier to click buttons ([#2304](https://github.com/emilk/egui/pull/2304)).
* `egui::color` has been renamed `egui::ecolor` ([#2399](https://github.com/emilk/egui/pull/2399)).
### Fixed 🐛
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
* Improve mixed CJK/Latin line-breaking ([#1986](https://github.com/emilk/egui/pull/1986)).
* Improved text rendering ([#2071](https://github.com/emilk/egui/pull/2071)).
* Constrain menu popups to the screen ([#2191](https://github.com/emilk/egui/pull/2191)).
* Less jitter when calling `Context::set_pixels_per_point` ([#2239](https://github.com/emilk/egui/pull/2239)).
* Fixed popups and color edit going outside the screen.
* Fixed keyboard support in `DragValue` ([#2342](https://github.com/emilk/egui/pull/2342)).
* If you nest `ScrollAreas` inside each other, the inner area will now move its scroll bar so it is always visible ([#2371](https://github.com/emilk/egui/pull/2371)).
* Ignore key-repeats for `input.key_pressed` ([#2334](https://github.com/emilk/egui/pull/2334), [#2389](https://github.com/emilk/egui/pull/2389)).
* Fixed issue with calling `set_pixels_per_point` each frame ([#2352](https://github.com/emilk/egui/pull/2352)).
* Fix bug in `ScrollArea::show_rows` ([#2258](https://github.com/emilk/egui/pull/2258)).
* Fix bug in `plot::Line::fill` ([#2275](https://github.com/emilk/egui/pull/2275)).
* Only emit `changed` events in `radio_value` and `selectable_value` if the value actually changed ([#2343](https://github.com/emilk/egui/pull/2343)).
* Fixed sizing bug in `Grid` ([#2384](https://github.com/emilk/egui/pull/2384)).
* `ComboBox::width` now correctly sets the outer width ([#2406](https://github.com/emilk/egui/pull/2406)).
## 0.19.0 - 2022-08-20
### Added ⭐
* Added `*_released` & `*_clicked` methods for `PointerState` ([#1582](https://github.com/emilk/egui/pull/1582)).
* Added `PointerButton::Extra1` and `PointerButton::Extra2` ([#1592](https://github.com/emilk/egui/pull/1592)).
* Added `egui::hex_color!` to create `Color32`'s from hex strings under the `color-hex` feature ([#1596](https://github.com/emilk/egui/pull/1596)).
* Optimized painting of filled circles (e.g. for scatter plots) by 10x or more ([#1616](https://github.com/emilk/egui/pull/1616)).
* Added opt-in feature `deadlock_detection` to detect double-lock of mutexes on the same thread ([#1619](https://github.com/emilk/egui/pull/1619)).
* Added `InputState::stable_dt`: a more stable estimate for the delta-time in reactive mode ([#1625](https://github.com/emilk/egui/pull/1625)).
* You can now specify a texture filter for your textures ([#1636](https://github.com/emilk/egui/pull/1636)).
* Added functions keys in `egui::Key` ([#1665](https://github.com/emilk/egui/pull/1665)).
* Added support for using `PaintCallback` shapes with the WGPU backend ([#1684](https://github.com/emilk/egui/pull/1684)).
* Added `Context::request_repaint_after` ([#1694](https://github.com/emilk/egui/pull/1694)).
* `ctrl-h` now acts like backspace in `TextEdit` ([#1812](https://github.com/emilk/egui/pull/1812)).
* Added `custom_formatter` method for `Slider` and `DragValue` ([#1851](https://github.com/emilk/egui/issues/1851)).
* Added `RawInput::has_focus` which backends can set to indicate whether the UI as a whole has the keyboard focus ([#1859](https://github.com/emilk/egui/pull/1859)).
* Added `PointerState::button_double_clicked()` and `PointerState::button_triple_clicked()` ([#1906](https://github.com/emilk/egui/issues/1906)).
* Added `custom_formatter`, `binary`, `octal`, and `hexadecimal` to `DragValue` and `Slider` ([#1953](https://github.com/emilk/egui/issues/1953))
### Changed 🔧
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
* `PaintCallback` shapes now require the whole callback to be put in an `Arc<dyn Any>` with the value being a backend-specific callback type ([#1684](https://github.com/emilk/egui/pull/1684)).
* Replaced `needs_repaint` in `FullOutput` with `repaint_after`. Used to force repaint after the set duration in reactive mode ([#1694](https://github.com/emilk/egui/pull/1694)).
* `Layout::left_to_right` and `Layout::right_to_left` now takes the vertical align as an argument. Previous default was `Align::Center`.
* Improved ergonomics of adding plot items. All plot items that take a series of 2D coordinates can now be created directly from `Vec<[f64; 2]>`. The `Value` and `Values` types were removed in favor of `PlotPoint` and `PlotPoints` respectively ([#1816](https://github.com/emilk/egui/pull/1816)).
* `TextBuffer` no longer needs to implement `AsRef<str>` ([#1824](https://github.com/emilk/egui/pull/1824)).
### Fixed 🐛
* Fixed `Response::changed` for `ui.toggle_value` ([#1573](https://github.com/emilk/egui/pull/1573)).
* Fixed `ImageButton`'s changing background padding on hover ([#1595](https://github.com/emilk/egui/pull/1595)).
* Fixed `Plot` auto-bounds bug ([#1599](https://github.com/emilk/egui/pull/1599)).
* Fixed dead-lock when alt-tabbing while also showing a tooltip ([#1618](https://github.com/emilk/egui/pull/1618)).
* Fixed `ScrollArea` scrolling when editing an unrelated `TextEdit` ([#1779](https://github.com/emilk/egui/pull/1779)).
* Fixed `Slider` not always generating events on change ([#1854](https://github.com/emilk/egui/pull/1854)).
* Fixed jitter of anchored windows for the first frame ([#1856](https://github.com/emilk/egui/pull/1856)).
* Fixed focus behavior when pressing Tab in a UI with no focused widget ([#1861](https://github.com/emilk/egui/pull/1861)).
* Fixed automatic plot bounds ([#1865](https://github.com/emilk/egui/pull/1865)).
## 0.18.1 - 2022-05-01
* Change `Shape::Callback` from `&dyn Any` to `&mut dyn Any` to support more backends.
## 0.18.0 - 2022-04-30
### Added ⭐
* Added `Shape::Callback` for backend-specific painting, [with an example](https://github.com/emilk/egui/tree/master/examples/custom_3d_glow) ([#1351](https://github.com/emilk/egui/pull/1351)).
* Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)).
* `Context::request_repaint` will now wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)).
* Added `Plot::allow_scroll`, `Plot::allow_zoom` no longer affects scrolling ([#1382](https://github.com/emilk/egui/pull/1382)).
* Added `Ui::push_id` to resolve id clashes ([#1374](https://github.com/emilk/egui/pull/1374)).
* Added `ComboBox::icon` ([#1405](https://github.com/emilk/egui/pull/1405)).
* Added `Ui::scroll_with_delta`.
* Added `Frame::outer_margin`.
* Added `Painter::hline` and `Painter::vline`.
* Added `Link` and `ui.link` ([#1506](https://github.com/emilk/egui/pull/1506)).
* Added triple-click support; triple-clicking a TextEdit field will select the whole paragraph ([#1512](https://github.com/emilk/egui/pull/1512)).
* Added `Plot::x_grid_spacer` and `Plot::y_grid_spacer` for custom grid spacing ([#1180](https://github.com/emilk/egui/pull/1180)).
* Added `Ui::spinner()` shortcut method ([#1494](https://github.com/emilk/egui/pull/1494)).
* Added `CursorIcon`s for resizing columns, rows, and the eight cardinal directions.
* Added `Ui::toggle_value`.
* Added ability to add any widgets to the header of a collapsing region ([#1538](https://github.com/emilk/egui/pull/1538)).
### Changed 🔧
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
* Renamed `Frame::margin` to `Frame::inner_margin`.
* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)).
* Warnings will be painted on screen when there is an `Id` clash for `Grid`, `Plot` or `ScrollArea` ([#1452](https://github.com/emilk/egui/pull/1452)).
* `Checkbox` and `RadioButton` with an empty label (`""`) will now take up much less space ([#1456](https://github.com/emilk/egui/pull/1456)).
* Replaced `Memory::top_most_layer` with more flexible `Memory::layer_ids`.
* Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)).
* Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)).
* Renamed `Painter::sub_region` to `Painter::with_clip_rect`.
### Fixed 🐛
* Fixed `ComboBox`es always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)).
* Text is darker and more readable in bright mode ([#1412](https://github.com/emilk/egui/pull/1412)).
* Fixed a lot of broken/missing doclinks ([#1419](https://github.com/emilk/egui/pull/1419)).
* Fixed `Ui::add_visible` sometimes leaving the `Ui` in a disabled state ([#1436](https://github.com/emilk/egui/issues/1436)).
* Added line breaking rules for Japanese text ([#1498](https://github.com/emilk/egui/pull/1498)).
### Deprecated ☢️
* Deprecated `CollapsingHeader::selectable` ([#1538](https://github.com/emilk/egui/pull/1538)).
### Removed 🔥
* Removed the `single_threaded/multi_threaded` flags - egui is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
### Contributors 🙏
* [4JX](https://github.com/4JX)
* [AlexxxRu](https://github.com/AlexxxRu)
* [ascclemens](https://github.com/ascclemens)
* [awaken1ng](https://github.com/awaken1ng)
* [bigfarts](https://github.com/bigfarts)
* [bobyclaws](https://github.com/bobyclaws)
* [Bromeon](https://github.com/Bromeon)
* [cloudhead](https://github.com/cloudhead)
* [collin-kemper](https://github.com/collin-kemper)
* [cpterry](https://github.com/cpterry)
* [dbuch](https://github.com/dbuch)
* [DusterTheFirst](https://github.com/DusterTheFirst)
* [Edgeworth ](https://github.com/Edgeworth )
* [elwerene](https://github.com/elwerene)
* [follower](https://github.com/follower)
* [Friz64](https://github.com/Friz64)
* [Hunter522 ](https://github.com/Hunter522 )
* [Jake-Shadle](https://github.com/Jake-Shadle)
* [jean-airoldie ](https://github.com/jean-airoldie )
* [JelNiSlaw](https://github.com/JelNiSlaw)
* [juancampa](https://github.com/juancampa)
* [LU15W1R7H](https://github.com/LU15W1R7H)
* [mbillingr](https://github.com/mbillingr)
* [nicklasmoeller](https://github.com/nicklasmoeller)
* [rukai](https://github.com/rukai)
* [tami5](https://github.com/tami5)
* [Titaniumtown](https://github.com/Titaniumtown)
* [trevyn](https://github.com/trevyn)
* [waynr](https://github.com/waynr)
* [zam-5 ](https://github.com/zam-5 )
## 0.17.0 - 2022-02-22 - Improved font selection and image handling
### Added ⭐
* Much improved font selection ([#1154](https://github.com/emilk/egui/pull/1154)):
* You can now select any font size and family using `RichText::size` amd `RichText::family` and the new `FontId`.
* Easily change text styles with `Style::text_styles`.
* Added `Ui::text_style_height`.
* Added `TextStyle::resolve`.
* Made the v-align and scale of user fonts tweakable ([#1241](https://github.com/emilk/egui/pull/1027)).
* Plot:
* Added `Plot::x_axis_formatter` and `Plot::y_axis_formatter` for custom axis labels ([#1130](https://github.com/emilk/egui/pull/1130)).
* Added `Plot::allow_boxed_zoom()`, `Plot::boxed_zoom_pointer()` for boxed zooming on plots ([#1188](https://github.com/emilk/egui/pull/1188)).
* Added plot pointer coordinates with `Plot::coordinates_formatter` ([#1235](https://github.com/emilk/egui/pull/1235)).
* Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)).
* `Context::load_texture` to convert an image into a texture which can be displayed using e.g. `ui.image(texture, size)` ([#1110](https://github.com/emilk/egui/pull/1110)).
* `Ui::input_mut` to modify how subsequent widgets see the `InputState` and a convenience method `InputState::consume_key` for shortcuts or hotkeys ([#1212](https://github.com/emilk/egui/pull/1212)).
* Added `Ui::add_visible` and `Ui::add_visible_ui`.
* Added `CollapsingHeader::icon` to override the default open/close icon using a custom function. ([1147](https://github.com/emilk/egui/pull/1147)).
* Added `ui.data()`, `ctx.data()`, `ctx.options()` and `ctx.tessellation_options()` ([#1175](https://github.com/emilk/egui/pull/1175)).
* Added `Response::on_hover_text_at_pointer` as a convenience akin to `Response::on_hover_text` ([1179](https://github.com/emilk/egui/pull/1179)).
* Opt-in dependency on `tracing` crate for logging warnings ([#1192](https://github.com/emilk/egui/pull/1192)).
* Added `ui.weak(text)`.
* Added `Slider::step_by` ([1225](https://github.com/emilk/egui/pull/1225)).
* Added `Context::move_to_top` and `Context::top_most_layer` for managing the layer on the top ([#1242](https://github.com/emilk/egui/pull/1242)).
* Support a subset of macOS' emacs input field keybindings in `TextEdit` ([#1243](https://github.com/emilk/egui/pull/1243)).
* Added ability to scroll an UI into view without specifying an alignment ([1247](https://github.com/emilk/egui/pull/1247)).
* Added `Ui::scroll_to_rect` ([1252](https://github.com/emilk/egui/pull/1252)).
### Changed 🔧
* ⚠️ `Context::input` and `Ui::input` now locks a mutex. This can lead to a dead-lock is used in an `if let` binding!
* `if let Some(pos) = ui.input().pointer.latest_pos()` and similar must now be rewritten on two lines.
* Search for this problem in your code using the regex `if let .*input`.
* Better contrast in the default light mode style ([#1238](https://github.com/emilk/egui/pull/1238)).
* Renamed `CtxRef` to `Context` ([#1050](https://github.com/emilk/egui/pull/1050)).
* `Context` can now be cloned and stored between frames ([#1050](https://github.com/emilk/egui/pull/1050)).
* Renamed `Ui::visible` to `Ui::is_visible`.
* Split `Event::Text` into `Event::Text` and `Event::Paste` ([#1058](https://github.com/emilk/egui/pull/1058)).
* Replaced `Style::body_text_style` with more generic `Style::text_styles` ([#1154](https://github.com/emilk/egui/pull/1154)).
* `TextStyle` is no longer `Copy` ([#1154](https://github.com/emilk/egui/pull/1154)).
* Replaced `TextEdit::text_style` with `TextEdit::font` ([#1154](https://github.com/emilk/egui/pull/1154)).
* `Plot::highlight` now takes a `bool` argument ([#1159](https://github.com/emilk/egui/pull/1159)).
* `ScrollArea::show` now returns a `ScrollAreaOutput`, so you might need to add `.inner` after the call to it ([#1166](https://github.com/emilk/egui/pull/1166)).
* Replaced `corner_radius: f32` with `rounding: Rounding`, allowing per-corner rounding settings ([#1206](https://github.com/emilk/egui/pull/1206)).
* Replaced Frame's `margin: Vec2` with `margin: Margin`, allowing for different margins on opposing sides ([#1219](https://github.com/emilk/egui/pull/1219)).
* Renamed `Plot::custom_label_func` to `Plot::label_formatter` ([#1235](https://github.com/emilk/egui/pull/1235)).
* `Areas::layer_id_at` ignores non-interatable layers (i.e. Tooltips) ([#1240](https://github.com/emilk/egui/pull/1240)).
* `ScrollArea`s will not shrink below a certain minimum size, set by `min_scrolled_width/min_scrolled_height` ([1255](https://github.com/emilk/egui/pull/1255)).
* For integrations:
* `Output` has now been renamed `PlatformOutput` and `Context::run` now returns the new `FullOutput` ([#1292](https://github.com/emilk/egui/pull/1292)).
* `FontImage` has been replaced by `TexturesDelta` (found in `FullOutput`), describing what textures were loaded and freed each frame ([#1110](https://github.com/emilk/egui/pull/1110)).
* The painter must support partial texture updates ([#1149](https://github.com/emilk/egui/pull/1149)).
* Added `RawInput::max_texture_side` which should be filled in with e.g. `GL_MAX_TEXTURE_SIZE` ([#1154](https://github.com/emilk/egui/pull/1154)).
### Fixed 🐛
* Plot `Orientation` was not public, although fields using this type were ([#1130](https://github.com/emilk/egui/pull/1130)).
* Context menus now respects the theme ([#1043](https://github.com/emilk/egui/pull/1043)).
* Calling `Context::set_pixels_per_point` before the first frame will now work.
* Tooltips that don't fit the window don't flicker anymore ([#1240](https://github.com/emilk/egui/pull/1240)).
* Scroll areas now follow text cursor ([#1252](https://github.com/emilk/egui/pull/1252)).
* Slider: correctly respond with drag and focus events when interacting with the value directly ([1270](https://github.com/emilk/egui/pull/1270)).
### Contributors 🙏
* [4JX](https://github.com/4JX)
* [55nknown](https://github.com/55nknown)
* [AlanRace](https://github.com/AlanRace)
* [AlexxxRu](https://github.com/AlexxxRu)
* [awaken1ng](https://github.com/awaken1ng)
* [BctfN0HUK7Yg](https://github.com/BctfN0HUK7Yg)
* [Bromeon](https://github.com/Bromeon)
* [cat-state](https://github.com/cat)
* [danielkeller](https://github.com/danielkeller)
* [dvec](https://github.com/dvec)
* [Friz64](https://github.com/Friz64)
* [Gordon01](https://github.com/Gordon01)
* [HackerFoo](https://github.com/HackerFoo)
* [juancampa](https://github.com/juancampa)
* [justinj](https://github.com/justinj)
* [lampsitter](https://github.com/lampsitter)
* [LordMZTE](https://github.com/LordMZTE)
* [manuel-i](https://github.com/manuel)
* [Mingun](https://github.com/Mingun)
* [niklaskorz](https://github.com/niklaskorz)
* [nongiach](https://github.com/nongiach)
* [parasyte](https://github.com/parasyte)
* [psiphi75](https://github.com/psiphi75)
* [s-nie](https://github.com/s)
* [t18b219k](https://github.com/t18b219k)
* [terhechte](https://github.com/terhechte)
* [xudesheng](https://github.com/xudesheng)
* [yusdacra](https://github.com/yusdacra)
## 0.16.1 - 2021-12-31 - Add back `CtxRef::begin_frame,end_frame`
### Added ⭐
* Added back `CtxRef::begin_frame,end_frame` as an alternative to `CtxRef::run`.
## 0.16.0 - 2021-12-29 - Context menus and rich text
### Added ⭐
* Added context menus: See `Ui::menu_button` and `Response::context_menu` ([#543](https://github.com/emilk/egui/pull/543)).
* Most widgets containing text (`Label`, `Button` etc) now supports rich text ([#855](https://github.com/emilk/egui/pull/855)).
* Plots:
* Added bar charts and box plots ([#863](https://github.com/emilk/egui/pull/863)).
* You can now query information about the plot (e.g. get the mouse position in plot coordinates, or the plot
bounds) while adding items. `Plot` ([#766](https://github.com/emilk/egui/pull/766) and
[#892](https://github.com/emilk/egui/pull/892)).
* You can now read and write the cursor of a `TextEdit` ([#848](https://github.com/emilk/egui/pull/848)).
* When using a custom font you can now specify a font index ([#873](https://github.com/emilk/egui/pull/873)).
* Added vertical sliders with `Slider::new(…).vertical()` ([#875](https://github.com/emilk/egui/pull/875)).
* Added `Button::image_and_text` ([#832](https://github.com/emilk/egui/pull/832)).
* Added `CollapsingHeader::open` to control if it is open or collapsed ([#1006](https://github.com/emilk/egui/pull/1006)).
* Added `egui::widgets::color_picker::color_picker_color32` to show the color picker.
### Changed 🔧
* MSRV (Minimum Supported Rust Version) is now `1.56.0`.
* `ui.add(Button::new("…").text_color(…))` is now `ui.button(RichText::new("…").color(…))` (same for `Label` )([#855](https://github.com/emilk/egui/pull/855)).
* Plots now provide a `show` method that has to be used to add items to and show the plot ([#766](https://github.com/emilk/egui/pull/766)).
* `menu::menu(ui, ...)` is now `ui.menu_button(...)` ([#543](https://github.com/emilk/egui/pull/543))
* Replaced `CtxRef::begin_frame` and `end_frame` with `CtxRef::run` ([#872](https://github.com/emilk/egui/pull/872)).
* Replaced `scroll_delta` and `zoom_delta` in `RawInput` with `Event::Scroll` and `Event::Zoom`.
* Unified the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)).
* Replaced `Ui::__test` with `egui::__run_test_ui` ([#872](https://github.com/emilk/egui/pull/872)).
### Fixed 🐛
* Fixed `ComboBox` and other popups getting clipped to parent window ([#885](https://github.com/emilk/egui/pull/885)).
* The color picker is now better at keeping the same hue even when saturation goes to zero ([#886](https://github.com/emilk/egui/pull/886)).
### Removed 🔥
* Removed `egui::math` (use `egui::emath` instead).
* Removed `egui::paint` (use `egui::epaint` instead).
### Contributors 🙏
* [5225225](https://github.com/5225225): [#849](https://github.com/emilk/egui/pull/849).
* [aevyrie](https://github.com/aevyrie): [#966](https://github.com/emilk/egui/pull/966).
* [B-Reif](https://github.com/B-Reif): [#875](https://github.com/emilk/egui/pull/875).
* [Bromeon](https://github.com/Bromeon): [#863](https://github.com/emilk/egui/pull/863), [#918](https://github.com/emilk/egui/pull/918).
* [d10sfan](https://github.com/d10sfan): [#832](https://github.com/emilk/egui/pull/832).
* [EmbersArc](https://github.com/EmbersArc): [#766](https://github.com/emilk/egui/pull/766), [#892](https://github.com/emilk/egui/pull/892).
* [Hperigo](https://github.com/Hperigo): [#905](https://github.com/emilk/egui/pull/905).
* [isegal](https://github.com/isegal): [#934](https://github.com/emilk/egui/pull/934).
* [mankinskin](https://github.com/mankinskin): [#543](https://github.com/emilk/egui/pull/543).
* [niladic](https://github.com/niladic): [#499](https://github.com/emilk/egui/pull/499), [#863](https://github.com/emilk/egui/pull/863).
* [singalen](https://github.com/singalen): [#973](https://github.com/emilk/egui/pull/973).
* [sumibi-yakitori](https://github.com/sumibi-yakitori): [#830](https://github.com/emilk/egui/pull/830), [#870](https://github.com/emilk/egui/pull/870).
* [t18b219k](https://github.com/t18b219k): [#868](https://github.com/emilk/egui/pull/868), [#888](https://github.com/emilk/egui/pull/888).
## 0.15.0 - 2021-10-24 - Syntax highlighting and hscroll
<img src="media/egui-0.15-code-editor.gif">
### Added ⭐
* Added horizontal scrolling support to `ScrollArea` and `Window` (opt-in).
* `TextEdit::layouter`: Add custom text layout for e.g. syntax highlighting or WYSIWYG.
* `Fonts::layout_job`: New text layout engine allowing mixing fonts, colors and styles, with underlining and strikethrough.
* Added `ui.add_enabled(bool, widget)` to easily add a possibly disabled widget.
* Added `ui.add_enabled_ui(bool, |ui| …)` to create a possibly disabled UI section.
* Added feature `"serialize"` separatedly from `"persistence"`.
* Added `egui::widgets::global_dark_light_mode_buttons` to easily add buttons for switching the egui theme.
* `TextEdit` can now be used to show text which can be selected and copied, but not edited.
* Added `Memory::caches` for caching things from one frame to the next.
### Changed 🔧
* Change the default monospace font to [Hack](https://github.com/source-foundry/Hack).
* Label text will now be centered, right-aligned and/or justified based on the layout of the `Ui` it is in.
* `Hyperlink` will now word-wrap just like a `Label`.
* All `Ui`s must now have a finite `max_rect`.
* Deprecated: `max_rect_finite`, `available_size_before_wrap_finite` and `available_rect_before_wrap_finite`.
* `Painter`/`Fonts`: text layout now expect a color when creating a `Galley`. You may override that color with `Painter::galley_with_color`.
* MSRV (Minimum Supported Rust Version) is now `1.54.0`.
* By default, `DragValue`s no longer show a tooltip when hovered. Change with `Style::explanation_tooltips`.
* Smaller and nicer color picker.
* `ScrollArea` will auto-shrink to content size unless told otherwise using `ScollArea::auto_shrink`.
* By default, `Slider`'s `clamp_to_range` is set to true.
* Renamed `TextEdit::enabled` to `TextEdit::interactive`.
* `ui.label` (and friends) now take `impl ToString` as argument instead of `impl Into<Label>`.
### Fixed 🐛
* Fixed wrongly sized multiline `TextEdit` in justified layouts.
* Fixed clip rectangle of windows that don't fit the central area.
* Show tooltips above widgets on touch screens.
* Fixed popups sometimes getting clipped by panels.
### Removed 🔥
* Replace `Button::enabled` with `ui.add_enabled`.
### Contributors 🙏
* [AlexApps99](https://github.com/AlexApps99)
* [baysmith](https://github.com/baysmith)
* [bpostlethwaite](https://github.com/bpostlethwaite)
* [cwfitzgerald](https://github.com/cwfitzgerald)
* [DrOptix](https://github.com/DrOptix)
* [JerzySpendel](https://github.com/JerzySpendel)
* [NiceneNerd](https://github.com/NiceneNerd)
* [parasyte](https://github.com/parasyte)
* [spersson](https://github.com/spersson)
* [Stock84-dev](https://github.com/Stock84-dev)
* [sumibi-yakitori](https://github.com/sumibi-yakitori)
* [t18b219k](https://github.com/t18b219k)
* [TobTobXX](https://github.com/TobTobXX)
* [zu1k](https://github.com/zu1k)
## 0.14.2 - 2021-08-28 - Window resize fix
### Fixed 🐛
* Fixed window resize bug introduced in `0.14.1`.
## 0.14.1 - 2021-08-28 - Layout bug fixes
### Added ⭐
* Added `Ui::horizontal_top`.
### Fixed 🐛
* Fixed `set_width/set_min_width/set_height/set_min_height/expand_to_include_x/expand_to_include_y`.
* Make minimum grid column width propagate properly.
* Make sure `TextEdit` contents expand to fill width if applicable.
* `ProgressBar`: add a minimum width and fix for having it in an infinite layout.
* Fixed sometimes not being able to click inside a combo box or popup menu.
## 0.14.0 - 2021-08-24 - Ui panels and bug fixes
### Added ⭐
* Panels can now be added to any `Ui`.
* Plot:
* [Line styles](https://github.com/emilk/egui/pull/482).
* Added `show_background` and `show_axes` methods to `Plot`.
* [Progress bar](https://github.com/emilk/egui/pull/519).
* `Grid::num_columns`: allow the last column to take up the rest of the space of the parent `Ui`.
* Added an API for dropping files into egui (see `RawInput`).
* `CollapsingHeader` can now optionally be selectable.
### Changed 🔧
* A single-line `TextEdit` will now clip text that doesn't fit in it, and scroll.
* Return closure return value from `Area::show`, `ComboBox::show_ui`, `ComboBox::combo_box_with_label`, `Window::show`, `popup::*`, `menu::menu`.
* Only move/resize windows with primary mouse button.
* Tooltips are now moved to not cover the widget they are attached to.
### Fixed 🐛
* Fixed custom font definitions getting replaced when `pixels_per_point` is changed.
* Fixed `lost_focus` for `TextEdit`.
* Clicking the edge of a menu button will now properly open the menu.
* Fixed hover detection close to an `Area`.
* Fixed case where `Plot`'s `min_auto_bounds` could be ignored after the first call to `Plot::ui`.
* Fixed slow startup when using large font files.
### Contributors 🙏
* [barrowsys](https://github.com/barrowsys)
* [EmbersArc](https://github.com/EmbersArc)
* [gents83](https://github.com/gents83 )
* [lucaspoffo](https://github.com/lucaspoffo)
* [mankinskin](https://github.com/mankinskin)
* [mental32](https://github.com/mental32)
* [mitchmindtree](https://github.com/mitchmindtree)
* [parasyte](https://github.com/parasyte)
* [rekka](https://github.com/rekka)
* [zu1k](https://github.com/zu1k)
## 0.13.1 - 2021-06-28 - Plot fixes
### Added ⭐
* Plot: you can now set the stroke of a `HLine/VLine`.
### Changed 🔧
* `Plot::new` now takes an `id_source: impl Hash` instead of a `name: impl ToString`. Functionally it is the same.
## 0.13.0 - 2021-06-24 - Better panels, plots and new visual style
### Added ⭐
* Plot:
* [More plot items: Arrows, Polygons, Text, Images](https://github.com/emilk/egui/pull/471).
* [Plot legend improvements](https://github.com/emilk/egui/pull/410).
* [Line markers for plots](https://github.com/emilk/egui/pull/363).
* Panels:
* Added right and bottom panels (`SidePanel::right` and `Panel::bottom`).
* Panels can now be resized.
* Added an option to overwrite frame of a `Panel`.
* [Improve accessibility / screen reader](https://github.com/emilk/egui/pull/412).
* Added `ScrollArea::show_rows` for efficient scrolling of huge UI:s.
* Added `ScrollArea::enable_scrolling` to allow freezing scrolling when editing TextEdit widgets within it
* Added `Ui::set_visible` as a way to hide widgets.
* Added `Style::override_text_style` to easily change the text style of everything in a `Ui` (or globally).
* You can now change `TextStyle` on checkboxes, radio buttons and `SelectableLabel`.
* Added support for [cint](https://crates.io/crates/cint) under `cint` feature.
* Added features `extra_asserts` and `extra_debug_asserts` to enable additional checks.
* `TextEdit` now supports edits on a generic buffer using `TextBuffer`.
* Added `Context::set_debug_on_hover` and `egui::trace!(ui)`
### Changed 🔧
* Minimum Rust version is now 1.51 (used to be 1.52)
* [Tweaked the default visuals style](https://github.com/emilk/egui/pull/450).
* Plot: Renamed `Curve` to `Line`.
* `TopPanel::top` is now `TopBottomPanel::top`.
* `SidePanel::left` no longet takes the default width by argument, but by a builder call.
* `SidePanel::left` is resizable by default.
### Fixed 🐛
* Fixed uneven lettering on non-integral device scales ("extortion lettering").
* Fixed invisible scroll bar when native window is too narrow for egui.
## 0.12.0 - 2021-05-10 - Multitouch, user memory, window pivots, and improved plots
### Added ⭐
* Added anchors to windows and areas so you can put a window in e.g. the top right corner.
* Make labels interactive with `Label::sense(Sense::click())`.
* Added `Response::request_focus` and `Response::surrender_focus`.
* Added `TextEdit::code_editor` (VERY basic).
* [Pan and zoom plots](https://github.com/emilk/egui/pull/317).
* [Add plot legends](https://github.com/emilk/egui/pull/349).
* [Users can now store custom state in `egui::Memory`](https://github.com/emilk/egui/pull/257).
* Added `Response::on_disabled_hover_text` to show tooltip for disabled widgets.
* Zoom input: ctrl-scroll and (on `eframe` web) trackpad-pinch gesture.
* Support for raw [multi touch](https://github.com/emilk/egui/pull/306) events,
enabling zoom, rotate, and more. Works with `eframe` web on mobile devices,
and should work with `egui_glium` for certain touch devices/screens.
* Added (optional) compatibility with [mint](https://docs.rs/mint).
### Changed 🔧
* Make `Memory::has_focus` public (again).
* `Plot` must now be given a name that is unique within its scope.
* Tab only selects labels if the `screen_reader` option is turned on.
* Renamed `ui.wrap` to `ui.scope`.
### Fixed 🐛
* Fixed [defocus-bug on touch screens](https://github.com/emilk/egui/issues/288).
* Fixed bug with the layout of wide `DragValue`s.
### Removed 🔥
* Moved experimental markup language to `egui_demo_lib`
## 0.11.0 - 2021-04-05 - Optimization, screen reader & new layout logic
### Added ⭐
* You can now give focus to any clickable widget with tab/shift-tab.
* Use space or enter to click the selected widget.
* Use arrow keys to adjust sliders and `DragValue`s.
* egui will now output events when widgets gain keyboard focus.
* This can be hooked up to a screen reader to aid the visually impaired
* Added the option to restrict the dragging bounds of `Window` and `Area` to a specified area using `drag_bounds(rect)`.
* Added support for small and raised text.
* Added `ui.set_row_height`.
* Added `DebugOptions::show_widgets` to debug layouting by hovering widgets.
* Added `ComboBox` to more easily customize combo boxes.
* Added `Slider::new` and `DragValue::new` to replace old type-specific constructors.
* Added `TextEdit::password` to hide input characters.
### Changed 🔧
* `ui.advance_cursor` is now called `ui.add_space`.
* `kb_focus` is now just called `focus`.
### Fixed 🐛
* Fixed some bugs related to centered layouts.
* Fixed secondary-click to open a menu.
* [Fix panic for zero-range sliders and zero-speed drag values](https://github.com/emilk/egui/pull/216).
* Fixed false id clash error for wrapping text.
* Fixed bug that would close a popup (e.g. the color picker) when clicking inside of it.
### Deprecated ☢️
* Deprectated `combo_box_with_label` in favor of new `ComboBox`.
* Deprectated type-specific constructors for `Slider` and `DragValue` (`Slider::f32`, `DragValue::usize` etc).
## 0.10.0 - 2021-02-28 - Plot and polish
<img src="media/egui-0.10-plot.gif" width="50%">
### Added ⭐
* Added `egui::plot::Plot` to plot some 2D data.
* Added `Ui::hyperlink_to(label, url)`.
* Sliders can now have a value prefix and suffix (e.g. the suffix `"°"` works like a unit).
* `Context::set_pixels_per_point` to control the scale of the UI.
* Added `Response::changed()` to query if e.g. a slider was dragged, text was entered or a checkbox was clicked.
* Added support for all integers in `DragValue` and `Slider` (except 128-bit).
### Changed 🔧
* Improve the positioning of tooltips.
* Only show tooltips if mouse is still.
* `Slider` will now show the value display by default, unless turned off with `.show_value(false)`.
* The `Slider` value is now a `DragValue` which when dragged can pick values outside of the slider range (unless `clamp_to_range` is set).
## 0.9.0 - 2021-02-07 - Light Mode and much more
<img src="media/0.9.0-disabled.gif" width="50%">
* Add support for secondary and middle mouse buttons.
* Add `Label` methods for code, strong, strikethrough, underline and italics.
* Add `ui.group(|ui| { … })` to visually group some widgets within a frame.
* Add `Ui` helpers for doing manual layout (`ui.put`, `ui.allocate_ui_at_rect` and more).
* Add `ui.set_enabled(false)` to disable all widgets in a `Ui` (grayed out and non-interactive).
* Add `TextEdit::hint_text` for showing a weak hint text when empty.
### Added ⭐
* Added support for secondary and middle mouse buttons.
* Added `Label` methods for code, strong, strikethrough, underline and italics.
* Added `ui.group(|ui| { … })` to visually group some widgets within a frame.
* Added `Ui` helpers for doing manual layout (`ui.put`, `ui.allocate_ui_at_rect` and more).
* Added `ui.set_enabled(false)` to disable all widgets in a `Ui` (grayed out and non-interactive).
* Added `TextEdit::hint_text` for showing a weak hint text when empty.
* `egui::popup::popup_below_widget`: show a popup area below another widget.
* Add `Slider::clamp_to_range(bool)`: if set, clamp the incoming and outgoing values to the slider range.
* Added `Slider::clamp_to_range(bool)`: if set, clamp the incoming and outgoing values to the slider range.
* Add: `ui.spacing()`, `ui.spacing_mut()`, `ui.visuals()`, `ui.visuals_mut()`.
* Add: `ctx.set_visuals()`.
* You can now control text wrapping with `Style::wrap`.
* Add `Grid::max_col_width`.
* Added `Grid::max_col_width`.
### Changed 🔧
* Text will now wrap at newlines, spaces, dashes, punctuation or in the middle of a words if necessary, in that order of priority.
* Widgets will now always line break at `\n` characters.
* Widgets will now more intelligently choose wether or not to wrap text.
* Widgets will now more intelligently choose whether or not to wrap text.
* `mouse` has been renamed `pointer` everywhere (to make it clear it includes touches too).
* Most parts of `Response` are now methods, so `if ui.button("…").clicked {` is now `if ui.button("…").clicked() {`.
* `Response::active` is now gone. You can use `response.dragged()` or `response.clicked()` instead.
* Backend: pointer (mouse/touch) position and buttons are now passed to egui in the event stream.
* `DragValue::range` is now called `clamp_range` and also clamps incoming values.
* Renamed `Triangles` to `Mesh`.
* The tesselator now wraps the clip rectangle and mesh in `struct ClippedMesh(Rect, Mesh)`.
* The tessellator now wraps the clip rectangle and mesh in `struct ClippedMesh(Rect, Mesh)`.
* `Mesh::split_to_u16` now returns a 16-bit indexed `Mesh16`.
### Fixed 🐛
* It is now possible to click widgets even when FPS is very low.
* Tessellator: handle sharp path corners better (switch to bevel instead of miter joints for > 90°).
@ -54,22 +661,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
<img src="media/widget_gallery_0.8.0.gif" width="50%">
### Added ⭐
* Added a simple grid layout (`Grid`).
* Added `ui.allocate_at_least` and `ui.allocate_exact_size`.
* Added function `InputState::key_down`.
* Added `Window::current_pos` to position a window.
### Changed 🔧
* New simpler and sleeker look!
* Rename `PaintCmd` to `Shape`.
* Renamed `PaintCmd` to `Shape`.
* Replace tuple `(Rect, Shape)` with tuple-struct `ClippedShape`.
* Rename feature `"serde"` to `"persistence"`.
* Renamed feature `"serde"` to `"persistence"`.
* Break out the modules `math` and `paint` into separate crates `emath` and `epaint`.
### Fixed 🐛
* Fixed a bug that would sometimes trigger a "Mismatching panels" panic in debug builds.
* `Image` and `ImageButton` will no longer stretch to fill a justified layout.
@ -77,45 +681,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 0.7.0 - 2021-01-04
### Added ⭐
* Add `ui.scroll_to_cursor` and `response.scroll_to_me` ([#81](https://github.com/emilk/egui/pull/81) by [lucaspoffo](https://github.com/lucaspoffo)).
* Add `window.id(…)` and `area.id(…)` for overriding the default `Id`.
* Added `ui.scroll_to_cursor` and `response.scroll_to_me` ([#81](https://github.com/emilk/egui/pull/81) by [lucaspoffo](https://github.com/lucaspoffo)).
* Added `window.id(…)` and `area.id(…)` for overriding the default `Id`.
### Changed 🔧
* Renamed `Srgba` to `Color32`.
* All color contructions now starts with `from_`, e.g. `Color32::from_rgb`.
* Renamed `FontFamily::VariableWidth` to `FontFamily::Proportional`.
* Removed `pixels_per_point` from `FontDefinitions`.
### Fixed 🐛
* `RepaintSignal` now implements `Sync` so it can be sent to a background thread.
* `TextEdit` widgets are now slightly larger to accommodate their frames.
### Deprecated
### Deprecated ☢️
* Deprecated `color::srgba`.
## 0.6.0 - 2020-12-26
### Added ⭐
* Turn off `Window` title bars with `window.title_bar(false)`.
* `ImageButton` - `ui.add(ImageButton::new(...))`.
* `ImageButton` - `ui.add(ImageButton::new())`.
* `ui.vertical_centered` and `ui.vertical_centered_justified`.
* `ui.allocate_painter` helper.
* Mouse-over explanation to duplicate ID warning.
* You can now easily constrain egui to a portion of the screen using `RawInput::screen_rect`.
* You can now control the minimum and maixumum number of decimals to show in a `Slider` or `DragValue`.
* Add `egui::math::Rot2`: rotation helper.
* Added `egui::math::Rot2`: rotation helper.
* `Response` now contains the `Id` of the widget it pertains to.
* `ui.allocate_response` that allocates space and checks for interactions.
* Add `response.interact(sense)`, e.g. to check for clicks on labels.
* Added `response.interact(sense)`, e.g. to check for clicks on labels.
### Changed 🔧
* `ui.allocate_space` now returns an `(Id, Rect)` tuple.
* `Arc<Context>` has been replaced with `CtxRef` everywhere.
* Slight tweak of the default `Style` and font sizes.
@ -128,15 +726,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Combo boxes has scroll bars when needed.
* Expand `Window` + `Resize` containers to be large enough for last frames content
* `ui.columns`: Columns now defaults to justified top-to-down layouts.
* Rename `Sense::nothing()` to `Sense::hover()`.
* Renamed `Sense::nothing()` to `Sense::hover()`.
* Replaced `parking_lot` dependency with `atomic_refcell` by default.
### Fixed 🐛
* The background for `CentralPanel` will now cover unused space too.
* `ui.columns`: Improve allocated size estimation.
### Deprecated
### Deprecated ☢️
* `RawInput::screen_size` - use `RawInput::screen_rect` instead.
* left/centered/right column functions on `Ui`.
* `ui.interact_hover` and `ui.hovered`.
@ -145,21 +742,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 0.5.0 - 2020-12-13
### Added ⭐
* Emoji support: 1216 different emojis that work in any text.
* The Demo app comes with a Font Book to explore the available glyphs.
* `ui.horizontal_wrapped(|ui| ...)`: Add widgets on a row but wrap at `max_size`.
* `ui.horizontal_wrapped(|ui| )`: Add widgets on a row but wrap at `max_size`.
* `ui.horizontal_wrapped_for_text`: Like `ui.horizontal_wrapped`, but with spacing made for embedding text.
* `ui.horizontal_for_text`: Like `ui.horizontal`, but with spacing made for embedding text.
* `egui::Layout` now supports justified layouts where contents is _also_ centered, right-aligned, etc.
* `ui.allocate_ui(size, |ui| ...)`: Easily create a child-`Ui` of a given size.
* `ui.allocate_ui(size, |ui| )`: Easily create a child-`Ui` of a given size.
* `SelectableLabel` (`ui.selectable_label` and `ui.selectable_value`): A text-button that can be selected.
* `ui.small_button`: A smaller button that looks good embedded in text.
* `ui.drag_angle_tau`: For those who want to specify angles as fractions of τ (a full turn).
* Add `Resize::id_source` and `ScrollArea::id_source` to let the user avoid Id clashes.
* Added `Resize::id_source` and `ScrollArea::id_source` to let the user avoid Id clashes.
### Changed 🔧
* New default font: [Ubuntu-Light](https://fonts.google.com/specimen/Ubuntu).
* Make it simpler to override fonts in `FontDefinitions`.
* Remove minimum button width.
@ -175,17 +770,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 0.4.0 - 2020-11-28
### Added ⭐
* `TextEdit` improvements:
* Much improved text editing, with better navigation and selection.
* Move focus between `TextEdit` widgets with tab and shift-tab.
* Undo edtis in a `TextEdit`.
* You can now check if a `TextEdit` lost keyboard focus with `response.lost_kb_focus`.
* You can now check if a `TextEdit` lost keyboard focus with `response.lost_focus`.
* Added `ui.text_edit_singleline` and `ui.text_edit_multiline`.
* You can now debug why your `Ui` is unexpectedly wide with `ui.style_mut().visuals.debug_expand_width = true;`
* You can now debug why your `Ui` is unexpectedly wide with `ui.style_mut().debug.show_expand_width = true;`
### Changed 🔧
* Pressing enter in a single-line `TextEdit` will now surrender keyboard focus for it.
* You must now be explicit when creating a `TextEdit` if you want it to be singeline or multiline.
* Improved automatic `Id` generation, making `Id` clashes less likely.
@ -194,23 +787,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Fixed incorrect text wrapping width on radio buttons
### Fixed 🐛
* Fixed bug where a lost widget could still retain keyboard focus.
## 0.3.0 - 2020-11-07
### Added ⭐
* Panels: you can now create panels using `SidePanel`, `TopPanel` and `CentralPanel`.
* You can now override the default egui fonts.
* Add ability to override text color with `visuals.override_text_color`.
* Added ability to override text color with `visuals.override_text_color`.
* The demo now includes a simple drag-and-drop example.
* The demo app now has a slider to scale all of egui.
### Changed 🔧
* `ui.horizontal(...)` etc returns `Response`.
* `ui.horizontal(…)` etc returns `Response`.
* Refactored the interface for `egui::app::App`.
* Windows are now constrained to the screen.
* `Context::begin_frame()` no longer returns a `Ui`. Instead put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
@ -222,8 +812,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* You can no longer throw windows.
### Fixed 🐛
* Fix a bug where some regions would slowly grow for non-integral scales (`pixels_per_point`).
* Fixed a bug where some regions would slowly grow for non-integral scales (`pixels_per_point`).
## 0.2.0 - 2020-10-10
@ -235,6 +824,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Optimization: coarse culling in the tessellator
* CHANGED: switch argument order of `ui.checkbox` and `ui.radio`
## 0.1.4 - 2020-09-08
This is when I started the CHANGELOG.md, after almost two years of development. Better late than never.
@ -246,3 +836,13 @@ This is when I started the CHANGELOG.md, after almost two years of development.
* Regions: resizing, vertical scrolling, collapsing headers (sections)
* Rendering: Anti-aliased rendering of lines, circles, text and convex polygons.
* Tooltips on hover
## Earlier:
* 2020-08-10: renamed the project to "egui"
* 2020-05-30: first release on crates.io (0.1.0)
* 2020-04-01: serious work starts (pandemic project)
* 2019-03-12: gave a talk about what would later become egui: https://www.youtube.com/watch?v=-pmwLHw5Gbs
* 2018-12-23: [initial commit](https://github.com/emilk/egui/commit/856bbf4dae4a69693a0324da34e8b0dd3754dfdf)
* 2018-11-04: started tinkering on a train

133
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,133 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[the egui discord](https://discord.gg/JFcEma9bJq).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View file

@ -1,26 +1,102 @@
# Contributing guidelines
You are welcome to contribute to egui via discussions, issues, pull requests and by publishing egui integrations.
## Introduction
`egui` has been an on-and-off weekend project of mine since late 2018. I am grateful to any help I can get, but bare in mind that sometimes I can be slow to respond because I am busy with other things!
/ Emil
## Discussion
You can ask questions, share screenshots and more at GitHub Discussions: https://github.com/emilk/egui/discussions
You can ask questions, share screenshots and more at [GitHub Discussions](https://github.com/emilk/egui/discussions).
There is an `egui` discord at <https://discord.gg/vbuv9Xan65>.
There is also an egui channel on the Embark discord at <https://discord.gg/vY8ZGS292W> (NOTE: I work at [Embark](https://www.embark-studios.com/), but egui is my hobby project).
## Filing an issue
Issues are for bug reports and feature requests. Issues are not for asking questions (use [Discussions](https://github.com/emilk/egui/discussions) for that).
[Issues](https://github.com/emilk/egui/issues) are for bug reports and feature requests. Issues are not for asking questions (use [Discussions](https://github.com/emilk/egui/discussions) or [Discord](https://discord.gg/vbuv9Xan65) for that).
Always make sure there is not already a similar issue to the one you are creating.
If you are filing a bug, please provide a way to reproduce it.
## Making a PR
Always file an issue (or find an existing one) and get feedback before you start working on a non-trivial pull request!
First file an issue (or find an existing one) and announce that you plan to work on something. That way we will avoid having several people doing double work. Please ask for feedback before you start working on something non-trivial!
Browse through [`ARCHITECTURE.md`](https://github.com/emilk/egui/blob/master/ARCHITECTURE.md) to get a sense of how all pieces connects.
You can test your code locally by running `./sh/check.sh`.
When you have something that works, open a draft PR. You may get some helpful feedback early!
When you feel the PR is ready to go, do a self-review of the code, and then open it for review.
Please keep pull requests small and focused.
Don't worry about having many small commits in the PR - they will be squashed to one commit once merged.
Do not include the `.js` and `.wasm` build artifacts generated for building for web.
`git` is not great at storing large files like these, so we only commit a new web demo after a new egui release.
## Creating an integration for egui
If you make an integration for egui for some engine or renderer, please share it with the world!
I will add a link to it from the egui README.md so others can easily find it.
If you make an integration for `egui` for some engine or renderer, please share it with the world!
I will add a link to it from the `egui` README.md so others can easily find it.
Read the section on integrations at <https://github.com/emilk/egui#integrations>.
## Code Conventions
Conventions unless otherwise specified:
* angles are in radians
* `Vec2::X` is right and `Vec2::Y` is down.
* `Pos2::ZERO` is left top.
While using an immediate mode gui is simple, implementing one is a lot more tricky. There are many subtle corner-case you need to think through. The `egui` source code is a bit messy, partially because it is still evolving.
* Read some code before writing your own.
* Follow the `egui` code style.
* Add blank lines around all `fn`, `struct`, `enum`, etc.
* `// Comment like this.` and not `//like this`.
* Use `TODO` instead of `FIXME`.
* Add your github handle to the `TODO`:s you write, e.g: `TODO(emilk): clean this up`.
* Write idiomatic rust.
* Avoid `unsafe`.
* Avoid code that can cause panics.
* Use good names for everything.
* Add docstrings to types, `struct` fields and all `pub fn`.
* Add some example code (doc-tests).
* Before making a function longer, consider adding a helper function.
* If you are only using it in one function, put the `use` statement in that function. This improves locality, making it easier to read and move the code.
* When importing a `trait` to use it's trait methods, do this: `use Trait as _;`. That lets the reader know why you imported it, even though it seems unused.
* Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/).
* Break the above rules when it makes sense.
### Good:
``` rust
/// The name of the thing.
fn name(&self) -> &str {
&self.name
}
fn foo(&self) {
// TODO(emilk): implement
}
```
### Bad:
``` rust
//some function
fn get_name(&self) -> &str {
&self.name
}
fn foo(&self) {
//FIXME: implement
}
```

4000
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,19 @@
[workspace]
resolver = "2"
members = [
"egui_demo_app",
"egui_demo_lib",
"egui_glium",
"egui_web",
"egui",
"emath",
"epaint",
"epi",
]
"crates/ecolor",
"crates/egui_demo_app",
"crates/egui_demo_lib",
"crates/egui_extras",
"crates/egui_glow",
"crates/egui-wgpu",
"crates/egui-winit",
"crates/egui",
"crates/emath",
"crates/epaint",
[patch.crates-io]
egui = { path = 'egui' }
egui_glium = { path = 'egui_glium' }
egui_web = { path = 'egui_web' }
"examples/*",
]
[profile.release]
# lto = true # VERY slightly smaller wasm
@ -21,3 +21,15 @@ egui_web = { path = 'egui_web' }
# opt-level = 1 # very slow and big wasm. Don't do this.
opt-level = 2 # fast and small wasm, basically same as `opt-level = 's'`
# opt-level = 3 # unecessarily large wasm for no performance gain
# debug = true # include debug symbols, useful when profiling wasm
[profile.dev]
# Can't leave this on by default, because it breaks the Windows build. Related: https://github.com/rust-lang/cargo/issues/4897
# split-debuginfo = "unpacked" # faster debug builds on mac
# opt-level = 1 # Make debug builds run faster
# Optimize all dependencies even in debug builds (does not affect workspace packages):
[profile.dev.package."*"]
opt-level = 2

124
Cranky.toml Normal file
View file

@ -0,0 +1,124 @@
# https://github.com/ericseppanen/cargo-cranky
# cargo install cargo-cranky && cargo cranky
deny = ["unsafe_code"]
warn = [
"clippy::all",
"clippy::await_holding_lock",
"clippy::bool_to_int_with_if",
"clippy::char_lit_as_u8",
"clippy::checked_conversions",
"clippy::cloned_instead_of_copied",
"clippy::dbg_macro",
"clippy::debug_assert_with_mut_call",
"clippy::derive_partial_eq_without_eq",
"clippy::disallowed_methods",
"clippy::disallowed_script_idents",
"clippy::doc_link_with_quotes",
"clippy::doc_markdown",
"clippy::empty_enum",
"clippy::enum_glob_use",
"clippy::equatable_if_let",
"clippy::exit",
"clippy::expl_impl_clone_on_copy",
"clippy::explicit_deref_methods",
"clippy::explicit_into_iter_loop",
"clippy::explicit_iter_loop",
"clippy::fallible_impl_from",
"clippy::filter_map_next",
"clippy::flat_map_option",
"clippy::float_cmp_const",
"clippy::fn_params_excessive_bools",
"clippy::fn_to_numeric_cast_any",
"clippy::from_iter_instead_of_collect",
"clippy::if_let_mutex",
"clippy::implicit_clone",
"clippy::imprecise_flops",
"clippy::index_refutable_slice",
"clippy::inefficient_to_string",
"clippy::invalid_upcast_comparisons",
"clippy::iter_not_returning_iterator",
"clippy::iter_on_empty_collections",
"clippy::iter_on_single_items",
"clippy::large_digit_groups",
"clippy::large_stack_arrays",
"clippy::large_types_passed_by_value",
"clippy::let_unit_value",
"clippy::linkedlist",
"clippy::lossy_float_literal",
"clippy::macro_use_imports",
"clippy::manual_assert",
"clippy::manual_instant_elapsed",
"clippy::manual_ok_or",
"clippy::manual_string_new",
"clippy::map_err_ignore",
"clippy::map_flatten",
"clippy::map_unwrap_or",
"clippy::match_on_vec_items",
"clippy::match_same_arms",
"clippy::match_wild_err_arm",
"clippy::match_wildcard_for_single_variants",
"clippy::mem_forget",
"clippy::mismatched_target_os",
"clippy::mismatching_type_param_order",
"clippy::missing_enforced_import_renames",
"clippy::missing_errors_doc",
"clippy::missing_safety_doc",
"clippy::mut_mut",
"clippy::mutex_integer",
"clippy::needless_borrow",
"clippy::needless_continue",
"clippy::needless_for_each",
"clippy::needless_pass_by_value",
"clippy::negative_feature_names",
"clippy::nonstandard_macro_braces",
"clippy::option_option",
"clippy::path_buf_push_overwrite",
"clippy::ptr_as_ptr",
"clippy::rc_mutex",
"clippy::ref_option_ref",
"clippy::rest_pat_in_fully_bound_structs",
"clippy::same_functions_in_if_condition",
"clippy::semicolon_if_nothing_returned",
"clippy::single_match_else",
"clippy::str_to_string",
"clippy::string_add_assign",
"clippy::string_add",
"clippy::string_lit_as_bytes",
"clippy::string_to_string",
"clippy::todo",
"clippy::trailing_empty_array",
"clippy::trait_duplication_in_bounds",
"clippy::unimplemented",
"clippy::unnecessary_wraps",
"clippy::unnested_or_patterns",
"clippy::unused_peekable",
"clippy::unused_rounding",
"clippy::unused_self",
"clippy::useless_transmute",
"clippy::verbose_file_reads",
"clippy::zero_sized_map_values",
"elided_lifetimes_in_paths",
"future_incompatible",
"nonstandard_style",
"rust_2018_idioms",
"rust_2021_prelude_collisions",
"rustdoc::missing_crate_level_docs",
"semicolon_in_expressions_from_macros",
"trivial_numeric_casts",
"unused_extern_crates",
"unused_import_braces",
"unused_lifetimes",
]
allow = [
"clippy::manual_range_contains", # This one is just annoying
# Some of these we should try to put in "warn":
"clippy::type_complexity",
"clippy::undocumented_unsafe_blocks",
"trivial_casts",
"unsafe_op_in_unsafe_fn", # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668
"unused_qualifications",
]

View file

@ -1,4 +1,4 @@
Copyright (c) 2021 Emil Ernerfeldt
Copyright (c) 2018-2021 Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated

334
README.md
View file

@ -1,52 +1,39 @@
# 🖌 egui: an easy-to-use GUI in pure Rust
[<img alt="github" src="https://img.shields.io/badge/github-emilk/egui-8da0cb?logo=github" height="20">](https://github.com/emilk/egui)
[![Latest version](https://img.shields.io/crates/v/egui.svg)](https://crates.io/crates/egui)
[![Documentation](https://docs.rs/egui/badge.svg)](https://docs.rs/egui)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
[![Build Status](https://github.com/emilk/egui/workflows/CI/badge.svg)](https://github.com/emilk/egui/actions?workflow=CI)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
**dependencies**: [`rusttype`](https://crates.io/crates/rusttype) [`atomic_refcell`](https://crates.io/crates/atomic_refcell) [`ahash`](https://crates.io/crates/ahash)
[![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/emilk/egui/blob/master/LICENSE-MIT)
[![Apache](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/emilk/egui/blob/master/LICENSE-APACHE)
[![Discord](https://img.shields.io/discord/900275882684477440?label=egui%20discord)](https://discord.gg/JFcEma9bJq)
👉 [Click to run the web demo](https://www.egui.rs/#demo) 👈
egui is a simple, fast, and highly portable immediate mode GUI library for Rust. egui runs on the web, natively, and in your favorite game engine (or will soon).
egui (pronounced "e-gooey") is a simple, fast, and highly portable immediate mode GUI library for Rust. egui runs on the web, natively, and [in your favorite game engine](#integrations) (or will soon).
egui aims to be the easiest-to-use Rust GUI libary, and the simplest way to make a web app in Rust.
egui aims to be the easiest-to-use Rust GUI library, and the simplest way to make a web app in Rust.
egui can be used anywhere you can draw textured triangles, which means you can easily integrate it into your game engine of choice.
Sections:
* [Example](#example)
* [Quick start](#quick-start)
* [Demo](#demo)
* [Goals](#goals)
* [Who is egui for?](#who-is-egui-for)
* [State / features](#state)
* [How it works](#how-it-works)
* [Integrations](#integrations)
* [Why immediate mode](#why-immediate-mode)
* [FAQ](#faq)
* [Other](#other)
* [Credits](#credits)
## Quick start
([egui 的中文翻译文档 / chinese translation](https://github.com/Re-Ch-Love/egui-doc-cn/blob/main/README_zh-hans.md))
If you just want to write a GUI application in Rust (for the web or for native), go to <https://github.com/emilk/egui_template/> and follow the instructions there!
If you want to integrate egui into an existing engine, go to the [Integrations](#integrations) section.
If you have questions, use [Discussions](https://github.com/emilk/egui/discussions). If you want to contribute to egui, please read the [Contributing Guidelines](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
## Demo
[Click to run egui web demo](https://emilk.github.io/egui/index.html) (works in any browser with WASM and WebGL support).
To test the demo app locally, run `cargo run --release -p egui_demo_app`.
The native backend is currently using [`glium`](https://github.com/glium/glium) ([though there are plans to change that](https://github.com/emilk/egui/issues/93)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
`sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev`
**NOTE**: egui itself is completely platform agnostic.
### Example
## Example
``` rust
ui.heading("My egui Application");
@ -54,18 +41,42 @@ ui.horizontal(|ui| {
ui.label("Your name: ");
ui.text_edit_singleline(&mut name);
});
ui.add(egui::Slider::u32(&mut age, 0..=120).text("age"));
ui.add(egui::Slider::new(&mut age, 0..=120).text("age"));
if ui.button("Click each year").clicked() {
age += 1;
}
ui.label(format!("Hello '{}', age {}", name, age));
```
<img src="media/demo-2021-01-17.gif">
<img src="media/demo.gif">
## Quick start
There are simple examples in [the `examples/` folder](https://github.com/emilk/egui/blob/master/examples/). If you want to write a web app, then go to <https://github.com/emilk/eframe_template/> and follow the instructions. The official docs are at <https://docs.rs/egui>. For inspiration and more examples, check out the [the egui web demo](https://www.egui.rs/#demo) and follow the links in it to its source code.
If you want to integrate egui into an existing engine, go to the [Integrations](#integrations) section.
If you have questions, use [GitHub Discussions](https://github.com/emilk/egui/discussions). There is also [an egui discord server](https://discord.gg/JFcEma9bJq). If you want to contribute to egui, please read the [Contributing Guidelines](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md).
## Demo
[Click to run egui web demo](https://www.egui.rs/#demo) (works in any browser with WASM and WebGL support). Uses [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe).
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 libxkbcommon-dev libssl-dev`
On Fedora Rawhide you need to run:
`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!
## Goals
* The easiest to use GUI libary
* The easiest to use GUI library
* Responsive: target 60 Hz in debug build
* Friendly: difficult to make mistakes, and shouldn't panic
* Portable: the same code works on the web and as a native app
@ -73,10 +84,10 @@ ui.label(format!("Hello '{}', age {}", name, age));
* A simple 2D graphics API for custom painting ([`epaint`](https://docs.rs/epaint)).
* No callbacks
* Pure immediate mode
* Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs)
* Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/demo/toggle_switch.rs)
* Modular: You should be able to use small parts of egui and combine them in new ways
* Safe: there is no `unsafe` code in egui
* Minimal dependencies: [`rusttype`](https://crates.io/crates/rusttype), [`atomic_refcell`](https://crates.io/crates/atomic_refcell) and [`ahash`](https://crates.io/crates/ahash).
* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`nohash-hasher`](https://crates.io/crates/nohash-hasher) [`parking_lot`](https://crates.io/crates/parking_lot)
egui is *not* a framework. egui is a library you call into, not an environment you program for.
@ -84,7 +95,7 @@ egui is *not* a framework. egui is a library you call into, not an environment y
### Non-goals
* Become the most powerful GUI libary
* Become the most powerful GUI library
* Native looking interface
* Advanced and flexible layouts (that's fundamentally incompatible with immediate mode)
@ -98,12 +109,12 @@ But if you are writing something interactive in Rust that needs a simple GUI, eg
### egui vs Dear ImGui
The obvious alternative to egui is [`imgui-rs`](https://github.com/Gekkio/imgui-rs), the Rust wrapper around the C++ library [Dear ImGui](https://github.com/ocornut/imgui). Dear ImGui is a great library, which a lot more features and polish compared to egui. However, egui provides some benefits for Rust users:
The obvious alternative to egui is [`imgui-rs`](https://github.com/Gekkio/imgui-rs), the Rust wrapper around the C++ library [Dear ImGui](https://github.com/ocornut/imgui). Dear ImGui is a great library (and the main inspiration for egui), with a lot more features and polish. However, egui provides some benefits for Rust users:
* egui is pure Rust
* egui is easily compiled to WASM
* egui lets you use native Rust String types (`imgui-rs` forces you to use annoying macros and wrappers for zero-terminated strings)
* [Writing your own widgets in egui is simple](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs)
* egui lets you use native Rust string types (`imgui-rs` forces you to use annoying macros and wrappers for zero-terminated strings)
* [Writing your own widgets in egui is simple](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/demo/toggle_switch.rs)
egui also tries to improve your experience in other small ways:
@ -131,66 +142,89 @@ egui is in active development. It works well for what it does, but it lacks many
* Tooltips on hover
* More
<img src="media/widget_gallery_0.8.0.gif" width="50%">
<img src="media/widget_gallery.gif" width="50%">
## How it works
Light Theme:
Loop:
* Gather input (mouse, touches, keyboard, screen size, etc) and give it to egui
* Run application code (Immediate Mode GUI)
* Tell egui to tessellate the frame graphics to a triangle mesh
* Render the triangle mesh with your favorite graphics API (see [OpenGL example](https://github.com/emilk/egui/blob/master/egui_glium/src/painter.rs))
<img src="media/light_theme.png" width="50%">
## Integrations
egui is build to be easy to integrate into any existing game engine or platform you are working on.
egui is built to be easy to integrate into any existing game engine or platform you are working on.
egui itself doesn't know or care on what OS it is running or how to render things to the screen - that is the job of the egui integration.
The integration needs to do two things:
* **IO**: Supply egui with input (mouse position, keyboard presses, ...) and handle egui output (cursor changes, copy-paste integration, ...).
* **Painting**: Render the textured triangles that egui outputs.
An integration needs to do the following each frame:
### Official
* **Input**: Gather input (mouse, touches, keyboard, screen size, etc) and give it to egui
* Run the application code
* **Output**: Handle egui output (cursor changes, paste, texture allocations, …)
* **Painting**: Render the triangle mesh egui produces (see [OpenGL example](https://github.com/emilk/egui/blob/master/crates/egui_glow/src/painter.rs))
I maintain two official egui integrations:
### Official integrations
* [egui_web](https://crates.io/crates/egui_web) for making a web app. Compiles to WASM, renders with WebGL. [Click to run the egui demo](https://emilk.github.io/egui/index.html).
* [egui_glium](https://crates.io/crates/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium).
These are the official egui integrations:
The same code can be compiled to a native app or a web app.
* [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) for compiling the same app to web/wasm and desktop/native. Uses `egui-winit` and `egui_glow` or `egui-wgpu`.
* [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering egui with [glow](https://github.com/grovesNL/glow) on native and web, and for making native apps.
* [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API).
* [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit).
* [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium) (DEPRECATED - looking for new maintainer).
### 3rd party
### 3rd party integrations
* [`bevy_egui`](https://github.com/mvlabat/bevy_egui) egui bindings for [the Bevy game engine](https://bevyengine.org/).
* [`wgpu`](https://crates.io/crates/wgpu) WebGPU API wrapper:
* [egui_wgpu_backend](https://crates.io/crates/egui_wgpu_backend) with [example code](https://github.com/hasenbanck/egui_example)
* Alternative: [egui_winit_wgpu](https://github.com/Gonkalbell/egui_winit_wgpu) (not available to crates.io)
* [egui_sdl2_gl](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2)
* [emigui-miniquad](https://github.com/not-fl3/emigui-miniquad): backend for [Miniquad](https://github.com/not-fl3/miniquad). [Web demo](https://not-fl3.github.io/miniquad-samples/emigui.html) and [demo source](https://github.com/not-fl3/good-web-game/blob/master/examples/emigui.rs).
* [egui_winit_platform](https://github.com/hasenbanck/egui_winit_platform) provides bindings between [winit](https://crates.io/crates/winit) and egui. It only provides the first half of an egui integration (IO). Painting can be done with e.g. [egui_wgpu_backend](https://crates.io/crates/egui_wgpu_backend).
* [`amethyst_egui`](https://github.com/jgraef/amethyst_egui) for [the Amethyst game engine](https://amethyst.rs/).
* [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for [the Bevy game engine](https://bevyengine.org/).
* [`egui_glfw_gl`](https://github.com/cohaereo/egui_glfw_gl) for [GLFW](https://crates.io/crates/glfw).
* [`egui-glutin-gl`](https://github.com/h3r2tic/egui-glutin-gl/) for [glutin](https://crates.io/crates/glutin).
* [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2).
* [`egui_sdl2_platform`](https://github.com/ComLarsic/egui_sdl2_platform) for [SDL2](https://crates.io/crates/sdl2).
* [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
* [`egui_winit_vulkano`](https://github.com/hakolao/egui_winit_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
* [`egui-macroquad`](https://github.com/optozorax/egui-macroquad) for [macroquad](https://github.com/not-fl3/macroquad).
* [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad) for [Miniquad](https://github.com/not-fl3/miniquad).
* [`egui_speedy2d`](https://github.com/heretik31/egui_speedy2d) for [Speedy2d](https://github.com/QuantumBadger/Speedy2D).
* [`egui-tetra`](https://crates.io/crates/egui-tetra) for [Tetra](https://crates.io/crates/tetra), a 2D game framework.
* [`egui-winit-ash-integration`](https://github.com/MatchaChoco010/egui-winit-ash-integration) for [winit](https://github.com/rust-windowing/winit) and [ash](https://github.com/MaikKlein/ash).
* [`fltk-egui`](https://crates.io/crates/fltk-egui) for [fltk-rs](https://github.com/fltk-rs/fltk-rs).
* [`ggegui`](https://github.com/NemuiSen/ggegui) for the [ggez](https://ggez.rs/) game framework.
* [`godot-egui`](https://github.com/setzer22/godot-egui) for [godot-rust](https://github.com/godot-rust/godot-rust).
* [`nannou_egui`](https://github.com/nannou-org/nannou/tree/master/nannou_egui) for [nannou](https://nannou.cc).
* [`notan_egui`](https://github.com/Nazariglez/notan/tree/main/crates/notan_egui) for [notan](https://github.com/Nazariglez/notan).
* [`screen-13-egui`](https://github.com/attackgoat/screen-13/tree/master/contrib/screen-13-egui) for [Screen 13](https://github.com/attackgoat/screen-13).
* [`egui_skia`](https://github.com/lucasmerlin/egui_skia) for [skia](https://github.com/rust-skia/rust-skia/tree/master/skia-safe).
* [`smithay-egui`](https://github.com/Smithay/smithay-egui) for [smithay](https://github.com/Smithay/smithay/).
* [`tauri-egui`](https://github.com/tauri-apps/tauri-egui) for [tauri](https://github.com/tauri-apps/tauri).
Missing an integration for the thing you're working on? Create one, it's easy!
### Writing your own egui integration
You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html), paint [`egui::ClippedMesh`](https://docs.rs/epaint/):es and handle [`egui::Output`](https://docs.rs/egui/latest/egui/struct.Output.html). The basic structure is this:
You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html) and handle [`egui::FullOutput`](https://docs.rs/egui/latest/egui/struct.FullOutput.html). The basic structure is this:
``` rust
let mut egui_ctx = egui::Context::new();
let mut egui_ctx = egui::CtxRef::default();
// Game loop:
loop {
// Gather input (mouse, touches, keyboard, screen size, etc):
let raw_input: egui::RawInput = my_integration.gather_input();
egui_ctx.begin_frame(raw_input);
my_app.ui(&mut egui_ctx); // add panels, windows and widgets to `egui_ctx` here
let (output, shapes) = egui_ctx.end_frame();
let clipped_meshes = egui_ctx.tessellate(shapes); // create triangles to paint
my_integration.paint(clipped_meshes);
my_integration.set_cursor_icon(output.cursor_icon);
// Also see `egui::Output` for more
let full_output = egui_ctx.run(raw_input, |egui_ctx| {
my_app.ui(egui_ctx); // add panels, windows and widgets to `egui_ctx` here
});
let clipped_primitives = egui_ctx.tessellate(full_output.shapes); // creates triangles to paint
my_integration.paint(&full_output.textures_delta, clipped_primitives);
let platform_output = full_output.platform_output;
my_integration.set_cursor_icon(platform_output.cursor_icon);
if !platform_output.copied_text.is_empty() {
my_integration.set_clipboard_text(platform_output.copied_text);
}
// See `egui::FullOutput` and `egui::PlatformOutput` for more
}
```
For a reference OpenGL backend, see [the `egui_glium` painter](https://github.com/emilk/egui/blob/master/egui_glium/src/painter.rs) or [the `egui_web` `WebGL` painter](https://github.com/emilk/egui/blob/master/egui_web/src/webgl1.rs).
For a reference OpenGL backend, see [the `egui_glium` painter](https://github.com/emilk/egui/blob/master/crates/egui_glium/src/painter.rs) or [the `egui_glow` painter](https://github.com/emilk/egui/blob/master/crates/egui_glow/src/painter.rs).
### Debugging your integration
@ -207,25 +241,138 @@ For a reference OpenGL backend, see [the `egui_glium` painter](https://github.co
* egui uses premultiplied alpha, so make sure your blending function is `(ONE, ONE_MINUS_SRC_ALPHA)`.
* Make sure your texture sampler is clamped (`GL_CLAMP_TO_EDGE`).
* Use an sRGBA-aware texture if available (e.g. `GL_SRGB8_ALPHA8`).
* egui prefers linear color spaces for all blending so:
* Use an sRGBA-aware texture if available (e.g. `GL_SRGB8_ALPHA8`).
* Otherwise: remember to decode gamma in the fragment shader.
* Decode the gamma of the incoming vertex colors in your vertex shader.
* Turn on sRGBA/linear framebuffer if available (`GL_FRAMEBUFFER_SRGB`).
* Decode the gamma of the incoming vertex colors in your vertex shader.
* Turn on sRGBA/linear framebuffer if available (`GL_FRAMEBUFFER_SRGB`).
* Otherwise: gamma-encode the colors before you write them again.
## Why immediate mode
`egui` is an [immediate mode GUI library](https://en.wikipedia.org/wiki/Immediate_mode_GUI), as opposed to a *retained mode* GUI library. The difference between retained mode and immediate mode is best illustrated with the example of a button: In a retained GUI you create a button, add it to some UI and install some on-click handler (callback). The button is retained in the UI, and to change the text on it you need to store some sort of reference to it. By contrast, in immediate mode you show the button and interact with it immediately, and you do so every frame (e.g. 60 times per second). This means there is no need for any on-click handler, nor to store any reference to it. In `egui` this looks like this: `if ui.button("Save file").clicked() { save(file); }`.
A more detailed description of immediate mode can be found [in the `egui` docs](https://docs.rs/egui/latest/egui/#understanding-immediate-mode).
There are advantages and disadvantages to both systems.
The short of it is this: immediate mode GUI libraries are easier to use, but less powerful.
### Advantages of immediate mode
#### Usability
The main advantage of immediate mode is that the application code becomes vastly simpler:
* You never need to have any on-click handlers and callbacks that disrupts your code flow.
* You don't have to worry about a lingering callback calling something that is gone.
* Your GUI code can easily live in a simple function (no need for an object just for the UI).
* You don't have to worry about app state and GUI state being out-of-sync (i.e. the GUI showing something outdated), because the GUI isn't storing any state - it is showing the latest state *immediately*.
In other words, a whole lot of code, complexity and bugs are gone, and you can focus your time on something more interesting than writing GUI code.
### Disadvantages of immediate mode
#### Layout
The main disadvantage of immediate mode is it makes layout more difficult. Say you want to show a small dialog window in the center of the screen. To position the window correctly the GUI library must first know the size of it. To know the size of the window the GUI library must first layout the contents of the window. In retained mode this is easy: the GUI library does the window layout, positions the window, then checks for interaction ("was the OK button clicked?").
In immediate mode you run into a paradox: to know the size of the window, we must do the layout, but the layout code also checks for interaction ("was the OK button clicked?") and so it needs to know the window position *before* showing the window contents. This means we must decide where to show the window *before* we know its size!
This is a fundamental shortcoming of immediate mode GUIs, and any attempt to resolve it comes with its own downsides.
One workaround is to store the size and use it the next frame. This produces a frame-delay for the correct layout, producing occasional flickering the first frame something shows up. `egui` does this for some things such as windows and grid layouts.
You can also call the layout code twice (once to get the size, once to do the interaction), but that is not only more expensive, it's also complex to implement, and in some cases twice is not enough. `egui` never does this.
For "atomic" widgets (e.g. a button) `egui` knows the size before showing it, so centering buttons, labels etc is possible in `egui` without any special workarounds.
#### CPU usage
Since an immediate mode GUI does a full layout each frame, the layout code needs to be quick. If you have a very complex GUI this can tax the CPU. In particular, having a very large UI in a scroll area (with very long scrollback) can be slow, as the content needs to be layed out each frame.
If you design the GUI with this in mind and refrain from huge scroll areas (or only lay out the part that is in view) then the performance hit is generally pretty small. For most cases you can expect `egui` to take up 1-2 ms per frame, but `egui` still has a lot of room for optimization (it's not something I've focused on yet). You can also set up `egui` to only repaint when there is interaction (e.g. mouse movement).
If your GUI is highly interactive, then immediate mode may actually be more performant compared to retained mode. Go to any web page and resize the browser window, and you'll notice that the browser is very slow to do the layout and eats a lot of CPU doing it. Resize a window in `egui` by contrast, and you'll get smooth 60 FPS at no extra CPU cost.
#### IDs
There are some GUI state that you want the GUI library to retain, even in an immediate mode library such as `egui`. This includes position and sizes of windows and how far the user has scrolled in some UI. In these cases you need to provide `egui` with a seed of a unique identifier (unique within the parent UI). For instance: by default `egui` uses the window titles as unique IDs to store window positions. If you want two windows with the same name (or one window with a dynamic name) you must provide some other ID source to `egui` (some unique integer or string).
`egui` also needs to track which widget is being interacted with (e.g. which slider is being dragged). `egui` uses unique id:s for this awell, but in this case the IDs are automatically generated, so there is no need for the user to worry about it. In particular, having two buttons with the same name is no problem (this is in contrast with [`Dear ImGui`](https://github.com/ocornut/imgui)).
Overall, ID handling is a rare inconvenience, and not a big disadvantage.
## FAQ
Also see [GitHub Discussions](https://github.com/emilk/egui/discussions/categories/q-a).
### Can I use `egui` with non-latin characters?
Yes! But you need to install your own font (`.ttf` or `.otf`) using `Context::set_fonts`.
### Can I customize the look of egui?
Yes! You can customize the colors, spacing, fonts and sizes of everything using `Context::set_style`.
Here is an example (from https://github.com/AlexxxRu/TinyPomodoro):
<img src="media/pompodoro-skin.png" width="50%">
### How do I use egui with `async`?
If you call `.await` in your GUI code, the UI will freeze, which is very bad UX. Instead, keep the GUI thread non-blocking and communicate with any concurrent tasks (`async` tasks or other threads) with something like:
* Channels (e.g. [`std::sync::mpsc::channel`](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html)). Make sure to use [`try_recv`](https://doc.rust-lang.org/std/sync/mpsc/struct.Receiver.html#method.try_recv) so you don't block the gui thread!
* `Arc<Mutex<Value>>` (background thread sets a value; GUI thread reads it)
* [`poll_promise::Promise`](https://docs.rs/poll-promise) (example: [`examples/download_image/`](https://github.com/emilk/egui/blob/master/examples/download_image/))
* [`eventuals::Eventual`](https://docs.rs/eventuals/latest/eventuals/struct.Eventual.html)
* [`tokio::sync::watch::channel`](https://docs.rs/tokio/latest/tokio/sync/watch/fn.channel.html)
### What about accessibility, such as screen readers?
egui includes optional support for [AccessKit](https://accesskit.dev/), which currently implements the native accessibility APIs on Windows and macOS. This feature is enabled by default in eframe. For platforms that AccessKit doesn't yet support, including web, there is an experimental built-in screen reader; in [the web demo](https://www.egui.rs/#demo) you can enable it in the "Backend" tab.
The original discussion of accessibility in egui is at <https://github.com/emilk/egui/issues/167>. Now that AccessKit support is merged, providing a strong foundation for future accessibility work, please open new issues on specific accessibility problems.
### What is the difference between [egui](https://docs.rs/egui) and [eframe](https://github.com/emilk/egui/tree/master/crates/eframe)?
`egui` is a 2D user interface library for laying out and interacting with buttons, sliders, etc.
`egui` has no idea if it is running on the web or natively, and does not know how to collect input or show things on screen.
That is the job of *the integration* or *backend*.
It is common to use `egui` from a game engine (using e.g. [`bevy_egui`](https://docs.rs/bevy_egui)),
but you can also use `egui` stand-alone using `eframe`. `eframe` has integration for web and native, and handles input and rendering.
The _frame_ in `eframe` stands both for the frame in which your egui app resides and also for "framework" (`frame` is a framework, `egui` is a library).
### How do I render 3D stuff in an egui area?
There are multiple ways to combine egui with 3D. The simplest way is to use a 3D library and have egui sit on top of the 3D view. See for instance [`bevy_egui`](https://github.com/mvlabat/bevy_egui) or [`three-d`](https://github.com/asny/three-d).
If you want to embed 3D into an egui view there are two options.
#### `Shape::Callback`
Examples:
* <https://github.com/emilk/egui/blob/master/examples/custom_3d_three-d.rs>
* <https://github.com/emilk/egui/blob/master/examples/custom_3d_glow.rs>
`Shape::Callback` will call your code when egui gets painted, to show anything using whatever the background rendering context is. When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) this will be [`glow`](https://github.com/grovesNL/glow). Other integrations will give you other rendering contexts, if they support `Shape::Callback` at all.
#### Render-to-texture
You can also render your 3D scene to a texture and display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use.
Examples:
* Using [`egui-miniquad`]( https://github.com/not-fl3/egui-miniquad): https://github.com/not-fl3/egui-miniquad/blob/master/examples/render_to_egui_image.rs
* Using [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium): <https://github.com/emilk/egui/blob/master/crates/egui_glium/examples/native_texture.rs>.
## Other
### Conventions and design choices
All coordinates are in screen space coordinates, with (0, 0) in the top left corner
All coordinates are in locial "points" which may consist of many physical pixels.
All coordinates are in "points" which may consist of many physical pixels.
All colors have premultiplied alpha.
egui uses the builder pattern for construction widgets. For instance: `ui.add(Label::new("Hello").text_color(RED));` I am not a big fan of the builder pattern (it is quite verbose both in implementation and in use) but until Rust has named, default arguments it is the best we can do. To alleviate some of the verbosity there are common-case helper functions, like `ui.label("Hello");`.
Instead of using matching `begin/end` style function calls (which can be error prone) egui prefers to use `FnOnce` closures passed to a wrapping function. Lambdas are a bit ugly though, so I'd like to find a nicer solution to this.
Instead of using matching `begin/end` style function calls (which can be error prone) egui prefers to use `FnOnce` closures passed to a wrapping function. Lambdas are a bit ugly though, so I'd like to find a nicer solution to this. More discussion of this at <https://github.com/emilk/egui/issues/1004#issuecomment-1001650754>.
egui uses a single `RwLock` for short-time locks on each access of `Context` data. This is to leave implementation simple and transactional and allow users to run their UI logic in parallel. Instead of creating mutex guards, egui uses closures passed to a wrapping function, e.g. `ctx.input(|i| i.key_down(Key::A))`. This is to make it less likely that a user would accidentally double-lock the `Context`, which would lead to a deadlock.
### Inspiration
@ -233,19 +380,44 @@ The one and only [Dear ImGui](https://github.com/ocornut/imgui) is a great Immed
### Name
The name of the library and the project is "egui" and pronounced as "e-gooey".
The name of the library and the project is "egui" and pronounced as "e-gooey". Please don't write it as "EGUI".
The library was originally called "Emigui", but was renamed to "egui" in 2020.
### Credits / Licenses
## Credits
egui author: Emil Ernerfeldt
egui author and maintainer: Emil Ernerfeldt [(@emilk](https://github.com/emilk)).
egui is under MIT OR Apache-2.0 license.
Notable contributions by:
Fonts:
* [@n2](https://github.com/n2): [Mobile web input and IME support](https://github.com/emilk/egui/pull/253).
* [@optozorax](https://github.com/optozorax): [Arbitrary widget data storage](https://github.com/emilk/egui/pull/257).
* [@quadruple-output](https://github.com/quadruple-output): [Multitouch](https://github.com/emilk/egui/pull/306).
* [@EmbersArc](https://github.com/EmbersArc): [Plots](https://github.com/emilk/egui/pulls?q=+is%3Apr+author%3AEmbersArc).
* [@AsmPrgmC3](https://github.com/AsmPrgmC3): [Proper sRGBA blending for web](https://github.com/emilk/egui/pull/650).
* [@AlexApps99](https://github.com/AlexApps99): [`egui_glow`](https://github.com/emilk/egui/pull/685).
* [@mankinskin](https://github.com/mankinskin): [Context menus](https://github.com/emilk/egui/pull/543).
* [@t18b219k](https://github.com/t18b219k): [Port glow painter to web](https://github.com/emilk/egui/pull/868).
* [@danielkeller](https://github.com/danielkeller): [`Context` refactor](https://github.com/emilk/egui/pull/1050).
* [@MaximOsipenko](https://github.com/MaximOsipenko): [`Context` lock refactor](https://github.com/emilk/egui/pull/2625).
* And [many more](https://github.com/emilk/egui/graphs/contributors?type=a).
egui is licensed under [MIT](LICENSE-MIT) OR [Apache-2.0](LICENSE-APACHE).
* The flattening algorithm for the cubic bezier curve and quadratic bezier curve is from [lyon_geom](https://docs.rs/lyon_geom/latest/lyon_geom/)
Default fonts:
* `emoji-icon-font.ttf`: [Copyright (c) 2014 John Slegers](https://github.com/jslegers/emoji-icon-font) , MIT License
* `Hack-Regular.ttf`: <https://github.com/source-foundry/Hack>, [MIT Licence](https://github.com/source-foundry/Hack/blob/master/LICENSE.md)
* `NotoEmoji-Regular.ttf`: [google.com/get/noto](https://google.com/get/noto), [SIL Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL)
* `ProggyClean.ttf`: Copyright (c) 2004, 2005 Tristan Grimmer. MIT License. <http://www.proggyfonts.net/>
* `Ubuntu-Light.ttf` by [Dalton Maag](http://www.daltonmaag.com/): [Ubuntu font licence](https://ubuntu.com/legal/font-licence)
---
<div align="center">
<img src="media/rerun_io_logo.png" width="50%">
egui development is sponsored by [Rerun](https://www.rerun.io/), a startup doing<br>
visualizations for computer vision and robotics.
</div>

46
bacon.toml Normal file
View file

@ -0,0 +1,46 @@
# This is a configuration file for the bacon tool
# More info at https://github.com/Canop/bacon
default_job = "cranky"
[jobs]
[jobs.cranky]
command = ["cargo", "cranky", "--all-targets", "--all-features", "--color", "always"]
need_stdout = false
watch = ["tests", "benches", "examples"]
[jobs.test]
command = ["cargo", "test", "--color", "always"]
need_stdout = true
watch = ["tests"]
[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--all-features", "--no-deps"]
need_stdout = false
# if the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--color", "always", "--all-features", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change
# You can run your application and have the result displayed in bacon,
# *if* it makes sense for this crate. You can run an example the same
# way. Don't forget the `--color always` part or the errors won't be
# properly parsed.
[jobs.run]
command = ["cargo", "run", "--color", "always"]
need_stdout = true
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal prefs.toml file instead.
[keybindings]
i = "job:initial"
c = "job:cranky"
d = "job:doc-open"
t = "job:test"
r = "job:run"

View file

@ -1,4 +0,0 @@
#!/bin/bash
set -eu
cargo run --bin egui_demo_app --release --all-features

View file

@ -1,25 +0,0 @@
#!/bin/bash
set -eu
CRATE_NAME="egui_demo_app"
# This is required to enable the web_sys clipboard API which egui_web uses
# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
export RUSTFLAGS=--cfg=web_sys_unstable_apis
# Clear output from old stuff:
rm -f docs/${CRATE_NAME}_bg.wasm
echo "Building rust…"
BUILD=release
cargo build --release --all-features -p ${CRATE_NAME} --lib --target wasm32-unknown-unknown
echo "Generating JS bindings for wasm…"
TARGET_NAME="${CRATE_NAME}.wasm"
wasm-bindgen "target/wasm32-unknown-unknown/$BUILD/$TARGET_NAME" \
--out-dir docs --no-modules --no-typescript
echo "Finished: docs/${CRATE_NAME}.wasm"
open http://localhost:8888/index.html

View file

@ -1,21 +0,0 @@
#!/bin/bash
set -eu
cargo check --workspace --all-targets
cargo check --workspace --all-targets --all-features
cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown
cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features
cargo fmt --all -- --check
CARGO_INCREMENTAL=0 cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::all
cargo test --workspace --all-targets --all-features
cargo test --workspace --doc
# TODO: doesn't error, but at least prints a warning.
cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui_glium --lib --no-deps
cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps
# For finding bloat:
# cargo bloat --release --bin demo_glium -n 200 | rg egui
# what compiles slowly?
# cargo clean; cargo +nightly build -p egui -Z timings

1
clippy.toml Normal file
View file

@ -0,0 +1 @@
doc-valid-idents = ["AccessKit", ".."]

View file

@ -0,0 +1,13 @@
# Changelog for ecolor
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)).
## 0.20.0 - 2022-12-08
* Split out `ecolor` crate from `epaint`

50
crates/ecolor/Cargo.toml Normal file
View file

@ -0,0 +1,50 @@
[package]
name = "ecolor"
version = "0.21.0"
authors = [
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
"Andreas Reich <reichandreas@gmx.de>",
]
description = "Color structs and color conversion utilities"
edition = "2021"
rust-version = "1.65"
homepage = "https://github.com/emilk/egui"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/emilk/egui"
categories = ["mathematics", "encoding"]
keywords = ["gui", "color", "conversion", "gamedev", "images"]
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
[package.metadata.docs.rs]
all-features = true
[lib]
[features]
default = []
## Enable additional checks if debug assertions are enabled (debug builds).
extra_debug_asserts = []
## Always enable additional checks.
extra_asserts = []
[dependencies]
#! ### Optional dependencies
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `ecolor` types to `&[u8]`.
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
## [`cint`](https://docs.rs/cint) enables interopability with other color libraries.
cint = { version = "0.3.1", optional = true }
## Enable the [`hex_color`] macro.
color-hex = { version = "0.2.0", optional = true }
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
## Allow serialization using [`serde`](https://docs.rs/serde).
serde = { version = "1", optional = true, features = ["derive"] }

11
crates/ecolor/README.md Normal file
View file

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

@ -0,0 +1,161 @@
use super::*;
use cint::{Alpha, ColorInterop, EncodedSrgb, Hsv, LinearSrgb, PremultipliedAlpha};
// ---- Color32 ----
impl From<Alpha<EncodedSrgb<u8>>> for Color32 {
fn from(srgba: Alpha<EncodedSrgb<u8>>) -> Self {
let Alpha {
color: EncodedSrgb { r, g, b },
alpha: a,
} = srgba;
Color32::from_rgba_unmultiplied(r, g, b, a)
}
}
// No From<Color32> for Alpha<_> because Color32 is premultiplied
impl From<PremultipliedAlpha<EncodedSrgb<u8>>> for Color32 {
fn from(srgba: PremultipliedAlpha<EncodedSrgb<u8>>) -> Self {
let PremultipliedAlpha {
color: EncodedSrgb { r, g, b },
alpha: a,
} = srgba;
Color32::from_rgba_premultiplied(r, g, b, a)
}
}
impl From<Color32> for PremultipliedAlpha<EncodedSrgb<u8>> {
fn from(col: Color32) -> Self {
let (r, g, b, a) = col.to_tuple();
PremultipliedAlpha {
color: EncodedSrgb { r, g, b },
alpha: a,
}
}
}
impl From<PremultipliedAlpha<EncodedSrgb<f32>>> for Color32 {
fn from(srgba: PremultipliedAlpha<EncodedSrgb<f32>>) -> Self {
let PremultipliedAlpha {
color: EncodedSrgb { r, g, b },
alpha: a,
} = srgba;
// This is a bit of an abuse of the function name but it does what we want.
let r = linear_u8_from_linear_f32(r);
let g = linear_u8_from_linear_f32(g);
let b = linear_u8_from_linear_f32(b);
let a = linear_u8_from_linear_f32(a);
Color32::from_rgba_premultiplied(r, g, b, a)
}
}
impl From<Color32> for PremultipliedAlpha<EncodedSrgb<f32>> {
fn from(col: Color32) -> Self {
let (r, g, b, a) = col.to_tuple();
// This is a bit of an abuse of the function name but it does what we want.
let r = linear_f32_from_linear_u8(r);
let g = linear_f32_from_linear_u8(g);
let b = linear_f32_from_linear_u8(b);
let a = linear_f32_from_linear_u8(a);
PremultipliedAlpha {
color: EncodedSrgb { r, g, b },
alpha: a,
}
}
}
impl ColorInterop for Color32 {
type CintTy = PremultipliedAlpha<EncodedSrgb<u8>>;
}
// ---- Rgba ----
impl From<PremultipliedAlpha<LinearSrgb<f32>>> for Rgba {
fn from(srgba: PremultipliedAlpha<LinearSrgb<f32>>) -> Self {
let PremultipliedAlpha {
color: LinearSrgb { r, g, b },
alpha: a,
} = srgba;
Rgba([r, g, b, a])
}
}
impl From<Rgba> for PremultipliedAlpha<LinearSrgb<f32>> {
fn from(col: Rgba) -> Self {
let (r, g, b, a) = col.to_tuple();
PremultipliedAlpha {
color: LinearSrgb { r, g, b },
alpha: a,
}
}
}
impl ColorInterop for Rgba {
type CintTy = PremultipliedAlpha<LinearSrgb<f32>>;
}
// ---- Hsva ----
impl From<Alpha<Hsv<f32>>> for Hsva {
fn from(srgba: Alpha<Hsv<f32>>) -> Self {
let Alpha {
color: Hsv { h, s, v },
alpha: a,
} = srgba;
Hsva::new(h, s, v, a)
}
}
impl From<Hsva> for Alpha<Hsv<f32>> {
fn from(col: Hsva) -> Self {
let Hsva { h, s, v, a } = col;
Alpha {
color: Hsv { h, s, v },
alpha: a,
}
}
}
impl ColorInterop for Hsva {
type CintTy = Alpha<Hsv<f32>>;
}
// ---- HsvaGamma ----
impl ColorInterop for HsvaGamma {
type CintTy = Alpha<Hsv<f32>>;
}
impl From<Alpha<Hsv<f32>>> for HsvaGamma {
fn from(srgba: Alpha<Hsv<f32>>) -> Self {
let Alpha {
color: Hsv { h, s, v },
alpha: a,
} = srgba;
Hsva::new(h, s, v, a).into()
}
}
impl From<HsvaGamma> for Alpha<Hsv<f32>> {
fn from(col: HsvaGamma) -> Self {
let Hsva { h, s, v, a } = col.into();
Alpha {
color: Hsv { h, s, v },
alpha: a,
}
}
}

View file

@ -0,0 +1,216 @@
use crate::{gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8, Rgba};
/// This format is used for space-efficient color representation (32 bits).
///
/// Instead of manipulating this directly it is often better
/// to first convert it to either [`Rgba`] or [`crate::Hsva`].
///
/// Internally this uses 0-255 gamma space `sRGBA` color with premultiplied alpha.
/// Alpha channel is in linear space.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct Color32(pub(crate) [u8; 4]);
impl std::ops::Index<usize> for Color32 {
type Output = u8;
#[inline(always)]
fn index(&self, index: usize) -> &u8 {
&self.0[index]
}
}
impl std::ops::IndexMut<usize> for Color32 {
#[inline(always)]
fn index_mut(&mut self, index: usize) -> &mut u8 {
&mut self.0[index]
}
}
impl Color32 {
// Mostly follows CSS names:
pub const TRANSPARENT: Color32 = Color32::from_rgba_premultiplied(0, 0, 0, 0);
pub const BLACK: Color32 = Color32::from_rgb(0, 0, 0);
pub const DARK_GRAY: Color32 = Color32::from_rgb(96, 96, 96);
pub const GRAY: Color32 = Color32::from_rgb(160, 160, 160);
pub const LIGHT_GRAY: Color32 = Color32::from_rgb(220, 220, 220);
pub const WHITE: Color32 = Color32::from_rgb(255, 255, 255);
pub const BROWN: Color32 = Color32::from_rgb(165, 42, 42);
pub const DARK_RED: Color32 = Color32::from_rgb(0x8B, 0, 0);
pub const RED: Color32 = Color32::from_rgb(255, 0, 0);
pub const LIGHT_RED: Color32 = Color32::from_rgb(255, 128, 128);
pub const YELLOW: Color32 = Color32::from_rgb(255, 255, 0);
pub const LIGHT_YELLOW: Color32 = Color32::from_rgb(255, 255, 0xE0);
pub const KHAKI: Color32 = Color32::from_rgb(240, 230, 140);
pub const DARK_GREEN: Color32 = Color32::from_rgb(0, 0x64, 0);
pub const GREEN: Color32 = Color32::from_rgb(0, 255, 0);
pub const LIGHT_GREEN: Color32 = Color32::from_rgb(0x90, 0xEE, 0x90);
pub const DARK_BLUE: Color32 = Color32::from_rgb(0, 0, 0x8B);
pub const BLUE: Color32 = Color32::from_rgb(0, 0, 255);
pub const LIGHT_BLUE: Color32 = Color32::from_rgb(0xAD, 0xD8, 0xE6);
pub const GOLD: Color32 = Color32::from_rgb(255, 215, 0);
pub const DEBUG_COLOR: Color32 = Color32::from_rgba_premultiplied(0, 200, 0, 128);
/// An ugly color that is planned to be replaced before making it to the screen.
pub const TEMPORARY_COLOR: Color32 = Color32::from_rgb(64, 254, 0);
#[inline(always)]
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self([r, g, b, 255])
}
#[inline(always)]
pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self {
Self([r, g, b, 0])
}
/// From `sRGBA` with premultiplied alpha.
#[inline(always)]
pub const fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
Self([r, g, b, a])
}
/// From `sRGBA` WITHOUT premultiplied alpha.
pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
if a == 255 {
Self::from_rgb(r, g, b) // common-case optimization
} else if a == 0 {
Self::TRANSPARENT // common-case optimization
} else {
let r_lin = linear_f32_from_gamma_u8(r);
let g_lin = linear_f32_from_gamma_u8(g);
let b_lin = linear_f32_from_gamma_u8(b);
let a_lin = linear_f32_from_linear_u8(a);
let r = gamma_u8_from_linear_f32(r_lin * a_lin);
let g = gamma_u8_from_linear_f32(g_lin * a_lin);
let b = gamma_u8_from_linear_f32(b_lin * a_lin);
Self::from_rgba_premultiplied(r, g, b, a)
}
}
#[inline(always)]
pub const fn from_gray(l: u8) -> Self {
Self([l, l, l, 255])
}
#[inline(always)]
pub const fn from_black_alpha(a: u8) -> Self {
Self([0, 0, 0, a])
}
pub fn from_white_alpha(a: u8) -> Self {
Rgba::from_white_alpha(linear_f32_from_linear_u8(a)).into()
}
#[inline(always)]
pub const fn from_additive_luminance(l: u8) -> Self {
Self([l, l, l, 0])
}
#[inline(always)]
pub const fn is_opaque(&self) -> bool {
self.a() == 255
}
#[inline(always)]
pub const fn r(&self) -> u8 {
self.0[0]
}
#[inline(always)]
pub const fn g(&self) -> u8 {
self.0[1]
}
#[inline(always)]
pub const fn b(&self) -> u8 {
self.0[2]
}
#[inline(always)]
pub const fn a(&self) -> u8 {
self.0[3]
}
/// Returns an opaque version of self
pub fn to_opaque(self) -> Self {
Rgba::from(self).to_opaque().into()
}
/// Returns an additive version of self
#[inline(always)]
pub const fn additive(self) -> Self {
let [r, g, b, _] = self.to_array();
Self([r, g, b, 0])
}
/// Premultiplied RGBA
#[inline(always)]
pub const fn to_array(&self) -> [u8; 4] {
[self.r(), self.g(), self.b(), self.a()]
}
/// Premultiplied RGBA
#[inline(always)]
pub const fn to_tuple(&self) -> (u8, u8, u8, u8) {
(self.r(), self.g(), self.b(), self.a())
}
pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
Rgba::from(*self).to_srgba_unmultiplied()
}
/// Multiply with 0.5 to make color half as opaque, perceptually.
///
/// Fast multiplication in gamma-space.
///
/// This is perceptually even, and faster that [`Self::linear_multiply`].
#[inline]
pub fn gamma_multiply(self, factor: f32) -> Color32 {
crate::ecolor_assert!(0.0 <= factor && factor <= 1.0);
let Self([r, g, b, a]) = self;
Self([
(r as f32 * factor + 0.5) as u8,
(g as f32 * factor + 0.5) as u8,
(b as f32 * factor + 0.5) as u8,
(a as f32 * factor + 0.5) as u8,
])
}
/// Multiply with 0.5 to make color half as opaque in linear space.
///
/// This is using linear space, which is not perceptually even.
/// You may want to use [`Self::gamma_multiply`] instead.
pub fn linear_multiply(self, factor: f32) -> Color32 {
crate::ecolor_assert!(0.0 <= factor && factor <= 1.0);
// As an unfortunate side-effect of using premultiplied alpha
// we need a somewhat expensive conversion to linear space and back.
Rgba::from(self).multiply(factor).into()
}
/// Converts to floating point values in the range 0-1 without any gamma space conversion.
///
/// Use this with great care! In almost all cases, you want to convert to [`crate::Rgba`] instead
/// in order to obtain linear space color values.
#[inline]
pub fn to_normalized_gamma_f32(self) -> [f32; 4] {
let Self([r, g, b, a]) = self;
[
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0,
]
}
}

View file

@ -0,0 +1,39 @@
/// Construct a [`crate::Color32`] from a hex RGB or RGBA string.
///
/// ```
/// # use ecolor::{hex_color, Color32};
/// assert_eq!(hex_color!("#202122"), Color32::from_rgb(0x20, 0x21, 0x22));
/// assert_eq!(hex_color!("#abcdef12"), Color32::from_rgba_unmultiplied(0xab, 0xcd, 0xef, 0x12));
/// ```
#[macro_export]
macro_rules! hex_color {
($s:literal) => {{
let array = color_hex::color_from_hex!($s);
if array.len() == 3 {
$crate::Color32::from_rgb(array[0], array[1], array[2])
} else {
#[allow(unconditional_panic)]
$crate::Color32::from_rgba_unmultiplied(array[0], array[1], array[2], array[3])
}
}};
}
#[test]
fn test_from_rgb_hex() {
assert_eq!(
crate::Color32::from_rgb(0x20, 0x21, 0x22),
hex_color!("#202122")
);
assert_eq!(
crate::Color32::from_rgb_additive(0x20, 0x21, 0x22),
hex_color!("#202122").additive()
);
}
#[test]
fn test_from_rgba_hex() {
assert_eq!(
crate::Color32::from_rgba_unmultiplied(0x20, 0x21, 0x22, 0x50),
hex_color!("20212250")
);
}

231
crates/ecolor/src/hsva.rs Normal file
View file

@ -0,0 +1,231 @@
use crate::{
gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8,
linear_u8_from_linear_f32, Color32, Rgba,
};
/// Hue, saturation, value, alpha. All in the range [0, 1].
/// No premultiplied alpha.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Hsva {
/// hue 0-1
pub h: f32,
/// saturation 0-1
pub s: f32,
/// value 0-1
pub v: f32,
/// alpha 0-1. A negative value signifies an additive color (and alpha is ignored).
pub a: f32,
}
impl Hsva {
pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self {
Self { h, s, v, a }
}
/// From `sRGBA` with premultiplied alpha
pub fn from_srgba_premultiplied(srgba: [u8; 4]) -> Self {
Self::from_rgba_premultiplied(
linear_f32_from_gamma_u8(srgba[0]),
linear_f32_from_gamma_u8(srgba[1]),
linear_f32_from_gamma_u8(srgba[2]),
linear_f32_from_linear_u8(srgba[3]),
)
}
/// From `sRGBA` without premultiplied alpha
pub fn from_srgba_unmultiplied(srgba: [u8; 4]) -> Self {
Self::from_rgba_unmultiplied(
linear_f32_from_gamma_u8(srgba[0]),
linear_f32_from_gamma_u8(srgba[1]),
linear_f32_from_gamma_u8(srgba[2]),
linear_f32_from_linear_u8(srgba[3]),
)
}
/// From linear RGBA with premultiplied alpha
pub fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
#![allow(clippy::many_single_char_names)]
if a == 0.0 {
if r == 0.0 && b == 0.0 && a == 0.0 {
Hsva::default()
} else {
Hsva::from_additive_rgb([r, g, b])
}
} else {
let (h, s, v) = hsv_from_rgb([r / a, g / a, b / a]);
Hsva { h, s, v, a }
}
}
/// From linear RGBA without premultiplied alpha
pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
#![allow(clippy::many_single_char_names)]
let (h, s, v) = hsv_from_rgb([r, g, b]);
Hsva { h, s, v, a }
}
pub fn from_additive_rgb(rgb: [f32; 3]) -> Self {
let (h, s, v) = hsv_from_rgb(rgb);
Hsva {
h,
s,
v,
a: -0.5, // anything negative is treated as additive
}
}
pub fn from_rgb(rgb: [f32; 3]) -> Self {
let (h, s, v) = hsv_from_rgb(rgb);
Hsva { h, s, v, a: 1.0 }
}
pub fn from_srgb([r, g, b]: [u8; 3]) -> Self {
Self::from_rgb([
linear_f32_from_gamma_u8(r),
linear_f32_from_gamma_u8(g),
linear_f32_from_gamma_u8(b),
])
}
// ------------------------------------------------------------------------
pub fn to_opaque(self) -> Self {
Self { a: 1.0, ..self }
}
pub fn to_rgb(&self) -> [f32; 3] {
rgb_from_hsv((self.h, self.s, self.v))
}
pub fn to_srgb(&self) -> [u8; 3] {
let [r, g, b] = self.to_rgb();
[
gamma_u8_from_linear_f32(r),
gamma_u8_from_linear_f32(g),
gamma_u8_from_linear_f32(b),
]
}
pub fn to_rgba_premultiplied(&self) -> [f32; 4] {
let [r, g, b, a] = self.to_rgba_unmultiplied();
let additive = a < 0.0;
if additive {
[r, g, b, 0.0]
} else {
[a * r, a * g, a * b, a]
}
}
/// Represents additive colors using a negative alpha.
pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
let Hsva { h, s, v, a } = *self;
let [r, g, b] = rgb_from_hsv((h, s, v));
[r, g, b, a]
}
pub fn to_srgba_premultiplied(&self) -> [u8; 4] {
let [r, g, b, a] = self.to_rgba_premultiplied();
[
gamma_u8_from_linear_f32(r),
gamma_u8_from_linear_f32(g),
gamma_u8_from_linear_f32(b),
linear_u8_from_linear_f32(a),
]
}
pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
let [r, g, b, a] = self.to_rgba_unmultiplied();
[
gamma_u8_from_linear_f32(r),
gamma_u8_from_linear_f32(g),
gamma_u8_from_linear_f32(b),
linear_u8_from_linear_f32(a.abs()),
]
}
}
impl From<Hsva> for Rgba {
fn from(hsva: Hsva) -> Rgba {
Rgba(hsva.to_rgba_premultiplied())
}
}
impl From<Rgba> for Hsva {
fn from(rgba: Rgba) -> Hsva {
Self::from_rgba_premultiplied(rgba.0[0], rgba.0[1], rgba.0[2], rgba.0[3])
}
}
impl From<Hsva> for Color32 {
fn from(hsva: Hsva) -> Color32 {
Color32::from(Rgba::from(hsva))
}
}
impl From<Color32> for Hsva {
fn from(srgba: Color32) -> Hsva {
Hsva::from(Rgba::from(srgba))
}
}
/// All ranges in 0-1, rgb is linear.
pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) {
#![allow(clippy::many_single_char_names)]
let min = r.min(g.min(b));
let max = r.max(g.max(b)); // value
let range = max - min;
let h = if max == min {
0.0 // hue is undefined
} else if max == r {
(g - b) / (6.0 * range)
} else if max == g {
(b - r) / (6.0 * range) + 1.0 / 3.0
} else {
// max == b
(r - g) / (6.0 * range) + 2.0 / 3.0
};
let h = (h + 1.0).fract(); // wrap
let s = if max == 0.0 { 0.0 } else { 1.0 - min / max };
(h, s, max)
}
/// All ranges in 0-1, rgb is linear.
pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] {
#![allow(clippy::many_single_char_names)]
let h = (h.fract() + 1.0).fract(); // wrap
let s = s.clamp(0.0, 1.0);
let f = h * 6.0 - (h * 6.0).floor();
let p = v * (1.0 - s);
let q = v * (1.0 - f * s);
let t = v * (1.0 - (1.0 - f) * s);
match (h * 6.0).floor() as i32 % 6 {
0 => [v, t, p],
1 => [q, v, p],
2 => [p, v, t],
3 => [p, q, v],
4 => [t, p, v],
5 => [v, p, q],
_ => unreachable!(),
}
}
#[test]
#[ignore] // a bit expensive
fn test_hsv_roundtrip() {
for r in 0..=255 {
for g in 0..=255 {
for b in 0..=255 {
let srgba = Color32::from_rgb(r, g, b);
let hsva = Hsva::from(srgba);
assert_eq!(srgba, Color32::from(hsva));
}
}
}
}

View file

@ -0,0 +1,66 @@
use crate::{gamma_from_linear, linear_from_gamma, Color32, Hsva, Rgba};
/// Like Hsva but with the `v` value (brightness) being gamma corrected
/// so that it is somewhat perceptually even.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct HsvaGamma {
/// hue 0-1
pub h: f32,
/// saturation 0-1
pub s: f32,
/// value 0-1, in gamma-space (~perceptually even)
pub v: f32,
/// alpha 0-1. A negative value signifies an additive color (and alpha is ignored).
pub a: f32,
}
impl From<HsvaGamma> for Rgba {
fn from(hsvag: HsvaGamma) -> Rgba {
Hsva::from(hsvag).into()
}
}
impl From<HsvaGamma> for Color32 {
fn from(hsvag: HsvaGamma) -> Color32 {
Rgba::from(hsvag).into()
}
}
impl From<HsvaGamma> for Hsva {
fn from(hsvag: HsvaGamma) -> Hsva {
let HsvaGamma { h, s, v, a } = hsvag;
Hsva {
h,
s,
v: linear_from_gamma(v),
a,
}
}
}
impl From<Rgba> for HsvaGamma {
fn from(rgba: Rgba) -> HsvaGamma {
Hsva::from(rgba).into()
}
}
impl From<Color32> for HsvaGamma {
fn from(srgba: Color32) -> HsvaGamma {
Hsva::from(srgba).into()
}
}
impl From<Hsva> for HsvaGamma {
fn from(hsva: Hsva) -> HsvaGamma {
let Hsva { h, s, v, a } = hsva;
HsvaGamma {
h,
s,
v: gamma_from_linear(v),
a,
}
}
}

173
crates/ecolor/src/lib.rs Normal file
View file

@ -0,0 +1,173 @@
//! Color conversions and types.
//!
//! If you want a compact color representation, use [`Color32`].
//! If you want to manipulate RGBA colors use [`Rgba`].
//! If you want to manipulate colors in a way closer to how humans think about colors, use [`HsvaGamma`].
//!
//! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!
#![allow(clippy::wrong_self_convention)]
#[cfg(feature = "cint")]
mod cint_impl;
#[cfg(feature = "cint")]
pub use cint_impl::*;
mod color32;
pub use color32::*;
mod hsva_gamma;
pub use hsva_gamma::*;
mod hsva;
pub use hsva::*;
#[cfg(feature = "color-hex")]
mod hex_color_macro;
mod rgba;
pub use rgba::*;
// ----------------------------------------------------------------------------
// Color conversion:
impl From<Color32> for Rgba {
fn from(srgba: Color32) -> Rgba {
Rgba([
linear_f32_from_gamma_u8(srgba.0[0]),
linear_f32_from_gamma_u8(srgba.0[1]),
linear_f32_from_gamma_u8(srgba.0[2]),
linear_f32_from_linear_u8(srgba.0[3]),
])
}
}
impl From<Rgba> for Color32 {
fn from(rgba: Rgba) -> Color32 {
Color32([
gamma_u8_from_linear_f32(rgba.0[0]),
gamma_u8_from_linear_f32(rgba.0[1]),
gamma_u8_from_linear_f32(rgba.0[2]),
linear_u8_from_linear_f32(rgba.0[3]),
])
}
}
/// gamma [0, 255] -> linear [0, 1].
pub fn linear_f32_from_gamma_u8(s: u8) -> f32 {
if s <= 10 {
s as f32 / 3294.6
} else {
((s as f32 + 14.025) / 269.025).powf(2.4)
}
}
/// linear [0, 255] -> linear [0, 1].
/// Useful for alpha-channel.
#[inline(always)]
pub fn linear_f32_from_linear_u8(a: u8) -> f32 {
a as f32 / 255.0
}
/// linear [0, 1] -> gamma [0, 255] (clamped).
/// Values outside this range will be clamped to the range.
pub fn gamma_u8_from_linear_f32(l: f32) -> u8 {
if l <= 0.0 {
0
} else if l <= 0.0031308 {
fast_round(3294.6 * l)
} else if l <= 1.0 {
fast_round(269.025 * l.powf(1.0 / 2.4) - 14.025)
} else {
255
}
}
/// linear [0, 1] -> linear [0, 255] (clamped).
/// Useful for alpha-channel.
#[inline(always)]
pub fn linear_u8_from_linear_f32(a: f32) -> u8 {
fast_round(a * 255.0)
}
fn fast_round(r: f32) -> u8 {
(r + 0.5).floor() as _ // rust does a saturating cast since 1.45
}
#[test]
pub fn test_srgba_conversion() {
for b in 0..=255 {
let l = linear_f32_from_gamma_u8(b);
assert!(0.0 <= l && l <= 1.0);
assert_eq!(gamma_u8_from_linear_f32(l), b);
}
}
/// gamma [0, 1] -> linear [0, 1] (not clamped).
/// Works for numbers outside this range (e.g. negative numbers).
pub fn linear_from_gamma(gamma: f32) -> f32 {
if gamma < 0.0 {
-linear_from_gamma(-gamma)
} else if gamma <= 0.04045 {
gamma / 12.92
} else {
((gamma + 0.055) / 1.055).powf(2.4)
}
}
/// linear [0, 1] -> gamma [0, 1] (not clamped).
/// Works for numbers outside this range (e.g. negative numbers).
pub fn gamma_from_linear(linear: f32) -> f32 {
if linear < 0.0 {
-gamma_from_linear(-linear)
} else if linear <= 0.0031308 {
12.92 * linear
} else {
1.055 * linear.powf(1.0 / 2.4) - 0.055
}
}
// ----------------------------------------------------------------------------
/// An assert that is only active when `epaint` is compiled with the `extra_asserts` feature
/// or with the `extra_debug_asserts` feature in debug builds.
#[macro_export]
macro_rules! ecolor_assert {
($($arg: tt)*) => {
if cfg!(any(
feature = "extra_asserts",
all(feature = "extra_debug_asserts", debug_assertions),
)) {
assert!($($arg)*);
}
}
}
// ----------------------------------------------------------------------------
/// Cheap and ugly.
/// Made for graying out disabled `Ui`s.
pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 {
let [mut r, mut g, mut b, mut a] = color.to_array();
if a == 0 {
r /= 2;
g /= 2;
b /= 2;
} else if a < 170 {
// Cheapish and looks ok.
// Works for e.g. grid stripes.
let div = (2 * 255 / a as i32) as u8;
r = r / 2 + target.r() / div;
g = g / 2 + target.g() / div;
b = b / 2 + target.b() / div;
a /= 2;
} else {
r = r / 2 + target.r() / 2;
g = g / 2 + target.g() / 2;
b = b / 2 + target.b() / 2;
}
Color32::from_rgba_premultiplied(r, g, b, a)
}

266
crates/ecolor/src/rgba.rs Normal file
View file

@ -0,0 +1,266 @@
use crate::{
gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8,
linear_u8_from_linear_f32,
};
/// 0-1 linear space `RGBA` color with premultiplied alpha.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct Rgba(pub(crate) [f32; 4]);
impl std::ops::Index<usize> for Rgba {
type Output = f32;
#[inline(always)]
fn index(&self, index: usize) -> &f32 {
&self.0[index]
}
}
impl std::ops::IndexMut<usize> for Rgba {
#[inline(always)]
fn index_mut(&mut self, index: usize) -> &mut f32 {
&mut self.0[index]
}
}
#[inline(always)]
pub(crate) fn f32_hash<H: std::hash::Hasher>(state: &mut H, f: f32) {
if f == 0.0 {
state.write_u8(0);
} else if f.is_nan() {
state.write_u8(1);
} else {
use std::hash::Hash;
f.to_bits().hash(state);
}
}
#[allow(clippy::derive_hash_xor_eq)]
impl std::hash::Hash for Rgba {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
crate::f32_hash(state, self.0[0]);
crate::f32_hash(state, self.0[1]);
crate::f32_hash(state, self.0[2]);
crate::f32_hash(state, self.0[3]);
}
}
impl Rgba {
pub const TRANSPARENT: Rgba = Rgba::from_rgba_premultiplied(0.0, 0.0, 0.0, 0.0);
pub const BLACK: Rgba = Rgba::from_rgb(0.0, 0.0, 0.0);
pub const WHITE: Rgba = Rgba::from_rgb(1.0, 1.0, 1.0);
pub const RED: Rgba = Rgba::from_rgb(1.0, 0.0, 0.0);
pub const GREEN: Rgba = Rgba::from_rgb(0.0, 1.0, 0.0);
pub const BLUE: Rgba = Rgba::from_rgb(0.0, 0.0, 1.0);
#[inline(always)]
pub const fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
Self([r, g, b, a])
}
#[inline(always)]
pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
Self([r * a, g * a, b * a, a])
}
#[inline(always)]
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
let r = linear_f32_from_gamma_u8(r);
let g = linear_f32_from_gamma_u8(g);
let b = linear_f32_from_gamma_u8(b);
let a = linear_f32_from_linear_u8(a);
Self::from_rgba_premultiplied(r, g, b, a)
}
#[inline(always)]
pub fn from_srgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
let r = linear_f32_from_gamma_u8(r);
let g = linear_f32_from_gamma_u8(g);
let b = linear_f32_from_gamma_u8(b);
let a = linear_f32_from_linear_u8(a);
Self::from_rgba_premultiplied(r * a, g * a, b * a, a)
}
#[inline(always)]
pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self {
Self([r, g, b, 1.0])
}
#[inline(always)]
pub const fn from_gray(l: f32) -> Self {
Self([l, l, l, 1.0])
}
pub fn from_luminance_alpha(l: f32, a: f32) -> Self {
crate::ecolor_assert!(0.0 <= l && l <= 1.0);
crate::ecolor_assert!(0.0 <= a && a <= 1.0);
Self([l * a, l * a, l * a, a])
}
/// Transparent black
#[inline(always)]
pub fn from_black_alpha(a: f32) -> Self {
crate::ecolor_assert!(0.0 <= a && a <= 1.0);
Self([0.0, 0.0, 0.0, a])
}
/// Transparent white
#[inline(always)]
pub fn from_white_alpha(a: f32) -> Self {
crate::ecolor_assert!(0.0 <= a && a <= 1.0, "a: {}", a);
Self([a, a, a, a])
}
/// Return an additive version of this color (alpha = 0)
#[inline(always)]
pub fn additive(self) -> Self {
let [r, g, b, _] = self.0;
Self([r, g, b, 0.0])
}
/// Multiply with e.g. 0.5 to make us half transparent
#[inline(always)]
pub fn multiply(self, alpha: f32) -> Self {
Self([
alpha * self[0],
alpha * self[1],
alpha * self[2],
alpha * self[3],
])
}
#[inline(always)]
pub fn r(&self) -> f32 {
self.0[0]
}
#[inline(always)]
pub fn g(&self) -> f32 {
self.0[1]
}
#[inline(always)]
pub fn b(&self) -> f32 {
self.0[2]
}
#[inline(always)]
pub fn a(&self) -> f32 {
self.0[3]
}
/// How perceptually intense (bright) is the color?
#[inline]
pub fn intensity(&self) -> f32 {
0.3 * self.r() + 0.59 * self.g() + 0.11 * self.b()
}
/// Returns an opaque version of self
pub fn to_opaque(&self) -> Self {
if self.a() == 0.0 {
// Additive or fully transparent black.
Self::from_rgb(self.r(), self.g(), self.b())
} else {
// un-multiply alpha:
Self::from_rgb(
self.r() / self.a(),
self.g() / self.a(),
self.b() / self.a(),
)
}
}
/// Premultiplied RGBA
#[inline(always)]
pub fn to_array(&self) -> [f32; 4] {
[self.r(), self.g(), self.b(), self.a()]
}
/// Premultiplied RGBA
#[inline(always)]
pub fn to_tuple(&self) -> (f32, f32, f32, f32) {
(self.r(), self.g(), self.b(), self.a())
}
/// unmultiply the alpha
pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
let a = self.a();
if a == 0.0 {
// Additive, let's assume we are black
self.0
} else {
[self.r() / a, self.g() / a, self.b() / a, a]
}
}
/// unmultiply the alpha
pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
let [r, g, b, a] = self.to_rgba_unmultiplied();
[
gamma_u8_from_linear_f32(r),
gamma_u8_from_linear_f32(g),
gamma_u8_from_linear_f32(b),
linear_u8_from_linear_f32(a.abs()),
]
}
}
impl std::ops::Add for Rgba {
type Output = Rgba;
#[inline(always)]
fn add(self, rhs: Rgba) -> Rgba {
Rgba([
self[0] + rhs[0],
self[1] + rhs[1],
self[2] + rhs[2],
self[3] + rhs[3],
])
}
}
impl std::ops::Mul<Rgba> for Rgba {
type Output = Rgba;
#[inline(always)]
fn mul(self, other: Rgba) -> Rgba {
Rgba([
self[0] * other[0],
self[1] * other[1],
self[2] * other[2],
self[3] * other[3],
])
}
}
impl std::ops::Mul<f32> for Rgba {
type Output = Rgba;
#[inline(always)]
fn mul(self, factor: f32) -> Rgba {
Rgba([
self[0] * factor,
self[1] * factor,
self[2] * factor,
self[3] * factor,
])
}
}
impl std::ops::Mul<Rgba> for f32 {
type Output = Rgba;
#[inline(always)]
fn mul(self, rgba: Rgba) -> Rgba {
Rgba([
self * rgba[0],
self * rgba[1],
self * rgba[2],
self * rgba[3],
])
}
}

229
crates/eframe/CHANGELOG.md Normal file
View file

@ -0,0 +1,229 @@
# Changelog for eframe
All notable changes to the `eframe` crate.
NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/CHANGELOG.md), [`egui_glow`](../egui_glow/CHANGELOG.md),and [`egui-wgpu`](../egui-wgpu/CHANGELOG.md) have their own changelogs!
## 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](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
* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)).
* Allow empty textures with the glow renderer.
#### Desktop/Native:
* Don't repaint when just moving window ([#1980](https://github.com/emilk/egui/pull/1980)).
* Added `NativeOptions::event_loop_builder` hook for apps to change platform specific event loop options ([#1952](https://github.com/emilk/egui/pull/1952)).
* Enabled deferred render state initialization to support Android ([#1952](https://github.com/emilk/egui/pull/1952)).
* Added `shader_version` to `NativeOptions` for cross compiling support on different target OpenGL | ES versions (on native `glow` renderer only) ([#1993](https://github.com/emilk/egui/pull/1993)).
* Fix: app state is now saved when user presses Cmd-Q on Mac ([#2013](https://github.com/emilk/egui/pull/2013)).
* Added `center` to `NativeOptions` and `monitor_size` to `WindowInfo` on desktop ([#2035](https://github.com/emilk/egui/pull/2035)).
* Improve IME support ([#2046](https://github.com/emilk/egui/pull/2046)).
* Added mouse-passthrough option ([#2080](https://github.com/emilk/egui/pull/2080)).
* Added `NativeOptions::fullsize_content` option on Mac to build titlebar-less windows with floating window controls ([#2049](https://github.com/emilk/egui/pull/2049)).
* Wgpu device/adapter/surface creation has now various configuration options exposed via `NativeOptions/WebOptions::wgpu_options` ([#2207](https://github.com/emilk/egui/pull/2207)).
* Fix: Make sure that `native_pixels_per_point` is updated ([#2256](https://github.com/emilk/egui/pull/2256)).
* Added optional, but enabled by default, integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)).
* Fix: Less flickering on resize on Windows ([#2280](https://github.com/emilk/egui/pull/2280)).
#### Web:
* ⚠️ BREAKING: `start_web` is a now `async` ([#2107](https://github.com/emilk/egui/pull/2107)).
* Web: You can now use WebGL on top of `wgpu` by enabling the `wgpu` feature (and disabling `glow` via disabling default features) ([#2107](https://github.com/emilk/egui/pull/2107)).
* Web: Add `WebInfo::user_agent` ([#2202](https://github.com/emilk/egui/pull/2202)).
* Web: you can access your application from JS using `AppRunner::app_mut`. See `crates/egui_demo_app/src/lib.rs` ([#1886](https://github.com/emilk/egui/pull/1886)).
## 0.19.0 - 2022-08-20
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
* Added `wgpu` rendering backed ([#1564](https://github.com/emilk/egui/pull/1564)):
* Added features `wgpu` and `glow`.
* Added `NativeOptions::renderer` to switch between the rendering backends.
* `egui_glow`: remove calls to `gl.get_error` in release builds to speed up rendering ([#1583](https://github.com/emilk/egui/pull/1583)).
* Added `App::post_rendering` for e.g. reading the framebuffer ([#1591](https://github.com/emilk/egui/pull/1591)).
* Use `Arc` for `glow::Context` instead of `Rc` ([#1640](https://github.com/emilk/egui/pull/1640)).
* Fixed bug where the result returned from `App::on_exit_event` would sometimes be ignored ([#1696](https://github.com/emilk/egui/pull/1696)).
* Added `NativeOptions::follow_system_theme` and `NativeOptions::default_theme` ([#1726](https://github.com/emilk/egui/pull/1726)).
* Selectively expose parts of the API based on target arch (`wasm32` or not) ([#1867](https://github.com/emilk/egui/pull/1867)).
#### Desktop/Native:
* Fixed clipboard on Wayland ([#1613](https://github.com/emilk/egui/pull/1613)).
* Added ability to read window position and size with `frame.info().window_info` ([#1617](https://github.com/emilk/egui/pull/1617)).
* Allow running on native without hardware accelerated rendering. Change with `NativeOptions::hardware_acceleration` ([#1681](https://github.com/emilk/egui/pull/1681), [#1693](https://github.com/emilk/egui/pull/1693)).
* Fixed window position persistence ([#1745](https://github.com/emilk/egui/pull/1745)).
* Fixed mouse cursor change on Linux ([#1747](https://github.com/emilk/egui/pull/1747)).
* Added `Frame::set_visible` ([#1808](https://github.com/emilk/egui/pull/1808)).
* Added fullscreen support ([#1866](https://github.com/emilk/egui/pull/1866)).
* You can now continue execution after closing the native desktop window ([#1889](https://github.com/emilk/egui/pull/1889)).
* `Frame::quit` has been renamed to `Frame::close` and `App::on_exit_event` is now `App::on_close_event` ([#1943](https://github.com/emilk/egui/pull/1943)).
#### Web:
* Added ability to stop/re-run web app from JavaScript. ⚠️ You need to update your CSS with `html, body: { height: 100%; width: 100%; }` ([#1803](https://github.com/emilk/egui/pull/1650)).
* Added `WebOptions::follow_system_theme` and `WebOptions::default_theme` ([#1726](https://github.com/emilk/egui/pull/1726)).
* Added option to select WebGL version ([#1803](https://github.com/emilk/egui/pull/1803)).
## 0.18.0 - 2022-04-30
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
* Removed `eframe::epi` - everything is now in `eframe` (`eframe::App`, `eframe::Frame` etc) ([#1545](https://github.com/emilk/egui/pull/1545)).
* Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)).
* Changed app creation/setup ([#1363](https://github.com/emilk/egui/pull/1363)):
* Removed `App::setup` and `App::name`.
* Provide `CreationContext` when creating app with egui context, storage, integration info and glow context.
* Change interface of `run_native` and `start_web`.
* Added `Frame::storage()` and `Frame::storage_mut()` ([#1418](https://github.com/emilk/egui/pull/1418)).
* You can now load/save state in `App::update`
* Changed `App::update` to take `&mut Frame` instead of `&Frame`.
* `Frame` is no longer `Clone` or `Sync`.
* Added `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)).
#### Desktop/Native:
* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)).
* Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)).
* Added new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
* `dark-light` (dark mode detection) is now an opt-in feature ([#1437](https://github.com/emilk/egui/pull/1437)).
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
* Moved app persistence to a background thread, allowing for smoother frame rates (on native).
* Added `Frame::set_window_pos` ([#1505](https://github.com/emilk/egui/pull/1505)).
#### Web:
* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)).
* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)).
## 0.17.0 - 2022-02-22
* Removed `Frame::alloc_texture`. Use `egui::Context::load_texture` instead ([#1110](https://github.com/emilk/egui/pull/1110)).
* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)).
* Log using the `tracing` crate. Log to stdout by adding `tracing_subscriber::fmt::init();` to your `main` ([#1192](https://github.com/emilk/egui/pull/1192)).
#### Desktop/Native:
* The default native backend is now `egui_glow` (instead of `egui_glium`) ([#1020](https://github.com/emilk/egui/pull/1020)).
* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)).
* Fixed horizontal scrolling direction on Linux.
* Added `App::on_exit_event` ([#1038](https://github.com/emilk/egui/pull/1038))
* Added `NativeOptions::initial_window_pos`.
* Fixed `enable_drag` for Windows OS ([#1108](https://github.com/emilk/egui/pull/1108)).
#### Web:
* The default web painter is now `egui_glow` (instead of WebGL) ([#1020](https://github.com/emilk/egui/pull/1020)).
* Fixed glow failure on Chromium ([#1092](https://github.com/emilk/egui/pull/1092)).
* Updated `eframe::IntegrationInfo::web_location_hash` on `hashchange` event ([#1140](https://github.com/emilk/egui/pull/1140)).
* Expose all parts of the location/url in `frame.info().web_info` ([#1258](https://github.com/emilk/egui/pull/1258)).
## 0.16.0 - 2021-12-29
* `Frame` can now be cloned, saved, and passed to background threads ([#999](https://github.com/emilk/egui/pull/999)).
* Added `Frame::request_repaint` to replace `repaint_signal` ([#999](https://github.com/emilk/egui/pull/999)).
* Added `Frame::alloc_texture/free_texture` to replace `tex_allocator` ([#999](https://github.com/emilk/egui/pull/999)).
#### Web:
* Fixed [dark rendering in WebKitGTK](https://github.com/emilk/egui/issues/794) ([#888](https://github.com/emilk/egui/pull/888/)).
* Added feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)).
## 0.15.0 - 2021-10-24
* `Frame` now provides `set_window_title` to set window title dynamically ([#828](https://github.com/emilk/egui/pull/828)).
* `Frame` now provides `set_decorations` to set whether to show window decorations.
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
* Added `App::persist_native_window` and `App::persist_egui_memory` to control what gets persisted.
#### Desktop/Native:
* Increase native scroll speed.
* Added new backend `egui_glow` as an alternative to `egui_glium`. Enable with `default-features = false, features = ["default_fonts", "egui_glow"]`.
#### Web:
* Implement `eframe::NativeTexture` trait for the WebGL painter.
* Deprecate `Painter::register_webgl_texture.
* Fixed multiline paste.
* Fixed painting with non-opaque backgrounds.
* Improve text input on mobile and for IME.
## 0.14.0 - 2021-08-24
* Added dragging and dropping files into egui.
* Improve http fetch API.
* `run_native` now returns when the app is closed.
* Web: Made text thicker and less pixelated.
## 0.13.1 - 2021-06-24
* Fixed `http` feature flag and docs
## 0.13.0 - 2021-06-24
* `App::setup` now takes a `Frame` and `Storage` by argument.
* `App::load` has been removed. Implement `App::setup` instead.
* Web: Default to light visuals unless the system reports a preference for dark mode.
* Web: Improve alpha blending, making fonts look much better (especially in light mode)
* Web: Fix double-paste bug
## 0.12.0 - 2021-05-10
* Moved options out of `trait App` into new `NativeOptions`.
* Added option for `always_on_top`.
* Web: Scroll faster when scrolling with mouse wheel.
## 0.11.0 - 2021-04-05
* You can now turn your window transparent with the `App::transparent` option.
* You can now disable window decorations with the `App::decorated` option.
* Web: [Fix mobile and IME text input](https://github.com/emilk/egui/pull/253)
* Web: Hold down a modifier key when clicking a link to open it in a new tab.
Contributors: [n2](https://github.com/n2)
## 0.10.0 - 2021-02-28
* [You can now set your own app icons](https://github.com/emilk/egui/pull/193).
* You can control the initial size of the native window with `App::initial_window_size`.
* You can control the maximum egui web canvas size with `App::max_size_points`.
* `Frame::tex_allocator()` no longer returns an `Option` (there is always a texture allocator).
## 0.9.0 - 2021-02-07
* [Added support for HTTP body](https://github.com/emilk/egui/pull/139).
* Web: Right-clicks will no longer open browser context menu.
* Web: Fix a bug where one couldn't select items in a combo box on a touch screen.
## 0.8.0 - 2021-01-17
* Simplify `TextureAllocator` interface.
* WebGL2 is now supported, with improved texture sampler. WebGL1 will be used as a fallback.
* Web: Slightly improved alpha-blending (work-around for non-existing linear-space blending).
* Web: Call `prevent_default` for arrow keys when entering text
## 0.7.0 - 2021-01-04
* Initial release of `eframe`

169
crates/eframe/Cargo.toml Normal file
View file

@ -0,0 +1,169 @@
[package]
name = "eframe"
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"
rust-version = "1.65"
homepage = "https://github.com/emilk/egui/tree/master/crates/eframe"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/emilk/egui/tree/master/crates/eframe"
categories = ["gui", "game-development"]
keywords = ["egui", "gui", "gamedev"]
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
[package.metadata.docs.rs]
all-features = true
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
[lib]
[features]
default = ["accesskit", "default_fonts", "glow"]
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
accesskit = ["egui/accesskit", "egui-winit/accesskit"]
## Detect dark mode system preference using [`dark-light`](https://docs.rs/dark-light).
##
## See also [`NativeOptions::follow_system_theme`] and [`NativeOptions::default_theme`].
dark-light = ["dep:dark-light"]
## If set, egui will use `include_bytes!` to bundle some fonts.
## If you plan on specifying your own fonts you may disable this feature.
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", "dep:glutin-winit"]
## Enable saving app state to disk.
persistence = [
"directories-next",
"egui-winit/serde",
"egui/persistence",
"ron",
"serde",
]
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
##
## Only enabled on native, because of the low resolution (1ms) of time keeping in browsers.
## `eframe` will call `puffin::GlobalProfiler::lock().new_frame()` for you
puffin = ["dep:puffin", "egui_glow?/puffin", "egui-wgpu?/puffin"]
## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web.
##
## For other platforms, use the "accesskit" feature instead.
web_screen_reader = ["tts"]
## If set, eframe will look for the env-var `EFRAME_SCREENSHOT_TO` and write a screenshot to that location, and then quit.
## This is used to generate images for the examples.
__screenshot = ["dep:image"]
## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu)).
## This overrides the `glow` feature.
wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
[dependencies]
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
"bytemuck",
"tracing",
] }
thiserror = "1.0.37"
tracing = { version = "0.1", default-features = false, features = ["std"] }
#! ### Optional dependencies
## Enable this when generating docs.
document-features = { version = "0.2", 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.21.1", path = "../egui-winit", default-features = false, features = [
"clipboard",
"links",
] }
raw-window-handle = { version = "0.5.0" }
winit = "0.28.1"
# optional native:
dark-light = { version = "1.0", optional = true }
directories-next = { version = "2", optional = true }
egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true, features = [
"winit",
] } # if wgpu is used, use it with winit
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", optional = true }
glutin-winit = { version = "0.3.0", optional = true }
image = { version = "0.24", optional = true, default-features = false, features = [
"png",
] }
puffin = { version = "0.14", optional = true }
wgpu = { version = "0.15.0", optional = true }
# -------------------------------------------
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
bytemuck = "1.7"
js-sys = "0.3"
percent-encoding = "2.1"
wasm-bindgen = "=0.2.84"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3.58", features = [
"BinaryType",
"Blob",
"Clipboard",
"ClipboardEvent",
"CompositionEvent",
"console",
"CssStyleDeclaration",
"DataTransfer",
"DataTransferItem",
"DataTransferItemList",
"Document",
"DomRect",
"DragEvent",
"Element",
"Event",
"EventListener",
"EventTarget",
"ExtSRgb",
"File",
"FileList",
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
"HtmlInputElement",
"InputEvent",
"KeyboardEvent",
"Location",
"MediaQueryList",
"MouseEvent",
"Navigator",
"Performance",
"Storage",
"Touch",
"TouchEvent",
"TouchList",
"WebGl2RenderingContext",
"WebglDebugRendererInfo",
"WebGlRenderingContext",
"WheelEvent",
"Window",
] }
# optional web:
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"] }

64
crates/eframe/README.md Normal file
View file

@ -0,0 +1,64 @@
# eframe: the [`egui`](https://github.com/emilk/egui) framework
[![Latest version](https://img.shields.io/crates/v/eframe.svg)](https://crates.io/crates/eframe)
[![Documentation](https://docs.rs/eframe/badge.svg)](https://docs.rs/eframe)
[![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)
`eframe` is the official framework library for writing apps using [`egui`](https://github.com/emilk/egui). The app can be compiled both to run natively (cross platform) or be compiled to a web app (using WASM).
To get started, see the [examples](https://github.com/emilk/egui/tree/master/examples).
To learn how to set up `eframe` for web and native, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
There is also a tutorial video at <https://www.youtube.com/watch?v=NtUkr_z7l84>.
For how to use `egui`, see [the egui docs](https://docs.rs/egui).
---
`eframe` uses [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering, and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
To use on Linux, first run:
```
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.
You can opt-in to the using [`egui_wgpu`](https://github.com/emilk/egui/tree/master/crates/egui_wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`.
## Alternatives
`eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others.
You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/winit) to build your own app as demonstrated in <https://github.com/emilk/egui/blob/master/crates/egui_glow/examples/pure_glow.rs>.
## Problems with running egui on the web
`eframe` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and WASM, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides.
* Rendering: Getting pixel-perfect rendering right on the web is very difficult.
* Search: you cannot search an egui web page like you would a normal web page.
* Bringing up an on-screen keyboard on mobile: there is no JS function to do this, so `eframe` fakes it by adding some invisible DOM elements. It doesn't always work.
* Mobile text editing is not as good as for a normal web app.
* Accessibility: There is an experimental screen reader for `eframe`, but it has to be enabled explicitly. There is no JS function to ask "Does the user want a screen reader?" (and there should probably not be such a function, due to user tracking/integrity concerns).
* No integration with browser settings for colors and fonts.
In many ways, `eframe` is trying to make the browser do something it wasn't designed to do (though there are many things browser vendors could do to improve how well libraries like egui work).
The suggested use for `eframe` are for web apps where performance and responsiveness are more important than accessibility and mobile text editing.
## Companion crates
Not all rust crates work when compiled to WASM, but here are some useful crates have been designed to work well both natively and as WASM:
* Audio: [`cpal`](https://github.com/RustAudio/cpal).
* HTTP client: [`ehttp`](https://github.com/emilk/ehttp) and [`reqwest`](https://github.com/seanmonstar/reqwest).
* Time: [`chrono`](https://github.com/chronotope/chrono).
* WebSockets: [`ewebsock`](https://github.com/rerun-io/ewebsock).
## Name
The _frame_ in `eframe` stands both for the frame in which your `egui` app resides and also for "framework" (`frame` is a framework, `egui` is a library).

1065
crates/eframe/src/epi.rs Normal file

File diff suppressed because it is too large Load diff

262
crates/eframe/src/lib.rs Normal file
View file

@ -0,0 +1,262 @@
//! eframe - the [`egui`] framework crate
//!
//! If you are planning to write an app for web or native,
//! and want to use [`egui`] for everything, then `eframe` is for you!
//!
//! To get started, see the [examples](https://github.com/emilk/egui/tree/master/examples).
//! To learn how to set up `eframe` for web and native, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
//!
//! In short, you implement [`App`] (especially [`App::update`]) and then
//! call [`crate::run_native`] from your `main.rs`, and/or call `eframe::start_web` from your `lib.rs`.
//!
//! ## Usage, native:
//! ``` no_run
//! use eframe::egui;
//!
//! fn main() {
//! let native_options = eframe::NativeOptions::default();
//! eframe::run_native("My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))));
//! }
//!
//! #[derive(Default)]
//! struct MyEguiApp {}
//!
//! impl MyEguiApp {
//! fn new(cc: &eframe::CreationContext<'_>) -> Self {
//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
//! // Restore app state using cc.storage (requires the "persistence" feature).
//! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
//! // for e.g. egui::PaintCallback.
//! Self::default()
//! }
//! }
//!
//! impl eframe::App for MyEguiApp {
//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
//! egui::CentralPanel::default().show(ctx, |ui| {
//! ui.heading("Hello World!");
//! });
//! }
//! }
//! ```
//!
//! ## Usage, web:
//! ``` no_run
//! #[cfg(target_arch = "wasm32")]
//! use wasm_bindgen::prelude::*;
//!
//! /// Call this once from the HTML.
//! #[cfg(target_arch = "wasm32")]
//! #[wasm_bindgen]
//! pub async fn start(canvas_id: &str) -> Result<AppRunnerRef, eframe::wasm_bindgen::JsValue> {
//! let web_options = eframe::WebOptions::default();
//! eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))).await
//! }
//! ```
//!
//! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!
#![allow(clippy::needless_doctest_main)]
// Re-export all useful libraries:
pub use {egui, egui::emath, egui::epaint};
#[cfg(feature = "glow")]
pub use {egui_glow, glow};
#[cfg(feature = "wgpu")]
pub use {egui_wgpu, wgpu};
mod epi;
// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
pub use epi::*;
// ----------------------------------------------------------------------------
// When compiling for web
#[cfg(target_arch = "wasm32")]
pub mod web;
#[cfg(target_arch = "wasm32")]
pub use wasm_bindgen;
#[cfg(target_arch = "wasm32")]
use web::AppRunnerRef;
#[cfg(target_arch = "wasm32")]
pub use web_sys;
/// Install event listeners to register different input events
/// and start running the given app.
///
/// ``` no_run
/// #[cfg(target_arch = "wasm32")]
/// use wasm_bindgen::prelude::*;
///
/// /// This is the entry-point for all the web-assembly.
/// /// This is called from the HTML.
/// /// It loads the app, installs some callbacks, then returns.
/// /// It returns a handle to the running app that can be stopped calling `AppRunner::stop_web`.
/// /// You can add more callbacks like this if you want to call in to your code.
/// #[cfg(target_arch = "wasm32")]
/// #[wasm_bindgen]
/// pub struct WebHandle {
/// handle: AppRunnerRef,
/// }
/// #[cfg(target_arch = "wasm32")]
/// #[wasm_bindgen]
/// pub async fn start(canvas_id: &str) -> Result<WebHandle, eframe::wasm_bindgen::JsValue> {
/// let web_options = eframe::WebOptions::default();
/// eframe::start_web(
/// canvas_id,
/// web_options,
/// Box::new(|cc| Box::new(MyEguiApp::new(cc))),
/// )
/// .await
/// .map(|handle| WebHandle { handle })
/// }
/// ```
///
/// # Errors
/// Failing to initialize WebGL graphics.
#[cfg(target_arch = "wasm32")]
pub async fn start_web(
canvas_id: &str,
web_options: WebOptions,
app_creator: AppCreator,
) -> std::result::Result<AppRunnerRef, wasm_bindgen::JsValue> {
let handle = web::start(canvas_id, web_options, app_creator).await?;
Ok(handle)
}
// ----------------------------------------------------------------------------
// 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.
///
/// The first argument is name of your app, used for the title bar of the native window
/// and the save location of persistence (see [`App::save`]).
///
/// Call from `fn main` like this:
/// ``` no_run
/// use eframe::egui;
///
/// fn main() {
/// let native_options = eframe::NativeOptions::default();
/// eframe::run_native("MyApp", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))));
/// }
///
/// #[derive(Default)]
/// struct MyEguiApp {}
///
/// impl MyEguiApp {
/// fn new(cc: &eframe::CreationContext<'_>) -> Self {
/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
/// // Restore app state using cc.storage (requires the "persistence" feature).
/// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
/// // for e.g. egui::PaintCallback.
/// Self::default()
/// }
/// }
///
/// impl eframe::App for MyEguiApp {
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| {
/// ui.heading("Hello World!");
/// });
/// }
/// }
/// ```
///
/// # Errors
/// 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,
app_creator: AppCreator,
) -> Result<()> {
let renderer = native_options.renderer;
#[cfg(not(feature = "__screenshot"))]
assert!(
std::env::var("EFRAME_SCREENSHOT_TO").is_err(),
"EFRAME_SCREENSHOT_TO found without compiling with the '__screenshot' feature"
);
match renderer {
#[cfg(feature = "glow")]
Renderer::Glow => {
tracing::debug!("Using the glow renderer");
native::run::run_glow(app_name, native_options, app_creator)
}
#[cfg(feature = "wgpu")]
Renderer::Wgpu => {
tracing::debug!("Using the wgpu renderer");
native::run::run_wgpu(app_name, native_options, app_creator)
}
}
}
// ----------------------------------------------------------------------------
/// The different problems that can occur when trying to run `eframe`.
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[cfg(not(target_arch = "wasm32"))]
#[error("winit error: {0}")]
Winit(#[from] winit::error::OsError),
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
#[error("glutin error: {0}")]
Glutin(#[from] glutin::error::Error),
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
#[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}")]
Wgpu(#[from] egui_wgpu::WgpuError),
}
pub type Result<T> = std::result::Result<T, Error>;
// ---------------------------------------------------------------------------
#[cfg(not(target_arch = "wasm32"))]
#[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)*);
};
}
pub(crate) use profile_function;
/// 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"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub(crate) use profiling_scopes::*;

View file

@ -0,0 +1,567 @@
use winit::event_loop::EventLoopWindowTarget;
#[cfg(target_os = "macos")]
use winit::platform::macos::WindowBuilderExtMacOS as _;
#[cfg(feature = "accesskit")]
use egui::accesskit;
use egui::NumExt as _;
#[cfg(feature = "accesskit")]
use egui_winit::accesskit_winit;
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,
height: points.y as f64,
}
}
pub fn read_window_info(
window: &winit::window::Window,
pixels_per_point: f32,
window_state: &WindowState,
) -> WindowInfo {
let position = window
.outer_position()
.ok()
.map(|pos| pos.to_logical::<f32>(pixels_per_point.into()))
.map(|pos| egui::Pos2 { x: pos.x, y: pos.y });
let monitor = window.current_monitor().is_some();
let monitor_size = if monitor {
let size = window.current_monitor().unwrap().size();
Some(egui::vec2(size.width as _, size.height as _))
} else {
None
};
let size = window
.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,
},
monitor_size,
}
}
pub fn window_builder<E>(
event_loop: &EventLoopWindowTarget<E>,
title: &str,
native_options: &epi::NativeOptions,
window_settings: Option<WindowSettings>,
) -> winit::window::WindowBuilder {
let epi::NativeOptions {
maximized,
decorated,
fullscreen,
#[cfg(target_os = "macos")]
fullsize_content,
drag_and_drop_support,
icon_data,
initial_window_pos,
initial_window_size,
min_window_size,
max_window_size,
resizable,
transparent,
centered,
..
} = native_options;
let window_icon = icon_data.clone().and_then(load_icon);
let mut window_builder = winit::window::WindowBuilder::new()
.with_title(title)
.with_decorations(*decorated)
.with_fullscreen(fullscreen.then(|| winit::window::Fullscreen::Borderless(None)))
.with_maximized(*maximized)
.with_resizable(*resizable)
.with_transparent(*transparent)
.with_window_icon(window_icon)
// Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
// We must also keep the window hidden until AccessKit is initialized.
.with_visible(false);
#[cfg(target_os = "macos")]
if *fullsize_content {
window_builder = window_builder
.with_title_hidden(true)
.with_titlebar_transparent(true)
.with_fullsize_content_view(true);
}
if let Some(min_size) = *min_window_size {
window_builder = window_builder.with_min_inner_size(points_to_size(min_size));
}
if let Some(max_size) = *max_window_size {
window_builder = window_builder.with_max_inner_size(points_to_size(max_size));
}
window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support);
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 {
if let Some(pos) = *initial_window_pos {
window_builder = window_builder.with_position(winit::dpi::LogicalPosition {
x: pos.x as f64,
y: pos.y as f64,
});
}
if let Some(initial_window_size) = *initial_window_size {
let initial_window_size =
initial_window_size.at_most(largest_monitor_point_size(event_loop));
window_builder = window_builder.with_inner_size(points_to_size(initial_window_size));
}
*initial_window_size
};
if *centered {
if let Some(monitor) = event_loop.available_monitors().next() {
let monitor_size = monitor.size();
let inner_size = inner_size_points.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 });
if monitor_size.width > 0 && monitor_size.height > 0 {
let x = (monitor_size.width - inner_size.x as u32) / 2;
let y = (monitor_size.height - inner_size.y as u32) / 2;
window_builder = window_builder.with_position(winit::dpi::LogicalPosition {
x: x as f64,
y: y as f64,
});
}
}
}
window_builder
}
pub fn apply_native_options_to_window(
window: &winit::window::Window,
native_options: &crate::NativeOptions,
) {
use winit::window::WindowLevel;
window.set_window_level(if native_options.always_on_top {
WindowLevel::AlwaysOnTop
} else {
WindowLevel::Normal
});
}
fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui::Vec2 {
let mut max_size = egui::Vec2::ZERO;
for monitor in event_loop.available_monitors() {
let size = monitor.size().to_logical::<f32>(monitor.scale_factor());
let size = egui::vec2(size.width, size.height);
max_size = max_size.max(size);
}
if max_size == egui::Vec2::ZERO {
egui::Vec2::splat(16000.0)
} else {
max_size
}
}
fn load_icon(icon_data: epi::IconData) -> Option<winit::window::Icon> {
winit::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok()
}
#[cfg(target_os = "windows")]
fn window_builder_drag_and_drop(
window_builder: winit::window::WindowBuilder,
enable: bool,
) -> winit::window::WindowBuilder {
use winit::platform::windows::WindowBuilderExtWindows as _;
window_builder.with_drag_and_drop(enable)
}
#[cfg(not(target_os = "windows"))]
fn window_builder_drag_and_drop(
window_builder: winit::window::WindowBuilder,
_enable: bool,
) -> winit::window::WindowBuilder {
// drag and drop can only be disabled on windows
window_builder
}
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: _,
window_size,
window_title,
decorated,
fullscreen,
drag_window,
window_pos,
visible: _, // handled in post_present
always_on_top,
minimized,
maximized,
} = app_output;
if let Some(decorated) = decorated {
window.set_decorations(decorated);
}
if let Some(window_size) = window_size {
window.set_inner_size(
winit::dpi::PhysicalSize {
width: (current_pixels_per_point * window_size.x).round(),
height: (current_pixels_per_point * window_size.y).round(),
}
.to_logical::<f32>(native_pixels_per_point(window) as f64),
);
}
if let Some(fullscreen) = fullscreen {
window.set_fullscreen(fullscreen.then_some(winit::window::Fullscreen::Borderless(None)));
}
if let Some(window_title) = window_title {
window.set_title(&window_title);
}
if let Some(window_pos) = window_pos {
window.set_outer_position(winit::dpi::PhysicalPosition {
x: window_pos.x as f64,
y: window_pos.y as f64,
});
}
if drag_window {
let _ = window.drag_window();
}
if let Some(always_on_top) = always_on_top {
use winit::window::WindowLevel;
window.set_window_level(if always_on_top {
WindowLevel::AlwaysOnTop
} else {
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;
}
}
// ----------------------------------------------------------------------------
/// For loading/saving app state and/or egui memory to disk.
pub fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
#[cfg(feature = "persistence")]
if let Some(storage) = super::file_storage::FileStorage::from_app_name(_app_name) {
return Some(Box::new(storage));
}
None
}
// ----------------------------------------------------------------------------
/// Everything needed to make a winit-based integration for [`epi`].
pub struct EpiIntegration {
pub frame: epi::Frame,
last_auto_save: std::time::Instant,
pub egui_ctx: egui::Context,
pending_full_output: egui::FullOutput,
egui_winit: egui_winit::State,
/// When set, it is time to close the native window.
close: bool,
can_drag_window: bool,
window_state: WindowState,
}
impl EpiIntegration {
pub fn new<E>(
event_loop: &EventLoopWindowTarget<E>,
max_texture_side: usize,
window: &winit::window::Window,
system_theme: Option<Theme>,
storage: Option<Box<dyn epi::Storage>>,
#[cfg(feature = "glow")] gl: Option<std::sync::Arc<glow::Context>>,
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
) -> Self {
let egui_ctx = egui::Context::default();
let memory = load_egui_memory(storage.as_deref()).unwrap_or_default();
egui_ctx.memory_mut(|mem| *mem = memory);
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_state),
},
output: epi::backend::AppOutput {
visible: Some(true),
..Default::default()
},
storage,
#[cfg(feature = "glow")]
gl,
#[cfg(feature = "wgpu")]
wgpu_render_state,
};
let mut egui_winit = egui_winit::State::new(event_loop);
egui_winit.set_max_texture_side(max_texture_side);
egui_winit.set_pixels_per_point(native_pixels_per_point);
Self {
frame,
last_auto_save: std::time::Instant::now(),
egui_ctx,
egui_winit,
pending_full_output: Default::default(),
close: false,
can_drag_window: false,
window_state,
}
}
#[cfg(feature = "accesskit")]
pub fn init_accesskit<E: From<accesskit_winit::ActionRequestEvent> + Send>(
&mut self,
window: &winit::window::Window,
event_loop_proxy: winit::event_loop::EventLoopProxy<E>,
) {
let egui_ctx = self.egui_ctx.clone();
self.egui_winit
.init_accesskit(window, event_loop_proxy, move || {
// This function is called when an accessibility client
// (e.g. screen reader) makes its first request. If we got here,
// we know that an accessibility tree is actually wanted.
egui_ctx.enable_accesskit();
// Enqueue a repaint so we'll receive a full tree update soon.
egui_ctx.request_repaint();
egui_ctx.accesskit_placeholder_tree_update()
});
}
pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
crate::profile_function!();
let saved_memory: egui::Memory = self.egui_ctx.memory(|mem| mem.clone());
self.egui_ctx
.memory_mut(|mem| mem.set_everything_is_visible(true));
let full_output = self.update(app, window);
self.pending_full_output.append(full_output); // Handle it next frame
self.egui_ctx.memory_mut(|mem| *mem = saved_memory); // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations();
}
/// If `true`, it is time to close the native window.
pub fn should_close(&self) -> bool {
self.close
}
pub fn on_event(
&mut self,
app: &mut dyn epi::App,
event: &winit::event::WindowEvent<'_>,
) -> EventResponse {
use winit::event::{ElementState, MouseButton, WindowEvent};
match event {
WindowEvent::CloseRequested => {
tracing::debug!("Received WindowEvent::CloseRequested");
self.close = app.on_close_event();
tracing::debug!("App::on_close_event returned {}", self.close);
}
WindowEvent::Destroyed => {
tracing::debug!("Received WindowEvent::Destroyed");
self.close = true;
}
WindowEvent::MouseInput {
button: MouseButton::Left,
state: ElementState::Pressed,
..
} => self.can_drag_window = true,
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
self.frame.info.native_pixels_per_point = Some(*scale_factor as _);
}
_ => {}
}
self.egui_winit.on_event(&self.egui_ctx, event)
}
#[cfg(feature = "accesskit")]
pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
self.egui_winit.on_accesskit_action_request(request);
}
pub fn update(
&mut self,
app: &mut dyn epi::App,
window: &winit::window::Window,
) -> 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.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);
{
let mut app_output = self.frame.take_app_output();
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
self.can_drag_window = false;
if app_output.close {
self.close = app.on_close_event();
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,
&mut self.window_state,
);
}
let frame_time = frame_start.elapsed().as_secs_f64() as f32;
self.frame.info.cpu_usage = Some(frame_time);
full_output
}
pub fn post_rendering(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
let inner_size = window.inner_size();
let window_size_px = [inner_size.width, inner_size.height];
app.post_rendering(window_size_px, &self.frame);
}
pub fn post_present(&mut self, window: &winit::window::Window) {
if let Some(visible) = self.frame.output.visible.take() {
window.set_visible(visible);
}
}
pub fn handle_platform_output(
&mut self,
window: &winit::window::Window,
platform_output: egui::PlatformOutput,
) {
self.egui_winit
.handle_platform_output(window, &self.egui_ctx, platform_output);
}
// ------------------------------------------------------------------------
// Persistance stuff:
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
let now = std::time::Instant::now();
if now - self.last_auto_save > app.auto_save_interval() {
self.save(app, window);
self.last_auto_save = now;
}
}
pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) {
#[cfg(feature = "persistence")]
if let Some(storage) = self.frame.storage_mut() {
crate::profile_function!();
if _app.persist_native_window() {
crate::profile_scope!("native_window");
epi::set_value(
storage,
STORAGE_WINDOW_KEY,
&WindowSettings::from_display(_window),
);
}
if _app.persist_egui_memory() {
crate::profile_scope!("egui_memory");
self.egui_ctx
.memory(|mem| epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, mem));
}
{
crate::profile_scope!("App::save");
_app.save(storage);
}
crate::profile_scope!("Storage::flush");
storage.flush();
}
}
}
#[cfg(feature = "persistence")]
const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
#[cfg(feature = "persistence")]
const STORAGE_WINDOW_KEY: &str = "window";
pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<WindowSettings> {
#[cfg(feature = "persistence")]
{
epi::get_value(_storage?, STORAGE_WINDOW_KEY)
}
#[cfg(not(feature = "persistence"))]
None
}
pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Memory> {
#[cfg(feature = "persistence")]
{
epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY)
}
#[cfg(not(feature = "persistence"))]
None
}

View file

@ -0,0 +1,117 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
// ----------------------------------------------------------------------------
/// A key-value store backed by a [RON](https://github.com/ron-rs/ron) file on disk.
/// Used to restore egui state, glium window position/size and app state.
pub struct FileStorage {
ron_filepath: PathBuf,
kv: HashMap<String, String>,
dirty: bool,
last_save_join_handle: Option<std::thread::JoinHandle<()>>,
}
impl Drop for FileStorage {
fn drop(&mut self) {
if let Some(join_handle) = self.last_save_join_handle.take() {
join_handle.join().ok();
}
}
}
impl FileStorage {
/// Store the state in this .ron file.
pub fn from_ron_filepath(ron_filepath: impl Into<PathBuf>) -> Self {
let ron_filepath: PathBuf = ron_filepath.into();
tracing::debug!("Loading app state from {:?}…", ron_filepath);
Self {
kv: read_ron(&ron_filepath).unwrap_or_default(),
ron_filepath,
dirty: false,
last_save_join_handle: None,
}
}
/// Find a good place to put the files that the OS likes.
pub fn from_app_name(app_name: &str) -> Option<Self> {
if let Some(proj_dirs) = directories_next::ProjectDirs::from("", "", app_name) {
let data_dir = proj_dirs.data_dir().to_path_buf();
if let Err(err) = std::fs::create_dir_all(&data_dir) {
tracing::warn!(
"Saving disabled: Failed to create app path at {:?}: {}",
data_dir,
err
);
None
} else {
Some(Self::from_ron_filepath(data_dir.join("app.ron")))
}
} else {
tracing::warn!("Saving disabled: Failed to find path to data_dir.");
None
}
}
}
impl crate::Storage for FileStorage {
fn get_string(&self, key: &str) -> Option<String> {
self.kv.get(key).cloned()
}
fn set_string(&mut self, key: &str, value: String) {
if self.kv.get(key) != Some(&value) {
self.kv.insert(key.to_owned(), value);
self.dirty = true;
}
}
fn flush(&mut self) {
if self.dirty {
self.dirty = false;
let file_path = self.ron_filepath.clone();
let kv = self.kv.clone();
if let Some(join_handle) = self.last_save_join_handle.take() {
// wait for previous save to complete.
join_handle.join().ok();
}
let join_handle = std::thread::spawn(move || {
let file = std::fs::File::create(&file_path).unwrap();
let config = Default::default();
ron::ser::to_writer_pretty(file, &kv, config).unwrap();
tracing::trace!("Persisted to {:?}", file_path);
});
self.last_save_join_handle = Some(join_handle);
}
}
}
// ----------------------------------------------------------------------------
fn read_ron<T>(ron_path: impl AsRef<Path>) -> Option<T>
where
T: serde::de::DeserializeOwned,
{
match std::fs::File::open(ron_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
match ron::de::from_reader(reader) {
Ok(value) => Some(value),
Err(err) => {
tracing::warn!("Failed to parse RON: {}", err);
None
}
}
}
Err(_err) => {
// File probably doesn't exist. That's fine.
None
}
}
}

View file

@ -0,0 +1,6 @@
mod epi_integration;
pub mod run;
/// File storage which can be used by native backends.
#[cfg(feature = "persistence")]
pub mod file_storage;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,585 @@
use egui::{
mutex::{Mutex, MutexGuard},
TexturesDelta,
};
use crate::{epi, App};
use super::{web_painter::WebPainter, *};
// ----------------------------------------------------------------------------
/// Data gathered between frames.
#[derive(Default)]
pub struct WebInput {
/// Required because we don't get a position on touched
pub latest_touch_pos: Option<egui::Pos2>,
/// Required to maintain a stable touch position for multi-touch gestures.
pub latest_touch_pos_id: Option<egui::TouchId>,
pub raw: egui::RawInput,
}
impl WebInput {
pub fn new_frame(&mut self, canvas_size: egui::Vec2) -> egui::RawInput {
egui::RawInput {
screen_rect: Some(egui::Rect::from_min_size(Default::default(), canvas_size)),
pixels_per_point: Some(native_pixels_per_point()), // We ALWAYS use the native pixels-per-point
time: Some(now_sec()),
..self.raw.take()
}
}
}
// ----------------------------------------------------------------------------
use std::sync::atomic::Ordering::SeqCst;
/// Stores when to do the next repaint.
pub struct NeedRepaint(Mutex<f64>);
impl Default for NeedRepaint {
fn default() -> Self {
Self(Mutex::new(f64::NEG_INFINITY)) // start with a repaint
}
}
impl NeedRepaint {
/// Returns the time (in [`now_sec`] scale) when
/// we should next repaint.
pub fn when_to_repaint(&self) -> f64 {
*self.0.lock()
}
/// Unschedule repainting.
pub fn clear(&self) {
*self.0.lock() = f64::INFINITY;
}
pub fn repaint_after(&self, num_seconds: f64) {
let mut repaint_time = self.0.lock();
*repaint_time = repaint_time.min(now_sec() + num_seconds);
}
pub fn repaint_asap(&self) {
*self.0.lock() = f64::NEG_INFINITY;
}
}
pub struct IsDestroyed(std::sync::atomic::AtomicBool);
impl Default for IsDestroyed {
fn default() -> Self {
Self(false.into())
}
}
impl IsDestroyed {
pub fn fetch(&self) -> bool {
self.0.load(SeqCst)
}
pub fn set_true(&self) {
self.0.store(true, SeqCst);
}
}
// ----------------------------------------------------------------------------
fn user_agent() -> Option<String> {
web_sys::window()?.navigator().user_agent().ok()
}
fn web_location() -> epi::Location {
let location = web_sys::window().unwrap().location();
let hash = percent_decode(&location.hash().unwrap_or_default());
let query = location
.search()
.unwrap_or_default()
.strip_prefix('?')
.map(percent_decode)
.unwrap_or_default();
let query_map = parse_query_map(&query)
.iter()
.map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
.collect();
epi::Location {
url: percent_decode(&location.href().unwrap_or_default()),
protocol: percent_decode(&location.protocol().unwrap_or_default()),
host: percent_decode(&location.host().unwrap_or_default()),
hostname: percent_decode(&location.hostname().unwrap_or_default()),
port: percent_decode(&location.port().unwrap_or_default()),
hash,
query,
query_map,
origin: percent_decode(&location.origin().unwrap_or_default()),
}
}
fn parse_query_map(query: &str) -> BTreeMap<&str, &str> {
query
.split('&')
.filter_map(|pair| {
if pair.is_empty() {
None
} else {
Some(if let Some((key, value)) = pair.split_once('=') {
(key, value)
} else {
(pair, "")
})
}
})
.collect()
}
#[test]
fn test_parse_query() {
assert_eq!(parse_query_map(""), BTreeMap::default());
assert_eq!(parse_query_map("foo"), BTreeMap::from_iter([("foo", "")]));
assert_eq!(
parse_query_map("foo=bar"),
BTreeMap::from_iter([("foo", "bar")])
);
assert_eq!(
parse_query_map("foo=bar&baz=42"),
BTreeMap::from_iter([("foo", "bar"), ("baz", "42")])
);
assert_eq!(
parse_query_map("foo&baz=42"),
BTreeMap::from_iter([("foo", ""), ("baz", "42")])
);
assert_eq!(
parse_query_map("foo&baz&&"),
BTreeMap::from_iter([("foo", ""), ("baz", "")])
);
}
// ----------------------------------------------------------------------------
pub struct AppRunner {
pub(crate) frame: epi::Frame,
egui_ctx: egui::Context,
painter: ActiveWebPainter,
pub(crate) input: WebInput,
app: Box<dyn epi::App>,
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
pub(crate) is_destroyed: std::sync::Arc<IsDestroyed>,
last_save_time: f64,
screen_reader: super::screen_reader::ScreenReader,
pub(crate) text_cursor_pos: Option<egui::Pos2>,
pub(crate) mutable_text_under_cursor: bool,
textures_delta: TexturesDelta,
pub events_to_unsubscribe: Vec<EventToUnsubscribe>,
}
impl Drop for AppRunner {
fn drop(&mut self) {
tracing::debug!("AppRunner has fully dropped");
}
}
impl AppRunner {
/// # Errors
/// Failure to initialize WebGL renderer.
pub async fn new(
canvas_id: &str,
web_options: crate::WebOptions,
app_creator: epi::AppCreator,
) -> Result<Self, String> {
let painter = ActiveWebPainter::new(canvas_id, &web_options).await?;
let system_theme = if web_options.follow_system_theme {
super::system_theme()
} else {
None
};
let info = epi::IntegrationInfo {
web_info: epi::WebInfo {
user_agent: user_agent().unwrap_or_default(),
location: web_location(),
},
system_theme,
cpu_usage: None,
native_pixels_per_point: Some(native_pixels_per_point()),
};
let storage = LocalStorage::default();
let egui_ctx = egui::Context::default();
egui_ctx.set_os(egui::os::OperatingSystem::from_user_agent(
&user_agent().unwrap_or_default(),
));
load_memory(&egui_ctx);
let theme = system_theme.unwrap_or(web_options.default_theme);
egui_ctx.set_visuals(theme.egui_visuals());
let app = app_creator(&epi::CreationContext {
egui_ctx: egui_ctx.clone(),
integration_info: info.clone(),
storage: Some(&storage),
#[cfg(feature = "glow")]
gl: Some(painter.gl().clone()),
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
wgpu_render_state: painter.render_state(),
#[cfg(all(feature = "wgpu", feature = "glow"))]
wgpu_render_state: None,
});
let frame = epi::Frame {
info,
output: Default::default(),
storage: Some(Box::new(storage)),
#[cfg(feature = "glow")]
gl: Some(painter.gl().clone()),
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
wgpu_render_state: painter.render_state(),
#[cfg(all(feature = "wgpu", feature = "glow"))]
wgpu_render_state: None,
};
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
{
let needs_repaint = needs_repaint.clone();
egui_ctx.set_request_repaint_callback(move || {
needs_repaint.repaint_asap();
});
}
let mut runner = Self {
frame,
egui_ctx,
painter,
input: Default::default(),
app,
needs_repaint,
is_destroyed: Default::default(),
last_save_time: now_sec(),
screen_reader: Default::default(),
text_cursor_pos: None,
mutable_text_under_cursor: false,
textures_delta: Default::default(),
events_to_unsubscribe: Default::default(),
};
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
Ok(runner)
}
pub fn egui_ctx(&self) -> &egui::Context {
&self.egui_ctx
}
/// Get mutable access to the concrete [`App`] we enclose.
///
/// This will panic if your app does not implement [`App::as_any_mut`].
pub fn app_mut<ConreteApp: 'static + App>(&mut self) -> &mut ConreteApp {
self.app
.as_any_mut()
.expect("Your app must implement `as_any_mut`, but it doesn't")
.downcast_mut::<ConreteApp>()
.unwrap()
}
pub fn auto_save(&mut self) {
let now = now_sec();
let time_since_last_save = now - self.last_save_time;
if time_since_last_save > self.app.auto_save_interval().as_secs_f64() {
if self.app.persist_egui_memory() {
save_memory(&self.egui_ctx);
}
if let Some(storage) = self.frame.storage_mut() {
self.app.save(storage);
}
self.last_save_time = now;
}
}
pub fn canvas_id(&self) -> &str {
self.painter.canvas_id()
}
pub fn warm_up(&mut self) -> Result<(), JsValue> {
if self.app.warm_up_enabled() {
let saved_memory: egui::Memory = self.egui_ctx.memory(|m| m.clone());
self.egui_ctx
.memory_mut(|m| m.set_everything_is_visible(true));
self.logic()?;
self.egui_ctx.memory_mut(|m| *m = saved_memory); // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations();
}
Ok(())
}
pub fn destroy(&mut self) -> Result<(), JsValue> {
let is_destroyed_already = self.is_destroyed.fetch();
if is_destroyed_already {
tracing::warn!("App was destroyed already");
Ok(())
} else {
tracing::debug!("Destroying");
for x in self.events_to_unsubscribe.drain(..) {
x.unsubscribe()?;
}
self.painter.destroy();
self.is_destroyed.set_true();
Ok(())
}
}
/// Returns how long to wait until the next repaint.
///
/// Call [`Self::paint`] later to paint
pub fn logic(&mut self) -> Result<(std::time::Duration, Vec<egui::ClippedPrimitive>), JsValue> {
let frame_start = now_sec();
resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points());
let canvas_size = canvas_size_in_points(self.canvas_id());
let raw_input = self.input.new_frame(canvas_size);
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
self.app.update(egui_ctx, &mut self.frame);
});
let egui::FullOutput {
platform_output,
repaint_after,
textures_delta,
shapes,
} = full_output;
self.handle_platform_output(platform_output);
self.textures_delta.append(textures_delta);
let clipped_primitives = self.egui_ctx.tessellate(shapes);
{
let app_output = self.frame.take_app_output();
let epi::backend::AppOutput {} = app_output;
}
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
Ok((repaint_after, clipped_primitives))
}
/// Paint the results of the last call to [`Self::logic`].
pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> {
let textures_delta = std::mem::take(&mut self.textures_delta);
self.painter.paint_and_update_textures(
self.app.clear_color(&self.egui_ctx.style().visuals),
clipped_primitives,
self.egui_ctx.pixels_per_point(),
&textures_delta,
)?;
Ok(())
}
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
if self.egui_ctx.options(|o| o.screen_reader) {
self.screen_reader
.speak(&platform_output.events_description());
}
let egui::PlatformOutput {
cursor_icon,
open_url,
copied_text,
events: _, // already handled
mutable_text_under_cursor,
text_cursor_pos,
#[cfg(feature = "accesskit")]
accesskit_update: _, // not currently implemented
} = platform_output;
set_cursor_icon(cursor_icon);
if let Some(open) = open_url {
super::open_url(&open.url, open.new_tab);
}
#[cfg(web_sys_unstable_apis)]
if !copied_text.is_empty() {
set_clipboard_text(&copied_text);
}
#[cfg(not(web_sys_unstable_apis))]
let _ = copied_text;
self.mutable_text_under_cursor = mutable_text_under_cursor;
if self.text_cursor_pos != text_cursor_pos {
text_agent::move_text_cursor(text_cursor_pos, self.canvas_id());
self.text_cursor_pos = text_cursor_pos;
}
}
}
// ----------------------------------------------------------------------------
pub type AppRunnerRef = Arc<Mutex<AppRunner>>;
pub struct TargetEvent {
target: EventTarget,
event_name: String,
closure: Closure<dyn FnMut(web_sys::Event)>,
}
pub struct IntervalHandle {
pub handle: i32,
pub closure: Closure<dyn FnMut()>,
}
pub enum EventToUnsubscribe {
TargetEvent(TargetEvent),
#[allow(dead_code)]
IntervalHandle(IntervalHandle),
}
impl EventToUnsubscribe {
pub fn unsubscribe(self) -> Result<(), JsValue> {
match self {
EventToUnsubscribe::TargetEvent(handle) => {
handle.target.remove_event_listener_with_callback(
handle.event_name.as_str(),
handle.closure.as_ref().unchecked_ref(),
)?;
Ok(())
}
EventToUnsubscribe::IntervalHandle(handle) => {
let window = web_sys::window().unwrap();
window.clear_interval_with_handle(handle.handle);
Ok(())
}
}
}
}
pub struct AppRunnerContainer {
pub runner: AppRunnerRef,
/// Set to `true` if there is a panic.
/// Used to ignore callbacks after a panic.
pub panicked: Arc<AtomicBool>,
pub events: Vec<EventToUnsubscribe>,
}
impl AppRunnerContainer {
/// Convenience function to reduce boilerplate and ensure that all event handlers
/// are dealt with in the same way
pub fn add_event_listener<E: wasm_bindgen::JsCast>(
&mut self,
target: &EventTarget,
event_name: &'static str,
mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static,
) -> Result<(), JsValue> {
// Create a JS closure based on the FnMut provided
let closure = Closure::wrap({
// Clone atomics
let runner_ref = self.runner.clone();
let panicked = self.panicked.clone();
Box::new(move |event: web_sys::Event| {
// Only call the wrapped closure if the egui code has not panicked
if !panicked.load(Ordering::SeqCst) {
// Cast the event to the expected event type
let event = event.unchecked_into::<E>();
closure(event, runner_ref.lock());
}
}) as Box<dyn FnMut(web_sys::Event)>
});
// Add the event listener to the target
target.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
let handle = TargetEvent {
target: target.clone(),
event_name: event_name.to_owned(),
closure,
};
self.events.push(EventToUnsubscribe::TargetEvent(handle));
Ok(())
}
}
// ----------------------------------------------------------------------------
/// Install event listeners to register different input events
/// and start running the given app.
pub async fn start(
canvas_id: &str,
web_options: crate::WebOptions,
app_creator: epi::AppCreator,
) -> Result<AppRunnerRef, JsValue> {
#[cfg(not(web_sys_unstable_apis))]
tracing::warn!(
"eframe compiled without RUSTFLAGS='--cfg=web_sys_unstable_apis'. Copying text won't work."
);
let mut runner = AppRunner::new(canvas_id, web_options, app_creator).await?;
runner.warm_up()?;
start_runner(runner)
}
/// Install event listeners to register different input events
/// and starts running the given [`AppRunner`].
fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
let mut runner_container = AppRunnerContainer {
runner: Arc::new(Mutex::new(app_runner)),
panicked: Arc::new(AtomicBool::new(false)),
events: Vec::with_capacity(20),
};
super::events::install_canvas_events(&mut runner_container)?;
super::events::install_document_events(&mut runner_container)?;
text_agent::install_text_agent(&mut runner_container)?;
super::events::paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?;
// Disable all event handlers on panic
let previous_hook = std::panic::take_hook();
runner_container.runner.lock().events_to_unsubscribe = runner_container.events;
std::panic::set_hook(Box::new(move |panic_info| {
tracing::info!("egui disabled all event handlers due to panic");
runner_container.panicked.store(true, SeqCst);
// Propagate panic info to the previously registered panic hook
previous_hook(panic_info);
}));
Ok(runner_container.runner)
}
// ----------------------------------------------------------------------------
#[derive(Default)]
struct LocalStorage {}
impl epi::Storage for LocalStorage {
fn get_string(&self, key: &str) -> Option<String> {
local_storage_get(key)
}
fn set_string(&mut self, key: &str, value: String) {
local_storage_set(key, &value);
}
fn flush(&mut self) {}
}

View file

@ -0,0 +1,538 @@
use std::sync::atomic::{AtomicBool, Ordering};
use egui::Key;
use super::*;
struct IsDestroyed(pub bool);
pub fn paint_and_schedule(
runner_ref: &AppRunnerRef,
panicked: Arc<AtomicBool>,
) -> Result<(), JsValue> {
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<IsDestroyed, JsValue> {
let mut runner_lock = runner_ref.lock();
let is_destroyed = runner_lock.is_destroyed.fetch();
if !is_destroyed && runner_lock.needs_repaint.when_to_repaint() <= now_sec() {
runner_lock.needs_repaint.clear();
let (repaint_after, clipped_primitives) = runner_lock.logic()?;
runner_lock.paint(&clipped_primitives)?;
runner_lock
.needs_repaint
.repaint_after(repaint_after.as_secs_f64());
runner_lock.auto_save();
}
Ok(IsDestroyed(is_destroyed))
}
fn request_animation_frame(
runner_ref: AppRunnerRef,
panicked: Arc<AtomicBool>,
) -> Result<(), JsValue> {
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())?;
closure.forget(); // We must forget it, or else the callback is canceled on drop
Ok(())
}
// Only paint and schedule if there has been no panic
if !panicked.load(Ordering::SeqCst) {
let is_destroyed = paint_if_needed(runner_ref)?;
if !is_destroyed.0 {
request_animation_frame(runner_ref.clone(), panicked)?;
}
}
Ok(())
}
pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
runner_container.add_event_listener(
&document,
"keydown",
|event: web_sys::KeyboardEvent, mut runner_lock| {
if event.is_composing() || event.key_code() == 229 {
// https://web.archive.org/web/20200526195704/https://www.fxsitecompat.dev/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/
return;
}
let modifiers = modifiers_from_event(&event);
runner_lock.input.raw.modifiers = modifiers;
let key = event.key();
let egui_key = translate_key(&key);
if let Some(key) = egui_key {
runner_lock.input.raw.events.push(egui::Event::Key {
key,
pressed: true,
repeat: false, // egui will fill this in for us!
modifiers,
});
}
if !modifiers.ctrl
&& !modifiers.command
&& !should_ignore_key(&key)
// When text agent is shown, it sends text event instead.
&& text_agent::text_agent().hidden()
{
runner_lock.input.raw.events.push(egui::Event::Text(key));
}
runner_lock.needs_repaint.repaint_asap();
let egui_wants_keyboard = runner_lock.egui_ctx().wants_keyboard_input();
#[allow(clippy::if_same_then_else)]
let prevent_default = if egui_key == Some(Key::Tab) {
// Always prevent moving cursor to url bar.
// egui wants to use tab to move to the next text field.
true
} else if egui_key == Some(Key::P) {
#[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(),
"Backspace" // so we don't go back to previous page when deleting text
| "ArrowDown" | "ArrowLeft" | "ArrowRight" | "ArrowUp" // cmd-left is "back" on Mac (https://github.com/emilk/egui/issues/58)
)
} else {
// We never want to prevent:
// * F5 / cmd-R (refresh)
// * cmd-shift-C (debug tools)
// * cmd/ctrl-c/v/x (or we stop copy/past/cut events)
false
};
// tracing::debug!(
// "On key-down {:?}, egui_wants_keyboard: {}, prevent_default: {}",
// event.key().as_str(),
// egui_wants_keyboard,
// prevent_default
// );
if prevent_default {
event.prevent_default();
// event.stop_propagation();
}
},
)?;
runner_container.add_event_listener(
&document,
"keyup",
|event: web_sys::KeyboardEvent, mut runner_lock| {
let modifiers = modifiers_from_event(&event);
runner_lock.input.raw.modifiers = modifiers;
if let Some(key) = translate_key(&event.key()) {
runner_lock.input.raw.events.push(egui::Event::Key {
key,
pressed: false,
repeat: false,
modifiers,
});
}
runner_lock.needs_repaint.repaint_asap();
},
)?;
#[cfg(web_sys_unstable_apis)]
runner_container.add_event_listener(
&document,
"paste",
|event: web_sys::ClipboardEvent, mut runner_lock| {
if let Some(data) = event.clipboard_data() {
if let Ok(text) = data.get_data("text") {
let text = text.replace("\r\n", "\n");
if !text.is_empty() {
runner_lock.input.raw.events.push(egui::Event::Paste(text));
runner_lock.needs_repaint.repaint_asap();
}
event.stop_propagation();
event.prevent_default();
}
}
},
)?;
#[cfg(web_sys_unstable_apis)]
runner_container.add_event_listener(
&document,
"cut",
|_: web_sys::ClipboardEvent, mut runner_lock| {
runner_lock.input.raw.events.push(egui::Event::Cut);
runner_lock.needs_repaint.repaint_asap();
},
)?;
#[cfg(web_sys_unstable_apis)]
runner_container.add_event_listener(
&document,
"copy",
|_: web_sys::ClipboardEvent, mut runner_lock| {
runner_lock.input.raw.events.push(egui::Event::Copy);
runner_lock.needs_repaint.repaint_asap();
},
)?;
for event_name in &["load", "pagehide", "pageshow", "resize"] {
runner_container.add_event_listener(
&window,
event_name,
|_: web_sys::Event, runner_lock| {
runner_lock.needs_repaint.repaint_asap();
},
)?;
}
runner_container.add_event_listener(
&window,
"hashchange",
|_: web_sys::Event, mut runner_lock| {
// `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here
runner_lock.frame.info.web_info.location.hash = location_hash();
},
)?;
Ok(())
}
pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap();
let prevent_default_events = [
// By default, right-clicks open a context menu.
// We don't want to do that (right clicks is handled by egui):
"contextmenu",
// Allow users to use ctrl-p for e.g. a command palette
"afterprint",
];
for event_name in prevent_default_events {
let closure =
move |event: web_sys::MouseEvent,
mut _runner_lock: egui::mutex::MutexGuard<'_, AppRunner>| {
event.prevent_default();
// event.stop_propagation();
// tracing::debug!("Preventing event {:?}", event_name);
};
runner_container.add_event_listener(&canvas, event_name, closure)?;
}
runner_container.add_event_listener(
&canvas,
"mousedown",
|event: web_sys::MouseEvent, mut runner_lock: egui::mutex::MutexGuard<'_, AppRunner>| {
if let Some(button) = button_from_mouse_event(&event) {
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
let modifiers = runner_lock.input.raw.modifiers;
runner_lock
.input
.raw
.events
.push(egui::Event::PointerButton {
pos,
button,
pressed: true,
modifiers,
});
runner_lock.needs_repaint.repaint_asap();
}
event.stop_propagation();
// Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here.
},
)?;
runner_container.add_event_listener(
&canvas,
"mousemove",
|event: web_sys::MouseEvent, mut runner_lock| {
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
runner_lock
.input
.raw
.events
.push(egui::Event::PointerMoved(pos));
runner_lock.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
},
)?;
runner_container.add_event_listener(
&canvas,
"mouseup",
|event: web_sys::MouseEvent, mut runner_lock| {
if let Some(button) = button_from_mouse_event(&event) {
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
let modifiers = runner_lock.input.raw.modifiers;
runner_lock
.input
.raw
.events
.push(egui::Event::PointerButton {
pos,
button,
pressed: false,
modifiers,
});
runner_lock.needs_repaint.repaint_asap();
text_agent::update_text_agent(runner_lock);
}
event.stop_propagation();
event.prevent_default();
},
)?;
runner_container.add_event_listener(
&canvas,
"mouseleave",
|event: web_sys::MouseEvent, mut runner_lock| {
runner_lock.input.raw.events.push(egui::Event::PointerGone);
runner_lock.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
},
)?;
runner_container.add_event_listener(
&canvas,
"touchstart",
|event: web_sys::TouchEvent, mut runner_lock| {
let mut latest_touch_pos_id = runner_lock.input.latest_touch_pos_id;
let pos =
pos_from_touch_event(runner_lock.canvas_id(), &event, &mut latest_touch_pos_id);
runner_lock.input.latest_touch_pos_id = latest_touch_pos_id;
runner_lock.input.latest_touch_pos = Some(pos);
let modifiers = runner_lock.input.raw.modifiers;
runner_lock
.input
.raw
.events
.push(egui::Event::PointerButton {
pos,
button: egui::PointerButton::Primary,
pressed: true,
modifiers,
});
push_touches(&mut runner_lock, egui::TouchPhase::Start, &event);
runner_lock.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
},
)?;
runner_container.add_event_listener(
&canvas,
"touchmove",
|event: web_sys::TouchEvent, mut runner_lock| {
let mut latest_touch_pos_id = runner_lock.input.latest_touch_pos_id;
let pos =
pos_from_touch_event(runner_lock.canvas_id(), &event, &mut latest_touch_pos_id);
runner_lock.input.latest_touch_pos_id = latest_touch_pos_id;
runner_lock.input.latest_touch_pos = Some(pos);
runner_lock
.input
.raw
.events
.push(egui::Event::PointerMoved(pos));
push_touches(&mut runner_lock, egui::TouchPhase::Move, &event);
runner_lock.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
},
)?;
runner_container.add_event_listener(
&canvas,
"touchend",
|event: web_sys::TouchEvent, mut runner_lock| {
if let Some(pos) = runner_lock.input.latest_touch_pos {
let modifiers = runner_lock.input.raw.modifiers;
// First release mouse to click:
runner_lock
.input
.raw
.events
.push(egui::Event::PointerButton {
pos,
button: egui::PointerButton::Primary,
pressed: false,
modifiers,
});
// Then remove hover effect:
runner_lock.input.raw.events.push(egui::Event::PointerGone);
push_touches(&mut runner_lock, egui::TouchPhase::End, &event);
runner_lock.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
}
// Finally, focus or blur text agent to toggle mobile keyboard:
text_agent::update_text_agent(runner_lock);
},
)?;
runner_container.add_event_listener(
&canvas,
"touchcancel",
|event: web_sys::TouchEvent, mut runner_lock| {
push_touches(&mut runner_lock, egui::TouchPhase::Cancel, &event);
event.stop_propagation();
event.prevent_default();
},
)?;
runner_container.add_event_listener(
&canvas,
"wheel",
|event: web_sys::WheelEvent, mut runner_lock| {
let scroll_multiplier = match event.delta_mode() {
web_sys::WheelEvent::DOM_DELTA_PAGE => {
canvas_size_in_points(runner_lock.canvas_id()).y
}
web_sys::WheelEvent::DOM_DELTA_LINE => {
#[allow(clippy::let_and_return)]
let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit.
points_per_scroll_line
}
_ => 1.0, // DOM_DELTA_PIXEL
};
let mut delta =
-scroll_multiplier * egui::vec2(event.delta_x() as f32, event.delta_y() as f32);
// Report a zoom event in case CTRL (on Windows or Linux) or CMD (on Mac) is pressed.
// This if-statement is equivalent to how `Modifiers.command` is determined in
// `modifiers_from_event()`, but we cannot directly use that fn for a [`WheelEvent`].
if event.ctrl_key() || event.meta_key() {
let factor = (delta.y / 200.0).exp();
runner_lock.input.raw.events.push(egui::Event::Zoom(factor));
} else {
if event.shift_key() {
// Treat as horizontal scrolling.
// Note: one Mac we already get horizontal scroll events when shift is down.
delta = egui::vec2(delta.x + delta.y, 0.0);
}
runner_lock
.input
.raw
.events
.push(egui::Event::Scroll(delta));
}
runner_lock.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
},
)?;
runner_container.add_event_listener(
&canvas,
"dragover",
|event: web_sys::DragEvent, mut runner_lock| {
if let Some(data_transfer) = event.data_transfer() {
runner_lock.input.raw.hovered_files.clear();
for i in 0..data_transfer.items().length() {
if let Some(item) = data_transfer.items().get(i) {
runner_lock.input.raw.hovered_files.push(egui::HoveredFile {
mime: item.type_(),
..Default::default()
});
}
}
runner_lock.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
}
},
)?;
runner_container.add_event_listener(
&canvas,
"dragleave",
|event: web_sys::DragEvent, mut runner_lock| {
runner_lock.input.raw.hovered_files.clear();
runner_lock.needs_repaint.repaint_asap();
event.stop_propagation();
event.prevent_default();
},
)?;
runner_container.add_event_listener(&canvas, "drop", {
let runner_ref = runner_container.runner.clone();
move |event: web_sys::DragEvent, mut runner_lock| {
if let Some(data_transfer) = event.data_transfer() {
runner_lock.input.raw.hovered_files.clear();
runner_lock.needs_repaint.repaint_asap();
// Unlock the runner so it can be locked after a future await point
drop(runner_lock);
if let Some(files) = data_transfer.files() {
for i in 0..files.length() {
if let Some(file) = files.get(i) {
let name = file.name();
let last_modified = std::time::UNIX_EPOCH
+ std::time::Duration::from_millis(file.last_modified() as u64);
tracing::debug!("Loading {:?} ({} bytes)…", name, file.size());
let future = wasm_bindgen_futures::JsFuture::from(file.array_buffer());
let runner_ref = runner_ref.clone();
let future = async move {
match future.await {
Ok(array_buffer) => {
let bytes = js_sys::Uint8Array::new(&array_buffer).to_vec();
tracing::debug!(
"Loaded {:?} ({} bytes).",
name,
bytes.len()
);
// Re-lock the mutex on the other side of the await point
let mut runner_lock = runner_ref.lock();
runner_lock.input.raw.dropped_files.push(
egui::DroppedFile {
name,
last_modified: Some(last_modified),
bytes: Some(bytes.into()),
..Default::default()
},
);
runner_lock.needs_repaint.repaint_asap();
}
Err(err) => {
tracing::error!("Failed to read file: {:?}", err);
}
}
};
wasm_bindgen_futures::spawn_local(future);
}
}
}
event.stop_propagation();
event.prevent_default();
}
}
})?;
Ok(())
}

View file

@ -0,0 +1,217 @@
use super::{canvas_element, canvas_origin, AppRunner};
pub fn pos_from_mouse_event(canvas_id: &str, event: &web_sys::MouseEvent) -> egui::Pos2 {
let canvas = canvas_element(canvas_id).unwrap();
let rect = canvas.get_bounding_client_rect();
egui::Pos2 {
x: event.client_x() as f32 - rect.left() as f32,
y: event.client_y() as f32 - rect.top() as f32,
}
}
pub fn button_from_mouse_event(event: &web_sys::MouseEvent) -> Option<egui::PointerButton> {
match event.button() {
0 => Some(egui::PointerButton::Primary),
1 => Some(egui::PointerButton::Middle),
2 => Some(egui::PointerButton::Secondary),
3 => Some(egui::PointerButton::Extra1),
4 => Some(egui::PointerButton::Extra2),
_ => None,
}
}
/// A single touch is translated to a pointer movement. When a second touch is added, the pointer
/// should not jump to a different position. Therefore, we do not calculate the average position
/// of all touches, but we keep using the same touch as long as it is available.
///
/// `touch_id_for_pos` is the [`TouchId`](egui::TouchId) of the [`Touch`](web_sys::Touch) we previously used to determine the
/// pointer position.
pub fn pos_from_touch_event(
canvas_id: &str,
event: &web_sys::TouchEvent,
touch_id_for_pos: &mut Option<egui::TouchId>,
) -> egui::Pos2 {
let touch_for_pos = if let Some(touch_id_for_pos) = touch_id_for_pos {
// search for the touch we previously used for the position
// (unfortunately, `event.touches()` is not a rust collection):
(0..event.touches().length())
.into_iter()
.map(|i| event.touches().get(i).unwrap())
.find(|touch| egui::TouchId::from(touch.identifier()) == *touch_id_for_pos)
} else {
None
};
// Use the touch found above or pick the first, or return a default position if there is no
// touch at all. (The latter is not expected as the current method is only called when there is
// at least one touch.)
touch_for_pos
.or_else(|| event.touches().get(0))
.map_or(Default::default(), |touch| {
*touch_id_for_pos = Some(egui::TouchId::from(touch.identifier()));
pos_from_touch(canvas_origin(canvas_id), &touch)
})
}
fn pos_from_touch(canvas_origin: egui::Pos2, touch: &web_sys::Touch) -> egui::Pos2 {
egui::Pos2 {
x: touch.page_x() as f32 - canvas_origin.x,
y: touch.page_y() as f32 - canvas_origin.y,
}
}
pub fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web_sys::TouchEvent) {
let canvas_origin = canvas_origin(runner.canvas_id());
for touch_idx in 0..event.changed_touches().length() {
if let Some(touch) = event.changed_touches().item(touch_idx) {
runner.input.raw.events.push(egui::Event::Touch {
device_id: egui::TouchDeviceId(0),
id: egui::TouchId::from(touch.identifier()),
phase,
pos: pos_from_touch(canvas_origin, &touch),
force: touch.force(),
});
}
}
}
/// Web sends all keys as strings, so it is up to us to figure out if it is
/// a real text input or the name of a key.
pub fn should_ignore_key(key: &str) -> bool {
let is_function_key = key.starts_with('F') && key.len() > 1;
is_function_key
|| matches!(
key,
"Alt"
| "ArrowDown"
| "ArrowLeft"
| "ArrowRight"
| "ArrowUp"
| "Backspace"
| "CapsLock"
| "ContextMenu"
| "Control"
| "Delete"
| "End"
| "Enter"
| "Esc"
| "Escape"
| "GroupNext" // https://github.com/emilk/egui/issues/510
| "Help"
| "Home"
| "Insert"
| "Meta"
| "NumLock"
| "PageDown"
| "PageUp"
| "Pause"
| "ScrollLock"
| "Shift"
| "Tab"
)
}
/// Web sends all all keys as strings, so it is up to us to figure out if it is
/// a real text input or the name of a key.
pub fn translate_key(key: &str) -> Option<egui::Key> {
use egui::Key;
match key {
"ArrowDown" => Some(Key::ArrowDown),
"ArrowLeft" => Some(Key::ArrowLeft),
"ArrowRight" => Some(Key::ArrowRight),
"ArrowUp" => Some(Key::ArrowUp),
"Esc" | "Escape" => Some(Key::Escape),
"Tab" => Some(Key::Tab),
"Backspace" => Some(Key::Backspace),
"Enter" => Some(Key::Enter),
"Space" | " " => Some(Key::Space),
"Help" | "Insert" => Some(Key::Insert),
"Delete" => Some(Key::Delete),
"Home" => Some(Key::Home),
"End" => Some(Key::End),
"PageUp" => Some(Key::PageUp),
"PageDown" => Some(Key::PageDown),
"-" => Some(Key::Minus),
"+" | "=" => Some(Key::PlusEquals),
"0" => Some(Key::Num0),
"1" => Some(Key::Num1),
"2" => Some(Key::Num2),
"3" => Some(Key::Num3),
"4" => Some(Key::Num4),
"5" => Some(Key::Num5),
"6" => Some(Key::Num6),
"7" => Some(Key::Num7),
"8" => Some(Key::Num8),
"9" => Some(Key::Num9),
"a" | "A" => Some(Key::A),
"b" | "B" => Some(Key::B),
"c" | "C" => Some(Key::C),
"d" | "D" => Some(Key::D),
"e" | "E" => Some(Key::E),
"f" | "F" => Some(Key::F),
"g" | "G" => Some(Key::G),
"h" | "H" => Some(Key::H),
"i" | "I" => Some(Key::I),
"j" | "J" => Some(Key::J),
"k" | "K" => Some(Key::K),
"l" | "L" => Some(Key::L),
"m" | "M" => Some(Key::M),
"n" | "N" => Some(Key::N),
"o" | "O" => Some(Key::O),
"p" | "P" => Some(Key::P),
"q" | "Q" => Some(Key::Q),
"r" | "R" => Some(Key::R),
"s" | "S" => Some(Key::S),
"t" | "T" => Some(Key::T),
"u" | "U" => Some(Key::U),
"v" | "V" => Some(Key::V),
"w" | "W" => Some(Key::W),
"x" | "X" => Some(Key::X),
"y" | "Y" => Some(Key::Y),
"z" | "Z" => Some(Key::Z),
"F1" => Some(Key::F1),
"F2" => Some(Key::F2),
"F3" => Some(Key::F3),
"F4" => Some(Key::F4),
"F5" => Some(Key::F5),
"F6" => Some(Key::F6),
"F7" => Some(Key::F7),
"F8" => Some(Key::F8),
"F9" => Some(Key::F9),
"F10" => Some(Key::F10),
"F11" => Some(Key::F11),
"F12" => Some(Key::F12),
"F13" => Some(Key::F13),
"F14" => Some(Key::F14),
"F15" => Some(Key::F15),
"F16" => Some(Key::F16),
"F17" => Some(Key::F17),
"F18" => Some(Key::F18),
"F19" => Some(Key::F19),
"F20" => Some(Key::F20),
_ => None,
}
}
pub fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers {
egui::Modifiers {
alt: event.alt_key(),
ctrl: event.ctrl_key(),
shift: event.shift_key(),
// Ideally we should know if we are running or mac or not,
// but this works good enough for now.
mac_cmd: event.meta_key(),
// Ideally we should know if we are running or mac or not,
// but this works good enough for now.
command: event.ctrl_key() || event.meta_key(),
}
}

View file

@ -0,0 +1,258 @@
//! [`egui`] bindings for web apps (compiling to WASM).
#![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
pub mod backend;
mod events;
mod input;
pub mod screen_reader;
pub mod storage;
mod text_agent;
#[cfg(not(any(feature = "glow", feature = "wgpu")))]
compile_error!("You must enable either the 'glow' or 'wgpu' feature");
mod web_painter;
#[cfg(feature = "glow")]
mod web_painter_glow;
#[cfg(feature = "glow")]
pub(crate) type ActiveWebPainter = web_painter_glow::WebPainterGlow;
#[cfg(feature = "wgpu")]
mod web_painter_wgpu;
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu;
pub use backend::*;
pub use events::*;
pub use storage::*;
use std::collections::BTreeMap;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use egui::Vec2;
use wasm_bindgen::prelude::*;
use web_sys::EventTarget;
use input::*;
use crate::Theme;
// ----------------------------------------------------------------------------
/// Current time in seconds (since undefined point in time).
///
/// Monotonically increasing.
pub fn now_sec() -> f64 {
web_sys::window()
.expect("should have a Window")
.performance()
.expect("should have a Performance")
.now()
/ 1000.0
}
#[allow(dead_code)]
pub fn screen_size_in_native_points() -> Option<egui::Vec2> {
let window = web_sys::window()?;
Some(egui::vec2(
window.inner_width().ok()?.as_f64()? as f32,
window.inner_height().ok()?.as_f64()? as f32,
))
}
pub fn native_pixels_per_point() -> f32 {
let pixels_per_point = web_sys::window().unwrap().device_pixel_ratio() as f32;
if pixels_per_point > 0.0 && pixels_per_point.is_finite() {
pixels_per_point
} else {
1.0
}
}
pub fn system_theme() -> Option<Theme> {
let dark_mode = web_sys::window()?
.match_media("(prefers-color-scheme: dark)")
.ok()??
.matches();
Some(if dark_mode { Theme::Dark } else { Theme::Light })
}
pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
let document = web_sys::window()?.document()?;
let canvas = document.get_element_by_id(canvas_id)?;
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
}
pub fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement {
canvas_element(canvas_id)
.unwrap_or_else(|| panic!("Failed to find canvas with id {:?}", canvas_id))
}
fn canvas_origin(canvas_id: &str) -> egui::Pos2 {
let rect = canvas_element(canvas_id)
.unwrap()
.get_bounding_client_rect();
egui::pos2(rect.left() as f32, rect.top() as f32)
}
pub fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 {
let canvas = canvas_element(canvas_id).unwrap();
let pixels_per_point = native_pixels_per_point();
egui::vec2(
canvas.width() as f32 / pixels_per_point,
canvas.height() as f32 / pixels_per_point,
)
}
pub fn resize_canvas_to_screen_size(canvas_id: &str, max_size_points: egui::Vec2) -> Option<()> {
let canvas = canvas_element(canvas_id)?;
let parent = canvas.parent_element()?;
let width = parent.scroll_width();
let height = parent.scroll_height();
let canvas_real_size = Vec2 {
x: width as f32,
y: height as f32,
};
if width <= 0 || height <= 0 {
tracing::error!("egui canvas parent size is {}x{}. Try adding `html, body {{ height: 100%; width: 100% }}` to your CSS!", width, height);
}
let pixels_per_point = native_pixels_per_point();
let max_size_pixels = pixels_per_point * max_size_points;
let canvas_size_pixels = pixels_per_point * canvas_real_size;
let canvas_size_pixels = canvas_size_pixels.min(max_size_pixels);
let canvas_size_points = canvas_size_pixels / pixels_per_point;
// Make sure that the height and width are always even numbers.
// otherwise, the page renders blurry on some platforms.
// See https://github.com/emilk/egui/issues/103
fn round_to_even(v: f32) -> f32 {
(v / 2.0).round() * 2.0
}
canvas
.style()
.set_property(
"width",
&format!("{}px", round_to_even(canvas_size_points.x)),
)
.ok()?;
canvas
.style()
.set_property(
"height",
&format!("{}px", round_to_even(canvas_size_points.y)),
)
.ok()?;
canvas.set_width(round_to_even(canvas_size_pixels.x) as u32);
canvas.set_height(round_to_even(canvas_size_pixels.y) as u32);
Some(())
}
// ----------------------------------------------------------------------------
pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> {
let document = web_sys::window()?.document()?;
document
.body()?
.style()
.set_property("cursor", cursor_web_name(cursor))
.ok()
}
#[cfg(web_sys_unstable_apis)]
pub fn set_clipboard_text(s: &str) {
if let Some(window) = web_sys::window() {
if let Some(clipboard) = window.navigator().clipboard() {
let promise = clipboard.write_text(s);
let future = wasm_bindgen_futures::JsFuture::from(promise);
let future = async move {
if let Err(err) = future.await {
tracing::error!("Copy/cut action denied: {:?}", err);
}
};
wasm_bindgen_futures::spawn_local(future);
}
}
}
fn cursor_web_name(cursor: egui::CursorIcon) -> &'static str {
match cursor {
egui::CursorIcon::Alias => "alias",
egui::CursorIcon::AllScroll => "all-scroll",
egui::CursorIcon::Cell => "cell",
egui::CursorIcon::ContextMenu => "context-menu",
egui::CursorIcon::Copy => "copy",
egui::CursorIcon::Crosshair => "crosshair",
egui::CursorIcon::Default => "default",
egui::CursorIcon::Grab => "grab",
egui::CursorIcon::Grabbing => "grabbing",
egui::CursorIcon::Help => "help",
egui::CursorIcon::Move => "move",
egui::CursorIcon::NoDrop => "no-drop",
egui::CursorIcon::None => "none",
egui::CursorIcon::NotAllowed => "not-allowed",
egui::CursorIcon::PointingHand => "pointer",
egui::CursorIcon::Progress => "progress",
egui::CursorIcon::ResizeHorizontal => "ew-resize",
egui::CursorIcon::ResizeNeSw => "nesw-resize",
egui::CursorIcon::ResizeNwSe => "nwse-resize",
egui::CursorIcon::ResizeVertical => "ns-resize",
egui::CursorIcon::ResizeEast => "e-resize",
egui::CursorIcon::ResizeSouthEast => "se-resize",
egui::CursorIcon::ResizeSouth => "s-resize",
egui::CursorIcon::ResizeSouthWest => "sw-resize",
egui::CursorIcon::ResizeWest => "w-resize",
egui::CursorIcon::ResizeNorthWest => "nw-resize",
egui::CursorIcon::ResizeNorth => "n-resize",
egui::CursorIcon::ResizeNorthEast => "ne-resize",
egui::CursorIcon::ResizeColumn => "col-resize",
egui::CursorIcon::ResizeRow => "row-resize",
egui::CursorIcon::Text => "text",
egui::CursorIcon::VerticalText => "vertical-text",
egui::CursorIcon::Wait => "wait",
egui::CursorIcon::ZoomIn => "zoom-in",
egui::CursorIcon::ZoomOut => "zoom-out",
}
}
pub fn open_url(url: &str, new_tab: bool) -> Option<()> {
let name = if new_tab { "_blank" } else { "_self" };
web_sys::window()?
.open_with_url_and_target(url, name)
.ok()?;
Some(())
}
/// e.g. "#fragment" part of "www.example.com/index.html#fragment",
///
/// Percent decoded
pub fn location_hash() -> String {
percent_decode(
&web_sys::window()
.unwrap()
.location()
.hash()
.unwrap_or_default(),
)
}
pub fn percent_decode(s: &str) -> String {
percent_encoding::percent_decode_str(s)
.decode_utf8_lossy()
.to_string()
}

View file

@ -0,0 +1,49 @@
pub struct ScreenReader {
#[cfg(feature = "tts")]
tts: Option<tts::Tts>,
}
#[cfg(not(feature = "tts"))]
#[allow(clippy::derivable_impls)] // False positive
impl Default for ScreenReader {
fn default() -> Self {
Self {}
}
}
#[cfg(feature = "tts")]
impl Default for ScreenReader {
fn default() -> Self {
let tts = match tts::Tts::default() {
Ok(screen_reader) => {
tracing::debug!("Initialized screen reader.");
Some(screen_reader)
}
Err(err) => {
tracing::warn!("Failed to load screen reader: {}", err);
None
}
};
Self { tts }
}
}
impl ScreenReader {
#[cfg(not(feature = "tts"))]
#[allow(clippy::unused_self)]
pub fn speak(&mut self, _text: &str) {}
#[cfg(feature = "tts")]
pub fn speak(&mut self, text: &str) {
if text.is_empty() {
return;
}
if let Some(tts) = &mut self.tts {
tracing::debug!("Speaking: {:?}", text);
let interrupt = true;
if let Err(err) = tts.speak(text, interrupt) {
tracing::warn!("Failed to read: {}", err);
}
}
}
}

View file

@ -0,0 +1,43 @@
fn local_storage() -> Option<web_sys::Storage> {
web_sys::window()?.local_storage().ok()?
}
pub fn local_storage_get(key: &str) -> Option<String> {
local_storage().map(|storage| storage.get_item(key).ok())??
}
pub fn local_storage_set(key: &str, value: &str) {
local_storage().map(|storage| storage.set_item(key, value));
}
#[cfg(feature = "persistence")]
pub fn load_memory(ctx: &egui::Context) {
if let Some(memory_string) = local_storage_get("egui_memory_ron") {
match ron::from_str(&memory_string) {
Ok(memory) => {
ctx.memory_mut(|m| *m = memory);
}
Err(err) => {
tracing::error!("Failed to parse memory RON: {}", err);
}
}
}
}
#[cfg(not(feature = "persistence"))]
pub fn load_memory(_: &egui::Context) {}
#[cfg(feature = "persistence")]
pub fn save_memory(ctx: &egui::Context) {
match ctx.memory(|mem| ron::to_string(mem)) {
Ok(ron) => {
local_storage_set("egui_memory_ron", &ron);
}
Err(err) => {
tracing::error!("Failed to serialize memory as RON: {}", err);
}
}
}
#[cfg(not(feature = "persistence"))]
pub fn save_memory(_: &egui::Context) {}

View file

@ -0,0 +1,225 @@
//! The text agent is an `<input>` element used to trigger
//! mobile keyboard and IME input.
use super::{canvas_element, AppRunner, AppRunnerContainer};
use egui::mutex::MutexGuard;
use std::cell::Cell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
static AGENT_ID: &str = "egui_text_agent";
pub fn text_agent() -> web_sys::HtmlInputElement {
web_sys::window()
.unwrap()
.document()
.unwrap()
.get_element_by_id(AGENT_ID)
.unwrap()
.dyn_into()
.unwrap()
}
/// Text event handler,
pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().expect("document should have a body");
let input = document
.create_element("input")?
.dyn_into::<web_sys::HtmlInputElement>()?;
let input = std::rc::Rc::new(input);
input.set_id(AGENT_ID);
let is_composing = Rc::new(Cell::new(false));
{
let style = input.style();
// Transparent
style.set_property("opacity", "0").unwrap();
// Hide under canvas
style.set_property("z-index", "-1").unwrap();
}
// Set size as small as possible, in case user may click on it.
input.set_size(1);
input.set_autofocus(true);
input.set_hidden(true);
// When IME is off
runner_container.add_event_listener(&input, "input", {
let input_clone = input.clone();
let is_composing = is_composing.clone();
move |_event: web_sys::InputEvent, mut runner_lock| {
let text = input_clone.value();
if !text.is_empty() && !is_composing.get() {
input_clone.set_value("");
runner_lock.input.raw.events.push(egui::Event::Text(text));
runner_lock.needs_repaint.repaint_asap();
}
}
})?;
{
// When IME is on, handle composition event
runner_container.add_event_listener(&input, "compositionstart", {
let input_clone = input.clone();
let is_composing = is_composing.clone();
move |_event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| {
is_composing.set(true);
input_clone.set_value("");
runner_lock
.input
.raw
.events
.push(egui::Event::CompositionStart);
runner_lock.needs_repaint.repaint_asap();
}
})?;
runner_container.add_event_listener(
&input,
"compositionupdate",
move |event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| {
if let Some(event) = event.data().map(egui::Event::CompositionUpdate) {
runner_lock.input.raw.events.push(event);
runner_lock.needs_repaint.repaint_asap();
}
},
)?;
runner_container.add_event_listener(&input, "compositionend", {
let input_clone = input.clone();
move |event: web_sys::CompositionEvent, mut runner_lock: MutexGuard<'_, AppRunner>| {
is_composing.set(false);
input_clone.set_value("");
if let Some(event) = event.data().map(egui::Event::CompositionEnd) {
runner_lock.input.raw.events.push(event);
runner_lock.needs_repaint.repaint_asap();
}
}
})?;
}
// When input lost focus, focus on it again.
// It is useful when user click somewhere outside canvas.
runner_container.add_event_listener(
&input,
"focusout",
move |_event: web_sys::MouseEvent, _| {
// Delay 10 ms, and focus again.
let func = js_sys::Function::new_no_args(&format!(
"document.getElementById('{}').focus()",
AGENT_ID
));
window
.set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10)
.unwrap();
},
)?;
body.append_child(&input)?;
Ok(())
}
/// Focus or blur text agent to toggle mobile keyboard.
pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
use web_sys::HtmlInputElement;
let window = web_sys::window()?;
let document = window.document()?;
let input: HtmlInputElement = document.get_element_by_id(AGENT_ID)?.dyn_into().unwrap();
let canvas_style = canvas_element(runner.canvas_id())?.style();
if runner.mutable_text_under_cursor {
let is_already_editing = input.hidden();
if is_already_editing {
input.set_hidden(false);
input.focus().ok()?;
// Move up canvas so that text edit is shown at ~30% of screen height.
// Only on touch screens, when keyboard popups.
if let Some(latest_touch_pos) = runner.input.latest_touch_pos {
let window_height = window.inner_height().ok()?.as_f64()? as f32;
let current_rel = latest_touch_pos.y / window_height;
// estimated amount of screen covered by keyboard
let keyboard_fraction = 0.5;
if current_rel > keyboard_fraction {
// below the keyboard
let target_rel = 0.3;
// Note: `delta` is negative, since we are moving the canvas UP
let delta = target_rel - current_rel;
let delta = delta.max(-keyboard_fraction); // Don't move it crazy much
let new_pos_percent = format!("{}%", (delta * 100.0).round());
canvas_style.set_property("position", "absolute").ok()?;
canvas_style.set_property("top", &new_pos_percent).ok()?;
}
}
}
} else {
// Drop runner lock
drop(runner);
// Holding the runner lock while calling input.blur() causes a panic.
// This is most probably caused by the browser running the event handler
// for the triggered blur event synchronously, meaning that the mutex
// lock does not get dropped by the time another event handler is called.
//
// Why this didn't exist before #1290 is a mystery to me, but it exists now
// and this apparently is the fix for it
//
// ¯\_(ツ)_/¯ - @DusterTheFirst
input.blur().ok()?;
input.set_hidden(true);
canvas_style.set_property("position", "absolute").ok()?;
canvas_style.set_property("top", "0%").ok()?; // move back to normal position
}
Some(())
}
/// If context is running under mobile device?
fn is_mobile() -> Option<bool> {
const MOBILE_DEVICE: [&str; 6] = ["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"];
let user_agent = web_sys::window()?.navigator().user_agent().ok()?;
let is_mobile = MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name));
Some(is_mobile)
}
// Move text agent to text cursor's position, on desktop/laptop,
// candidate window moves following text element (agent),
// so it appears that the IME candidate window moves with text cursor.
// On mobile devices, there is no need to do that.
pub fn move_text_cursor(cursor: Option<egui::Pos2>, canvas_id: &str) -> Option<()> {
let style = text_agent().style();
// Note: movint agent on mobile devices will lead to unpredictable scroll.
if is_mobile() == Some(false) {
cursor.as_ref().and_then(|&egui::Pos2 { x, y }| {
let canvas = canvas_element(canvas_id)?;
let bounding_rect = text_agent().get_bounding_client_rect();
let y = (y + (canvas.scroll_top() + canvas.offset_top()) as f32)
.min(canvas.client_height() as f32 - bounding_rect.height() as f32);
let x = x + (canvas.scroll_left() + canvas.offset_left()) as f32;
// Canvas is translated 50% horizontally in html.
let x = (x - canvas.offset_width() as f32 / 2.0)
.min(canvas.client_width() as f32 - bounding_rect.width() as f32);
style.set_property("position", "absolute").ok()?;
style.set_property("top", &format!("{}px", y)).ok()?;
style.set_property("left", &format!("{}px", x)).ok()
})
} else {
style.set_property("position", "absolute").ok()?;
style.set_property("top", "0px").ok()?;
style.set_property("left", "0px").ok()
}
}

View file

@ -0,0 +1,29 @@
use wasm_bindgen::JsValue;
/// Renderer for a browser canvas.
/// As of writing we're not allowing to decide on the painter at runtime,
/// therefore this trait is merely there for specifying and documenting the interface.
pub(crate) trait WebPainter {
// Create a new web painter targeting a given canvas.
// fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String>
// where
// Self: Sized;
/// Id of the canvas in use.
fn canvas_id(&self) -> &str;
/// Maximum size of a texture in one direction.
fn max_texture_side(&self) -> usize;
/// Update all internal textures and paint gui.
fn paint_and_update_textures(
&mut self,
clear_color: [f32; 4],
clipped_primitives: &[egui::ClippedPrimitive],
pixels_per_point: f32,
textures_delta: &egui::TexturesDelta,
) -> Result<(), JsValue>;
/// Destroy all resources.
fn destroy(&mut self);
}

View file

@ -0,0 +1,184 @@
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;
use egui_glow::glow;
use crate::{WebGlContextOption, WebOptions};
use super::web_painter::WebPainter;
pub(crate) struct WebPainterGlow {
canvas: HtmlCanvasElement,
canvas_id: String,
painter: egui_glow::Painter,
}
impl WebPainterGlow {
pub fn gl(&self) -> &std::sync::Arc<glow::Context> {
self.painter.gl()
}
pub async fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String> {
let canvas = super::canvas_element_or_die(canvas_id);
let (gl, shader_prefix) =
init_glow_context_from_canvas(&canvas, options.webgl_context_option)?;
let gl = std::sync::Arc::new(gl);
let painter = egui_glow::Painter::new(gl, shader_prefix, None)
.map_err(|error| format!("Error starting glow painter: {}", error))?;
Ok(Self {
canvas,
canvas_id: canvas_id.to_owned(),
painter,
})
}
}
impl WebPainter for WebPainterGlow {
fn max_texture_side(&self) -> usize {
self.painter.max_texture_side()
}
fn canvas_id(&self) -> &str {
&self.canvas_id
}
fn paint_and_update_textures(
&mut self,
clear_color: [f32; 4],
clipped_primitives: &[egui::ClippedPrimitive],
pixels_per_point: f32,
textures_delta: &egui::TexturesDelta,
) -> Result<(), JsValue> {
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
for (id, image_delta) in &textures_delta.set {
self.painter.set_texture(*id, image_delta);
}
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color);
self.painter
.paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives);
for &id in &textures_delta.free {
self.painter.free_texture(id);
}
Ok(())
}
fn destroy(&mut self) {
self.painter.destroy();
}
}
/// Returns glow context and shader prefix.
fn init_glow_context_from_canvas(
canvas: &HtmlCanvasElement,
options: WebGlContextOption,
) -> Result<(glow::Context, &'static str), String> {
let result = match options {
// Force use WebGl1
WebGlContextOption::WebGl1 => init_webgl1(canvas),
// Force use WebGl2
WebGlContextOption::WebGl2 => init_webgl2(canvas),
// Trying WebGl2 first
WebGlContextOption::BestFirst => init_webgl2(canvas).or_else(|| init_webgl1(canvas)),
// Trying WebGl1 first (useful for testing).
WebGlContextOption::CompatibilityFirst => {
init_webgl1(canvas).or_else(|| init_webgl2(canvas))
}
};
if let Some(result) = result {
Ok(result)
} else {
Err("WebGL isn't supported".into())
}
}
fn init_webgl1(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static str)> {
let gl1_ctx = canvas
.get_context("webgl")
.expect("Failed to query about WebGL2 context");
let gl1_ctx = gl1_ctx?;
tracing::debug!("WebGL1 selected.");
let gl1_ctx = gl1_ctx
.dyn_into::<web_sys::WebGlRenderingContext>()
.unwrap();
let shader_prefix = if webgl1_requires_brightening(&gl1_ctx) {
tracing::debug!("Enabling webkitGTK brightening workaround.");
"#define APPLY_BRIGHTENING_GAMMA"
} else {
""
};
let gl = glow::Context::from_webgl1_context(gl1_ctx);
Some((gl, shader_prefix))
}
fn init_webgl2(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static str)> {
let gl2_ctx = canvas
.get_context("webgl2")
.expect("Failed to query about WebGL2 context");
let gl2_ctx = gl2_ctx?;
tracing::debug!("WebGL2 selected.");
let gl2_ctx = gl2_ctx
.dyn_into::<web_sys::WebGl2RenderingContext>()
.unwrap();
let gl = glow::Context::from_webgl2_context(gl2_ctx);
let shader_prefix = "";
Some((gl, shader_prefix))
}
fn webgl1_requires_brightening(gl: &web_sys::WebGlRenderingContext) -> bool {
// See https://github.com/emilk/egui/issues/794
// detect WebKitGTK
// WebKitGTK use WebKit default unmasked vendor and renderer
// but safari use same vendor and renderer
// so exclude "Mac OS X" user-agent.
let user_agent = web_sys::window().unwrap().navigator().user_agent().unwrap();
!user_agent.contains("Mac OS X") && is_safari_and_webkit_gtk(gl)
}
/// detecting Safari and `webkitGTK`.
///
/// Safari and `webkitGTK` use unmasked renderer :Apple GPU
///
/// If we detect safari or `webkitGTKs` returns true.
///
/// This function used to avoid displaying linear color with `sRGB` supported systems.
fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {
// This call produces a warning in Firefox ("WEBGL_debug_renderer_info is deprecated in Firefox and will be removed.")
// but unless we call it we get errors in Chrome when we call `get_parameter` below.
// TODO(emilk): do something smart based on user agent?
if gl
.get_extension("WEBGL_debug_renderer_info")
.unwrap()
.is_some()
{
if let Ok(renderer) =
gl.get_parameter(web_sys::WebglDebugRendererInfo::UNMASKED_RENDERER_WEBGL)
{
if let Some(renderer) = renderer.as_string() {
if renderer.contains("Apple") {
return true;
}
}
}
}
false
}

View file

@ -0,0 +1,282 @@
use std::sync::Arc;
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;
use egui::mutex::RwLock;
use egui_wgpu::{renderer::ScreenDescriptor, RenderState, SurfaceErrorAction};
use crate::WebOptions;
use super::web_painter::WebPainter;
pub(crate) struct WebPainterWgpu {
canvas: HtmlCanvasElement,
canvas_id: String,
surface: wgpu::Surface,
surface_configuration: wgpu::SurfaceConfiguration,
limits: wgpu::Limits,
render_state: Option<RenderState>,
on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
depth_format: Option<wgpu::TextureFormat>,
depth_texture_view: Option<wgpu::TextureView>,
}
impl WebPainterWgpu {
#[allow(unused)] // only used if `wgpu` is the only active feature.
pub fn render_state(&self) -> Option<RenderState> {
self.render_state.clone()
}
pub fn generate_depth_texture_view(
&self,
render_state: &RenderState,
width_in_pixels: u32,
height_in_pixels: u32,
) -> Option<wgpu::TextureView> {
let device = &render_state.device;
self.depth_format.map(|depth_format| {
device
.create_texture(&wgpu::TextureDescriptor {
label: Some("egui_depth_texture"),
size: wgpu::Extent3d {
width: width_in_pixels,
height: height_in_pixels,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: depth_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[depth_format],
})
.create_view(&wgpu::TextureViewDescriptor::default())
})
}
#[allow(unused)] // only used if `wgpu` is the only active feature.
pub async fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String> {
tracing::debug!("Creating wgpu painter");
let canvas = super::canvas_element_or_die(canvas_id);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: options.wgpu_options.backends,
dx12_shader_compiler: Default::default(),
});
let surface = instance
.create_surface_from_canvas(&canvas)
.map_err(|err| format!("failed to create wgpu surface: {err}"))?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: options.wgpu_options.power_preference,
force_fallback_adapter: false,
compatible_surface: None,
})
.await
.ok_or_else(|| "No suitable GPU adapters found on the system".to_owned())?;
let (device, queue) = adapter
.request_device(
&options.wgpu_options.device_descriptor,
None, // Capture doesn't work in the browser environment.
)
.await
.map_err(|err| format!("Failed to find wgpu device: {}", err))?;
let target_format =
egui_wgpu::preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats);
let depth_format = options.wgpu_options.depth_format;
let renderer = egui_wgpu::Renderer::new(&device, target_format, depth_format, 1);
let render_state = RenderState {
device: Arc::new(device),
queue: Arc::new(queue),
target_format,
renderer: Arc::new(RwLock::new(renderer)),
};
let surface_configuration = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: target_format,
width: 0,
height: 0,
present_mode: options.wgpu_options.present_mode,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![target_format],
};
tracing::debug!("wgpu painter initialized.");
Ok(Self {
canvas,
canvas_id: canvas_id.to_owned(),
render_state: Some(render_state),
surface,
surface_configuration,
depth_format,
depth_texture_view: None,
limits: options.wgpu_options.device_descriptor.limits.clone(),
on_surface_error: options.wgpu_options.on_surface_error.clone(),
})
}
}
impl WebPainter for WebPainterWgpu {
fn canvas_id(&self) -> &str {
&self.canvas_id
}
fn max_texture_side(&self) -> usize {
self.limits.max_texture_dimension_2d as _
}
fn paint_and_update_textures(
&mut self,
clear_color: [f32; 4],
clipped_primitives: &[egui::ClippedPrimitive],
pixels_per_point: f32,
textures_delta: &egui::TexturesDelta,
) -> Result<(), JsValue> {
let size_in_pixels = [self.canvas.width(), self.canvas.height()];
let render_state = if let Some(render_state) = &self.render_state {
render_state
} else {
return Err(JsValue::from_str(
"Can't paint, wgpu renderer was already disposed",
));
};
let mut encoder =
render_state
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("egui_webpainter_paint_and_update_textures"),
});
// Upload all resources for the GPU.
let screen_descriptor = ScreenDescriptor {
size_in_pixels,
pixels_per_point,
};
let user_cmd_bufs = {
let mut renderer = render_state.renderer.write();
for (id, image_delta) in &textures_delta.set {
renderer.update_texture(
&render_state.device,
&render_state.queue,
*id,
image_delta,
);
}
renderer.update_buffers(
&render_state.device,
&render_state.queue,
&mut encoder,
clipped_primitives,
&screen_descriptor,
)
};
// Resize surface if needed
let is_zero_sized_surface = size_in_pixels[0] == 0 || size_in_pixels[1] == 0;
let frame = if is_zero_sized_surface {
None
} else {
if size_in_pixels[0] != self.surface_configuration.width
|| size_in_pixels[1] != self.surface_configuration.height
{
self.surface_configuration.width = size_in_pixels[0];
self.surface_configuration.height = size_in_pixels[1];
self.surface
.configure(&render_state.device, &self.surface_configuration);
self.depth_texture_view = self.generate_depth_texture_view(
render_state,
size_in_pixels[0],
size_in_pixels[1],
);
}
let frame = match self.surface.get_current_texture() {
Ok(frame) => frame,
#[allow(clippy::single_match_else)]
Err(e) => match (*self.on_surface_error)(e) {
SurfaceErrorAction::RecreateSurface => {
self.surface
.configure(&render_state.device, &self.surface_configuration);
return Ok(());
}
SurfaceErrorAction::SkipFrame => {
return Ok(());
}
},
};
{
let renderer = render_state.renderer.read();
let frame_view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &frame_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: clear_color[0] as f64,
g: clear_color[1] as f64,
b: clear_color[2] as f64,
a: clear_color[3] as f64,
}),
store: true,
},
})],
depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| {
wgpu::RenderPassDepthStencilAttachment {
view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: false,
}),
stencil_ops: None,
}
}),
label: Some("egui_render"),
});
renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor);
}
Some(frame)
};
{
let mut renderer = render_state.renderer.write();
for id in &textures_delta.free {
renderer.free_texture(id);
}
}
// Submit the commands: both the main buffer and user-defined ones.
render_state.queue.submit(
user_cmd_bufs
.into_iter()
.chain(std::iter::once(encoder.finish())),
);
if let Some(frame) = frame {
frame.present();
}
Ok(())
}
fn destroy(&mut self) {
self.render_state = None;
}
}

View file

@ -0,0 +1,37 @@
# Changelog for egui-wgpu
All notable changes to the `egui-wgpu` integration will be noted in this file.
## Unreleased
## 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
* Renamed `RenderPass` to `Renderer`.
* Renamed `RenderPass::execute` to `RenderPass::render`.
* Renamed `RenderPass::execute_with_renderpass` to `Renderer::render` (replacing existing `Renderer::render`)
* Reexported `Renderer`.
* You can now use `egui-wgpu` on web, using WebGL ([#2107](https://github.com/emilk/egui/pull/2107)).
* `Renderer` no longer handles pass creation and depth buffer creation ([#2136](https://github.com/emilk/egui/pull/2136))
* `PrepareCallback` now passes `wgpu::CommandEncoder` ([#2136](https://github.com/emilk/egui/pull/2136))
* `PrepareCallback` can now returns `wgpu::CommandBuffer` that are bundled into a single `wgpu::Queue::submit` call ([#2230](https://github.com/emilk/egui/pull/2230))
* Only a single vertex & index buffer is now created and resized when necessary (previously, vertex/index buffers were allocated for every mesh) ([#2148](https://github.com/emilk/egui/pull/2148)).
* `Renderer::update_texture` no longer creates a new `wgpu::Sampler` with every new texture ([#2198](https://github.com/emilk/egui/pull/2198))
* `Painter`'s instance/device/adapter/surface creation is now configurable via `WgpuConfiguration` ([#2207](https://github.com/emilk/egui/pull/2207))
* Fix panic on using a depth buffer ([#2316](https://github.com/emilk/egui/pull/2316))
## 0.19.0 - 2022-08-20
* Enables deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)).
* Make `RenderPass` `Send` and `Sync` ([#1883](https://github.com/emilk/egui/pull/1883)).
## 0.18.0 - 2022-05-15
First published version since moving the code into the `egui` repository from <https://github.com/LU15W1R7H/eww>.

View file

@ -0,0 +1,56 @@
[package]
name = "egui-wgpu"
version = "0.21.0"
description = "Bindings for using egui natively using the wgpu library"
authors = [
"Nils Hasenbanck <nils@hasenbanck.de>",
"embotech <opensource@embotech.com>",
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
]
edition = "2021"
rust-version = "1.65"
homepage = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu"
categories = ["gui", "game-development"]
keywords = ["wgpu", "egui", "gui", "gamedev"]
include = [
"../LICENSE-APACHE",
"../LICENSE-MIT",
"**/*.rs",
"**/*.wgsl",
"Cargo.toml",
]
[package.metadata.docs.rs]
all-features = true
[features]
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
puffin = ["dep:puffin"]
## Enable [`winit`](https://docs.rs/winit) integration.
winit = ["dep:winit"]
[dependencies]
epaint = { version = "0.21.0", path = "../epaint", default-features = false, features = [
"bytemuck",
] }
bytemuck = "1.7"
tracing = { version = "0.1", default-features = false, features = ["std"] }
type-map = "0.5.0"
wgpu = "0.15.0"
#! ### Optional dependencies
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
winit = { version = "0.28", optional = true }
# Native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
puffin = { version = "0.14", optional = true }

View file

@ -0,0 +1,10 @@
# egui-wgpu
[![Latest version](https://img.shields.io/crates/v/egui-wgpu.svg)](https://crates.io/crates/egui-wgpu)
[![Documentation](https://docs.rs/egui-wgpu/badge.svg)](https://docs.rs/egui-wgpu)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu).
This was originally hosted at https://github.com/hasenbanck/egui_wgpu_backend

View file

@ -0,0 +1,91 @@
// Vertex shader bindings
struct VertexOutput {
@location(0) tex_coord: vec2<f32>,
@location(1) color: vec4<f32>, // gamma 0-1
@builtin(position) position: vec4<f32>,
};
struct Locals {
screen_size: vec2<f32>,
// Uniform buffers need to be at least 16 bytes in WebGL.
// See https://github.com/gfx-rs/wgpu/issues/2072
_padding: vec2<u32>,
};
@group(0) @binding(0) var<uniform> r_locals: Locals;
// 0-1 linear from 0-1 sRGB gamma
fn linear_from_gamma_rgb(srgb: vec3<f32>) -> vec3<f32> {
let cutoff = srgb < vec3<f32>(0.04045);
let lower = srgb / vec3<f32>(12.92);
let higher = pow((srgb + vec3<f32>(0.055)) / vec3<f32>(1.055), vec3<f32>(2.4));
return select(higher, lower, cutoff);
}
// 0-1 sRGB gamma from 0-1 linear
fn gamma_from_linear_rgb(rgb: vec3<f32>) -> vec3<f32> {
let cutoff = rgb < vec3<f32>(0.0031308);
let lower = rgb * vec3<f32>(12.92);
let higher = vec3<f32>(1.055) * pow(rgb, vec3<f32>(1.0 / 2.4)) - vec3<f32>(0.055);
return select(higher, lower, cutoff);
}
// 0-1 sRGBA gamma from 0-1 linear
fn gamma_from_linear_rgba(linear_rgba: vec4<f32>) -> vec4<f32> {
return vec4<f32>(gamma_from_linear_rgb(linear_rgba.rgb), linear_rgba.a);
}
// [u8; 4] SRGB as u32 -> [r, g, b, a] in 0.-1
fn unpack_color(color: u32) -> vec4<f32> {
return vec4<f32>(
f32(color & 255u),
f32((color >> 8u) & 255u),
f32((color >> 16u) & 255u),
f32((color >> 24u) & 255u),
) / 255.0;
}
fn position_from_screen(screen_pos: vec2<f32>) -> vec4<f32> {
return vec4<f32>(
2.0 * screen_pos.x / r_locals.screen_size.x - 1.0,
1.0 - 2.0 * screen_pos.y / r_locals.screen_size.y,
0.0,
1.0,
);
}
@vertex
fn vs_main(
@location(0) a_pos: vec2<f32>,
@location(1) a_tex_coord: vec2<f32>,
@location(2) a_color: u32,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coord = a_tex_coord;
out.color = unpack_color(a_color);
out.position = position_from_screen(a_pos);
return out;
}
// Fragment shader bindings
@group(1) @binding(0) var r_tex_color: texture_2d<f32>;
@group(1) @binding(1) var r_tex_sampler: sampler;
@fragment
fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
// We always have an sRGB aware texture at the moment.
let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
let tex_gamma = gamma_from_linear_rgba(tex_linear);
let out_color_gamma = in.color * tex_gamma;
return vec4<f32>(linear_from_gamma_rgb(out_color_gamma.rgb), out_color_gamma.a);
}
@fragment
fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
// We always have an sRGB aware texture at the moment.
let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
let tex_gamma = gamma_from_linear_rgba(tex_linear);
let out_color_gamma = in.color * tex_gamma;
return out_color_gamma;
}

155
crates/egui-wgpu/src/lib.rs Normal file
View file

@ -0,0 +1,155 @@
//! This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu).
//!
//! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!
#![allow(unsafe_code)]
pub use wgpu;
/// Low-level painting of [`egui`](https://github.com/emilk/egui) on [`wgpu`].
pub mod renderer;
pub use renderer::CallbackFn;
pub use renderer::Renderer;
/// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`].
#[cfg(feature = "winit")]
pub mod winit;
use std::sync::Arc;
use epaint::mutex::RwLock;
/// Access to the render state for egui.
#[derive(Clone)]
pub struct RenderState {
pub device: Arc<wgpu::Device>,
pub queue: Arc<wgpu::Queue>,
pub target_format: wgpu::TextureFormat,
pub renderer: Arc<RwLock<Renderer>>,
}
/// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`]
pub enum SurfaceErrorAction {
/// Do nothing and skip the current frame.
SkipFrame,
/// Instructs egui to recreate the surface, then skip the current frame.
RecreateSurface,
}
/// Configuration for using wgpu with eframe or the egui-wgpu winit feature.
#[derive(Clone)]
pub struct WgpuConfiguration {
/// Configuration passed on device request.
pub device_descriptor: wgpu::DeviceDescriptor<'static>,
/// Backends that should be supported (wgpu will pick one of these)
pub backends: wgpu::Backends,
/// Present mode used for the primary surface.
pub present_mode: wgpu::PresentMode,
/// Power preference for the adapter.
pub power_preference: wgpu::PowerPreference,
/// Callback for surface errors.
pub on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
pub depth_format: Option<wgpu::TextureFormat>,
}
impl Default for WgpuConfiguration {
fn default() -> Self {
Self {
device_descriptor: wgpu::DeviceDescriptor {
label: Some("egui wgpu device"),
features: wgpu::Features::default(),
limits: wgpu::Limits::default(),
},
backends: wgpu::Backends::PRIMARY | wgpu::Backends::GL,
present_mode: wgpu::PresentMode::AutoVsync,
power_preference: wgpu::PowerPreference::HighPerformance,
depth_format: None,
on_surface_error: Arc::new(|err| {
if err == wgpu::SurfaceError::Outdated {
// This error occurs when the app is minimized on Windows.
// Silently return here to prevent spamming the console with:
// "The underlying surface has changed, and therefore the swap chain must be updated"
} else {
tracing::warn!("Dropped frame with error: {err}");
}
SurfaceErrorAction::SkipFrame
}),
}
}
}
/// Find the framebuffer format that egui prefers
pub fn preferred_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat {
for &format in formats {
if matches!(
format,
wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
) {
return format;
}
}
formats[0] // take the first
}
// maybe use this-error?
#[derive(Debug)]
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 {
WgpuError::DeviceError(e) => e.source(),
WgpuError::SurfaceError(e) => e.source(),
}
}
}
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)
}
}
// ---------------------------------------------------------------------------
/// Profiling macro for feature "puffin"
macro_rules! profile_function {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
#[cfg(not(target_arch = "wasm32"))]
puffin::profile_function!($($arg)*);
};
}
pub(crate) use profile_function;
/// Profiling macro for feature "puffin"
macro_rules! profile_scope {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
#[cfg(not(target_arch = "wasm32"))]
puffin::profile_scope!($($arg)*);
};
}
pub(crate) use profile_scope;

View file

@ -0,0 +1,931 @@
#![allow(unsafe_code)]
use std::num::NonZeroU64;
use std::ops::Range;
use std::{borrow::Cow, collections::HashMap, num::NonZeroU32};
use type_map::concurrent::TypeMap;
use wgpu;
use wgpu::util::DeviceExt as _;
use epaint::{emath::NumExt, PaintCallbackInfo, Primitive, Vertex};
/// A callback function that can be used to compose an [`epaint::PaintCallback`] for custom WGPU
/// rendering.
///
/// The callback is composed of two functions: `prepare` and `paint`:
/// - `prepare` is called every frame before `paint`, and can use the passed-in
/// [`wgpu::Device`] and [`wgpu::Buffer`] to allocate or modify GPU resources such as buffers.
/// - `paint` is called after `prepare` and is given access to the [`wgpu::RenderPass`] so
/// that it can issue draw commands into the same [`wgpu::RenderPass`] that is used for
/// all other egui elements.
///
/// The final argument of both the `prepare` and `paint` callbacks is a the
/// [`paint_callback_resources`][crate::renderer::Renderer::paint_callback_resources].
/// `paint_callback_resources` has the same lifetime as the Egui render pass, so it can be used to
/// store buffers, pipelines, and other information that needs to be accessed during the render
/// pass.
///
/// # Example
///
/// See the [`custom3d_wgpu`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
pub struct CallbackFn {
prepare: Box<PrepareCallback>,
paint: Box<PaintCallback>,
}
type PrepareCallback = dyn Fn(
&wgpu::Device,
&wgpu::Queue,
&mut wgpu::CommandEncoder,
&mut TypeMap,
) -> Vec<wgpu::CommandBuffer>
+ Sync
+ Send;
type PaintCallback =
dyn for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + Sync + Send;
impl Default for CallbackFn {
fn default() -> Self {
CallbackFn {
prepare: Box::new(|_, _, _, _| Vec::new()),
paint: Box::new(|_, _, _| ()),
}
}
}
impl CallbackFn {
pub fn new() -> Self {
Self::default()
}
/// Set the prepare callback.
///
/// The passed-in `CommandEncoder` is egui's and can be used directly to register
/// wgpu commands for simple use cases.
/// This allows reusing the same [`wgpu::CommandEncoder`] for all callbacks and egui
/// rendering itself.
///
/// For more complicated use cases, one can also return a list of arbitrary
/// `CommandBuffer`s and have complete control over how they get created and fed.
/// In particular, this gives an opportunity to parallelize command registration and
/// prevents a faulty callback from poisoning the main wgpu pipeline.
///
/// When using eframe, the main egui command buffer, as well as all user-defined
/// command buffers returned by this function, are guaranteed to all be submitted
/// at once in a single call.
pub fn prepare<F>(mut self, prepare: F) -> Self
where
F: Fn(
&wgpu::Device,
&wgpu::Queue,
&mut wgpu::CommandEncoder,
&mut TypeMap,
) -> Vec<wgpu::CommandBuffer>
+ Sync
+ Send
+ 'static,
{
self.prepare = Box::new(prepare) as _;
self
}
/// Set the paint callback
pub fn paint<F>(mut self, paint: F) -> Self
where
F: for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap)
+ Sync
+ Send
+ 'static,
{
self.paint = Box::new(paint) as _;
self
}
}
/// Information about the screen used for rendering.
pub struct ScreenDescriptor {
/// Size of the window in physical pixels.
pub size_in_pixels: [u32; 2],
/// HiDPI scale factor (pixels per point).
pub pixels_per_point: f32,
}
impl ScreenDescriptor {
/// size in "logical" points
fn screen_size_in_points(&self) -> [f32; 2] {
[
self.size_in_pixels[0] as f32 / self.pixels_per_point,
self.size_in_pixels[1] as f32 / self.pixels_per_point,
]
}
}
/// Uniform buffer used when rendering.
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
struct UniformBuffer {
screen_size_in_points: [f32; 2],
// Uniform buffers need to be at least 16 bytes in WebGL.
// See https://github.com/gfx-rs/wgpu/issues/2072
_padding: [u32; 2],
}
struct SlicedBuffer {
buffer: wgpu::Buffer,
slices: Vec<Range<wgpu::BufferAddress>>,
capacity: wgpu::BufferAddress,
}
/// Renderer for a egui based GUI.
pub struct Renderer {
pipeline: wgpu::RenderPipeline,
index_buffer: SlicedBuffer,
vertex_buffer: SlicedBuffer,
uniform_buffer: wgpu::Buffer,
uniform_bind_group: wgpu::BindGroup,
texture_bind_group_layout: wgpu::BindGroupLayout,
/// Map of egui texture IDs to textures and their associated bindgroups (texture view +
/// sampler). The texture may be None if the TextureId is just a handle to a user-provided
/// sampler.
textures: HashMap<epaint::TextureId, (Option<wgpu::Texture>, wgpu::BindGroup)>,
next_user_texture_id: u64,
samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>,
/// Storage for use by [`epaint::PaintCallback`]'s that need to store resources such as render
/// pipelines that must have the lifetime of the renderpass.
pub paint_callback_resources: TypeMap,
}
impl Renderer {
/// Creates a renderer for a egui UI.
///
/// `output_color_format` should preferably be [`wgpu::TextureFormat::Rgba8Unorm`] or
/// [`wgpu::TextureFormat::Bgra8Unorm`], i.e. in gamma-space.
pub fn new(
device: &wgpu::Device,
output_color_format: wgpu::TextureFormat,
output_depth_format: Option<wgpu::TextureFormat>,
msaa_samples: u32,
) -> Self {
crate::profile_function!();
let shader = wgpu::ShaderModuleDescriptor {
label: Some("egui"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))),
};
let module = device.create_shader_module(shader);
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("egui_uniform_buffer"),
contents: bytemuck::cast_slice(&[UniformBuffer {
screen_size_in_points: [0.0, 0.0],
_padding: Default::default(),
}]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("egui_uniform_bind_group_layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(std::mem::size_of::<UniformBuffer>() as _),
ty: wgpu::BufferBindingType::Uniform,
},
count: None,
}],
});
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("egui_uniform_bind_group"),
layout: &uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &uniform_buffer,
offset: 0,
size: None,
}),
}],
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("egui_texture_bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("egui_pipeline_layout"),
bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
push_constant_ranges: &[],
});
let depth_stencil = output_depth_format.map(|format| wgpu::DepthStencilState {
format,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("egui_pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
entry_point: "vs_main",
module: &module,
buffers: &[wgpu::VertexBufferLayout {
array_stride: 5 * 4,
step_mode: wgpu::VertexStepMode::Vertex,
// 0: vec2 position
// 1: vec2 texture coordinates
// 2: uint color
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32],
}],
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
unclipped_depth: false,
conservative: false,
cull_mode: None,
front_face: wgpu::FrontFace::default(),
polygon_mode: wgpu::PolygonMode::default(),
strip_index_format: None,
},
depth_stencil,
multisample: wgpu::MultisampleState {
alpha_to_coverage_enabled: false,
count: msaa_samples,
mask: !0,
},
fragment: Some(wgpu::FragmentState {
module: &module,
entry_point: if output_color_format.describe().srgb {
tracing::warn!("Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm", output_color_format);
"fs_main_linear_framebuffer"
} else {
"fs_main_gamma_framebuffer" // this is what we prefer
},
targets: &[Some(wgpu::ColorTargetState {
format: output_color_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::OneMinusDstAlpha,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
multiview: None,
});
const VERTEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
(std::mem::size_of::<Vertex>() * 1024) as _;
const INDEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
(std::mem::size_of::<u32>() * 1024 * 3) as _;
Self {
pipeline,
vertex_buffer: SlicedBuffer {
buffer: create_vertex_buffer(device, VERTEX_BUFFER_START_CAPACITY),
slices: Vec::with_capacity(64),
capacity: VERTEX_BUFFER_START_CAPACITY,
},
index_buffer: SlicedBuffer {
buffer: create_index_buffer(device, INDEX_BUFFER_START_CAPACITY),
slices: Vec::with_capacity(64),
capacity: INDEX_BUFFER_START_CAPACITY,
},
uniform_buffer,
uniform_bind_group,
texture_bind_group_layout,
textures: HashMap::new(),
next_user_texture_id: 0,
samplers: HashMap::new(),
paint_callback_resources: TypeMap::default(),
}
}
/// Executes the egui renderer onto an existing wgpu renderpass.
pub fn render<'rp>(
&'rp self,
render_pass: &mut wgpu::RenderPass<'rp>,
paint_jobs: &[epaint::ClippedPrimitive],
screen_descriptor: &ScreenDescriptor,
) {
crate::profile_function!();
let pixels_per_point = screen_descriptor.pixels_per_point;
let size_in_pixels = screen_descriptor.size_in_pixels;
// Whether or not we need to reset the render pass because a paint callback has just
// run.
let mut needs_reset = true;
let mut index_buffer_slices = self.index_buffer.slices.iter();
let mut vertex_buffer_slices = self.vertex_buffer.slices.iter();
for epaint::ClippedPrimitive {
clip_rect,
primitive,
} in paint_jobs
{
if needs_reset {
render_pass.set_viewport(
0.0,
0.0,
size_in_pixels[0] as f32,
size_in_pixels[1] as f32,
0.0,
1.0,
);
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
needs_reset = false;
}
{
let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels);
if rect.width == 0 || rect.height == 0 {
// Skip rendering zero-sized clip areas.
if let Primitive::Mesh(_) = primitive {
// If this is a mesh, we need to advance the index and vertex buffer iterators:
index_buffer_slices.next().unwrap();
vertex_buffer_slices.next().unwrap();
}
continue;
}
render_pass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height);
}
match primitive {
Primitive::Mesh(mesh) => {
let index_buffer_slice = index_buffer_slices.next().unwrap();
let vertex_buffer_slice = vertex_buffer_slices.next().unwrap();
if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) {
render_pass.set_bind_group(1, bind_group, &[]);
render_pass.set_index_buffer(
self.index_buffer.buffer.slice(index_buffer_slice.clone()),
wgpu::IndexFormat::Uint32,
);
render_pass.set_vertex_buffer(
0,
self.vertex_buffer.buffer.slice(vertex_buffer_slice.clone()),
);
render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
} else {
tracing::warn!("Missing texture: {:?}", mesh.texture_id);
}
}
Primitive::Callback(callback) => {
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
c
} else {
// We already warned in the `prepare` callback
continue;
};
if callback.rect.is_positive() {
crate::profile_scope!("callback");
needs_reset = true;
{
// We're setting a default viewport for the render pass as a
// courtesy for the user, so that they don't have to think about
// it in the simple case where they just want to fill the whole
// paint area.
//
// The user still has the possibility of setting their own custom
// viewport during the paint callback, effectively overriding this
// one.
let min = (callback.rect.min.to_vec2() * pixels_per_point).round();
let max = (callback.rect.max.to_vec2() * pixels_per_point).round();
render_pass.set_viewport(
min.x,
min.y,
max.x - min.x,
max.y - min.y,
0.0,
1.0,
);
}
(cbfn.paint)(
PaintCallbackInfo {
viewport: callback.rect,
clip_rect: *clip_rect,
pixels_per_point,
screen_size_px: size_in_pixels,
},
render_pass,
&self.paint_callback_resources,
);
}
}
}
}
render_pass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);
}
/// Should be called before `render()`.
pub fn update_texture(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
id: epaint::TextureId,
image_delta: &epaint::ImageDelta,
) {
crate::profile_function!();
let width = image_delta.image.width() as u32;
let height = image_delta.image.height() as u32;
let size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let data_color32 = match &image_delta.image {
epaint::ImageData::Color(image) => {
assert_eq!(
width as usize * height as usize,
image.pixels.len(),
"Mismatch between texture size and texel count"
);
Cow::Borrowed(&image.pixels)
}
epaint::ImageData::Font(image) => {
assert_eq!(
width as usize * height as usize,
image.pixels.len(),
"Mismatch between texture size and texel count"
);
Cow::Owned(image.srgba_pixels(None).collect::<Vec<_>>())
}
};
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
let queue_write_data_to_texture = |texture, origin| {
queue.write_texture(
wgpu::ImageCopyTexture {
texture,
mip_level: 0,
origin,
aspect: wgpu::TextureAspect::All,
},
data_bytes,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: NonZeroU32::new(4 * width),
rows_per_image: NonZeroU32::new(height),
},
size,
);
};
if let Some(pos) = image_delta.pos {
// update the existing texture
let (texture, _bind_group) = self
.textures
.get(&id)
.expect("Tried to update a texture that has not been allocated yet.");
let origin = wgpu::Origin3d {
x: pos[0] as u32,
y: pos[1] as u32,
z: 0,
};
queue_write_data_to_texture(
texture.as_ref().expect("Tried to update user texture."),
origin,
);
} else {
// allocate a new texture
// Use same label for all resources associated with this texture id (no point in retyping the type)
let label_str = format!("egui_texid_{:?}", id);
let label = Some(label_str.as_str());
let texture = device.create_texture(&wgpu::TextureDescriptor {
label,
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb, // Minspec for wgpu WebGL emulation is WebGL2, so this should always be supported.
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
});
let sampler = self
.samplers
.entry(image_delta.options)
.or_insert_with(|| create_sampler(image_delta.options, device));
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label,
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
&texture.create_view(&wgpu::TextureViewDescriptor::default()),
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
});
let origin = wgpu::Origin3d::ZERO;
queue_write_data_to_texture(&texture, origin);
self.textures.insert(id, (Some(texture), bind_group));
};
}
pub fn free_texture(&mut self, id: &epaint::TextureId) {
self.textures.remove(id);
}
/// Get the WGPU texture and bind group associated to a texture that has been allocated by egui.
///
/// This could be used by custom paint hooks to render images that have been added through with
/// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html)
/// or [`epaint::Context::load_texture`](https://docs.rs/egui/latest/egui/struct.Context.html#method.load_texture).
pub fn texture(
&self,
id: &epaint::TextureId,
) -> Option<&(Option<wgpu::Texture>, wgpu::BindGroup)> {
self.textures.get(id)
}
/// Registers a `wgpu::Texture` with a `epaint::TextureId`.
///
/// This enables the application to reference the texture inside an image ui element.
/// This effectively enables off-screen rendering inside the egui UI. Texture must have
/// the texture format `TextureFormat::Rgba8UnormSrgb` and
/// Texture usage `TextureUsage::SAMPLED`.
pub fn register_native_texture(
&mut self,
device: &wgpu::Device,
texture: &wgpu::TextureView,
texture_filter: wgpu::FilterMode,
) -> epaint::TextureId {
self.register_native_texture_with_sampler_options(
device,
texture,
wgpu::SamplerDescriptor {
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
mag_filter: texture_filter,
min_filter: texture_filter,
..Default::default()
},
)
}
/// Registers a `wgpu::Texture` with an existing `epaint::TextureId`.
///
/// This enables applications to reuse `TextureId`s.
pub fn update_egui_texture_from_wgpu_texture(
&mut self,
device: &wgpu::Device,
texture: &wgpu::TextureView,
texture_filter: wgpu::FilterMode,
id: epaint::TextureId,
) {
self.update_egui_texture_from_wgpu_texture_with_sampler_options(
device,
texture,
wgpu::SamplerDescriptor {
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
mag_filter: texture_filter,
min_filter: texture_filter,
..Default::default()
},
id,
);
}
/// Registers a `wgpu::Texture` with a `epaint::TextureId` while also accepting custom
/// `wgpu::SamplerDescriptor` options.
///
/// This allows applications to specify individual minification/magnification filters as well as
/// custom mipmap and tiling options.
///
/// The `Texture` must have the format `TextureFormat::Rgba8UnormSrgb` and usage
/// `TextureUsage::SAMPLED`. Any compare function supplied in the `SamplerDescriptor` will be
/// ignored.
#[allow(clippy::needless_pass_by_value)] // false positive
pub fn register_native_texture_with_sampler_options(
&mut self,
device: &wgpu::Device,
texture: &wgpu::TextureView,
sampler_descriptor: wgpu::SamplerDescriptor<'_>,
) -> epaint::TextureId {
crate::profile_function!();
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
compare: None,
..sampler_descriptor
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(texture),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
});
let id = epaint::TextureId::User(self.next_user_texture_id);
self.textures.insert(id, (None, bind_group));
self.next_user_texture_id += 1;
id
}
/// Registers a `wgpu::Texture` with an existing `epaint::TextureId` while also accepting custom
/// `wgpu::SamplerDescriptor` options.
///
/// This allows applications to reuse `TextureId`s created with custom sampler options.
#[allow(clippy::needless_pass_by_value)] // false positive
pub fn update_egui_texture_from_wgpu_texture_with_sampler_options(
&mut self,
device: &wgpu::Device,
texture: &wgpu::TextureView,
sampler_descriptor: wgpu::SamplerDescriptor<'_>,
id: epaint::TextureId,
) {
crate::profile_function!();
let (_user_texture, user_texture_binding) = self
.textures
.get_mut(&id)
.expect("Tried to update a texture that has not been allocated yet.");
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
compare: None,
..sampler_descriptor
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(texture),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
});
*user_texture_binding = bind_group;
}
/// Uploads the uniform, vertex and index data used by the renderer.
/// Should be called before `render()`.
///
/// Returns all user-defined command buffers gathered from prepare callbacks.
pub fn update_buffers(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
paint_jobs: &[epaint::ClippedPrimitive],
screen_descriptor: &ScreenDescriptor,
) -> Vec<wgpu::CommandBuffer> {
crate::profile_function!();
let screen_size_in_points = screen_descriptor.screen_size_in_points();
{
crate::profile_scope!("uniforms");
// Update uniform buffer
queue.write_buffer(
&self.uniform_buffer,
0,
bytemuck::cast_slice(&[UniformBuffer {
screen_size_in_points,
_padding: Default::default(),
}]),
);
}
// Determine how many vertices & indices need to be rendered.
let (vertex_count, index_count) = {
crate::profile_scope!("count_vertices_indices");
paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {
match &clipped_primitive.primitive {
Primitive::Mesh(mesh) => {
(acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len())
}
Primitive::Callback(_) => acc,
}
})
};
{
// Resize index buffer if needed:
self.index_buffer.slices.clear();
let required_size = (std::mem::size_of::<u32>() * index_count) as u64;
if self.index_buffer.capacity < required_size {
self.index_buffer.capacity =
(self.index_buffer.capacity * 2).at_least(required_size);
self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity);
}
}
{
// Resize vertex buffer if needed:
self.vertex_buffer.slices.clear();
let required_size = (std::mem::size_of::<Vertex>() * vertex_count) as u64;
if self.vertex_buffer.capacity < required_size {
self.vertex_buffer.capacity =
(self.vertex_buffer.capacity * 2).at_least(required_size);
self.vertex_buffer.buffer =
create_vertex_buffer(device, self.vertex_buffer.capacity);
}
}
// Upload index & vertex data and call user callbacks
let mut user_cmd_bufs = Vec::new(); // collect user command buffers
crate::profile_scope!("primitives");
for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
match primitive {
Primitive::Mesh(mesh) => {
{
let index_offset = self.index_buffer.slices.last().unwrap_or(&(0..0)).end;
let data = bytemuck::cast_slice(&mesh.indices);
queue.write_buffer(&self.index_buffer.buffer, index_offset, data);
self.index_buffer
.slices
.push(index_offset..(data.len() as wgpu::BufferAddress + index_offset));
}
{
let vertex_offset = self.vertex_buffer.slices.last().unwrap_or(&(0..0)).end;
let data = bytemuck::cast_slice(&mesh.vertices);
queue.write_buffer(&self.vertex_buffer.buffer, vertex_offset, data);
self.vertex_buffer.slices.push(
vertex_offset..(data.len() as wgpu::BufferAddress + vertex_offset),
);
}
}
Primitive::Callback(callback) => {
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
c
} else {
tracing::warn!("Unknown paint callback: expected `egui_wgpu::CallbackFn`");
continue;
};
crate::profile_scope!("callback");
user_cmd_bufs.extend((cbfn.prepare)(
device,
queue,
encoder,
&mut self.paint_callback_resources,
));
}
}
}
user_cmd_bufs
}
}
fn create_sampler(
options: epaint::textures::TextureOptions,
device: &wgpu::Device,
) -> wgpu::Sampler {
let mag_filter = match options.magnification {
epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
};
let min_filter = match options.minification {
epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
};
device.create_sampler(&wgpu::SamplerDescriptor {
label: Some(&format!(
"egui sampler (mag: {:?}, min {:?})",
mag_filter, min_filter
)),
mag_filter,
min_filter,
..Default::default()
})
}
fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
crate::profile_function!();
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("egui_vertex_buffer"),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
size,
mapped_at_creation: false,
})
}
fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
crate::profile_function!();
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("egui_index_buffer"),
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
size,
mapped_at_creation: false,
})
}
/// A Rect in physical pixel space, used for setting cliipping rectangles.
struct ScissorRect {
x: u32,
y: u32,
width: u32,
height: u32,
}
impl ScissorRect {
fn new(clip_rect: &epaint::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self {
// Transform clip rect to physical pixels:
let clip_min_x = pixels_per_point * clip_rect.min.x;
let clip_min_y = pixels_per_point * clip_rect.min.y;
let clip_max_x = pixels_per_point * clip_rect.max.x;
let clip_max_y = pixels_per_point * clip_rect.max.y;
// Round to integer:
let clip_min_x = clip_min_x.round() as u32;
let clip_min_y = clip_min_y.round() as u32;
let clip_max_x = clip_max_x.round() as u32;
let clip_max_y = clip_max_y.round() as u32;
// Clamp:
let clip_min_x = clip_min_x.clamp(0, target_size[0]);
let clip_min_y = clip_min_y.clamp(0, target_size[1]);
let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]);
let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]);
Self {
x: clip_min_x,
y: clip_min_y,
width: clip_max_x - clip_min_x,
height: clip_max_y - clip_min_y,
}
}
}
#[test]
fn renderer_impl_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Renderer>();
}

View file

@ -0,0 +1,418 @@
use std::sync::Arc;
use epaint::mutex::RwLock;
use tracing::error;
use crate::{renderer, RenderState, Renderer, SurfaceErrorAction, WgpuConfiguration};
struct SurfaceState {
surface: wgpu::Surface,
alpha_mode: wgpu::CompositeAlphaMode,
width: u32,
height: u32,
}
/// Everything you need to paint egui with [`wgpu`] on [`winit`].
///
/// Alternatively you can use [`crate::renderer`] directly.
pub struct Painter {
configuration: WgpuConfiguration,
msaa_samples: u32,
support_transparent_backbuffer: bool,
depth_format: Option<wgpu::TextureFormat>,
depth_texture_view: Option<wgpu::TextureView>,
instance: wgpu::Instance,
adapter: Option<wgpu::Adapter>,
render_state: Option<RenderState>,
surface_state: Option<SurfaceState>,
}
impl Painter {
/// Manages [`wgpu`] state, including surface state, required to render egui.
///
/// Only the [`wgpu::Instance`] is initialized here. Device selection and the initialization
/// of render + surface state is deferred until the painter is given its first window target
/// via [`set_window()`](Self::set_window). (Ensuring that a device that's compatible with the
/// native window is chosen)
///
/// Before calling [`paint_and_update_textures()`](Self::paint_and_update_textures) a
/// [`wgpu::Surface`] must be initialized (and corresponding render state) by calling
/// [`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,
support_transparent_backbuffer: bool,
) -> Self {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: configuration.backends,
dx12_shader_compiler: Default::default(), //
});
Self {
configuration,
msaa_samples,
support_transparent_backbuffer,
depth_format: (depth_bits > 0).then_some(wgpu::TextureFormat::Depth32Float),
depth_texture_view: None,
instance,
adapter: None,
render_state: None,
surface_state: None,
}
}
/// Get the [`RenderState`].
///
/// Will return [`None`] if the render state has not been initialized yet.
pub fn render_state(&self) -> Option<RenderState> {
self.render_state.clone()
}
async fn init_render_state(
&self,
adapter: &wgpu::Adapter,
target_format: wgpu::TextureFormat,
) -> Result<RenderState, wgpu::RequestDeviceError> {
adapter
.request_device(&self.configuration.device_descriptor, None)
.await
.map(|(device, queue)| {
let renderer =
Renderer::new(&device, target_format, self.depth_format, self.msaa_samples);
RenderState {
device: Arc::new(device),
queue: Arc::new(queue),
target_format,
renderer: Arc::new(RwLock::new(renderer)),
}
})
}
// We want to defer the initialization of our render state until we have a surface
// so we can take its format into account.
//
// After we've initialized our render state once though we expect all future surfaces
// will have the same format and so this render state will remain valid.
async fn ensure_render_state_for_surface(
&mut self,
surface: &wgpu::Surface,
) -> Result<(), wgpu::RequestDeviceError> {
if self.adapter.is_none() {
self.adapter = self
.instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: self.configuration.power_preference,
compatible_surface: Some(surface),
force_fallback_adapter: false,
})
.await;
}
if self.render_state.is_none() {
match &self.adapter {
Some(adapter) => {
let swapchain_format = crate::preferred_framebuffer_format(
&surface.get_capabilities(adapter).formats,
);
let rs = self.init_render_state(adapter, swapchain_format).await?;
self.render_state = Some(rs);
}
None => return Err(wgpu::RequestDeviceError {}),
}
}
Ok(())
}
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`]
///
/// This creates a [`wgpu::Surface`] for the given Window (as well as initializing render
/// state if needed) that is used for egui rendering.
///
/// This must be called before trying to render via
/// [`paint_and_update_textures`](Self::paint_and_update_textures)
///
/// # Portability
///
/// _In particular it's important to note that on Android a it's only possible to create
/// a window surface between `Resumed` and `Paused` lifecycle events, and Winit will panic on
/// attempts to query the raw window handle while paused._
///
/// On Android [`set_window`](Self::set_window) should be called with `Some(window)` for each
/// `Resumed` event and `None` for each `Paused` event. Currently, on all other platforms
/// [`set_window`](Self::set_window) may be called with `Some(window)` as soon as you have a
/// valid [`winit::window::Window`].
///
/// # Safety
///
/// The raw Window handle associated with the given `window` must be a valid object to create a
/// surface upon and must remain valid for the lifetime of the created surface. (The surface may
/// be cleared by passing `None`).
///
/// # Errors
/// If the provided wgpu configuration does not match an available device.
pub async unsafe fn set_window(
&mut self,
window: Option<&winit::window::Window>,
) -> Result<(), crate::WgpuError> {
match window {
Some(window) => {
let surface = self.instance.create_surface(&window)?;
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();
self.surface_state = Some(SurfaceState {
surface,
width: size.width,
height: size.height,
alpha_mode,
});
self.resize_and_generate_depth_texture_view(size.width, size.height);
}
None => {
self.surface_state = None;
}
}
Ok(())
}
/// Returns the maximum texture dimension supported if known
///
/// This API will only return a known dimension after `set_window()` has been called
/// at least once, since the underlying device and render state are initialized lazily
/// once we have a window (that may determine the choice of adapter/device).
pub fn max_texture_side(&self) -> Option<usize> {
self.render_state
.as_ref()
.map(|rs| rs.device.limits().max_texture_dimension_2d as usize)
}
fn resize_and_generate_depth_texture_view(
&mut self,
width_in_pixels: u32,
height_in_pixels: u32,
) {
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| {
render_state
.device
.create_texture(&wgpu::TextureDescriptor {
label: Some("egui_depth_texture"),
size: wgpu::Extent3d {
width: width_in_pixels,
height: height_in_pixels,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: depth_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[depth_format],
})
.create_view(&wgpu::TextureViewDescriptor::default())
});
}
pub fn on_window_resized(&mut self, width_in_pixels: u32, height_in_pixels: u32) {
if self.surface_state.is_some() {
self.resize_and_generate_depth_texture_view(width_in_pixels, height_in_pixels);
} else {
error!("Ignoring window resize notification with no surface created via Painter::set_window()");
}
}
pub fn paint_and_update_textures(
&mut self,
pixels_per_point: f32,
clear_color: [f32; 4],
clipped_primitives: &[epaint::ClippedPrimitive],
textures_delta: &epaint::textures::TexturesDelta,
) {
crate::profile_function!();
let render_state = match self.render_state.as_mut() {
Some(rs) => rs,
None => return,
};
let surface_state = match self.surface_state.as_ref() {
Some(rs) => rs,
None => return,
};
let output_frame = {
crate::profile_scope!("get_current_texture");
// This is what vsync-waiting happens, at least on Mac.
surface_state.surface.get_current_texture()
};
let output_frame = match output_frame {
Ok(frame) => frame,
#[allow(clippy::single_match_else)]
Err(e) => match (*self.configuration.on_surface_error)(e) {
SurfaceErrorAction::RecreateSurface => {
Self::configure_surface(
surface_state,
render_state,
self.configuration.present_mode,
);
return;
}
SurfaceErrorAction::SkipFrame => {
return;
}
},
};
let mut encoder =
render_state
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("encoder"),
});
// Upload all resources for the GPU.
let screen_descriptor = renderer::ScreenDescriptor {
size_in_pixels: [surface_state.width, surface_state.height],
pixels_per_point,
};
let user_cmd_bufs = {
let mut renderer = render_state.renderer.write();
for (id, image_delta) in &textures_delta.set {
renderer.update_texture(
&render_state.device,
&render_state.queue,
*id,
image_delta,
);
}
renderer.update_buffers(
&render_state.device,
&render_state.queue,
&mut encoder,
clipped_primitives,
&screen_descriptor,
)
};
{
let renderer = render_state.renderer.read();
let frame_view = output_frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &frame_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: clear_color[0] as f64,
g: clear_color[1] as f64,
b: clear_color[2] as f64,
a: clear_color[3] as f64,
}),
store: true,
},
})],
depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| {
wgpu::RenderPassDepthStencilAttachment {
view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: true,
}),
stencil_ops: None,
}
}),
label: Some("egui_render"),
});
renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor);
}
{
let mut renderer = render_state.renderer.write();
for id in &textures_delta.free {
renderer.free_texture(id);
}
}
let encoded = {
crate::profile_scope!("CommandEncoder::finish");
encoder.finish()
};
// Submit the commands: both the main buffer and user-defined ones.
{
crate::profile_scope!("Queue::submit");
render_state
.queue
.submit(user_cmd_bufs.into_iter().chain(std::iter::once(encoded)));
};
// Redraw egui
{
crate::profile_scope!("present");
output_frame.present();
}
}
#[allow(clippy::unused_self)]
pub fn destroy(&mut self) {
// TODO(emilk): something here?
}
}

View file

@ -0,0 +1,64 @@
# Changelog for egui-winit
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](https://docs.rs/egui-winit) build ([#2420](https://github.com/emilk/egui/pull/2420)).
## 0.20.0 - 2022-12-08
* The default features of the `winit` crate are not enabled if the default features of `egui-winit` are disabled too ([#1971](https://github.com/emilk/egui/pull/1971)).
* Added new feature `wayland` which enables Wayland support ([#1971](https://github.com/emilk/egui/pull/1971)).
* Don't repaint when just moving window ([#1980](https://github.com/emilk/egui/pull/1980)).
* Added optional integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)).
## 0.19.0 - 2022-08-20
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
* Fixed clipboard on Wayland ([#1613](https://github.com/emilk/egui/pull/1613)).
* Allow deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)).
* Fixed window position persistence ([#1745](https://github.com/emilk/egui/pull/1745)).
* Fixed mouse cursor change on Linux ([#1747](https://github.com/emilk/egui/pull/1747)).
* Use the new `RawInput::has_focus` field to indicate whether the window has the keyboard focus ([#1859](https://github.com/emilk/egui/pull/1859)).
## 0.18.0 - 2022-04-30
* Reexport `egui` crate
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
* Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)).
* Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)).
* Removed the features `dark-light` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)).
## 0.17.0 - 2022-02-22
* Fixed horizontal scrolling direction on Linux.
* Replaced `std::time::Instant` with `instant::Instant` for WebAssembly compatability ([#1023](https://github.com/emilk/egui/pull/1023))
* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)).
* Fixed `enable_drag` on Windows OS ([#1108](https://github.com/emilk/egui/pull/1108)).
* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)).
* Require knowledge about max texture side (e.g. `GL_MAX_TEXTURE_SIZE`)) ([#1154](https://github.com/emilk/egui/pull/1154)).
## 0.16.0 - 2021-12-29
* Added helper `EpiIntegration` ([#871](https://github.com/emilk/egui/pull/871)).
* Fixed shift key getting stuck enabled with the X11 option `shift:both_capslock` enabled ([#849](https://github.com/emilk/egui/pull/849)).
* Removed `State::is_quit_event` and `State::is_quit_shortcut` ([#881](https://github.com/emilk/egui/pull/881)).
* Updated `winit` to 0.26 ([#930](https://github.com/emilk/egui/pull/930)).
## 0.15.0 - 2021-10-24
First stand-alone release. Previously part of `egui_glium`.

View file

@ -0,0 +1,76 @@
[package]
name = "egui-winit"
version = "0.21.1"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui with winit"
edition = "2021"
rust-version = "1.65"
homepage = "https://github.com/emilk/egui/tree/master/crates/egui-winit"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/emilk/egui/tree/master/crates/egui-winit"
categories = ["gui", "game-development"]
keywords = ["winit", "egui", "gui", "gamedev"]
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
[package.metadata.docs.rs]
all-features = true
[features]
default = ["clipboard", "links", "wayland", "winit/default"]
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
accesskit = ["accesskit_winit", "egui/accesskit"]
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`egui::epaint::Vertex`], [`egui::Vec2`] etc to `&[u8]`.
bytemuck = ["egui/bytemuck"]
## Enable cut/copy/paste to OS clipboard.
## If disabled a clipboard will be simulated so you can still copy/paste within the egui app.
clipboard = ["arboard", "smithay-clipboard"]
## Enable opening links in a browser when an egui hyperlink is clicked.
links = ["webbrowser"]
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
puffin = ["dep:puffin"]
## Allow serialization of [`WindowSettings`] using [`serde`](https://docs.rs/serde).
serde = ["egui/serde", "dep:serde"]
## Enables Wayland support.
wayland = ["winit/wayland"]
[dependencies]
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
"tracing",
] }
instant = { version = "0.1", features = [
"wasm-bindgen",
] } # We use instant so we can (maybe) compile for web
tracing = { version = "0.1", default-features = false, features = ["std"] }
winit = { version = "0.28", default-features = false }
#! ### Optional dependencies
# feature accesskit
accesskit_winit = { version = "0.10.0", optional = true }
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
puffin = { version = "0.14", optional = true }
serde = { version = "1.0", optional = true, features = ["derive"] }
webbrowser = { version = "0.8.3", optional = true }
[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies]
smithay-clipboard = { version = "0.6.3", optional = true }
[target.'cfg(not(target_os = "android"))'.dependencies]
arboard = { version = "3.2", optional = true, default-features = false }
[target.'cfg(target_os = "android")'.dependencies]
# TODO(emilk): this is probably not the right place for specifying native-activity, but we need to do it somewhere for the CI
android-activity = { version = "0.4", features = ["native-activity"] }

View file

@ -0,0 +1,11 @@
# egui-winit
[![Latest version](https://img.shields.io/crates/v/egui-winit.svg)](https://crates.io/crates/egui-winit)
[![Documentation](https://docs.rs/egui-winit/badge.svg)](https://docs.rs/egui-winit)
[![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)
This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [`winit`](https://crates.io/crates/winit).
The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc.

View file

@ -0,0 +1,146 @@
use std::os::raw::c_void;
/// Handles interfacing with the OS clipboard.
///
/// If the "clipboard" feature is off, or we cannot connect to the OS clipboard,
/// then a fallback clipboard that just works works within the same app is used instead.
pub struct Clipboard {
#[cfg(all(feature = "arboard", not(target_os = "android")))]
arboard: Option<arboard::Clipboard>,
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "smithay-clipboard"
))]
smithay: Option<smithay_clipboard::Clipboard>,
/// Fallback manual clipboard.
clipboard: String,
}
impl Clipboard {
#[allow(unused_variables)]
pub fn new(#[allow(unused_variables)] wayland_display: Option<*mut c_void>) -> Self {
Self {
#[cfg(all(feature = "arboard", not(target_os = "android")))]
arboard: init_arboard(),
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "smithay-clipboard"
))]
smithay: init_smithay_clipboard(wayland_display),
clipboard: Default::default(),
}
}
pub fn get(&mut self) -> Option<String> {
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "smithay-clipboard"
))]
if let Some(clipboard) = &mut self.smithay {
return match clipboard.load() {
Ok(text) => Some(text),
Err(err) => {
tracing::error!("smithay paste error: {err}");
None
}
};
}
#[cfg(all(feature = "arboard", not(target_os = "android")))]
if let Some(clipboard) = &mut self.arboard {
return match clipboard.get_text() {
Ok(text) => Some(text),
Err(err) => {
tracing::error!("arboard paste error: {err}");
None
}
};
}
Some(self.clipboard.clone())
}
pub fn set(&mut self, text: String) {
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "smithay-clipboard"
))]
if let Some(clipboard) = &mut self.smithay {
clipboard.store(text);
return;
}
#[cfg(all(feature = "arboard", not(target_os = "android")))]
if let Some(clipboard) = &mut self.arboard {
if let Err(err) = clipboard.set_text(text) {
tracing::error!("arboard copy/cut error: {err}");
}
return;
}
self.clipboard = text;
}
}
#[cfg(all(feature = "arboard", not(target_os = "android")))]
fn init_arboard() -> Option<arboard::Clipboard> {
tracing::debug!("Initializing arboard clipboard…");
match arboard::Clipboard::new() {
Ok(clipboard) => Some(clipboard),
Err(err) => {
tracing::warn!("Failed to initialize arboard clipboard: {err}");
None
}
}
}
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "smithay-clipboard"
))]
fn init_smithay_clipboard(
wayland_display: Option<*mut c_void>,
) -> Option<smithay_clipboard::Clipboard> {
if let Some(display) = wayland_display {
tracing::debug!("Initializing smithay clipboard…");
#[allow(unsafe_code)]
Some(unsafe { smithay_clipboard::Clipboard::new(display) })
} else {
tracing::debug!("Cannot initialize smithay clipboard without a display handle");
None
}
}

View file

@ -0,0 +1,920 @@
//! [`egui`] bindings for [`winit`](https://github.com/rust-windowing/winit).
//!
//! The library translates winit events to egui, handled copy/paste,
//! updates the cursor, open links clicked in egui, etc.
//!
//! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!
#![allow(clippy::manual_range_contains)]
use std::os::raw::c_void;
#[cfg(feature = "accesskit")]
pub use accesskit_winit;
pub use egui;
#[cfg(feature = "accesskit")]
use egui::accesskit;
pub use winit;
pub mod clipboard;
mod window_settings;
pub use window_settings::WindowSettings;
use winit::event_loop::EventLoopWindowTarget;
pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 {
window.scale_factor() as f32
}
pub fn screen_size_in_pixels(window: &winit::window::Window) -> egui::Vec2 {
let size = window.inner_size();
egui::vec2(size.width as f32, size.height as f32)
}
// ----------------------------------------------------------------------------
#[must_use]
pub struct EventResponse {
/// If true, egui consumed this event, i.e. wants exclusive use of this event
/// (e.g. a mouse click on an egui window, or entering text into a text field).
///
/// For instance, if you use egui for a game, you should only
/// pass on the events to your game when [`Self::consumed`] is `false.
///
/// Note that egui uses `tab` to move focus between elements, so this will always be `true` for tabs.
pub consumed: bool,
/// Do we need an egui refresh because of this event?
pub repaint: bool,
}
// ----------------------------------------------------------------------------
/// Handles the integration between egui and winit.
pub struct State {
start_time: instant::Instant,
egui_input: egui::RawInput,
pointer_pos_in_points: Option<egui::Pos2>,
any_pointer_button_down: bool,
current_cursor_icon: Option<egui::CursorIcon>,
/// What egui uses.
current_pixels_per_point: f32,
clipboard: clipboard::Clipboard,
/// If `true`, mouse inputs will be treated as touches.
/// Useful for debugging touch support in egui.
///
/// Creates duplicate touches, if real touch inputs are coming.
simulate_touch_screen: bool,
/// Is Some(…) when a touch is being translated to a pointer.
///
/// Only one touch will be interpreted as pointer at any time.
pointer_touch_id: Option<u64>,
/// track ime state
input_method_editor_started: bool,
#[cfg(feature = "accesskit")]
accesskit: Option<accesskit_winit::Adapter>,
}
impl State {
pub fn new<T>(event_loop: &EventLoopWindowTarget<T>) -> Self {
Self::new_with_wayland_display(wayland_display(event_loop))
}
pub fn new_with_wayland_display(wayland_display: Option<*mut c_void>) -> Self {
let egui_input = egui::RawInput {
has_focus: false, // winit will tell us when we have focus
..Default::default()
};
Self {
start_time: instant::Instant::now(),
egui_input,
pointer_pos_in_points: None,
any_pointer_button_down: false,
current_cursor_icon: None,
current_pixels_per_point: 1.0,
clipboard: clipboard::Clipboard::new(wayland_display),
simulate_touch_screen: false,
pointer_touch_id: None,
input_method_editor_started: false,
#[cfg(feature = "accesskit")]
accesskit: None,
}
}
#[cfg(feature = "accesskit")]
pub fn init_accesskit<T: From<accesskit_winit::ActionRequestEvent> + Send>(
&mut self,
window: &winit::window::Window,
event_loop_proxy: winit::event_loop::EventLoopProxy<T>,
initial_tree_update_factory: impl 'static + FnOnce() -> accesskit::TreeUpdate + Send,
) {
self.accesskit = Some(accesskit_winit::Adapter::new(
window,
initial_tree_update_factory,
event_loop_proxy,
));
}
/// Call this once a graphics context has been created to update the maximum texture dimensions
/// that egui will use.
pub fn set_max_texture_side(&mut self, max_texture_side: usize) {
self.egui_input.max_texture_side = Some(max_texture_side);
}
/// Call this when a new native Window is created for rendering to initialize the `pixels_per_point`
/// for that window.
///
/// In particular, on Android it is necessary to call this after each `Resumed` lifecycle
/// event, each time a new native window is created.
///
/// Once this has been initialized for a new window then this state will be maintained by handling
/// [`winit::event::WindowEvent::ScaleFactorChanged`] events.
pub fn set_pixels_per_point(&mut self, pixels_per_point: f32) {
self.egui_input.pixels_per_point = Some(pixels_per_point);
self.current_pixels_per_point = pixels_per_point;
}
/// The number of physical pixels per logical point,
/// as configured on the current egui context (see [`egui::Context::pixels_per_point`]).
#[inline]
pub fn pixels_per_point(&self) -> f32 {
self.current_pixels_per_point
}
/// The current input state.
/// This is changed by [`Self::on_event`] and cleared by [`Self::take_egui_input`].
#[inline]
pub fn egui_input(&self) -> &egui::RawInput {
&self.egui_input
}
/// Prepare for a new frame by extracting the accumulated input,
/// as well as setting [the time](egui::RawInput::time) and [screen rectangle](egui::RawInput::screen_rect).
pub fn take_egui_input(&mut self, window: &winit::window::Window) -> egui::RawInput {
let pixels_per_point = self.pixels_per_point();
self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
// On Windows, a minimized window will have 0 width and height.
// See: https://github.com/rust-windowing/winit/issues/208
// This solves an issue where egui window positions would be changed when minimizing on Windows.
let screen_size_in_pixels = screen_size_in_pixels(window);
let screen_size_in_points = screen_size_in_pixels / pixels_per_point;
self.egui_input.screen_rect =
if screen_size_in_points.x > 0.0 && screen_size_in_points.y > 0.0 {
Some(egui::Rect::from_min_size(
egui::Pos2::ZERO,
screen_size_in_points,
))
} else {
None
};
self.egui_input.take()
}
/// Call this when there is a new event.
///
/// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`].
pub fn on_event(
&mut self,
egui_ctx: &egui::Context,
event: &winit::event::WindowEvent<'_>,
) -> EventResponse {
use winit::event::WindowEvent;
match event {
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
let pixels_per_point = *scale_factor as f32;
self.egui_input.pixels_per_point = Some(pixels_per_point);
self.current_pixels_per_point = pixels_per_point;
EventResponse {
repaint: true,
consumed: false,
}
}
WindowEvent::MouseInput { state, button, .. } => {
self.on_mouse_button_input(*state, *button);
EventResponse {
repaint: true,
consumed: egui_ctx.wants_pointer_input(),
}
}
WindowEvent::MouseWheel { delta, .. } => {
self.on_mouse_wheel(*delta);
EventResponse {
repaint: true,
consumed: egui_ctx.wants_pointer_input(),
}
}
WindowEvent::CursorMoved { position, .. } => {
self.on_cursor_moved(*position);
EventResponse {
repaint: true,
consumed: egui_ctx.is_using_pointer(),
}
}
WindowEvent::CursorLeft { .. } => {
self.pointer_pos_in_points = None;
self.egui_input.events.push(egui::Event::PointerGone);
EventResponse {
repaint: true,
consumed: false,
}
}
// WindowEvent::TouchpadPressure {device_id, pressure, stage, .. } => {} // TODO
WindowEvent::Touch(touch) => {
self.on_touch(touch);
let consumed = match touch.phase {
winit::event::TouchPhase::Started
| winit::event::TouchPhase::Ended
| winit::event::TouchPhase::Cancelled => egui_ctx.wants_pointer_input(),
winit::event::TouchPhase::Moved => egui_ctx.is_using_pointer(),
};
EventResponse {
repaint: true,
consumed,
}
}
WindowEvent::ReceivedCharacter(ch) => {
// On Mac we get here when the user presses Cmd-C (copy), ctrl-W, etc.
// We need to ignore these characters that are side-effects of commands.
let is_mac_cmd = cfg!(target_os = "macos")
&& (self.egui_input.modifiers.ctrl || self.egui_input.modifiers.mac_cmd);
let consumed = if is_printable_char(*ch) && !is_mac_cmd {
self.egui_input
.events
.push(egui::Event::Text(ch.to_string()));
egui_ctx.wants_keyboard_input()
} else {
false
};
EventResponse {
repaint: true,
consumed,
}
}
WindowEvent::Ime(ime) => {
// on Mac even Cmd-C is preessed during ime, a `c` is pushed to Preedit.
// So no need to check is_mac_cmd.
//
// How winit produce `Ime::Enabled` and `Ime::Disabled` differs in MacOS
// and Windows.
//
// - On Windows, before and after each Commit will produce an Enable/Disabled
// event.
// - On MacOS, only when user explicit enable/disable ime. No Disabled
// after Commit.
//
// We use input_method_editor_started to mannualy insert CompositionStart
// between Commits.
match ime {
winit::event::Ime::Enabled | winit::event::Ime::Disabled => (),
winit::event::Ime::Commit(text) => {
self.input_method_editor_started = false;
self.egui_input
.events
.push(egui::Event::CompositionEnd(text.clone()));
}
winit::event::Ime::Preedit(text, ..) => {
if !self.input_method_editor_started {
self.input_method_editor_started = true;
self.egui_input.events.push(egui::Event::CompositionStart);
}
self.egui_input
.events
.push(egui::Event::CompositionUpdate(text.clone()));
}
};
EventResponse {
repaint: true,
consumed: egui_ctx.wants_keyboard_input(),
}
}
WindowEvent::KeyboardInput { input, .. } => {
self.on_keyboard_input(input);
let consumed = egui_ctx.wants_keyboard_input()
|| input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab);
EventResponse {
repaint: true,
consumed,
}
}
WindowEvent::Focused(has_focus) => {
self.egui_input.has_focus = *has_focus;
// We will not be given a KeyboardInput event when the modifiers are released while
// the window does not have focus. Unset all modifier state to be safe.
self.egui_input.modifiers = egui::Modifiers::default();
EventResponse {
repaint: true,
consumed: false,
}
}
WindowEvent::HoveredFile(path) => {
self.egui_input.hovered_files.push(egui::HoveredFile {
path: Some(path.clone()),
..Default::default()
});
EventResponse {
repaint: true,
consumed: false,
}
}
WindowEvent::HoveredFileCancelled => {
self.egui_input.hovered_files.clear();
EventResponse {
repaint: true,
consumed: false,
}
}
WindowEvent::DroppedFile(path) => {
self.egui_input.hovered_files.clear();
self.egui_input.dropped_files.push(egui::DroppedFile {
path: Some(path.clone()),
..Default::default()
});
EventResponse {
repaint: true,
consumed: false,
}
}
WindowEvent::ModifiersChanged(state) => {
self.egui_input.modifiers.alt = state.alt();
self.egui_input.modifiers.ctrl = state.ctrl();
self.egui_input.modifiers.shift = state.shift();
self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && state.logo();
self.egui_input.modifiers.command = if cfg!(target_os = "macos") {
state.logo()
} else {
state.ctrl()
};
EventResponse {
repaint: true,
consumed: false,
}
}
// Things that may require repaint:
WindowEvent::CloseRequested
| WindowEvent::CursorEntered { .. }
| WindowEvent::Destroyed
| WindowEvent::Occluded(_)
| WindowEvent::Resized(_)
| WindowEvent::ThemeChanged(_)
| WindowEvent::TouchpadPressure { .. } => EventResponse {
repaint: true,
consumed: false,
},
// Things we completely ignore:
WindowEvent::AxisMotion { .. }
| WindowEvent::Moved(_)
| WindowEvent::SmartMagnify { .. }
| WindowEvent::TouchpadRotate { .. } => EventResponse {
repaint: false,
consumed: false,
},
WindowEvent::TouchpadMagnify { delta, .. } => {
// Positive delta values indicate magnification (zooming in).
// Negative delta values indicate shrinking (zooming out).
let zoom_factor = (*delta as f32).exp();
self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
EventResponse {
repaint: true,
consumed: egui_ctx.wants_pointer_input(),
}
}
}
}
/// Call this when there is a new [`accesskit::ActionRequest`].
///
/// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`].
#[cfg(feature = "accesskit")]
pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
self.egui_input
.events
.push(egui::Event::AccessKitActionRequest(request));
}
fn on_mouse_button_input(
&mut self,
state: winit::event::ElementState,
button: winit::event::MouseButton,
) {
if let Some(pos) = self.pointer_pos_in_points {
if let Some(button) = translate_mouse_button(button) {
let pressed = state == winit::event::ElementState::Pressed;
self.egui_input.events.push(egui::Event::PointerButton {
pos,
button,
pressed,
modifiers: self.egui_input.modifiers,
});
if self.simulate_touch_screen {
if pressed {
self.any_pointer_button_down = true;
self.egui_input.events.push(egui::Event::Touch {
device_id: egui::TouchDeviceId(0),
id: egui::TouchId(0),
phase: egui::TouchPhase::Start,
pos,
force: 0.0,
});
} else {
self.any_pointer_button_down = false;
self.egui_input.events.push(egui::Event::PointerGone);
self.egui_input.events.push(egui::Event::Touch {
device_id: egui::TouchDeviceId(0),
id: egui::TouchId(0),
phase: egui::TouchPhase::End,
pos,
force: 0.0,
});
};
}
}
}
}
fn on_cursor_moved(&mut self, pos_in_pixels: winit::dpi::PhysicalPosition<f64>) {
let pos_in_points = egui::pos2(
pos_in_pixels.x as f32 / self.pixels_per_point(),
pos_in_pixels.y as f32 / self.pixels_per_point(),
);
self.pointer_pos_in_points = Some(pos_in_points);
if self.simulate_touch_screen {
if self.any_pointer_button_down {
self.egui_input
.events
.push(egui::Event::PointerMoved(pos_in_points));
self.egui_input.events.push(egui::Event::Touch {
device_id: egui::TouchDeviceId(0),
id: egui::TouchId(0),
phase: egui::TouchPhase::Move,
pos: pos_in_points,
force: 0.0,
});
}
} else {
self.egui_input
.events
.push(egui::Event::PointerMoved(pos_in_points));
}
}
fn on_touch(&mut self, touch: &winit::event::Touch) {
// Emit touch event
self.egui_input.events.push(egui::Event::Touch {
device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),
id: egui::TouchId::from(touch.id),
phase: match touch.phase {
winit::event::TouchPhase::Started => egui::TouchPhase::Start,
winit::event::TouchPhase::Moved => egui::TouchPhase::Move,
winit::event::TouchPhase::Ended => egui::TouchPhase::End,
winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
},
pos: egui::pos2(
touch.location.x as f32 / self.pixels_per_point(),
touch.location.y as f32 / self.pixels_per_point(),
),
force: match touch.force {
Some(winit::event::Force::Normalized(force)) => force as f32,
Some(winit::event::Force::Calibrated {
force,
max_possible_force,
..
}) => (force / max_possible_force) as f32,
None => 0_f32,
},
});
// If we're not yet tanslating a touch or we're translating this very
// touch …
if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap() == touch.id {
// … emit PointerButton resp. PointerMoved events to emulate mouse
match touch.phase {
winit::event::TouchPhase::Started => {
self.pointer_touch_id = Some(touch.id);
// First move the pointer to the right location
self.on_cursor_moved(touch.location);
self.on_mouse_button_input(
winit::event::ElementState::Pressed,
winit::event::MouseButton::Left,
);
}
winit::event::TouchPhase::Moved => {
self.on_cursor_moved(touch.location);
}
winit::event::TouchPhase::Ended => {
self.pointer_touch_id = None;
self.on_mouse_button_input(
winit::event::ElementState::Released,
winit::event::MouseButton::Left,
);
// The pointer should vanish completely to not get any
// hover effects
self.pointer_pos_in_points = None;
self.egui_input.events.push(egui::Event::PointerGone);
}
winit::event::TouchPhase::Cancelled => {
self.pointer_touch_id = None;
self.pointer_pos_in_points = None;
self.egui_input.events.push(egui::Event::PointerGone);
}
}
}
}
fn on_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) {
let delta = match delta {
winit::event::MouseScrollDelta::LineDelta(x, y) => {
let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461
egui::vec2(x, y) * points_per_scroll_line
}
winit::event::MouseScrollDelta::PixelDelta(delta) => {
egui::vec2(delta.x as f32, delta.y as f32) / self.pixels_per_point()
}
};
if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command {
// Treat as zoom instead:
let factor = (delta.y / 200.0).exp();
self.egui_input.events.push(egui::Event::Zoom(factor));
} else if self.egui_input.modifiers.shift {
// Treat as horizontal scrolling.
// Note: one Mac we already get horizontal scroll events when shift is down.
self.egui_input
.events
.push(egui::Event::Scroll(egui::vec2(delta.x + delta.y, 0.0)));
} else {
self.egui_input.events.push(egui::Event::Scroll(delta));
}
}
fn on_keyboard_input(&mut self, input: &winit::event::KeyboardInput) {
if let Some(keycode) = input.virtual_keycode {
let pressed = input.state == winit::event::ElementState::Pressed;
if pressed {
// VirtualKeyCode::Paste etc in winit are broken/untrustworthy,
// so we detect these things manually:
if is_cut_command(self.egui_input.modifiers, keycode) {
self.egui_input.events.push(egui::Event::Cut);
} else if is_copy_command(self.egui_input.modifiers, keycode) {
self.egui_input.events.push(egui::Event::Copy);
} else if is_paste_command(self.egui_input.modifiers, keycode) {
if let Some(contents) = self.clipboard.get() {
let contents = contents.replace("\r\n", "\n");
if !contents.is_empty() {
self.egui_input.events.push(egui::Event::Paste(contents));
}
}
}
}
if let Some(key) = translate_virtual_key_code(keycode) {
self.egui_input.events.push(egui::Event::Key {
key,
pressed,
repeat: false, // egui will fill this in for us!
modifiers: self.egui_input.modifiers,
});
}
}
}
/// Call with the output given by `egui`.
///
/// This will, if needed:
/// * update the cursor
/// * copy text to the clipboard
/// * open any clicked urls
/// * update the IME
/// *
pub fn handle_platform_output(
&mut self,
window: &winit::window::Window,
egui_ctx: &egui::Context,
platform_output: egui::PlatformOutput,
) {
let egui::PlatformOutput {
cursor_icon,
open_url,
copied_text,
events: _, // handled above
mutable_text_under_cursor: _, // only used in eframe web
text_cursor_pos,
#[cfg(feature = "accesskit")]
accesskit_update,
} = platform_output;
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
self.set_cursor_icon(window, cursor_icon);
if let Some(open_url) = open_url {
open_url_in_browser(&open_url.url);
}
if !copied_text.is_empty() {
self.clipboard.set(copied_text);
}
if let Some(egui::Pos2 { x, y }) = text_cursor_pos {
window.set_ime_position(winit::dpi::LogicalPosition { x, y });
}
#[cfg(feature = "accesskit")]
if let Some(accesskit) = self.accesskit.as_ref() {
if let Some(update) = accesskit_update {
accesskit.update_if_active(|| update);
}
}
}
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
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;
}
let is_pointer_in_window = self.pointer_pos_in_points.is_some();
if is_pointer_in_window {
self.current_cursor_icon = Some(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 {
// Remember to set the cursor again once the cursor returns to the screen:
self.current_cursor_icon = None;
}
}
}
fn open_url_in_browser(_url: &str) {
#[cfg(feature = "webbrowser")]
if let Err(err) = webbrowser::open(_url) {
tracing::warn!("Failed to open url: {}", err);
}
#[cfg(not(feature = "webbrowser"))]
{
tracing::warn!("Cannot open url - feature \"links\" not enabled.");
}
}
/// Winit sends special keys (backspace, delete, F1, …) as characters.
/// Ignore those.
/// We also ignore '\r', '\n', '\t'.
/// Newlines are handled by the `Key::Enter` event.
fn is_printable_char(chr: char) -> bool {
let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
|| '\u{f0000}' <= chr && chr <= '\u{ffffd}'
|| '\u{100000}' <= chr && chr <= '\u{10fffd}';
!is_in_private_use_area && !chr.is_ascii_control()
}
fn is_cut_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
(modifiers.command && keycode == winit::event::VirtualKeyCode::X)
|| (cfg!(target_os = "windows")
&& modifiers.shift
&& keycode == winit::event::VirtualKeyCode::Delete)
}
fn is_copy_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
(modifiers.command && keycode == winit::event::VirtualKeyCode::C)
|| (cfg!(target_os = "windows")
&& modifiers.ctrl
&& keycode == winit::event::VirtualKeyCode::Insert)
}
fn is_paste_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
(modifiers.command && keycode == winit::event::VirtualKeyCode::V)
|| (cfg!(target_os = "windows")
&& modifiers.shift
&& keycode == winit::event::VirtualKeyCode::Insert)
}
fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::PointerButton> {
match button {
winit::event::MouseButton::Left => Some(egui::PointerButton::Primary),
winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
winit::event::MouseButton::Other(1) => Some(egui::PointerButton::Extra1),
winit::event::MouseButton::Other(2) => Some(egui::PointerButton::Extra2),
winit::event::MouseButton::Other(_) => None,
}
}
fn translate_virtual_key_code(key: winit::event::VirtualKeyCode) -> Option<egui::Key> {
use egui::Key;
use winit::event::VirtualKeyCode;
Some(match key {
VirtualKeyCode::Down => Key::ArrowDown,
VirtualKeyCode::Left => Key::ArrowLeft,
VirtualKeyCode::Right => Key::ArrowRight,
VirtualKeyCode::Up => Key::ArrowUp,
VirtualKeyCode::Escape => Key::Escape,
VirtualKeyCode::Tab => Key::Tab,
VirtualKeyCode::Back => Key::Backspace,
VirtualKeyCode::Return => Key::Enter,
VirtualKeyCode::Space => Key::Space,
VirtualKeyCode::Insert => Key::Insert,
VirtualKeyCode::Delete => Key::Delete,
VirtualKeyCode::Home => Key::Home,
VirtualKeyCode::End => Key::End,
VirtualKeyCode::PageUp => Key::PageUp,
VirtualKeyCode::PageDown => Key::PageDown,
VirtualKeyCode::Minus => Key::Minus,
// Using Mac the key with the Plus sign on it is reported as the Equals key
// (with both English and Swedish keyboard).
VirtualKeyCode::Equals => Key::PlusEquals,
VirtualKeyCode::Key0 | VirtualKeyCode::Numpad0 => Key::Num0,
VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => Key::Num1,
VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => Key::Num2,
VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => Key::Num3,
VirtualKeyCode::Key4 | VirtualKeyCode::Numpad4 => Key::Num4,
VirtualKeyCode::Key5 | VirtualKeyCode::Numpad5 => Key::Num5,
VirtualKeyCode::Key6 | VirtualKeyCode::Numpad6 => Key::Num6,
VirtualKeyCode::Key7 | VirtualKeyCode::Numpad7 => Key::Num7,
VirtualKeyCode::Key8 | VirtualKeyCode::Numpad8 => Key::Num8,
VirtualKeyCode::Key9 | VirtualKeyCode::Numpad9 => Key::Num9,
VirtualKeyCode::A => Key::A,
VirtualKeyCode::B => Key::B,
VirtualKeyCode::C => Key::C,
VirtualKeyCode::D => Key::D,
VirtualKeyCode::E => Key::E,
VirtualKeyCode::F => Key::F,
VirtualKeyCode::G => Key::G,
VirtualKeyCode::H => Key::H,
VirtualKeyCode::I => Key::I,
VirtualKeyCode::J => Key::J,
VirtualKeyCode::K => Key::K,
VirtualKeyCode::L => Key::L,
VirtualKeyCode::M => Key::M,
VirtualKeyCode::N => Key::N,
VirtualKeyCode::O => Key::O,
VirtualKeyCode::P => Key::P,
VirtualKeyCode::Q => Key::Q,
VirtualKeyCode::R => Key::R,
VirtualKeyCode::S => Key::S,
VirtualKeyCode::T => Key::T,
VirtualKeyCode::U => Key::U,
VirtualKeyCode::V => Key::V,
VirtualKeyCode::W => Key::W,
VirtualKeyCode::X => Key::X,
VirtualKeyCode::Y => Key::Y,
VirtualKeyCode::Z => Key::Z,
VirtualKeyCode::F1 => Key::F1,
VirtualKeyCode::F2 => Key::F2,
VirtualKeyCode::F3 => Key::F3,
VirtualKeyCode::F4 => Key::F4,
VirtualKeyCode::F5 => Key::F5,
VirtualKeyCode::F6 => Key::F6,
VirtualKeyCode::F7 => Key::F7,
VirtualKeyCode::F8 => Key::F8,
VirtualKeyCode::F9 => Key::F9,
VirtualKeyCode::F10 => Key::F10,
VirtualKeyCode::F11 => Key::F11,
VirtualKeyCode::F12 => Key::F12,
VirtualKeyCode::F13 => Key::F13,
VirtualKeyCode::F14 => Key::F14,
VirtualKeyCode::F15 => Key::F15,
VirtualKeyCode::F16 => Key::F16,
VirtualKeyCode::F17 => Key::F17,
VirtualKeyCode::F18 => Key::F18,
VirtualKeyCode::F19 => Key::F19,
VirtualKeyCode::F20 => Key::F20,
_ => {
return None;
}
})
}
fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::CursorIcon> {
match cursor_icon {
egui::CursorIcon::None => None,
egui::CursorIcon::Alias => Some(winit::window::CursorIcon::Alias),
egui::CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll),
egui::CursorIcon::Cell => Some(winit::window::CursorIcon::Cell),
egui::CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu),
egui::CursorIcon::Copy => Some(winit::window::CursorIcon::Copy),
egui::CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair),
egui::CursorIcon::Default => Some(winit::window::CursorIcon::Default),
egui::CursorIcon::Grab => Some(winit::window::CursorIcon::Grab),
egui::CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing),
egui::CursorIcon::Help => Some(winit::window::CursorIcon::Help),
egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move),
egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),
egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Hand),
egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),
egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),
egui::CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize),
egui::CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize),
egui::CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize),
egui::CursorIcon::ResizeEast => Some(winit::window::CursorIcon::EResize),
egui::CursorIcon::ResizeSouthEast => Some(winit::window::CursorIcon::SeResize),
egui::CursorIcon::ResizeSouth => Some(winit::window::CursorIcon::SResize),
egui::CursorIcon::ResizeSouthWest => Some(winit::window::CursorIcon::SwResize),
egui::CursorIcon::ResizeWest => Some(winit::window::CursorIcon::WResize),
egui::CursorIcon::ResizeNorthWest => Some(winit::window::CursorIcon::NwResize),
egui::CursorIcon::ResizeNorth => Some(winit::window::CursorIcon::NResize),
egui::CursorIcon::ResizeNorthEast => Some(winit::window::CursorIcon::NeResize),
egui::CursorIcon::ResizeColumn => Some(winit::window::CursorIcon::ColResize),
egui::CursorIcon::ResizeRow => Some(winit::window::CursorIcon::RowResize),
egui::CursorIcon::Text => Some(winit::window::CursorIcon::Text),
egui::CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText),
egui::CursorIcon::Wait => Some(winit::window::CursorIcon::Wait),
egui::CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn),
egui::CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut),
}
}
/// Returns a Wayland display handle if the target is running Wayland
fn wayland_display<T>(_event_loop: &EventLoopWindowTarget<T>) -> Option<*mut c_void> {
#[cfg(feature = "wayland")]
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
use winit::platform::wayland::EventLoopWindowTargetExtWayland as _;
return _event_loop.wayland_display();
}
#[allow(unreachable_code)]
{
let _ = _event_loop;
None
}
}
// ---------------------------------------------------------------------------
/// Profiling macro for feature "puffin"
#[allow(unused_macros)]
macro_rules! profile_function {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
puffin::profile_function!($($arg)*);
};
}
#[allow(unused_imports)]
pub(crate) use profile_function;
/// Profiling macro for feature "puffin"
#[allow(unused_macros)]
macro_rules! profile_scope {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
puffin::profile_scope!($($arg)*);
};
}
#[allow(unused_imports)]
pub(crate) use profile_scope;

View file

@ -0,0 +1,144 @@
/// Can be used to store native window settings (position and size).
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct WindowSettings {
/// Position of window in physical pixels. This is either
/// the inner or outer position depending on the platform.
/// See [`winit::window::WindowBuilder::with_position`] for details.
position: Option<egui::Pos2>,
fullscreen: bool,
/// Inner size of window in logical pixels
inner_size_points: Option<egui::Vec2>,
}
impl WindowSettings {
pub fn from_display(window: &winit::window::Window) -> Self {
let inner_size_points = window.inner_size().to_logical::<f32>(window.scale_factor());
let position = if cfg!(macos) {
// MacOS uses inner position when positioning windows.
window
.inner_position()
.ok()
.map(|p| egui::pos2(p.x as f32, p.y as f32))
} else {
// Other platforms use the outer position.
window
.outer_position()
.ok()
.map(|p| egui::pos2(p.x as f32, p.y as f32))
};
Self {
position,
fullscreen: window.fullscreen().is_some(),
inner_size_points: Some(egui::vec2(
inner_size_points.width,
inner_size_points.height,
)),
}
}
pub fn inner_size_points(&self) -> Option<egui::Vec2> {
self.inner_size_points
}
pub fn initialize_window(
&self,
mut window: winit::window::WindowBuilder,
) -> winit::window::WindowBuilder {
// 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 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 {
window
.with_inner_size(winit::dpi::LogicalSize {
width: inner_size_points.x as f64,
height: inner_size_points.y as f64,
})
.with_fullscreen(
self.fullscreen
.then_some(winit::window::Fullscreen::Borderless(None)),
)
} else {
window
}
}
pub fn clamp_to_sane_values(&mut self, max_size: egui::Vec2) {
use egui::NumExt as _;
if let Some(size) = &mut self.inner_size_points {
// Prevent ridiculously small windows
let min_size = egui::Vec2::splat(64.0);
*size = size.at_least(min_size);
*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);
}
}
}

84
crates/egui/Cargo.toml Normal file
View file

@ -0,0 +1,84 @@
[package]
name = "egui"
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"
rust-version = "1.65"
homepage = "https://github.com/emilk/egui"
license = "MIT OR Apache-2.0"
readme = "../../README.md"
repository = "https://github.com/emilk/egui"
categories = ["gui", "game-development"]
keywords = ["gui", "imgui", "immediate", "portable", "gamedev"]
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
[package.metadata.docs.rs]
all-features = true
[lib]
[features]
default = ["default_fonts"]
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`.
bytemuck = ["epaint/bytemuck"]
## [`cint`](https://docs.rs/cint) enables interopability with other color libraries.
cint = ["epaint/cint"]
## Enable the [`hex_color`] macro.
color-hex = ["epaint/color-hex"]
## This will automatically detect deadlocks due to double-locking on the same thread.
## If your app freezes, you may want to enable this!
## Only affects [`epaint::mutex::RwLock`] (which egui uses a lot).
deadlock_detection = ["epaint/deadlock_detection"]
## If set, egui will use `include_bytes!` to bundle some fonts.
## If you plan on specifying your own fonts you may disable this feature.
default_fonts = ["epaint/default_fonts"]
## Enable additional checks if debug assertions are enabled (debug builds).
extra_debug_asserts = ["epaint/extra_debug_asserts"]
## Always enable additional checks.
extra_asserts = ["epaint/extra_asserts"]
## [`mint`](https://docs.rs/mint) enables interopability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra).
mint = ["epaint/mint"]
## Enable persistence of memory (window positions etc).
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.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
"std",
] }
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.9.0", optional = true }
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
ron = { version = "0.8", optional = true }
serde = { version = "1", optional = true, features = ["derive", "rc"] }
# egui doesn't log much, but when it does, it uses [`tracing`](https://docs.rs/tracing).
tracing = { version = "0.1", optional = true, default-features = false, features = [
"std",
] }

View file

@ -0,0 +1,7 @@
There are no stand-alone egui examples, because egui is not stand-alone!
See the top-level [examples](https://github.com/emilk/egui/tree/master/examples/) folder instead.
There are also plenty of examples in [the online demo](https://www.egui.rs/#demo). You can find the source code for it at <https://github.com/emilk/egui/tree/master/crates/egui_demo_lib>.
To learn how to set up `eframe` for web and native, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!

View file

@ -0,0 +1,114 @@
use crate::{emath::remap_clamp, Id, IdMap, InputState};
#[derive(Clone, Default)]
pub(crate) struct AnimationManager {
bools: IdMap<BoolAnim>,
values: IdMap<ValueAnim>,
}
#[derive(Clone, Debug)]
struct BoolAnim {
value: bool,
/// when did `value` last toggle?
toggle_time: f64,
}
#[derive(Clone, Debug)]
struct ValueAnim {
from_value: f32,
to_value: f32,
/// when did `value` last toggle?
toggle_time: f64,
}
impl AnimationManager {
/// See `Context::animate_bool` for documentation
pub fn animate_bool(
&mut self,
input: &InputState,
animation_time: f32,
id: Id,
value: bool,
) -> f32 {
match self.bools.get_mut(&id) {
None => {
self.bools.insert(
id,
BoolAnim {
value,
toggle_time: -f64::INFINITY, // long time ago
},
);
if value {
1.0
} else {
0.0
}
}
Some(anim) => {
if anim.value != value {
anim.value = value;
anim.toggle_time = input.time;
}
let time_since_toggle = (input.time - anim.toggle_time) as f32;
// On the frame we toggle we don't want to return the old value,
// so we extrapolate forwards:
let time_since_toggle = time_since_toggle + input.predicted_dt;
if value {
remap_clamp(time_since_toggle, 0.0..=animation_time, 0.0..=1.0)
} else {
remap_clamp(time_since_toggle, 0.0..=animation_time, 1.0..=0.0)
}
}
}
}
pub fn animate_value(
&mut self,
input: &InputState,
animation_time: f32,
id: Id,
value: f32,
) -> f32 {
match self.values.get_mut(&id) {
None => {
self.values.insert(
id,
ValueAnim {
from_value: value,
to_value: value,
toggle_time: -f64::INFINITY, // long time ago
},
);
value
}
Some(anim) => {
let time_since_toggle = (input.time - anim.toggle_time) as f32;
// On the frame we toggle we don't want to return the old value,
// so we extrapolate forwards:
let time_since_toggle = time_since_toggle + input.predicted_dt;
let current_value = remap_clamp(
time_since_toggle,
0.0..=animation_time,
anim.from_value..=anim.to_value,
);
if anim.to_value != value {
anim.from_value = current_value; //start new animation from current position of playing animation
anim.to_value = value;
anim.toggle_time = input.time;
}
if animation_time == 0.0 {
anim.from_value = value;
anim.to_value = value;
}
current_value
}
}
}
}

View file

@ -0,0 +1,514 @@
//! Area is a [`Ui`] that has no parent, it floats on the background.
//! It has no frame or own size. It is potentially movable.
//! It is the foundation for windows and popups.
use crate::*;
/// State that is persisted between frames.
// TODO(emilk): this is not currently stored in `Memory::data`, but maybe it should be?
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct State {
/// Last known pos of the pivot
pub pivot_pos: Pos2,
pub pivot: Align2,
/// Last know size. Used for catching clicks.
pub size: Vec2,
/// If false, clicks goes straight through to what is behind us.
/// Good for tooltips etc.
pub interactable: bool,
}
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.left_top_pos(), self.size)
}
}
/// An area on the screen that can be moved by dragging.
///
/// This forms the base of the [`Window`] container.
///
/// ```
/// # egui::__run_test_ctx(|ctx| {
/// egui::Area::new("my_area")
/// .fixed_pos(egui::pos2(32.0, 32.0))
/// .show(ctx, |ui| {
/// ui.label("Floating text!");
/// });
/// # });
/// ```
#[must_use = "You should call .show()"]
#[derive(Clone, Copy, Debug)]
pub struct Area {
pub(crate) id: Id,
movable: bool,
interactable: bool,
enabled: bool,
constrain: bool,
order: Order,
default_pos: Option<Pos2>,
pivot: Align2,
anchor: Option<(Align2, Vec2)>,
new_pos: Option<Pos2>,
drag_bounds: Option<Rect>,
}
impl Area {
pub fn new(id: impl Into<Id>) -> Self {
Self {
id: id.into(),
movable: true,
interactable: true,
constrain: false,
enabled: true,
order: Order::Middle,
default_pos: None,
new_pos: None,
pivot: Align2::LEFT_TOP,
anchor: None,
drag_bounds: None,
}
}
pub fn id(mut self, id: Id) -> Self {
self.id = id;
self
}
pub fn layer(&self) -> LayerId {
LayerId::new(self.order, self.id)
}
/// If false, no content responds to click
/// and widgets will be shown grayed out.
/// You won't be able to move the window.
/// Default: `true`.
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
/// moveable by dragging the area?
pub fn movable(mut self, movable: bool) -> Self {
self.movable = movable;
self.interactable |= movable;
self
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn is_movable(&self) -> bool {
self.movable && self.enabled
}
/// If false, clicks goes straight through to what is behind us.
/// Good for tooltips etc.
pub fn interactable(mut self, interactable: bool) -> Self {
self.interactable = interactable;
self.movable &= interactable;
self
}
/// `order(Order::Foreground)` for an Area that should always be on top
pub fn order(mut self, order: Order) -> Self {
self.order = order;
self
}
pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
self.default_pos = Some(default_pos.into());
self
}
/// Positions the window and prevents it from being moved
pub fn fixed_pos(mut self, fixed_pos: impl Into<Pos2>) -> Self {
self.new_pos = Some(fixed_pos.into());
self.movable = false;
self
}
/// Constrains this area to the screen bounds.
pub fn constrain(mut self, constrain: bool) -> Self {
self.constrain = constrain;
self
}
/// Where the "root" of the area is.
///
/// For instance, if you set this to [`Align2::RIGHT_TOP`]
/// then [`Self::fixed_pos`] will set the position of the right-top
/// corner of the area.
///
/// Default: [`Align2::LEFT_TOP`].
pub fn pivot(mut self, pivot: Align2) -> Self {
self.pivot = pivot;
self
}
/// Positions the window but you can still move it.
pub fn current_pos(mut self, current_pos: impl Into<Pos2>) -> Self {
self.new_pos = Some(current_pos.into());
self
}
/// Set anchor and distance.
///
/// An anchor of `Align2::RIGHT_TOP` means "put the right-top corner of the window
/// in the right-top corner of the screen".
///
/// The offset is added to the position, so e.g. an offset of `[-5.0, 5.0]`
/// would move the window left and down from the given anchor.
///
/// Anchoring also makes the window immovable.
///
/// It is an error to set both an anchor and a position.
pub fn anchor(mut self, align: Align2, offset: impl Into<Vec2>) -> Self {
self.anchor = Some((align, offset.into()));
self.movable(false)
}
/// Constrain the area up to which the window can be dragged.
pub fn drag_bounds(mut self, bounds: Rect) -> Self {
self.drag_bounds = Some(bounds);
self
}
pub(crate) fn get_pivot(&self) -> Align2 {
if let Some((pivot, _)) = self.anchor {
pivot
} else {
Align2::LEFT_TOP
}
}
}
pub(crate) struct Prepared {
layer_id: LayerId,
state: State,
move_response: Response,
enabled: bool,
drag_bounds: Option<Rect>,
/// We always make windows invisible the first frame to hide "first-frame-jitters".
///
/// This is so that we use the first frame to calculate the window size,
/// and then can correctly position the window and its contents the next frame,
/// without having one frame where the window is wrongly positioned or sized.
temporarily_invisible: bool,
}
impl Area {
pub fn show<R>(
self,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let prepared = self.begin(ctx);
let mut content_ui = prepared.content_ui(ctx);
let inner = add_contents(&mut content_ui);
let response = prepared.end(ctx, content_ui);
InnerResponse { inner, response }
}
pub(crate) fn begin(self, ctx: &Context) -> Prepared {
let Area {
id,
movable,
order,
interactable,
enabled,
default_pos,
new_pos,
pivot,
anchor,
drag_bounds,
constrain,
} = self;
let layer_id = LayerId::new(order, id);
let state = ctx.memory(|mem| mem.areas.get(id).copied());
let is_new = state.is_none();
if is_new {
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 {
pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)),
pivot,
size: Vec2::ZERO,
interactable,
});
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
state.interactable = interactable;
if let Some((anchor, offset)) = anchor {
let screen = ctx.available_rect();
state.set_left_top_pos(
anchor.align_size_within_rect(state.size, screen).left_top() + offset,
);
}
// interact right away to prevent frame-delay
let move_response = {
let interact_id = layer_id.id.with("move");
let sense = if movable {
Sense::click_and_drag()
} else if interactable {
Sense::click() // allow clicks to bring to front
} else {
Sense::hover()
};
let move_response = ctx.interact(
Rect::EVERYTHING,
ctx.style().spacing.item_spacing,
layer_id,
interact_id,
state.rect(),
sense,
enabled,
);
// Important check - don't try to move e.g. a combobox popup!
if movable {
if move_response.dragged() {
state.pivot_pos += ctx.input(|i| i.pointer.delta());
}
state.set_left_top_pos(
ctx.constrain_window_rect_to_area(state.rect(), drag_bounds)
.min,
);
}
if (move_response.dragged() || move_response.clicked())
|| pointer_pressed_on_area(ctx, layer_id)
|| !ctx.memory(|m| m.areas.visible_last_frame(&layer_id))
{
ctx.memory_mut(|m| m.areas.move_to_top(layer_id));
ctx.request_repaint();
}
move_response
};
state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos()));
if constrain {
state.set_left_top_pos(
ctx.constrain_window_rect_to_area(state.rect(), drag_bounds)
.left_top(),
);
}
Prepared {
layer_id,
state,
move_response,
enabled,
drag_bounds,
temporarily_invisible: is_new,
}
}
pub fn show_open_close_animation(&self, ctx: &Context, frame: &Frame, is_open: bool) {
// must be called first so animation managers know the latest state
let visibility_factor = ctx.animate_bool(self.id.with("close_animation"), is_open);
if is_open {
// we actually only show close animations.
// when opening a window we show it right away.
return;
}
if visibility_factor <= 0.0 {
return;
}
let layer_id = LayerId::new(self.order, self.id);
let area_rect = ctx.memory(|mem| mem.areas.get(self.id).map(|area| area.rect()));
if let Some(area_rect) = area_rect {
let clip_rect = ctx.available_rect();
let painter = Painter::new(ctx.clone(), layer_id, clip_rect);
// shrinkage: looks kinda a bad on its own
// let area_rect =
// Rect::from_center_size(area_rect.center(), visibility_factor * area_rect.size());
let frame = frame.multiply_with_opacity(visibility_factor);
painter.add(frame.paint(area_rect));
}
}
}
impl Prepared {
pub(crate) fn state(&self) -> &State {
&self.state
}
pub(crate) fn state_mut(&mut self) -> &mut State {
&mut self.state
}
pub(crate) fn drag_bounds(&self) -> Option<Rect> {
self.drag_bounds
}
pub(crate) fn content_ui(&self, ctx: &Context) -> Ui {
let screen_rect = ctx.screen_rect();
let bounds = if let Some(bounds) = self.drag_bounds {
bounds.intersect(screen_rect) // protect against infinite bounds
} else {
let central_area = ctx.available_rect();
let is_within_central_area = central_area.contains_rect(self.state.rect().shrink(1.0));
if is_within_central_area {
central_area // let's try to not cover side panels
} else {
screen_rect
}
};
let max_rect = Rect::from_min_max(
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.left_top_pos(), bounds.max)
.expand(clip_rect_margin)
.intersect(bounds);
let mut ui = Ui::new(
ctx.clone(),
self.layer_id,
self.layer_id.id,
max_rect,
clip_rect,
);
ui.set_enabled(self.enabled);
ui.set_visible(!self.temporarily_invisible);
ui
}
#[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`.
pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response {
let Prepared {
layer_id,
mut state,
move_response,
enabled: _,
drag_bounds: _,
temporarily_invisible: _,
} = self;
state.size = content_ui.min_rect().size();
ctx.memory_mut(|m| m.areas.set_state(layer_id, state));
move_response
}
}
fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
if let Some(pointer_pos) = ctx.pointer_interact_pos() {
let any_pressed = ctx.input(|i| i.pointer.any_pressed());
any_pressed && ctx.layer_id_at(pointer_pos) == Some(layer_id)
} else {
false
}
}
fn automatic_area_position(ctx: &Context) -> Pos2 {
let mut existing: Vec<Rect> = ctx.memory(|mem| {
mem.areas
.visible_windows()
.into_iter()
.map(State::rect)
.collect()
});
existing.sort_by_key(|r| r.left().round() as i32);
let available_rect = ctx.available_rect();
let spacing = 16.0;
let left = available_rect.left() + spacing;
let top = available_rect.top() + spacing;
if existing.is_empty() {
return pos2(left, top);
}
// Separate existing rectangles into columns:
let mut column_bbs = vec![existing[0]];
for &rect in &existing {
let current_column_bb = column_bbs.last_mut().unwrap();
if rect.left() < current_column_bb.right() {
// same column
*current_column_bb = current_column_bb.union(rect);
} else {
// new column
column_bbs.push(rect);
}
}
{
// Look for large spaces between columns (empty columns):
let mut x = left;
for col_bb in &column_bbs {
let available = col_bb.left() - x;
if available >= 300.0 {
return pos2(x, top);
}
x = col_bb.right() + spacing;
}
}
// Find first column with some available space at the bottom of it:
for col_bb in &column_bbs {
if col_bb.bottom() < available_rect.center().y {
return pos2(col_bb.left(), col_bb.bottom() + spacing);
}
}
// Maybe we can fit a new column?
let rightmost = column_bbs.last().unwrap().right();
if rightmost + 200.0 < available_rect.right() {
return pos2(rightmost + spacing, top);
}
// Ok, just put us in the column with the most space at the bottom:
let mut best_pos = pos2(left, column_bbs[0].bottom() + spacing);
for col_bb in &column_bbs {
let col_pos = pos2(col_bb.left(), col_bb.bottom() + spacing);
if col_pos.y < best_pos.y {
best_pos = col_pos;
}
}
best_pos
}

View file

@ -0,0 +1,681 @@
use std::hash::Hash;
use crate::*;
use epaint::Shape;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct InnerState {
open: bool,
/// Height of the region when open. Used for animations
#[cfg_attr(feature = "serde", serde(default))]
open_height: Option<f32>,
}
/// This is a a building block for building collapsing regions.
///
/// It is used by [`CollapsingHeader`] and [`Window`], but can also be used on its own.
///
/// See [`CollapsingState::show_header`] for how to show a collapsing header with a custom header.
#[derive(Clone, Debug)]
pub struct CollapsingState {
id: Id,
state: InnerState,
}
impl CollapsingState {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data_mut(|d| {
d.get_persisted::<InnerState>(id)
.map(|state| Self { id, state })
})
}
pub fn store(&self, ctx: &Context) {
ctx.data_mut(|d| d.insert_persisted(self.id, self.state));
}
pub fn id(&self) -> Id {
self.id
}
pub fn load_with_default_open(ctx: &Context, id: Id, default_open: bool) -> Self {
Self::load(ctx, id).unwrap_or(CollapsingState {
id,
state: InnerState {
open: default_open,
open_height: None,
},
})
}
pub fn is_open(&self) -> bool {
self.state.open
}
pub fn set_open(&mut self, open: bool) {
self.state.open = open;
}
pub fn toggle(&mut self, ui: &Ui) {
self.state.open = !self.state.open;
ui.ctx().request_repaint();
}
/// 0 for closed, 1 for open, with tweening
pub fn openness(&self, ctx: &Context) -> f32 {
if ctx.memory(|mem| mem.everything_is_visible()) {
1.0
} else {
ctx.animate_bool(self.id, self.state.open)
}
}
/// Will toggle when clicked, etc.
pub(crate) fn show_default_button_with_size(
&mut self,
ui: &mut Ui,
button_size: Vec2,
) -> Response {
let (_id, rect) = ui.allocate_space(button_size);
let response = ui.interact(rect, self.id, Sense::click());
if response.clicked() {
self.toggle(ui);
}
let openness = self.openness(ui.ctx());
paint_default_icon(ui, openness, &response);
response
}
/// Will toggle when clicked, etc.
fn show_default_button_indented(&mut self, ui: &mut Ui) -> Response {
self.show_button_indented(ui, paint_default_icon)
}
/// Will toggle when clicked, etc.
fn show_button_indented(
&mut self,
ui: &mut Ui,
icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static,
) -> Response {
let size = vec2(ui.spacing().indent, ui.spacing().icon_width);
let (_id, rect) = ui.allocate_space(size);
let response = ui.interact(rect, self.id, Sense::click());
if response.clicked() {
self.toggle(ui);
}
let (mut icon_rect, _) = ui.spacing().icon_rectangles(response.rect);
icon_rect.set_center(pos2(
response.rect.left() + ui.spacing().indent / 2.0,
response.rect.center().y,
));
let openness = self.openness(ui.ctx());
let small_icon_response = response.clone().with_new_rect(icon_rect);
icon_fn(ui, openness, &small_icon_response);
response
}
/// Shows header and body (if expanded).
///
/// The header will start with the default button in a horizontal layout, followed by whatever you add.
///
/// Will also store the state.
///
/// Returns the response of the collapsing button, the custom header, and the custom body.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// let id = ui.make_persistent_id("my_collapsing_header");
/// egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, false)
/// .show_header(ui, |ui| {
/// ui.label("Header"); // you can put checkboxes or whatever here
/// })
/// .body(|ui| ui.label("Body"));
/// # });
/// ```
pub fn show_header<HeaderRet>(
mut self,
ui: &mut Ui,
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 = prev_item_spacing;
(collapser, add_header(ui))
});
HeaderResponse {
state: self,
ui,
toggle_button_response: header_response.inner.0,
header_response: InnerResponse {
response: header_response.response,
inner: header_response.inner.1,
},
}
}
/// Show body if we are open, with a nice animation between closed and open.
/// Indent the body to show it belongs to the header.
///
/// Will also store the state.
pub fn show_body_indented<R>(
&mut self,
header_response: &Response,
ui: &mut Ui,
add_body: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let id = self.id;
self.show_body_unindented(ui, |ui| {
ui.indent(id, |ui| {
// make as wide as the header:
ui.expand_to_include_x(header_response.rect.right());
add_body(ui)
})
.inner
})
}
/// Show body if we are open, with a nice animation between closed and open.
/// Will also store the state.
pub fn show_body_unindented<R>(
&mut self,
ui: &mut Ui,
add_body: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let openness = self.openness(ui.ctx());
if openness <= 0.0 {
self.store(ui.ctx()); // we store any earlier toggling as promised in the docstring
None
} else if openness < 1.0 {
Some(ui.scope(|child_ui| {
let max_height = if self.state.open && self.state.open_height.is_none() {
// First frame of expansion.
// We don't know full height yet, but we will next frame.
// Just use a placeholder value that shows some movement:
10.0
} else {
let full_height = self.state.open_height.unwrap_or_default();
remap_clamp(openness, 0.0..=1.0, 0.0..=full_height)
};
let mut clip_rect = child_ui.clip_rect();
clip_rect.max.y = clip_rect.max.y.min(child_ui.max_rect().top() + max_height);
child_ui.set_clip_rect(clip_rect);
let ret = add_body(child_ui);
let mut min_rect = child_ui.min_rect();
self.state.open_height = Some(min_rect.height());
self.store(child_ui.ctx()); // remember the height
// Pretend children took up at most `max_height` space:
min_rect.max.y = min_rect.max.y.at_most(min_rect.top() + max_height);
child_ui.force_set_min_rect(min_rect);
ret
}))
} else {
let ret_response = ui.scope(add_body);
let full_size = ret_response.response.rect.size();
self.state.open_height = Some(full_size.y);
self.store(ui.ctx()); // remember the height
Some(ret_response)
}
}
/// Paint this [CollapsingState](CollapsingState)'s toggle button. Takes an [IconPainter](IconPainter) as the icon.
/// ```
/// # egui::__run_test_ui(|ui| {
/// fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) {
/// let stroke = ui.style().interact(&response).fg_stroke;
/// let radius = egui::lerp(2.0..=3.0, openness);
/// ui.painter().circle_filled(response.rect.center(), radius, stroke.color);
/// }
///
/// let mut state = egui::collapsing_header::CollapsingState::load_with_default_open(
/// ui.ctx(),
/// ui.make_persistent_id("my_collapsing_state"),
/// false,
/// );
///
/// let header_res = ui.horizontal(|ui| {
/// ui.label("Header");
/// state.show_toggle_button(ui, circle_icon);
/// });
///
/// state.show_body_indented(&header_res.response, ui, |ui| ui.label("Body"));
/// # });
/// ```
pub fn show_toggle_button(
&mut self,
ui: &mut Ui,
icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static,
) -> Response {
self.show_button_indented(ui, icon_fn)
}
}
/// From [`CollapsingState::show_header`].
#[must_use = "Remember to show the body"]
pub struct HeaderResponse<'ui, HeaderRet> {
state: CollapsingState,
ui: &'ui mut Ui,
toggle_button_response: Response,
header_response: InnerResponse<HeaderRet>,
}
impl<'ui, HeaderRet> HeaderResponse<'ui, HeaderRet> {
/// Returns the response of the collapsing button, the custom header, and the custom body.
pub fn body<BodyRet>(
mut self,
add_body: impl FnOnce(&mut Ui) -> BodyRet,
) -> (
Response,
InnerResponse<HeaderRet>,
Option<InnerResponse<BodyRet>>,
) {
let body_response =
self.state
.show_body_indented(&self.header_response.response, self.ui, add_body);
(
self.toggle_button_response,
self.header_response,
body_response,
)
}
/// Returns the response of the collapsing button, the custom header, and the custom body, without indentation.
pub fn body_unindented<BodyRet>(
mut self,
add_body: impl FnOnce(&mut Ui) -> BodyRet,
) -> (
Response,
InnerResponse<HeaderRet>,
Option<InnerResponse<BodyRet>>,
) {
let body_response = self.state.show_body_unindented(self.ui, add_body);
(
self.toggle_button_response,
self.header_response,
body_response,
)
}
}
// ----------------------------------------------------------------------------
/// Paint the arrow icon that indicated if the region is open or not
pub fn paint_default_icon(ui: &mut Ui, openness: f32, response: &Response) {
let visuals = ui.style().interact(response);
let rect = response.rect;
// Draw a pointy triangle arrow:
let rect = Rect::from_center_size(rect.center(), vec2(rect.width(), rect.height()) * 0.75);
let rect = rect.expand(visuals.expansion);
let mut points = vec![rect.left_top(), rect.right_top(), rect.center_bottom()];
use std::f32::consts::TAU;
let rotation = emath::Rot2::from_angle(remap(openness, 0.0..=1.0, -TAU / 4.0..=0.0));
for p in &mut points {
*p = rect.center() + rotation * (*p - rect.center());
}
ui.painter().add(Shape::convex_polygon(
points,
visuals.fg_stroke.color,
Stroke::NONE,
));
}
/// A function that paints an icon indicating if the region is open or not
pub type IconPainter = Box<dyn FnOnce(&mut Ui, f32, &Response)>;
/// A header which can be collapsed/expanded, revealing a contained [`Ui`] region.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// egui::CollapsingHeader::new("Heading")
/// .show(ui, |ui| {
/// ui.label("Body");
/// });
///
/// // Short version:
/// ui.collapsing("Heading", |ui| { ui.label("Body"); });
/// # });
/// ```
///
/// If you want to customize the header contents, see [`CollapsingState::show_header`].
#[must_use = "You should call .show()"]
pub struct CollapsingHeader {
text: WidgetText,
default_open: bool,
open: Option<bool>,
id_source: Id,
enabled: bool,
selectable: bool,
selected: bool,
show_background: bool,
icon: Option<IconPainter>,
}
impl CollapsingHeader {
/// The [`CollapsingHeader`] starts out collapsed unless you call `default_open`.
///
/// The label is used as an [`Id`] source.
/// If the label is unique and static this is fine,
/// but if it changes or there are several [`CollapsingHeader`] with the same title
/// you need to provide a unique id source with [`Self::id_source`].
pub fn new(text: impl Into<WidgetText>) -> Self {
let text = text.into();
let id_source = Id::new(text.text());
Self {
text,
default_open: false,
open: None,
id_source,
enabled: true,
selectable: false,
selected: false,
show_background: false,
icon: None,
}
}
/// By default, the [`CollapsingHeader`] is collapsed.
/// Call `.default_open(true)` to change this.
pub fn default_open(mut self, open: bool) -> Self {
self.default_open = open;
self
}
/// Calling `.open(Some(true))` will make the collapsing header open this frame (or stay open).
///
/// Calling `.open(Some(false))` will make the collapsing header close this frame (or stay closed).
///
/// Calling `.open(None)` has no effect (default).
pub fn open(mut self, open: Option<bool>) -> Self {
self.open = open;
self
}
/// Explicitly set the source of the [`Id`] of this widget, instead of using title label.
/// This is useful if the title label is dynamic or not unique.
pub fn id_source(mut self, id_source: impl Hash) -> Self {
self.id_source = Id::new(id_source);
self
}
/// If you set this to `false`, the [`CollapsingHeader`] will be grayed out and un-clickable.
///
/// This is a convenience for [`Ui::set_enabled`].
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
/// Can the [`CollapsingHeader`] be selected by clicking it? Default: `false`.
#[deprecated = "Use the more powerful egui::collapsing_header::CollapsingState::show_header"] // Deprecated in 2022-04-28, before egui 0.18
pub fn selectable(mut self, selectable: bool) -> Self {
self.selectable = selectable;
self
}
/// If you set this to 'true', the [`CollapsingHeader`] will be shown as selected.
///
/// Example:
/// ```
/// # egui::__run_test_ui(|ui| {
/// let mut selected = false;
/// let response = egui::CollapsingHeader::new("Select and open me")
/// .selectable(true)
/// .selected(selected)
/// .show(ui, |ui| ui.label("Body"));
/// if response.header_response.clicked() {
/// selected = true;
/// }
/// # });
/// ```
#[deprecated = "Use the more powerful egui::collapsing_header::CollapsingState::show_header"] // Deprecated in 2022-04-28, before egui 0.18
pub fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
/// Should the [`CollapsingHeader`] show a background behind it? Default: `false`.
///
/// To show it behind all [`CollapsingHeader`] you can just use:
/// ```
/// # egui::__run_test_ui(|ui| {
/// ui.visuals_mut().collapsing_header_frame = true;
/// # });
/// ```
pub fn show_background(mut self, show_background: bool) -> Self {
self.show_background = show_background;
self
}
/// Use the provided function to render a different [`CollapsingHeader`] icon.
/// Defaults to a triangle that animates as the [`CollapsingHeader`] opens and closes.
///
/// For example:
/// ```
/// # egui::__run_test_ui(|ui| {
/// fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) {
/// let stroke = ui.style().interact(&response).fg_stroke;
/// let radius = egui::lerp(2.0..=3.0, openness);
/// ui.painter().circle_filled(response.rect.center(), radius, stroke.color);
/// }
///
/// egui::CollapsingHeader::new("Circles")
/// .icon(circle_icon)
/// .show(ui, |ui| { ui.label("Hi!"); });
/// # });
/// ```
pub fn icon(mut self, icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static) -> Self {
self.icon = Some(Box::new(icon_fn));
self
}
}
struct Prepared {
header_response: Response,
state: CollapsingState,
openness: f32,
}
impl CollapsingHeader {
fn begin(self, ui: &mut Ui) -> Prepared {
assert!(
ui.layout().main_dir().is_vertical(),
"Horizontal collapsing is unimplemented"
);
let Self {
icon,
text,
default_open,
open,
id_source,
enabled: _,
selectable,
selected,
show_background,
} = self;
// TODO(emilk): horizontal layout, with icon and text as labels. Insert background behind using Frame.
let id = ui.make_persistent_id(id_source);
let button_padding = ui.spacing().button_padding;
let available = ui.available_rect_before_wrap();
let text_pos = available.min + vec2(ui.spacing().indent, 0.0);
let wrap_width = available.right() - text_pos.x;
let wrap = Some(false);
let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button);
let text_max_x = text_pos.x + text.size().x;
let mut desired_width = text_max_x + button_padding.x - available.left();
if ui.visuals().collapsing_header_frame {
desired_width = desired_width.max(available.width()); // fill full width
}
let mut desired_size = vec2(desired_width, text.size().y + 2.0 * button_padding.y);
desired_size = desired_size.at_least(ui.spacing().interact_size);
let (_, rect) = ui.allocate_space(desired_size);
let mut header_response = ui.interact(rect, id, Sense::click());
let text_pos = pos2(
text_pos.x,
header_response.rect.center().y - text.size().y / 2.0,
);
let mut state = CollapsingState::load_with_default_open(ui.ctx(), id, default_open);
if let Some(open) = open {
if open != state.is_open() {
state.toggle(ui);
header_response.mark_changed();
}
} else if header_response.clicked() {
state.toggle(ui);
header_response.mark_changed();
}
header_response
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text()));
let openness = state.openness(ui.ctx());
if ui.is_rect_visible(rect) {
let visuals = ui.style().interact_selectable(&header_response, selected);
if ui.visuals().collapsing_header_frame || show_background {
ui.painter().add(epaint::RectShape {
rect: header_response.rect.expand(visuals.expansion),
rounding: visuals.rounding,
fill: visuals.weak_bg_fill,
stroke: visuals.bg_stroke,
// stroke: Default::default(),
});
}
if selected || selectable && (header_response.hovered() || header_response.has_focus())
{
let rect = rect.expand(visuals.expansion);
ui.painter()
.rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke);
}
{
let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect);
icon_rect.set_center(pos2(
header_response.rect.left() + ui.spacing().indent / 2.0,
header_response.rect.center().y,
));
let icon_response = header_response.clone().with_new_rect(icon_rect);
if let Some(icon) = icon {
icon(ui, openness, &icon_response);
} else {
paint_default_icon(ui, openness, &icon_response);
}
}
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
}
Prepared {
header_response,
state,
openness,
}
}
#[inline]
pub fn show<R>(
self,
ui: &mut Ui,
add_body: impl FnOnce(&mut Ui) -> R,
) -> CollapsingResponse<R> {
self.show_dyn(ui, Box::new(add_body), true)
}
#[inline]
pub fn show_unindented<R>(
self,
ui: &mut Ui,
add_body: impl FnOnce(&mut Ui) -> R,
) -> CollapsingResponse<R> {
self.show_dyn(ui, Box::new(add_body), false)
}
fn show_dyn<'c, R>(
self,
ui: &mut Ui,
add_body: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
indented: bool,
) -> CollapsingResponse<R> {
// Make sure body is bellow header,
// and make sure it is one unit (necessary for putting a [`CollapsingHeader`] in a grid).
ui.vertical(|ui| {
ui.set_enabled(self.enabled);
let Prepared {
header_response,
mut state,
openness,
} = self.begin(ui); // show the header
let ret_response = if indented {
state.show_body_indented(&header_response, ui, add_body)
} else {
state.show_body_unindented(ui, add_body)
};
if let Some(ret_response) = ret_response {
CollapsingResponse {
header_response,
body_response: Some(ret_response.response),
body_returned: Some(ret_response.inner),
openness,
}
} else {
CollapsingResponse {
header_response,
body_response: None,
body_returned: None,
openness,
}
}
})
.inner
}
}
/// The response from showing a [`CollapsingHeader`].
pub struct CollapsingResponse<R> {
/// Response of the actual clickable header.
pub header_response: Response,
/// None iff collapsed.
pub body_response: Option<Response>,
/// None iff collapsed.
pub body_returned: Option<R>,
/// 0.0 if fully closed, 1.0 if fully open, and something in-between while animating.
pub openness: f32,
}
impl<R> CollapsingResponse<R> {
/// Was the [`CollapsingHeader`] fully closed (and not being animated)?
pub fn fully_closed(&self) -> bool {
self.openness <= 0.0
}
/// Was the [`CollapsingHeader`] fully open (and not being animated)?
pub fn fully_open(&self) -> bool {
self.openness >= 1.0
}
}

View file

@ -0,0 +1,429 @@
use epaint::Shape;
use crate::{style::WidgetVisuals, *};
/// Indicate wether or not a popup will be shown above or below the box.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AboveOrBelow {
Above,
Below,
}
/// A function that paints the [`ComboBox`] icon
pub type IconPainter = Box<dyn FnOnce(&Ui, Rect, &WidgetVisuals, bool, AboveOrBelow)>;
/// A drop-down selection menu with a descriptive label.
///
/// ```
/// # #[derive(Debug, PartialEq)]
/// # enum Enum { First, Second, Third }
/// # let mut selected = Enum::First;
/// # egui::__run_test_ui(|ui| {
/// egui::ComboBox::from_label("Select one!")
/// .selected_text(format!("{:?}", selected))
/// .show_ui(ui, |ui| {
/// ui.selectable_value(&mut selected, Enum::First, "First");
/// ui.selectable_value(&mut selected, Enum::Second, "Second");
/// ui.selectable_value(&mut selected, Enum::Third, "Third");
/// }
/// );
/// # });
/// ```
#[must_use = "You should call .show*"]
pub struct ComboBox {
id_source: Id,
label: Option<WidgetText>,
selected_text: WidgetText,
width: Option<f32>,
icon: Option<IconPainter>,
wrap_enabled: bool,
}
impl ComboBox {
/// Create new [`ComboBox`] with id and label
pub fn new(id_source: impl std::hash::Hash, label: impl Into<WidgetText>) -> Self {
Self {
id_source: Id::new(id_source),
label: Some(label.into()),
selected_text: Default::default(),
width: None,
icon: None,
wrap_enabled: false,
}
}
/// Label shown next to the combo box
pub fn from_label(label: impl Into<WidgetText>) -> Self {
let label = label.into();
Self {
id_source: Id::new(label.text()),
label: Some(label),
selected_text: Default::default(),
width: None,
icon: None,
wrap_enabled: false,
}
}
/// Without label.
pub fn from_id_source(id_source: impl std::hash::Hash) -> Self {
Self {
id_source: Id::new(id_source),
label: Default::default(),
selected_text: Default::default(),
width: None,
icon: None,
wrap_enabled: false,
}
}
/// Set the outer width of the button and menu.
pub fn width(mut self, width: f32) -> Self {
self.width = Some(width);
self
}
/// What we show as the currently selected value
pub fn selected_text(mut self, selected_text: impl Into<WidgetText>) -> Self {
self.selected_text = selected_text.into();
self
}
/// Use the provided function to render a different [`ComboBox`] icon.
/// Defaults to a triangle that expands when the cursor is hovering over the [`ComboBox`].
///
/// For example:
/// ```
/// # egui::__run_test_ui(|ui| {
/// # let text = "Selected text";
/// pub fn filled_triangle(
/// ui: &egui::Ui,
/// rect: egui::Rect,
/// visuals: &egui::style::WidgetVisuals,
/// _is_open: bool,
/// _above_or_below: egui::AboveOrBelow,
/// ) {
/// let rect = egui::Rect::from_center_size(
/// rect.center(),
/// egui::vec2(rect.width() * 0.6, rect.height() * 0.4),
/// );
/// ui.painter().add(egui::Shape::convex_polygon(
/// vec![rect.left_top(), rect.right_top(), rect.center_bottom()],
/// visuals.fg_stroke.color,
/// visuals.fg_stroke,
/// ));
/// }
///
/// egui::ComboBox::from_id_source("my-combobox")
/// .selected_text(text)
/// .icon(filled_triangle)
/// .show_ui(ui, |_ui| {});
/// # });
/// ```
pub fn icon(
mut self,
icon_fn: impl FnOnce(&Ui, Rect, &WidgetVisuals, bool, AboveOrBelow) + 'static,
) -> Self {
self.icon = Some(Box::new(icon_fn));
self
}
/// Controls whether text wrap is used for the selected text
pub fn wrap(mut self, wrap: bool) -> Self {
self.wrap_enabled = wrap;
self
}
/// Show the combo box, with the given ui code for the menu contents.
///
/// Returns `InnerResponse { inner: None }` if the combo box is closed.
pub fn show_ui<R>(
self,
ui: &mut Ui,
menu_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<Option<R>> {
self.show_ui_dyn(ui, Box::new(menu_contents))
}
fn show_ui_dyn<'c, R>(
self,
ui: &mut Ui,
menu_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<Option<R>> {
let Self {
id_source,
label,
selected_text,
width,
icon,
wrap_enabled,
} = self;
let button_id = ui.make_persistent_id(id_source);
ui.horizontal(|ui| {
let mut ir = combo_box_dyn(
ui,
button_id,
selected_text,
menu_contents,
icon,
wrap_enabled,
width,
);
if let Some(label) = label {
ir.response
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, label.text()));
ir.response |= ui.label(label);
} else {
ir.response
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, ""));
}
ir
})
.inner
}
/// Show a list of items with the given selected index.
///
///
/// ```
/// # #[derive(Debug, PartialEq)]
/// # enum Enum { First, Second, Third }
/// # let mut selected = Enum::First;
/// # egui::__run_test_ui(|ui| {
/// let alternatives = ["a", "b", "c", "d"];
/// let mut selected = 2;
/// egui::ComboBox::from_label("Select one!").show_index(
/// ui,
/// &mut selected,
/// alternatives.len(),
/// |i| alternatives[i].to_owned()
/// );
/// # });
/// ```
pub fn show_index(
self,
ui: &mut Ui,
selected: &mut usize,
len: usize,
get: impl Fn(usize) -> String,
) -> Response {
let slf = self.selected_text(get(*selected));
let mut changed = false;
let mut response = slf
.show_ui(ui, |ui| {
for i in 0..len {
if ui.selectable_label(i == *selected, get(i)).clicked() {
*selected = i;
changed = true;
}
}
})
.response;
if changed {
response.mark_changed();
}
response
}
}
fn combo_box_dyn<'c, R>(
ui: &mut Ui,
button_id: Id,
selected_text: WidgetText,
menu_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
icon: Option<IconPainter>,
wrap_enabled: bool,
width: Option<f32>,
) -> InnerResponse<Option<R>> {
let popup_id = button_id.with("popup");
let is_popup_open = ui.memory(|m| m.is_popup_open(popup_id));
let popup_height = ui.memory(|m| m.areas.get(popup_id).map_or(100.0, |state| state.size.y));
let above_or_below =
if ui.next_widget_position().y + ui.spacing().interact_size.y + popup_height
< ui.ctx().screen_rect().bottom()
{
AboveOrBelow::Below
} else {
AboveOrBelow::Above
};
let margin = ui.spacing().button_padding;
let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| {
let icon_spacing = ui.spacing().icon_spacing;
// We don't want to change width when user selects something new
let full_minimum_width = if wrap_enabled {
// Currently selected value's text will be wrapped if needed, so occupy the available width.
ui.available_width()
} else {
// Occupy at least the minimum width assigned to ComboBox.
let width = width.unwrap_or_else(|| ui.spacing().combo_width);
width - 2.0 * margin.x
};
let icon_size = Vec2::splat(ui.spacing().icon_width);
let wrap_width = if wrap_enabled {
// Use the available width, currently selected value's text will be wrapped if exceeds this value.
ui.available_width() - icon_spacing - icon_size.x
} else {
// Use all the width necessary to display the currently selected value's text.
f32::INFINITY
};
let galley =
selected_text.into_galley(ui, Some(wrap_enabled), wrap_width, TextStyle::Button);
// The width necessary to contain the whole widget with the currently selected value's text.
let width = if wrap_enabled {
full_minimum_width
} else {
// Occupy at least the minimum width needed to contain the widget with the currently selected value's text.
galley.size().x + icon_spacing + icon_size.x
};
// Case : wrap_enabled : occupy all the available width.
// Case : !wrap_enabled : occupy at least the minimum width assigned to Slider and ComboBox,
// increase if the currently selected value needs additional horizontal space to fully display its text (up to wrap_width (f32::INFINITY)).
let width = width.at_least(full_minimum_width);
let height = galley.size().y.max(icon_size.y);
let (_, rect) = ui.allocate_space(Vec2::new(width, height));
let button_rect = ui.min_rect().expand2(ui.spacing().button_padding);
let response = ui.interact(button_rect, button_id, Sense::click());
// response.active |= is_popup_open;
if ui.is_rect_visible(rect) {
let icon_rect = Align2::RIGHT_CENTER.align_size_within_rect(icon_size, rect);
let visuals = if is_popup_open {
&ui.visuals().widgets.open
} else {
ui.style().interact(&response)
};
if let Some(icon) = icon {
icon(
ui,
icon_rect.expand(visuals.expansion),
visuals,
is_popup_open,
above_or_below,
);
} else {
paint_default_icon(
ui.painter(),
icon_rect.expand(visuals.expansion),
visuals,
above_or_below,
);
}
let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect);
galley.paint_with_visuals(ui.painter(), text_rect.min, visuals);
}
});
if button_response.clicked() {
ui.memory_mut(|mem| mem.toggle_popup(popup_id));
}
let inner = crate::popup::popup_above_or_below_widget(
ui,
popup_id,
&button_response,
above_or_below,
|ui| {
ScrollArea::vertical()
.max_height(ui.spacing().combo_height)
.show(ui, menu_contents)
.inner
},
);
InnerResponse {
inner,
response: button_response,
}
}
fn button_frame(
ui: &mut Ui,
id: Id,
is_popup_open: bool,
sense: Sense,
add_contents: impl FnOnce(&mut Ui),
) -> Response {
let where_to_put_background = ui.painter().add(Shape::Noop);
let margin = ui.spacing().button_padding;
let interact_size = ui.spacing().interact_size;
let mut outer_rect = ui.available_rect_before_wrap();
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
let inner_rect = outer_rect.shrink2(margin);
let mut content_ui = ui.child_ui(inner_rect, *ui.layout());
add_contents(&mut content_ui);
let mut outer_rect = content_ui.min_rect().expand2(margin);
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
let response = ui.interact(outer_rect, id, sense);
if ui.is_rect_visible(outer_rect) {
let visuals = if is_popup_open {
&ui.visuals().widgets.open
} else {
ui.style().interact(&response)
};
ui.painter().set(
where_to_put_background,
epaint::RectShape {
rect: outer_rect.expand(visuals.expansion),
rounding: visuals.rounding,
fill: visuals.weak_bg_fill,
stroke: visuals.bg_stroke,
},
);
}
ui.advance_cursor_after_rect(outer_rect);
response
}
fn paint_default_icon(
painter: &Painter,
rect: Rect,
visuals: &WidgetVisuals,
above_or_below: AboveOrBelow,
) {
let rect = Rect::from_center_size(
rect.center(),
vec2(rect.width() * 0.7, rect.height() * 0.45),
);
match above_or_below {
AboveOrBelow::Above => {
// Upward pointing triangle
painter.add(Shape::convex_polygon(
vec![rect.left_bottom(), rect.right_bottom(), rect.center_top()],
visuals.fg_stroke.color,
Stroke::NONE,
));
}
AboveOrBelow::Below => {
// Downward pointing triangle
painter.add(Shape::convex_polygon(
vec![rect.left_top(), rect.right_top(), rect.center_bottom()],
visuals.fg_stroke.color,
Stroke::NONE,
));
}
}
}

View file

@ -0,0 +1,288 @@
//! Frame container
use crate::{layers::ShapeIdx, style::Margin, *};
use epaint::*;
/// Add a background, frame and/or margin to a rectangular background of a [`Ui`].
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// egui::Frame::none()
/// .fill(egui::Color32::RED)
/// .show(ui, |ui| {
/// ui.label("Label with red background");
/// });
/// # });
/// ```
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[must_use = "You should call .show()"]
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,
}
impl Frame {
pub fn none() -> Self {
Self::default()
}
/// For when you want to group a few widgets together within a frame.
pub fn group(style: &Style) -> Self {
Self {
inner_margin: Margin::same(6.0), // same and symmetric looks best in corners when nesting groups
rounding: style.visuals.widgets.noninteractive.rounding,
stroke: style.visuals.widgets.noninteractive.bg_stroke,
..Default::default()
}
}
pub fn side_top_panel(style: &Style) -> Self {
Self {
inner_margin: Margin::symmetric(8.0, 2.0),
fill: style.visuals.panel_fill,
..Default::default()
}
}
pub fn central_panel(style: &Style) -> Self {
Self {
inner_margin: Margin::same(8.0),
fill: style.visuals.panel_fill,
..Default::default()
}
}
pub fn window(style: &Style) -> Self {
Self {
inner_margin: style.spacing.window_margin,
rounding: style.visuals.window_rounding,
shadow: style.visuals.window_shadow,
fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(),
..Default::default()
}
}
pub fn menu(style: &Style) -> Self {
Self {
inner_margin: style.spacing.menu_margin,
rounding: style.visuals.menu_rounding,
shadow: style.visuals.popup_shadow,
fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(),
..Default::default()
}
}
pub fn popup(style: &Style) -> Self {
Self {
inner_margin: style.spacing.menu_margin,
rounding: style.visuals.menu_rounding,
shadow: style.visuals.popup_shadow,
fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(),
..Default::default()
}
}
/// A canvas to draw on.
///
/// In bright mode this will be very bright,
/// and in dark mode this will be very dark.
pub fn canvas(style: &Style) -> Self {
Self {
inner_margin: Margin::same(2.0),
rounding: style.visuals.widgets.noninteractive.rounding,
fill: style.visuals.extreme_bg_color,
stroke: style.visuals.window_stroke(),
..Default::default()
}
}
/// A dark canvas to draw on.
pub fn dark_canvas(style: &Style) -> Self {
Self {
fill: Color32::from_black_alpha(250),
..Self::canvas(style)
}
}
}
impl Frame {
#[inline]
pub fn fill(mut self, fill: Color32) -> Self {
self.fill = fill;
self
}
#[inline]
pub fn stroke(mut self, stroke: Stroke) -> Self {
self.stroke = stroke;
self
}
#[inline]
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
self.rounding = rounding.into();
self
}
/// Margin within the painted frame.
#[inline]
pub fn inner_margin(mut self, inner_margin: impl Into<Margin>) -> Self {
self.inner_margin = inner_margin.into();
self
}
/// Margin outside the painted frame.
#[inline]
pub fn outer_margin(mut self, outer_margin: impl Into<Margin>) -> Self {
self.outer_margin = outer_margin.into();
self
}
#[deprecated = "Renamed inner_margin in egui 0.18"]
#[inline]
pub fn margin(self, margin: impl Into<Margin>) -> Self {
self.inner_margin(margin)
}
#[inline]
pub fn shadow(mut self, shadow: Shadow) -> Self {
self.shadow = shadow;
self
}
pub fn multiply_with_opacity(mut self, opacity: f32) -> Self {
self.fill = self.fill.linear_multiply(opacity);
self.stroke.color = self.stroke.color.linear_multiply(opacity);
self.shadow.color = self.shadow.color.linear_multiply(opacity);
self
}
}
impl Frame {
/// inner margin plus outer margin.
#[inline]
pub fn total_margin(&self) -> Margin {
self.inner_margin + self.outer_margin
}
}
// ----------------------------------------------------------------------------
pub struct Prepared {
pub frame: Frame,
where_to_put_background: ShapeIdx,
pub content_ui: Ui,
}
impl Frame {
pub fn begin(self, ui: &mut Ui) -> Prepared {
let where_to_put_background = ui.painter().add(Shape::Noop);
let outer_rect_bounds = ui.available_rect_before_wrap();
let mut inner_rect = outer_rect_bounds;
inner_rect.min += self.outer_margin.left_top() + self.inner_margin.left_top();
inner_rect.max -= self.outer_margin.right_bottom() + self.inner_margin.right_bottom();
// Make sure we don't shrink to the negative:
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y);
let content_ui = ui.child_ui(inner_rect, *ui.layout());
// content_ui.set_clip_rect(outer_rect_bounds.shrink(self.stroke.width * 0.5)); // Can't do this since we don't know final size yet
Prepared {
frame: self,
where_to_put_background,
content_ui,
}
}
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
self.show_dyn(ui, Box::new(add_contents))
}
fn show_dyn<'c, R>(
self,
ui: &mut Ui,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let mut prepared = self.begin(ui);
let ret = add_contents(&mut prepared.content_ui);
let response = prepared.end(ui);
InnerResponse::new(ret, response)
}
pub fn paint(&self, outer_rect: Rect) -> Shape {
let Self {
inner_margin: _,
outer_margin: _,
rounding,
shadow,
fill,
stroke,
} = *self;
let frame_shape = Shape::Rect(epaint::RectShape {
rect: outer_rect,
rounding,
fill,
stroke,
});
if shadow == Default::default() {
frame_shape
} else {
let shadow = shadow.tessellate(outer_rect, rounding);
let shadow = Shape::Mesh(shadow);
Shape::Vec(vec![shadow, frame_shape])
}
}
}
impl Prepared {
fn paint_rect(&self) -> Rect {
let mut rect = self.content_ui.min_rect();
rect.min -= self.frame.inner_margin.left_top();
rect.max += self.frame.inner_margin.right_bottom();
rect
}
fn content_with_margin(&self) -> Rect {
let mut rect = self.content_ui.min_rect();
rect.min -= self.frame.inner_margin.left_top() + self.frame.outer_margin.left_top();
rect.max += self.frame.inner_margin.right_bottom() + self.frame.outer_margin.right_bottom();
rect
}
pub fn end(self, ui: &mut Ui) -> Response {
let paint_rect = self.paint_rect();
let Prepared {
frame,
where_to_put_background,
..
} = self;
if ui.is_rect_visible(paint_rect) {
let shape = frame.paint(paint_rect);
ui.painter().set(where_to_put_background, shape);
}
ui.allocate_rect(self.content_with_margin(), Sense::hover())
}
}

View file

@ -1,23 +1,23 @@
//! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], etc.
//! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], [`SidePanel`], etc.
//!
//! For instance, a [`Frame`] adds a frame and background to some contained UI.
pub(crate) mod area;
pub(crate) mod collapsing_header;
pub mod collapsing_header;
mod combo_box;
pub(crate) mod frame;
pub(crate) mod panel;
pub mod panel;
pub mod popup;
pub(crate) mod resize;
pub(crate) mod scroll_area;
pub mod scroll_area;
pub(crate) mod window;
pub use {
area::Area,
collapsing_header::*,
collapsing_header::{CollapsingHeader, CollapsingResponse},
combo_box::*,
frame::Frame,
panel::{CentralPanel, SidePanel, TopPanel},
panel::{CentralPanel, SidePanel, TopBottomPanel},
popup::*,
resize::Resize,
scroll_area::ScrollArea,

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,378 @@
//! Show popup windows, tooltips, context menus etc.
use crate::*;
// ----------------------------------------------------------------------------
/// Same state for all tooltips.
#[derive(Clone, Debug, Default)]
pub(crate) struct TooltipState {
last_common_id: Option<Id>,
individual_ids_and_sizes: ahash::HashMap<usize, (Id, Vec2)>,
}
impl TooltipState {
pub fn load(ctx: &Context) -> Option<Self> {
ctx.data_mut(|d| d.get_temp(Id::null()))
}
fn store(self, ctx: &Context) {
ctx.data_mut(|d| d.insert_temp(Id::null(), self));
}
fn individual_tooltip_size(&self, common_id: Id, index: usize) -> Option<Vec2> {
if self.last_common_id == Some(common_id) {
Some(self.individual_ids_and_sizes.get(&index)?.1)
} else {
None
}
}
fn set_individual_tooltip(
&mut self,
common_id: Id,
index: usize,
individual_id: Id,
size: Vec2,
) {
if self.last_common_id != Some(common_id) {
self.last_common_id = Some(common_id);
self.individual_ids_and_sizes.clear();
}
self.individual_ids_and_sizes
.insert(index, (individual_id, size));
}
}
// ----------------------------------------------------------------------------
/// Show a tooltip at the current pointer position (if any).
///
/// Most of the time it is easier to use [`Response::on_hover_ui`].
///
/// See also [`show_tooltip_text`].
///
/// Returns `None` if the tooltip could not be placed.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// if ui.ui_contains_pointer() {
/// egui::show_tooltip(ui.ctx(), egui::Id::new("my_tooltip"), |ui| {
/// ui.label("Helpful text");
/// });
/// }
/// # });
/// ```
pub fn show_tooltip<R>(
ctx: &Context,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
show_tooltip_at_pointer(ctx, id, add_contents)
}
/// Show a tooltip at the current pointer position (if any).
///
/// Most of the time it is easier to use [`Response::on_hover_ui`].
///
/// See also [`show_tooltip_text`].
///
/// Returns `None` if the tooltip could not be placed.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// if ui.ui_contains_pointer() {
/// egui::show_tooltip_at_pointer(ui.ctx(), egui::Id::new("my_tooltip"), |ui| {
/// ui.label("Helpful text");
/// });
/// }
/// # });
/// ```
pub fn show_tooltip_at_pointer<R>(
ctx: &Context,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
let suggested_pos = ctx
.input(|i| i.pointer.hover_pos())
.map(|pointer_pos| pointer_pos + vec2(16.0, 16.0));
show_tooltip_at(ctx, id, suggested_pos, add_contents)
}
/// Show a tooltip under the given area.
///
/// If the tooltip does not fit under the area, it tries to place it above it instead.
pub fn show_tooltip_for<R>(
ctx: &Context,
id: Id,
rect: &Rect,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
let expanded_rect = rect.expand2(vec2(2.0, 4.0));
let (above, position) = if ctx.input(|i| i.any_touches()) {
(true, expanded_rect.left_top())
} else {
(false, expanded_rect.left_bottom())
};
show_tooltip_at_avoid_dyn(
ctx,
id,
Some(position),
above,
expanded_rect,
Box::new(add_contents),
)
}
/// Show a tooltip at the given position.
///
/// Returns `None` if the tooltip could not be placed.
pub fn show_tooltip_at<R>(
ctx: &Context,
id: Id,
suggested_position: Option<Pos2>,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
let above = false;
show_tooltip_at_avoid_dyn(
ctx,
id,
suggested_position,
above,
Rect::NOTHING,
Box::new(add_contents),
)
}
fn show_tooltip_at_avoid_dyn<'c, R>(
ctx: &Context,
individual_id: Id,
suggested_position: Option<Pos2>,
above: bool,
mut avoid_rect: Rect,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> Option<R> {
let spacing = 4.0;
// if there are multiple tooltips open they should use the same common_id for the `tooltip_size` caching to work.
let mut frame_state =
ctx.frame_state(|fs| fs.tooltip_state)
.unwrap_or(crate::frame_state::TooltipFrameState {
common_id: individual_id,
rect: Rect::NOTHING,
count: 0,
});
let mut position = if frame_state.rect.is_positive() {
avoid_rect = avoid_rect.union(frame_state.rect);
if above {
frame_state.rect.left_top() - spacing * Vec2::Y
} else {
frame_state.rect.left_bottom() + spacing * Vec2::Y
}
} else if let Some(position) = suggested_position {
position
} else if ctx.memory(|mem| mem.everything_is_visible()) {
Pos2::ZERO
} else {
return None; // No good place for a tooltip :(
};
let mut long_state = TooltipState::load(ctx).unwrap_or_default();
let expected_size =
long_state.individual_tooltip_size(frame_state.common_id, frame_state.count);
let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0));
if above {
position.y -= expected_size.y;
}
position = position.at_most(ctx.screen_rect().max - expected_size);
// check if we intersect the avoid_rect
{
let new_rect = Rect::from_min_size(position, expected_size);
// Note: We use shrink so that we don't get false positives when the rects just touch
if new_rect.shrink(1.0).intersects(avoid_rect) {
if above {
// place below instead:
position = avoid_rect.left_bottom() + spacing * Vec2::Y;
} else {
// place above instead:
position = Pos2::new(position.x, avoid_rect.min.y - expected_size.y - spacing);
}
}
}
let position = position.at_least(ctx.screen_rect().min);
let area_id = frame_state.common_id.with(frame_state.count);
let InnerResponse { inner, response } =
show_tooltip_area_dyn(ctx, area_id, position, add_contents);
long_state.set_individual_tooltip(
frame_state.common_id,
frame_state.count,
individual_id,
response.rect.size(),
);
long_state.store(ctx);
frame_state.count += 1;
frame_state.rect = frame_state.rect.union(response.rect);
ctx.frame_state_mut(|fs| fs.tooltip_state = Some(frame_state));
Some(inner)
}
/// Show some text at the current pointer position (if any).
///
/// Most of the time it is easier to use [`Response::on_hover_text`].
///
/// See also [`show_tooltip`].
///
/// Returns `None` if the tooltip could not be placed.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// if ui.ui_contains_pointer() {
/// egui::show_tooltip_text(ui.ctx(), egui::Id::new("my_tooltip"), "Helpful text");
/// }
/// # });
/// ```
pub fn show_tooltip_text(ctx: &Context, id: Id, text: impl Into<WidgetText>) -> Option<()> {
show_tooltip(ctx, id, |ui| {
crate::widgets::Label::new(text).ui(ui);
})
}
/// Show a pop-over window.
fn show_tooltip_area_dyn<'c, R>(
ctx: &Context,
area_id: Id,
window_pos: Pos2,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
use containers::*;
Area::new(area_id)
.order(Order::Tooltip)
.fixed_pos(window_pos)
.constrain(true)
.interactable(false)
.drag_bounds(ctx.screen_rect())
.show(ctx, |ui| {
Frame::popup(&ctx.style())
.show(ui, |ui| {
ui.set_max_width(ui.spacing().tooltip_width);
add_contents(ui)
})
.inner
})
}
/// Was this popup visible last frame?
pub fn was_tooltip_open_last_frame(ctx: &Context, tooltip_id: Id) -> bool {
if let Some(state) = TooltipState::load(ctx) {
if let Some(common_id) = state.last_common_id {
for (count, (individual_id, _size)) in &state.individual_ids_and_sizes {
if *individual_id == tooltip_id {
let area_id = common_id.with(count);
let layer_id = LayerId::new(Order::Tooltip, area_id);
if ctx.memory(|mem| mem.areas.visible_last_frame(&layer_id)) {
return true;
}
}
}
}
}
false
}
/// Helper for [`popup_above_or_below_widget`].
pub fn popup_below_widget<R>(
ui: &Ui,
popup_id: Id,
widget_response: &Response,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
popup_above_or_below_widget(
ui,
popup_id,
widget_response,
AboveOrBelow::Below,
add_contents,
)
}
/// Shows a popup above or below another widget.
///
/// Useful for drop-down menus (combo boxes) or suggestion menus under text fields.
///
/// The opened popup will have the same width as the parent.
///
/// You must open the popup with [`Memory::open_popup`] or [`Memory::toggle_popup`].
///
/// Returns `None` if the popup is not open.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// let response = ui.button("Open popup");
/// let popup_id = ui.make_persistent_id("my_unique_id");
/// if response.clicked() {
/// ui.memory_mut(|mem| mem.toggle_popup(popup_id));
/// }
/// let below = egui::AboveOrBelow::Below;
/// egui::popup::popup_above_or_below_widget(ui, popup_id, &response, below, |ui| {
/// ui.set_min_width(200.0); // if you want to control the size
/// ui.label("Some more info, or things you can select:");
/// ui.label("…");
/// });
/// # });
/// ```
pub fn popup_above_or_below_widget<R>(
ui: &Ui,
popup_id: Id,
widget_response: &Response,
above_or_below: AboveOrBelow,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
if ui.memory(|mem| mem.is_popup_open(popup_id)) {
let (pos, pivot) = match above_or_below {
AboveOrBelow::Above => (widget_response.rect.left_top(), Align2::LEFT_BOTTOM),
AboveOrBelow::Below => (widget_response.rect.left_bottom(), Align2::LEFT_TOP),
};
let inner = Area::new(popup_id)
.order(Order::Foreground)
.constrain(true)
.fixed_pos(pos)
.pivot(pivot)
.show(ui.ctx(), |ui| {
// Note: we use a separate clip-rect for this area, so the popup can be outside the parent.
// See https://github.com/emilk/egui/issues/825
let frame = Frame::popup(ui.style());
let frame_margin = frame.total_margin();
frame
.show(ui, |ui| {
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
ui.set_width(widget_response.rect.width() - frame_margin.sum().x);
add_contents(ui)
})
.inner
})
.inner
})
.inner;
if ui.input(|i| i.key_pressed(Key::Escape)) || widget_response.clicked_elsewhere() {
ui.memory_mut(|mem| mem.close_popup());
}
Some(inner)
} else {
None
}
}

View file

@ -1,7 +1,7 @@
use crate::*;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct State {
/// This is the size that the user has picked by dragging the resize handles.
/// This may be smaller and/or larger than the actual size.
@ -16,8 +16,19 @@ pub(crate) struct State {
pub(crate) requested_size: Option<Vec2>,
}
impl State {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data_mut(|d| d.get_persisted(id))
}
pub fn store(self, ctx: &Context, id: Id) {
ctx.data_mut(|d| d.insert_persisted(id, self));
}
}
/// A region that can be resized by dragging the bottom right corner.
#[derive(Clone, Copy, Debug)]
#[must_use = "You should call .show()"]
pub struct Resize {
id: Option<Id>,
id_source: Option<Id>,
@ -41,7 +52,7 @@ impl Default for Resize {
resizable: true,
min_size: Vec2::splat(16.0),
max_size: Vec2::splat(f32::INFINITY),
default_size: vec2(320.0, 128.0), // TODO: preferred size of `Resize` area.
default_size: vec2(320.0, 128.0), // TODO(emilk): preferred size of [`Resize`] area.
with_stroke: true,
}
}
@ -54,7 +65,7 @@ impl Resize {
self
}
/// A source for the unique `Id`, e.g. `.id_source("second_resize_area")` or `.id_source(loop_index)`.
/// A source for the unique [`Id`], e.g. `.id_source("second_resize_area")` or `.id_source(loop_index)`.
pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self {
self.id_source = Some(Id::new(id_source));
self
@ -74,7 +85,7 @@ impl Resize {
/// Preferred / suggested height. Actual height will depend on contents.
///
/// Examples:
/// * if the contents is a `ScrollArea` then this decides the maximum size.
/// * if the contents is a [`ScrollArea`] then this decides the maximum size.
/// * if the contents is a canvas, this decides the height of it,
/// * if the contents is text and buttons, then the `default_height` is ignored
/// and the height is picked automatically..
@ -93,11 +104,13 @@ impl Resize {
self.min_size = min_size.into();
self
}
/// Won't shrink to smaller than this
pub fn min_width(mut self, min_width: f32) -> Self {
self.min_size.x = min_width;
self
}
/// Won't shrink to smaller than this
pub fn min_height(mut self, min_height: f32) -> Self {
self.min_size.y = min_height;
@ -159,7 +172,7 @@ impl Resize {
ui.make_persistent_id(id_source)
});
let mut state = ui.memory().resize.get(&id).cloned().unwrap_or_else(|| {
let mut state = State::load(ui.ctx(), id).unwrap_or_else(|| {
ui.ctx().request_repaint(); // counter frame delay
let default_size = self
@ -167,7 +180,7 @@ impl Resize {
.at_least(self.min_size)
.at_most(self.max_size)
.at_most(
ui.input().screen_rect().size() - 2.0 * ui.spacing().window_padding, // hack for windows
ui.ctx().screen_rect().size() - ui.spacing().window_margin.sum(), // hack for windows
);
State {
@ -206,7 +219,7 @@ impl Resize {
} else {
// We are not being actively resized, so auto-expand to include size of last frame.
// This prevents auto-shrinking if the contents contain width-filling widgets (separators etc)
// but it makes a lot of interactions with `Window`s nicer.
// but it makes a lot of interactions with [`Window`]s nicer.
state.desired_size = state.desired_size.max(state.last_content_size);
}
@ -281,25 +294,24 @@ impl Resize {
if self.with_stroke && corner_response.is_some() {
let rect = Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size);
let rect = rect.expand(2.0); // breathing room for content
ui.painter().add(paint::Shape::Rect {
ui.painter().add(Shape::rect_stroke(
rect,
corner_radius: 3.0,
fill: Default::default(),
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
});
3.0,
ui.visuals().widgets.noninteractive.bg_stroke,
));
}
if let Some(corner_response) = corner_response {
paint_resize_corner(ui, &corner_response);
if corner_response.hovered() || corner_response.dragged() {
ui.ctx().output().cursor_icon = CursorIcon::ResizeNwSe;
ui.ctx().set_cursor_icon(CursorIcon::ResizeNwSe);
}
}
ui.memory().resize.insert(id, state);
state.store(ui.ctx(), id);
if ui.ctx().style().visuals.debug_resize {
if ui.ctx().style().debug.show_resize {
ui.ctx().debug_painter().debug_rect(
Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size),
Color32::GREEN,
@ -314,21 +326,24 @@ impl Resize {
}
}
use crate::paint::Stroke;
use epaint::Stroke;
pub fn paint_resize_corner(ui: &mut Ui, response: &Response) {
let stroke = ui.style().interact(response).fg_stroke;
paint_resize_corner_with_style(ui, &response.rect, stroke);
paint_resize_corner_with_style(ui, &response.rect, stroke, Align2::RIGHT_BOTTOM);
}
pub fn paint_resize_corner_with_style(ui: &mut Ui, rect: &Rect, stroke: Stroke) {
pub fn paint_resize_corner_with_style(ui: &mut Ui, rect: &Rect, stroke: Stroke, corner: Align2) {
let painter = ui.painter();
let corner = painter.round_pos_to_pixels(rect.right_bottom());
let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect));
let mut w = 2.0;
while w <= rect.width() && w <= rect.height() {
painter.line_segment(
[pos2(corner.x - w, corner.y), pos2(corner.x, corner.y - w)],
[
pos2(cp.x - w * corner.x().to_sign(), cp.y),
pos2(cp.x, cp.y - w * corner.y().to_sign()),
],
stroke,
);
w += 4.0;

View file

@ -0,0 +1,912 @@
//! Coordinate system names:
//! * content: size of contents (generally large; that's why we want scroll bars)
//! * outer: size of scroll area including scroll bar(s)
//! * inner: excluding scroll bar(s). The area we clip the contents to.
#![allow(clippy::needless_range_loop)]
use crate::*;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct State {
/// Positive offset means scrolling down/right
pub offset: Vec2,
/// Were the scroll bars visible last frame?
show_scroll: [bool; 2],
/// The content were to large to fit large frame.
content_is_too_large: [bool; 2],
/// Momentum, used for kinetic scrolling
#[cfg_attr(feature = "serde", serde(skip))]
vel: Vec2,
/// Mouse offset relative to the top of the handle when started moving the handle.
scroll_start_offset_from_top_left: [Option<f32>; 2],
/// Is the scroll sticky. This is true while scroll handle is in the end position
/// and remains that way until the user moves the scroll_handle. Once unstuck (false)
/// it remains false until the scroll touches the end position, which reenables stickiness.
scroll_stuck_to_end: [bool; 2],
}
impl Default for State {
fn default() -> Self {
Self {
offset: Vec2::ZERO,
show_scroll: [false; 2],
content_is_too_large: [false; 2],
vel: Vec2::ZERO,
scroll_start_offset_from_top_left: [None; 2],
scroll_stuck_to_end: [true; 2],
}
}
}
impl State {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data_mut(|d| d.get_persisted(id))
}
pub fn store(self, ctx: &Context, id: Id) {
ctx.data_mut(|d| d.insert_persisted(id, self));
}
}
pub struct ScrollAreaOutput<R> {
/// What the user closure returned.
pub inner: R,
/// [`Id`] of the [`ScrollArea`].
pub id: Id,
/// The current state of the scroll area.
pub state: State,
/// The size of the content. If this is larger than [`Self::inner_rect`],
/// then there was need for scrolling.
pub content_size: Vec2,
/// Where on the screen the content is (excludes scroll bars).
pub inner_rect: Rect,
}
/// Add vertical and/or horizontal scrolling to a contained [`Ui`].
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// egui::ScrollArea::vertical().show(ui, |ui| {
/// // Add a lot of widgets here.
/// });
/// # });
/// ```
///
/// You can scroll to an element using [`Response::scroll_to_me`], [`Ui::scroll_to_cursor`] and [`Ui::scroll_to_rect`].
#[derive(Clone, Debug)]
#[must_use = "You should call .show()"]
pub struct ScrollArea {
/// Do we have horizontal/vertical scrolling?
has_bar: [bool; 2],
auto_shrink: [bool; 2],
max_size: Vec2,
min_scrolled_size: Vec2,
always_show_scroll: bool,
id_source: Option<Id>,
offset_x: Option<f32>,
offset_y: Option<f32>,
/// If false, we ignore scroll events.
scrolling_enabled: bool,
drag_to_scroll: bool,
/// If true for vertical or horizontal the scroll wheel will stick to the
/// end position until user manually changes position. It will become true
/// again once scroll handle makes contact with end.
stick_to_end: [bool; 2],
}
impl ScrollArea {
/// Create a horizontal scroll area.
pub fn horizontal() -> Self {
Self::new([true, false])
}
/// Create a vertical scroll area.
pub fn vertical() -> Self {
Self::new([false, true])
}
/// Create a bi-directional (horizontal and vertical) scroll area.
pub fn both() -> Self {
Self::new([true, true])
}
/// Create a scroll area where both direction of scrolling is disabled.
/// It's unclear why you would want to do this.
pub fn neither() -> Self {
Self::new([false, false])
}
/// Create a scroll area where you decide which axis has scrolling enabled.
/// For instance, `ScrollAre::new([true, false])` enable horizontal scrolling.
pub fn new(has_bar: [bool; 2]) -> Self {
Self {
has_bar,
auto_shrink: [true; 2],
max_size: Vec2::INFINITY,
min_scrolled_size: Vec2::splat(64.0),
always_show_scroll: false,
id_source: None,
offset_x: None,
offset_y: None,
scrolling_enabled: true,
drag_to_scroll: true,
stick_to_end: [false; 2],
}
}
/// The maximum width of the outer frame of the scroll area.
///
/// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default).
///
/// See also [`Self::auto_shrink`].
pub fn max_width(mut self, max_width: f32) -> Self {
self.max_size.x = max_width;
self
}
/// The maximum height of the outer frame of the scroll area.
///
/// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default).
///
/// See also [`Self::auto_shrink`].
pub fn max_height(mut self, max_height: f32) -> Self {
self.max_size.y = max_height;
self
}
/// The minimum width of a horizontal scroll area which requires scroll bars.
///
/// The [`ScrollArea`] will only become smaller than this if the content is smaller than this
/// (and so we don't require scroll bars).
///
/// Default: `64.0`.
pub fn min_scrolled_width(mut self, min_scrolled_width: f32) -> Self {
self.min_scrolled_size.x = min_scrolled_width;
self
}
/// The minimum height of a vertical scroll area which requires scroll bars.
///
/// The [`ScrollArea`] will only become smaller than this if the content is smaller than this
/// (and so we don't require scroll bars).
///
/// Default: `64.0`.
pub fn min_scrolled_height(mut self, min_scrolled_height: f32) -> Self {
self.min_scrolled_size.y = min_scrolled_height;
self
}
/// If `false` (default), the scroll bar will be hidden when not needed/
/// If `true`, the scroll bar will always be displayed even if not needed.
pub fn always_show_scroll(mut self, always_show_scroll: bool) -> Self {
self.always_show_scroll = always_show_scroll;
self
}
/// A source for the unique [`Id`], e.g. `.id_source("second_scroll_area")` or `.id_source(loop_index)`.
pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self {
self.id_source = Some(Id::new(id_source));
self
}
/// Set the horizontal and vertical scroll offset position.
///
/// Positive offset means scrolling down/right.
///
/// See also: [`Self::vertical_scroll_offset`], [`Self::horizontal_scroll_offset`],
/// [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
pub fn scroll_offset(mut self, offset: Vec2) -> Self {
self.offset_x = Some(offset.x);
self.offset_y = Some(offset.y);
self
}
/// Set the vertical scroll offset position.
///
/// Positive offset means scrolling down.
///
/// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
pub fn vertical_scroll_offset(mut self, offset: f32) -> Self {
self.offset_y = Some(offset);
self
}
/// Set the horizontal scroll offset position.
///
/// Positive offset means scrolling right.
///
/// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and
/// [`Response::scroll_to_me`](crate::Response::scroll_to_me)
pub fn horizontal_scroll_offset(mut self, offset: f32) -> Self {
self.offset_x = Some(offset);
self
}
/// Turn on/off scrolling on the horizontal axis.
pub fn hscroll(mut self, hscroll: bool) -> Self {
self.has_bar[0] = hscroll;
self
}
/// Turn on/off scrolling on the vertical axis.
pub fn vscroll(mut self, vscroll: bool) -> Self {
self.has_bar[1] = vscroll;
self
}
/// Turn on/off scrolling on the horizontal/vertical axes.
pub fn scroll2(mut self, has_bar: [bool; 2]) -> Self {
self.has_bar = has_bar;
self
}
/// Control the scrolling behavior.
///
/// * If `true` (default), the scroll area will respond to user scrolling.
/// * If `false`, the scroll area will not respond to user scrolling.
///
/// This can be used, for example, to optionally freeze scrolling while the user
/// is typing text in a [`TextEdit`] widget contained within the scroll area.
///
/// This controls both scrolling directions.
pub fn enable_scrolling(mut self, enable: bool) -> Self {
self.scrolling_enabled = enable;
self
}
/// Can the user drag the scroll area to scroll?
///
/// This is useful for touch screens.
///
/// If `true`, the [`ScrollArea`] will sense drags.
///
/// Default: `true`.
pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self {
self.drag_to_scroll = drag_to_scroll;
self
}
/// For each axis, should the containing area shrink if the content is small?
///
/// * If `true`, egui will add blank space outside the scroll area.
/// * If `false`, egui will add blank space inside the scroll area.
///
/// Default: `[true; 2]`.
pub fn auto_shrink(mut self, auto_shrink: [bool; 2]) -> Self {
self.auto_shrink = auto_shrink;
self
}
pub(crate) fn has_any_bar(&self) -> bool {
self.has_bar[0] || self.has_bar[1]
}
/// The scroll handle will stick to the rightmost position even while the content size
/// changes dynamically. This can be useful to simulate text scrollers coming in from right
/// hand side. The scroll handle remains stuck until user manually changes position. Once "unstuck"
/// it will remain focused on whatever content viewport the user left it on. If the scroll
/// handle is dragged all the way to the right it will again become stuck and remain there
/// until manually pulled from the end position.
pub fn stick_to_right(mut self, stick: bool) -> Self {
self.stick_to_end[0] = stick;
self
}
/// The scroll handle will stick to the bottom position even while the content size
/// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers.
/// The scroll handle remains stuck until user manually changes position. Once "unstuck"
/// it will remain focused on whatever content viewport the user left it on. If the scroll
/// handle is dragged to the bottom it will again become stuck and remain there until manually
/// pulled from the end position.
pub fn stick_to_bottom(mut self, stick: bool) -> Self {
self.stick_to_end[1] = stick;
self
}
}
struct Prepared {
id: Id,
state: State,
has_bar: [bool; 2],
auto_shrink: [bool; 2],
/// How much horizontal and vertical space are used up by the
/// width of the vertical bar, and the height of the horizontal bar?
current_bar_use: Vec2,
always_show_scroll: bool,
/// Where on the screen the content is (excludes scroll bars).
inner_rect: Rect,
content_ui: Ui,
/// Relative coordinates: the offset and size of the view of the inner UI.
/// `viewport.min == ZERO` means we scrolled to the top.
viewport: Rect,
scrolling_enabled: bool,
stick_to_end: [bool; 2],
}
impl ScrollArea {
fn begin(self, ui: &mut Ui) -> Prepared {
let Self {
has_bar,
auto_shrink,
max_size,
min_scrolled_size,
always_show_scroll,
id_source,
offset_x,
offset_y,
scrolling_enabled,
drag_to_scroll,
stick_to_end,
} = self;
let ctx = ui.ctx().clone();
let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area"));
let id = ui.make_persistent_id(id_source);
ui.ctx().check_for_id_clash(
id,
Rect::from_min_size(ui.available_rect_before_wrap().min, Vec2::ZERO),
"ScrollArea",
);
let mut state = State::load(&ctx, id).unwrap_or_default();
state.offset.x = offset_x.unwrap_or(state.offset.x);
state.offset.y = offset_y.unwrap_or(state.offset.y);
let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui);
let current_hscroll_bar_height = if !has_bar[0] {
0.0
} else if always_show_scroll {
max_scroll_bar_width
} else {
max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), state.show_scroll[0])
};
let current_vscroll_bar_width = if !has_bar[1] {
0.0
} else if always_show_scroll {
max_scroll_bar_width
} else {
max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), state.show_scroll[1])
};
let current_bar_use = vec2(current_vscroll_bar_width, current_hscroll_bar_height);
let available_outer = ui.available_rect_before_wrap();
let outer_size = available_outer.size().at_most(max_size);
let inner_size = {
let mut inner_size = outer_size - current_bar_use;
// Don't go so far that we shrink to zero.
// In particular, if we put a [`ScrollArea`] inside of a [`ScrollArea`], the inner
// one shouldn't collapse into nothingness.
// See https://github.com/emilk/egui/issues/1097
for d in 0..2 {
if has_bar[d] {
inner_size[d] = inner_size[d].max(min_scrolled_size[d]);
}
}
inner_size
};
let inner_rect = Rect::from_min_size(available_outer.min, inner_size);
let mut content_max_size = inner_size;
if true {
// Tell the inner Ui to *try* to fit the content without needing to scroll,
// i.e. better to wrap text and shrink images than showing a horizontal scrollbar!
} else {
// Tell the inner Ui to use as much space as possible, we can scroll to see it!
for d in 0..2 {
if has_bar[d] {
content_max_size[d] = f32::INFINITY;
}
}
}
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
let mut content_ui = ui.child_ui(content_max_rect, *ui.layout());
{
// Clip the content, but only when we really need to:
let clip_rect_margin = ui.visuals().clip_rect_margin;
let scroll_bar_inner_margin = ui.spacing().scroll_bar_inner_margin;
let mut content_clip_rect = ui.clip_rect();
for d in 0..2 {
if has_bar[d] {
if state.content_is_too_large[d] {
content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin;
content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin;
}
if state.show_scroll[d] {
// Make sure content doesn't cover scroll bars
let tiny_gap = 1.0;
content_clip_rect.max[1 - d] =
inner_rect.max[1 - d] + scroll_bar_inner_margin - tiny_gap;
}
} else {
// Nice handling of forced resizing beyond the possible:
content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d];
}
}
// Make sure we din't accidentally expand the clip rect
content_clip_rect = content_clip_rect.intersect(ui.clip_rect());
content_ui.set_clip_rect(content_clip_rect);
}
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
if (scrolling_enabled && drag_to_scroll)
&& (state.content_is_too_large[0] || state.content_is_too_large[1])
{
// Drag contents to scroll (for touch screens mostly).
// We must do this BEFORE adding content to the `ScrollArea`,
// or we will steal input from the widgets we contain.
let content_response = ui.interact(inner_rect, id.with("area"), Sense::drag());
if content_response.dragged() {
for d in 0..2 {
if has_bar[d] {
ui.input(|input| {
state.offset[d] -= input.pointer.delta()[d];
state.vel[d] = input.pointer.velocity()[d];
});
state.scroll_stuck_to_end[d] = false;
} else {
state.vel[d] = 0.0;
}
}
} else {
let stop_speed = 20.0; // Pixels per second.
let friction_coeff = 1000.0; // Pixels per second squared.
let dt = ui.input(|i| i.unstable_dt);
let friction = friction_coeff * dt;
if friction > state.vel.length() || state.vel.length() < stop_speed {
state.vel = Vec2::ZERO;
} else {
state.vel -= friction * state.vel.normalized();
// Offset has an inverted coordinate system compared to
// the velocity, so we subtract it instead of adding it
state.offset -= state.vel * dt;
ui.ctx().request_repaint();
}
}
}
Prepared {
id,
state,
has_bar,
auto_shrink,
current_bar_use,
always_show_scroll,
inner_rect,
content_ui,
viewport,
scrolling_enabled,
stick_to_end,
}
}
/// Show the [`ScrollArea`], and add the contents to the viewport.
///
/// If the inner area can be very long, consider using [`Self::show_rows`] instead.
pub fn show<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> ScrollAreaOutput<R> {
self.show_viewport_dyn(ui, Box::new(|ui, _viewport| add_contents(ui)))
}
/// Efficiently show only the visible part of a large number of rows.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// let text_style = egui::TextStyle::Body;
/// let row_height = ui.text_style_height(&text_style);
/// // let row_height = ui.spacing().interact_size.y; // if you are adding buttons instead of labels.
/// let total_rows = 10_000;
/// egui::ScrollArea::vertical().show_rows(ui, row_height, total_rows, |ui, row_range| {
/// for row in row_range {
/// let text = format!("Row {}/{}", row + 1, total_rows);
/// ui.label(text);
/// }
/// });
/// # });
/// ```
pub fn show_rows<R>(
self,
ui: &mut Ui,
row_height_sans_spacing: f32,
total_rows: usize,
add_contents: impl FnOnce(&mut Ui, std::ops::Range<usize>) -> R,
) -> ScrollAreaOutput<R> {
let spacing = ui.spacing().item_spacing;
let row_height_with_spacing = row_height_sans_spacing + spacing.y;
self.show_viewport(ui, |ui, viewport| {
ui.set_height((row_height_with_spacing * total_rows as f32 - spacing.y).at_least(0.0));
let mut min_row = (viewport.min.y / row_height_with_spacing).floor() as usize;
let mut max_row = (viewport.max.y / row_height_with_spacing).ceil() as usize + 1;
if max_row > total_rows {
let diff = max_row.saturating_sub(min_row);
max_row = total_rows;
min_row = total_rows.saturating_sub(diff);
}
let y_min = ui.max_rect().top() + min_row as f32 * row_height_with_spacing;
let y_max = ui.max_rect().top() + max_row as f32 * row_height_with_spacing;
let rect = Rect::from_x_y_ranges(ui.max_rect().x_range(), y_min..=y_max);
ui.allocate_ui_at_rect(rect, |viewport_ui| {
viewport_ui.skip_ahead_auto_ids(min_row); // Make sure we get consistent IDs.
add_contents(viewport_ui, min_row..max_row)
})
.inner
})
}
/// This can be used to only paint the visible part of the contents.
///
/// `add_contents` is given the viewport rectangle, which is the relative view of the content.
/// So if the passed rect has min = zero, then show the top left content (the user has not scrolled).
pub fn show_viewport<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui, Rect) -> R,
) -> ScrollAreaOutput<R> {
self.show_viewport_dyn(ui, Box::new(add_contents))
}
fn show_viewport_dyn<'c, R>(
self,
ui: &mut Ui,
add_contents: Box<dyn FnOnce(&mut Ui, Rect) -> R + 'c>,
) -> ScrollAreaOutput<R> {
let mut prepared = self.begin(ui);
let id = prepared.id;
let inner_rect = prepared.inner_rect;
let inner = add_contents(&mut prepared.content_ui, prepared.viewport);
let (content_size, state) = prepared.end(ui);
ScrollAreaOutput {
inner,
id,
state,
content_size,
inner_rect,
}
}
}
impl Prepared {
/// Returns content size and state
fn end(self, ui: &mut Ui) -> (Vec2, State) {
let Prepared {
id,
mut state,
inner_rect,
has_bar,
auto_shrink,
mut current_bar_use,
always_show_scroll,
content_ui,
viewport: _,
scrolling_enabled,
stick_to_end,
} = self;
let content_size = content_ui.min_size();
for d in 0..2 {
if has_bar[d] {
// We take the scroll target so only this ScrollArea will use it:
let scroll_target = content_ui
.ctx()
.frame_state_mut(|state| state.scroll_target[d].take());
if let Some((scroll, align)) = scroll_target {
let min = content_ui.min_rect().min[d];
let clip_rect = content_ui.clip_rect();
let visible_range = min..=min + clip_rect.size()[d];
let start = *scroll.start();
let end = *scroll.end();
let clip_start = clip_rect.min[d];
let clip_end = clip_rect.max[d];
let mut spacing = ui.spacing().item_spacing[d];
let delta = if let Some(align) = align {
let center_factor = align.to_factor();
let offset =
lerp(scroll, center_factor) - lerp(visible_range, center_factor);
// Depending on the alignment we need to add or subtract the spacing
spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0);
offset + spacing - state.offset[d]
} else if start < clip_start && end < clip_end {
-(clip_start - start + spacing).min(clip_end - end - spacing)
} else if end > clip_end && start > clip_start {
(end - clip_end + spacing).min(start - clip_start - spacing)
} else {
// Ui is already in view, no need to adjust scroll.
0.0
};
if delta != 0.0 {
state.offset[d] += delta;
ui.ctx().request_repaint();
}
}
}
}
let inner_rect = {
// At this point this is the available size for the inner rect.
let mut inner_size = inner_rect.size();
for d in 0..2 {
inner_size[d] = match (has_bar[d], auto_shrink[d]) {
(true, true) => inner_size[d].min(content_size[d]), // shrink scroll area if content is small
(true, false) => inner_size[d], // let scroll area be larger than content; fill with blank space
(false, true) => content_size[d], // Follow the content (expand/contract to fit it).
(false, false) => inner_size[d].max(content_size[d]), // Expand to fit content
};
}
Rect::from_min_size(inner_rect.min, inner_size)
};
let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use);
let content_is_too_large = [
content_size.x > inner_rect.width(),
content_size.y > inner_rect.height(),
];
let max_offset = content_size - inner_rect.size();
if scrolling_enabled && ui.rect_contains_pointer(outer_rect) {
for d in 0..2 {
if has_bar[d] {
let scroll_delta = ui.ctx().frame_state(|fs| fs.scroll_delta);
let scrolling_up = state.offset[d] > 0.0 && scroll_delta[d] > 0.0;
let scrolling_down = state.offset[d] < max_offset[d] && scroll_delta[d] < 0.0;
if scrolling_up || scrolling_down {
state.offset[d] -= scroll_delta[d];
// Clear scroll delta so no parent scroll will use it.
ui.ctx().frame_state_mut(|fs| fs.scroll_delta[d] = 0.0);
state.scroll_stuck_to_end[d] = false;
}
}
}
}
let show_scroll_this_frame = [
content_is_too_large[0] || always_show_scroll,
content_is_too_large[1] || always_show_scroll,
];
let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui);
// Avoid frame delay; start showing scroll bar right away:
if show_scroll_this_frame[0] && current_bar_use.y <= 0.0 {
current_bar_use.y = max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), true);
}
if show_scroll_this_frame[1] && current_bar_use.x <= 0.0 {
current_bar_use.x = max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), true);
}
for d in 0..2 {
let animation_t = current_bar_use[1 - d] / max_scroll_bar_width;
if animation_t == 0.0 {
continue;
}
// margin on either side of the scroll bar
let inner_margin = animation_t * ui.spacing().scroll_bar_inner_margin;
let outer_margin = animation_t * ui.spacing().scroll_bar_outer_margin;
let mut min_cross = inner_rect.max[1 - d] + inner_margin; // left of vertical scroll (d == 1)
let mut max_cross = outer_rect.max[1 - d] - outer_margin; // right of vertical scroll (d == 1)
let min_main = inner_rect.min[d]; // top of vertical scroll (d == 1)
let max_main = inner_rect.max[d]; // bottom of vertical scroll (d == 1)
if ui.clip_rect().max[1 - d] < max_cross + outer_margin {
// Move the scrollbar so it is visible. This is needed in some cases.
// For instance:
// * When we have a vertical-only scroll area in a top level panel,
// and that panel is not wide enough for the contents.
// * When one ScrollArea is nested inside another, and the outer
// is scrolled so that the scroll-bars of the inner ScrollArea (us)
// is outside the clip rectangle.
// Really this should use the tighter clip_rect that ignores clip_rect_margin, but we don't store that.
// clip_rect_margin is quite a hack. It would be nice to get rid of it.
let width = max_cross - min_cross;
max_cross = ui.clip_rect().max[1 - d] - outer_margin;
min_cross = max_cross - width;
}
let outer_scroll_rect = if d == 0 {
Rect::from_min_max(
pos2(inner_rect.left(), min_cross),
pos2(inner_rect.right(), max_cross),
)
} else {
Rect::from_min_max(
pos2(min_cross, inner_rect.top()),
pos2(max_cross, inner_rect.bottom()),
)
};
// maybe force increase in offset to keep scroll stuck to end position
if stick_to_end[d] && state.scroll_stuck_to_end[d] {
state.offset[d] = content_size[d] - inner_rect.size()[d];
}
let from_content =
|content| remap_clamp(content, 0.0..=content_size[d], min_main..=max_main);
let handle_rect = if d == 0 {
Rect::from_min_max(
pos2(from_content(state.offset.x), min_cross),
pos2(from_content(state.offset.x + inner_rect.width()), max_cross),
)
} else {
Rect::from_min_max(
pos2(min_cross, from_content(state.offset.y)),
pos2(
max_cross,
from_content(state.offset.y + inner_rect.height()),
),
)
};
let interact_id = id.with(d);
let sense = if self.scrolling_enabled {
Sense::click_and_drag()
} else {
Sense::hover()
};
let response = ui.interact(outer_scroll_rect, interact_id, sense);
if let Some(pointer_pos) = response.interact_pointer_pos() {
let scroll_start_offset_from_top_left = state.scroll_start_offset_from_top_left[d]
.get_or_insert_with(|| {
if handle_rect.contains(pointer_pos) {
pointer_pos[d] - handle_rect.min[d]
} else {
let handle_top_pos_at_bottom = max_main - handle_rect.size()[d];
// Calculate the new handle top position, centering the handle on the mouse.
let new_handle_top_pos = (pointer_pos[d] - handle_rect.size()[d] / 2.0)
.clamp(min_main, handle_top_pos_at_bottom);
pointer_pos[d] - new_handle_top_pos
}
});
let new_handle_top = pointer_pos[d] - *scroll_start_offset_from_top_left;
state.offset[d] = remap(new_handle_top, min_main..=max_main, 0.0..=content_size[d]);
// some manual action taken, scroll not stuck
state.scroll_stuck_to_end[d] = false;
} else {
state.scroll_start_offset_from_top_left[d] = None;
}
let unbounded_offset = state.offset[d];
state.offset[d] = state.offset[d].max(0.0);
state.offset[d] = state.offset[d].min(max_offset[d]);
if state.offset[d] != unbounded_offset {
state.vel[d] = 0.0;
}
if ui.is_rect_visible(outer_scroll_rect) {
// Avoid frame-delay by calculating a new handle rect:
let mut handle_rect = if d == 0 {
Rect::from_min_max(
pos2(from_content(state.offset.x), min_cross),
pos2(from_content(state.offset.x + inner_rect.width()), max_cross),
)
} else {
Rect::from_min_max(
pos2(min_cross, from_content(state.offset.y)),
pos2(
max_cross,
from_content(state.offset.y + inner_rect.height()),
),
)
};
let min_handle_size = ui.spacing().scroll_handle_min_length;
if handle_rect.size()[d] < min_handle_size {
handle_rect = Rect::from_center_size(
handle_rect.center(),
if d == 0 {
vec2(min_handle_size, handle_rect.size().y)
} else {
vec2(handle_rect.size().x, min_handle_size)
},
);
}
let visuals = if scrolling_enabled {
ui.style().interact(&response)
} else {
&ui.style().visuals.widgets.inactive
};
ui.painter().add(epaint::Shape::rect_filled(
outer_scroll_rect,
visuals.rounding,
ui.visuals().extreme_bg_color,
));
ui.painter().add(epaint::Shape::rect_filled(
handle_rect,
visuals.rounding,
visuals.bg_fill,
));
}
}
ui.advance_cursor_after_rect(outer_rect);
if show_scroll_this_frame != state.show_scroll {
ui.ctx().request_repaint();
}
let available_offset = content_size - inner_rect.size();
state.offset = state.offset.min(available_offset);
state.offset = state.offset.max(Vec2::ZERO);
// Is scroll handle at end of content, or is there no scrollbar
// yet (not enough content), but sticking is requested? If so, enter sticky mode.
// Only has an effect if stick_to_end is enabled but we save in
// state anyway so that entering sticky mode at an arbitrary time
// has appropriate effect.
state.scroll_stuck_to_end = [
(state.offset[0] == available_offset[0])
|| (self.stick_to_end[0] && available_offset[0] < 0.),
(state.offset[1] == available_offset[1])
|| (self.stick_to_end[1] && available_offset[1] < 0.),
];
state.show_scroll = show_scroll_this_frame;
state.content_is_too_large = content_is_too_large;
state.store(ui.ctx(), id);
(content_size, state)
}
}
/// Width of a vertical scrollbar, or height of a horizontal scroll bar
fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 {
ui.spacing().scroll_bar_inner_margin
+ ui.spacing().scroll_bar_width
+ ui.spacing().scroll_bar_outer_margin
}

View file

@ -1,6 +1,8 @@
// WARNING: the code in here is horrible. It is a behemoth that needs breaking up into simpler parts.
use crate::{paint::*, widgets::*, *};
use crate::collapsing_header::CollapsingState;
use crate::{widget_text::WidgetTextGalley, *};
use epaint::*;
use super::*;
@ -8,26 +10,27 @@ use super::*;
///
/// You can customize:
/// * title
/// * default, minimum, maximum and/or fixed size
/// * default, minimum, maximum and/or fixed size, collapsed/expanded
/// * if the window has a scroll area (off by default)
/// * if the window can be collapsed (minimized) to just the title bar (yes, by default)
/// * if there should be a close button (none by default)
///
/// ```
/// # let mut ctx = egui::CtxRef::default();
/// # ctx.begin_frame(Default::default());
/// # let ctx = &ctx;
/// # egui::__run_test_ctx(|ctx| {
/// egui::Window::new("My Window").show(ctx, |ui| {
/// ui.label("Hello World!");
/// });
/// # });
#[must_use = "You should call .show()"]
pub struct Window<'open> {
title_label: Label,
title: WidgetText,
open: Option<&'open mut bool>,
area: Area,
frame: Option<Frame>,
resize: Resize,
scroll: Option<ScrollArea>,
scroll: ScrollArea,
collapsible: bool,
default_open: bool,
with_title_bar: bool,
}
@ -35,12 +38,11 @@ impl<'open> Window<'open> {
/// The window title is used as a unique [`Id`] and must be unique, and should not change.
/// This is true even if you disable the title bar with `.title_bar(false)`.
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
pub fn new(title: impl Into<String>) -> Self {
let title = title.into();
let area = Area::new(&title);
let title_label = Label::new(title).text_style(TextStyle::Heading).wrap(false);
pub fn new(title: impl Into<WidgetText>) -> Self {
let title = title.into().fallback_text_style(TextStyle::Heading);
let area = Area::new(Id::new(title.text()));
Self {
title_label,
title,
open: None,
area,
frame: None,
@ -48,8 +50,9 @@ impl<'open> Window<'open> {
.with_stroke(false)
.min_size([96.0, 32.0])
.default_size([340.0, 420.0]), // Default inner size of a window
scroll: None,
scroll: ScrollArea::neither(),
collapsible: true,
default_open: true,
with_title_bar: true,
}
}
@ -76,15 +79,27 @@ impl<'open> Window<'open> {
self
}
/// Usage: `Window::new(...).mutate(|w| w.resize = w.resize.auto_expand_width(true))`
/// Not sure this is a good interface for this.
/// If `false` the window will be non-interactive.
pub fn interactable(mut self, interactable: bool) -> Self {
self.area = self.area.interactable(interactable);
self
}
/// If `false` the window will be immovable.
pub fn movable(mut self, movable: bool) -> Self {
self.area = self.area.movable(movable);
self
}
/// Usage: `Window::new(…).mutate(|w| w.resize = w.resize.auto_expand_width(true))`
// TODO(emilk): I'm not sure this is a good interface for this.
pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self {
mutate(&mut self);
self
}
/// Usage: `Window::new(...).resize(|r| r.auto_expand_width(true))`
/// Not sure this is a good interface for this.
/// Usage: `Window::new().resize(|r| r.auto_expand_width(true))`
// TODO(emilk): I'm not sure this is a good interface for this.
pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
self.resize = mutate(self.resize);
self
@ -101,6 +116,7 @@ impl<'open> Window<'open> {
self.resize = self.resize.min_width(min_width);
self
}
/// Set minimum height of the window.
pub fn min_height(mut self, min_height: f32) -> Self {
self.resize = self.resize.min_height(min_height);
@ -120,6 +136,52 @@ impl<'open> Window<'open> {
self
}
/// Sets the window position and prevents it from being dragged around.
pub fn fixed_pos(mut self, pos: impl Into<Pos2>) -> Self {
self.area = self.area.fixed_pos(pos);
self
}
/// Constrains this window to the screen bounds.
pub fn constrain(mut self, constrain: bool) -> Self {
self.area = self.area.constrain(constrain);
self
}
/// Where the "root" of the window is.
///
/// For instance, if you set this to [`Align2::RIGHT_TOP`]
/// then [`Self::fixed_pos`] will set the position of the right-top
/// corner of the window.
///
/// Default: [`Align2::LEFT_TOP`].
pub fn pivot(mut self, pivot: Align2) -> Self {
self.area = self.area.pivot(pivot);
self
}
/// Set anchor and distance.
///
/// An anchor of `Align2::RIGHT_TOP` means "put the right-top corner of the window
/// in the right-top corner of the screen".
///
/// The offset is added to the position, so e.g. an offset of `[-5.0, 5.0]`
/// would move the window left and down from the given anchor.
///
/// Anchoring also makes the window immovable.
///
/// It is an error to set both an anchor and a position.
pub fn anchor(mut self, align: Align2, offset: impl Into<Vec2>) -> Self {
self.area = self.area.anchor(align, offset);
self
}
/// Set initial collapsed state of the window
pub fn default_open(mut self, default_open: bool) -> Self {
self.default_open = default_open;
self
}
/// Set initial size of the window.
pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
self.resize = self.resize.default_size(default_size);
@ -131,29 +193,24 @@ impl<'open> Window<'open> {
self.resize = self.resize.default_width(default_width);
self
}
/// Set initial height of the window.
pub fn default_height(mut self, default_height: f32) -> Self {
self.resize = self.resize.default_height(default_height);
self
}
/// Set initial position and size of the window.
pub fn default_rect(self, rect: Rect) -> Self {
self.default_pos(rect.min).default_size(rect.size())
}
/// Sets the window position and prevents it from being dragged around.
pub fn fixed_pos(mut self, pos: impl Into<Pos2>) -> Self {
self.area = self.area.fixed_pos(pos);
self
}
/// Sets the window size and prevents it from being resized by dragging its edges.
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
self.resize = self.resize.fixed_size(size);
self
}
/// Set initial position and size of the window.
pub fn default_rect(self, rect: Rect) -> Self {
self.default_pos(rect.min).default_size(rect.size())
}
/// Sets the window pos and size and prevents it from being moved and resized by dragging its edges.
pub fn fixed_rect(self, rect: Rect) -> Self {
self.fixed_pos(rect.min).fixed_size(rect.size())
@ -184,52 +241,68 @@ impl<'open> Window<'open> {
/// Text will not wrap, but will instead make your window width expand.
pub fn auto_sized(mut self) -> Self {
self.resize = self.resize.auto_sized();
self.scroll = None;
self.scroll = ScrollArea::neither();
self
}
/// Enable/disable scrolling. `false` by default.
pub fn scroll(mut self, scroll: bool) -> Self {
if scroll {
if self.scroll.is_none() {
self.scroll = Some(ScrollArea::auto_sized());
/// Enable/disable horizontal/vertical scrolling. `false` by default.
pub fn scroll2(mut self, scroll: [bool; 2]) -> Self {
self.scroll = self.scroll.scroll2(scroll);
self
}
debug_assert!(
self.scroll.is_some(),
"Window::scroll called multiple times"
);
} else {
self.scroll = None;
/// Enable/disable horizontal scrolling. `false` by default.
pub fn hscroll(mut self, hscroll: bool) -> Self {
self.scroll = self.scroll.hscroll(hscroll);
self
}
/// Enable/disable vertical scrolling. `false` by default.
pub fn vscroll(mut self, vscroll: bool) -> Self {
self.scroll = self.scroll.vscroll(vscroll);
self
}
/// Constrain the area up to which the window can be dragged.
pub fn drag_bounds(mut self, bounds: Rect) -> Self {
self.area = self.area.drag_bounds(bounds);
self
}
}
impl<'open> Window<'open> {
/// Returns `None` if the windows is not open (if [`Window::open`] was called with `&mut false`.
pub fn show(self, ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui)) -> Option<Response> {
self.show_impl(ctx, Box::new(add_contents))
/// Returns `None` if the window is not open (if [`Window::open`] was called with `&mut false`).
/// Returns `Some(InnerResponse { inner: None })` if the window is collapsed.
#[inline]
pub fn show<R>(
self,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<Option<R>>> {
self.show_dyn(ctx, Box::new(add_contents))
}
fn show_impl<'c>(
fn show_dyn<'c, R>(
self,
ctx: &CtxRef,
add_contents: Box<dyn FnOnce(&mut Ui) + 'c>,
) -> Option<Response> {
ctx: &Context,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> Option<InnerResponse<Option<R>>> {
let Window {
title_label,
title,
open,
area,
frame,
resize,
scroll,
collapsible,
default_open,
with_title_bar,
} = self;
let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
let is_open = !matches!(open, Some(false)) || ctx.memory().everything_is_visible();
let is_explicitly_closed = matches!(open, Some(false));
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
area.show_open_close_animation(ctx, &frame, is_open);
if !is_open {
@ -239,16 +312,13 @@ impl<'open> Window<'open> {
let area_id = area.id;
let area_layer_id = area.layer();
let resize_id = area_id.with("resize");
let collapsing_id = area_id.with("collapsing");
let mut collapsing =
CollapsingState::load_with_default_open(ctx, area_id.with("collapsing"), default_open);
let is_maximized = !with_title_bar
|| collapsing_header::State::is_open(ctx, collapsing_id).unwrap_or_default();
let possible = PossibleInteractions {
movable: area.is_enabled() && area.is_movable(),
resizable: area.is_enabled() && resize.is_resizable() && is_maximized,
};
let is_collapsed = with_title_bar && !collapsing.is_open();
let possible = PossibleInteractions::new(&area, &resize, is_collapsed);
let area = area.movable(false); // We move it manually
let area = area.movable(false); // We move it manually, or the area will move the window when we want to resize it
let resize = resize.resizable(false); // We move it manually
let mut resize = resize.id(resize_id);
@ -258,7 +328,7 @@ impl<'open> Window<'open> {
// First interact (move etc) to avoid frame delay:
let last_frame_outer_rect = area.state().rect();
let interaction = if possible.movable || possible.resizable {
let interaction = if possible.movable || possible.resizable() {
window_interaction(
ctx,
possible,
@ -269,18 +339,21 @@ impl<'open> Window<'open> {
.and_then(|window_interaction| {
// Calculate roughly how much larger the window size is compared to the inner rect
let title_bar_height = if with_title_bar {
title_label.font_height(ctx.fonts(), &ctx.style()) + title_content_spacing
let style = ctx.style();
ctx.fonts(|f| title.font_height(f, &style)) + title_content_spacing
} else {
0.0
};
let margins = 2.0 * frame.margin + vec2(0.0, title_bar_height);
let margins = frame.outer_margin.sum()
+ frame.inner_margin.sum()
+ vec2(0.0, title_bar_height);
interact(
window_interaction,
ctx,
margins,
area_layer_id,
area.state_mut(),
&mut area,
resize_id,
)
})
@ -291,24 +364,17 @@ impl<'open> Window<'open> {
let mut area_content_ui = area.content_ui(ctx);
{
let content_inner = {
// BEGIN FRAME --------------------------------
let frame_stroke = frame.stroke;
let mut frame = frame.begin(&mut area_content_ui);
let default_expanded = true;
let mut collapsing = collapsing_header::State::from_memory_with_default_open(
ctx,
collapsing_id,
default_expanded,
);
let show_close_button = open.is_some();
let title_bar = if with_title_bar {
let title_bar = show_title_bar(
&mut frame.content_ui,
title_label,
title,
show_close_button,
collapsing_id,
&mut collapsing,
collapsible,
);
@ -318,27 +384,24 @@ impl<'open> Window<'open> {
None
};
let content_response = collapsing
.add_contents(&mut frame.content_ui, collapsing_id, |ui| {
let (content_inner, content_response) = collapsing
.show_body_unindented(&mut frame.content_ui, |ui| {
resize.show(ui, |ui| {
if title_bar.is_some() {
ui.advance_cursor(title_content_spacing);
ui.add_space(title_content_spacing);
}
if let Some(scroll) = scroll {
scroll.show(ui, add_contents);
if scroll.has_any_bar() {
scroll.show(ui, add_contents).inner
} else {
add_contents(ui);
add_contents(ui)
}
})
})
.map(|ir| ir.response);
.map_or((None, None), |ir| (Some(ir.inner), Some(ir.response)));
let outer_rect = frame.end(&mut area_content_ui);
if possible.resizable {
paint_resize_corner(&mut area_content_ui, outer_rect, frame_stroke);
}
let outer_rect = frame.end(&mut area_content_ui).rect;
paint_resize_corner(&mut area_content_ui, &possible, outer_rect, frame_stroke);
// END FRAME --------------------------------
@ -353,10 +416,7 @@ impl<'open> Window<'open> {
);
}
area_content_ui
.memory()
.collapsing_headers
.insert(collapsing_id, collapsing);
collapsing.store(ctx);
if let Some(interaction) = interaction {
paint_frame_interaction(
@ -366,7 +426,7 @@ impl<'open> Window<'open> {
ctx.style().visuals.widgets.active,
);
} else if let Some(hover_interaction) = hover_interaction {
if ctx.input().pointer.has_pointer() {
if ctx.input(|i| i.pointer.has_pointer()) {
paint_frame_interaction(
&mut area_content_ui,
outer_rect,
@ -375,19 +435,48 @@ impl<'open> Window<'open> {
);
}
}
content_inner
};
{
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);
Some(full_response)
let inner_response = InnerResponse {
inner: content_inner,
response: full_response,
};
Some(inner_response)
}
}
fn paint_resize_corner(ui: &mut Ui, outer_rect: Rect, stroke: Stroke) {
fn paint_resize_corner(
ui: &mut Ui,
possible: &PossibleInteractions,
outer_rect: Rect,
stroke: Stroke,
) {
let corner = if possible.resize_right && possible.resize_bottom {
Align2::RIGHT_BOTTOM
} else if possible.resize_left && possible.resize_bottom {
Align2::LEFT_BOTTOM
} else if possible.resize_left && possible.resize_top {
Align2::LEFT_TOP
} else if possible.resize_right && possible.resize_top {
Align2::RIGHT_TOP
} else {
return;
};
let corner_size = Vec2::splat(ui.visuals().resize_corner_size);
let handle_offset = -Vec2::splat(2.0);
let corner_rect =
Rect::from_min_size(outer_rect.max - corner_size + handle_offset, corner_size);
crate::resize::paint_resize_corner_with_style(ui, &corner_rect, stroke);
let corner_rect = corner.align_size_within_rect(corner_size, outer_rect);
let corner_rect = corner_rect.translate(-2.0 * corner.to_sign()); // move away from corner
crate::resize::paint_resize_corner_with_style(ui, &corner_rect, stroke, corner);
}
// ----------------------------------------------------------------------------
@ -395,7 +484,30 @@ fn paint_resize_corner(ui: &mut Ui, outer_rect: Rect, stroke: Stroke) {
#[derive(Clone, Copy, Debug)]
struct PossibleInteractions {
movable: bool,
resizable: bool,
// Which sides can we drag to resize?
resize_left: bool,
resize_right: bool,
resize_top: bool,
resize_bottom: bool,
}
impl PossibleInteractions {
fn new(area: &Area, resize: &Resize, is_collapsed: bool) -> Self {
let movable = area.is_enabled() && area.is_movable();
let resizable = area.is_enabled() && resize.is_resizable() && !is_collapsed;
let pivot = area.get_pivot();
Self {
movable,
resize_left: resizable && (movable || pivot.x() != Align::LEFT),
resize_right: resizable && (movable || pivot.x() != Align::RIGHT),
resize_top: resizable && (movable || pivot.y() != Align::TOP),
resize_bottom: resizable && (movable || pivot.y() != Align::BOTTOM),
}
}
pub fn resizable(&self) -> bool {
self.resize_left || self.resize_right || self.resize_top || self.resize_bottom
}
}
/// Either a move or resize
@ -412,13 +524,13 @@ pub(crate) struct WindowInteraction {
impl WindowInteraction {
pub fn set_cursor(&self, ctx: &Context) {
if (self.left && self.top) || (self.right && self.bottom) {
ctx.output().cursor_icon = CursorIcon::ResizeNwSe;
ctx.set_cursor_icon(CursorIcon::ResizeNwSe);
} else if (self.right && self.top) || (self.left && self.bottom) {
ctx.output().cursor_icon = CursorIcon::ResizeNeSw;
ctx.set_cursor_icon(CursorIcon::ResizeNeSw);
} else if self.left || self.right {
ctx.output().cursor_icon = CursorIcon::ResizeHorizontal;
ctx.set_cursor_icon(CursorIcon::ResizeHorizontal);
} else if self.bottom || self.top {
ctx.output().cursor_icon = CursorIcon::ResizeVertical;
ctx.set_cursor_icon(CursorIcon::ResizeVertical);
}
}
@ -432,29 +544,37 @@ fn interact(
ctx: &Context,
margins: Vec2,
area_layer_id: LayerId,
area_state: &mut area::State,
area: &mut area::Prepared,
resize_id: Id,
) -> Option<WindowInteraction> {
let new_rect = move_and_resize_window(ctx, &window_interaction)?;
let new_rect = ctx.round_rect_to_pixels(new_rect);
let new_rect = ctx.constrain_window_rect(new_rect);
// TODO: add this to a Window state instead as a command "move here next frame"
area_state.pos = new_rect.min;
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().set_left_top_pos(new_rect.left_top());
if window_interaction.is_resize() {
let mut resize_state = ctx.memory().resize.get(&resize_id).cloned().unwrap();
resize_state.requested_size = Some(new_rect.size() - margins);
ctx.memory().resize.insert(resize_id, resize_state);
if let Some(mut state) = resize::State::load(ctx, resize_id) {
state.requested_size = Some(new_rect.size() - margins);
state.store(ctx, resize_id);
}
}
ctx.memory().areas.move_to_top(area_layer_id);
ctx.memory_mut(|mem| mem.areas.move_to_top(area_layer_id));
Some(window_interaction)
}
fn move_and_resize_window(ctx: &Context, window_interaction: &WindowInteraction) -> Option<Rect> {
window_interaction.set_cursor(ctx);
let pointer_pos = ctx.input().pointer.interact_pos()?;
// Only move/resize windows with primary mouse button:
if !ctx.input(|i| i.pointer.primary_down()) {
return None;
}
let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?;
let mut rect = window_interaction.start_rect; // prevent drift
if window_interaction.is_resize() {
@ -476,8 +596,8 @@ fn move_and_resize_window(ctx: &Context, window_interaction: &WindowInteraction)
// but we want anything interactive in the window (e.g. slider) to steal
// the drag from us. It is therefor important not to move the window the first frame,
// but instead let other widgets to the steal. HACK.
if !ctx.input().pointer.any_pressed() {
let press_origin = ctx.input().pointer.press_origin()?;
if !ctx.input(|i| i.pointer.any_pressed()) {
let press_origin = ctx.input(|i| i.pointer.press_origin())?;
let delta = pointer_pos - press_origin;
rect = rect.translate(delta);
}
@ -495,29 +615,31 @@ fn window_interaction(
rect: Rect,
) -> Option<WindowInteraction> {
{
let drag_id = ctx.memory().interaction.drag_id;
let drag_id = ctx.memory(|mem| mem.interaction.drag_id);
if drag_id.is_some() && drag_id != Some(id) {
return None;
}
}
let mut window_interaction = { ctx.memory().window_interaction };
let mut window_interaction = ctx.memory(|mem| mem.window_interaction);
if window_interaction.is_none() {
if let Some(hover_window_interaction) = resize_hover(ctx, possible, area_layer_id, rect) {
hover_window_interaction.set_cursor(ctx);
if ctx.input().pointer.any_pressed() && ctx.input().pointer.any_down() {
ctx.memory().interaction.drag_id = Some(id);
ctx.memory().interaction.drag_is_window = true;
if ctx.input(|i| i.pointer.any_pressed() && i.pointer.primary_down()) {
ctx.memory_mut(|mem| {
mem.interaction.drag_id = Some(id);
mem.interaction.drag_is_window = true;
window_interaction = Some(hover_window_interaction);
ctx.memory().window_interaction = window_interaction;
mem.window_interaction = window_interaction;
});
}
}
}
if let Some(window_interaction) = window_interaction {
let is_active = ctx.memory().interaction.drag_id == Some(id);
let is_active = ctx.memory_mut(|mem| mem.interaction.drag_id == Some(id));
if is_active && window_interaction.area_layer_id == area_layer_id {
return Some(window_interaction);
@ -533,57 +655,64 @@ fn resize_hover(
area_layer_id: LayerId,
rect: Rect,
) -> Option<WindowInteraction> {
let pointer_pos = ctx.input().pointer.interact_pos()?;
let pointer = ctx.input(|i| i.pointer.interact_pos())?;
if ctx.input().pointer.any_down() && !ctx.input().pointer.any_pressed() {
if ctx.input(|i| i.pointer.any_down() && !i.pointer.any_pressed()) {
return None; // already dragging (something)
}
if let Some(top_layer_id) = ctx.layer_id_at(pointer_pos) {
if let Some(top_layer_id) = ctx.layer_id_at(pointer) {
if top_layer_id != area_layer_id && top_layer_id.order != Order::Background {
return None; // Another window is on top here
}
}
if ctx.memory().interaction.drag_interest {
if ctx.memory(|mem| mem.interaction.drag_interest) {
// Another widget will become active if we drag here
return None;
}
let side_grab_radius = ctx.style().interaction.resize_grab_radius_side;
let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner;
if !rect.expand(side_grab_radius).contains(pointer_pos) {
if !rect.expand(side_grab_radius).contains(pointer) {
return None;
}
let (mut left, mut right, mut top, mut bottom) = Default::default();
if possible.resizable {
right = (rect.right() - pointer_pos.x).abs() <= side_grab_radius;
bottom = (rect.bottom() - pointer_pos.y).abs() <= side_grab_radius;
let mut left = possible.resize_left && (rect.left() - pointer.x).abs() <= side_grab_radius;
let mut right = possible.resize_right && (rect.right() - pointer.x).abs() <= side_grab_radius;
let mut top = possible.resize_top && (rect.top() - pointer.y).abs() <= side_grab_radius;
let mut bottom =
possible.resize_bottom && (rect.bottom() - pointer.y).abs() <= side_grab_radius;
if rect.right_bottom().distance(pointer_pos) < corner_grab_radius {
if possible.resize_right
&& possible.resize_bottom
&& rect.right_bottom().distance(pointer) < corner_grab_radius
{
right = true;
bottom = true;
}
if possible.movable {
left = (rect.left() - pointer_pos.x).abs() <= side_grab_radius;
top = (rect.top() - pointer_pos.y).abs() <= side_grab_radius;
if rect.right_top().distance(pointer_pos) < corner_grab_radius {
if possible.resize_right
&& possible.resize_top
&& rect.right_top().distance(pointer) < corner_grab_radius
{
right = true;
top = true;
}
if rect.left_top().distance(pointer_pos) < corner_grab_radius {
if possible.resize_left
&& possible.resize_top
&& rect.left_top().distance(pointer) < corner_grab_radius
{
left = true;
top = true;
}
if rect.left_bottom().distance(pointer_pos) < corner_grab_radius {
if possible.resize_left
&& possible.resize_bottom
&& rect.left_bottom().distance(pointer) < corner_grab_radius
{
left = true;
bottom = true;
}
}
}
let any_resize = left || right || top || bottom;
if !any_resize && !possible.movable {
@ -611,44 +740,64 @@ fn paint_frame_interaction(
interaction: WindowInteraction,
visuals: style::WidgetVisuals,
) {
use paint::tessellator::path::add_circle_quadrant;
use epaint::tessellator::path::add_circle_quadrant;
let cr = ui.visuals().window_corner_radius;
let rounding = ui.visuals().window_rounding;
let Rect { min, max } = rect;
let mut points = Vec::new();
if interaction.right && !interaction.bottom && !interaction.top {
points.push(pos2(max.x, min.y + cr));
points.push(pos2(max.x, max.y - cr));
points.push(pos2(max.x, min.y + rounding.ne));
points.push(pos2(max.x, max.y - rounding.se));
}
if interaction.right && interaction.bottom {
points.push(pos2(max.x, min.y + cr));
points.push(pos2(max.x, max.y - cr));
add_circle_quadrant(&mut points, pos2(max.x - cr, max.y - cr), cr, 0.0);
points.push(pos2(max.x, min.y + rounding.ne));
points.push(pos2(max.x, max.y - rounding.se));
add_circle_quadrant(
&mut points,
pos2(max.x - rounding.se, max.y - rounding.se),
rounding.se,
0.0,
);
}
if interaction.bottom {
points.push(pos2(max.x - cr, max.y));
points.push(pos2(min.x + cr, max.y));
points.push(pos2(max.x - rounding.se, max.y));
points.push(pos2(min.x + rounding.sw, max.y));
}
if interaction.left && interaction.bottom {
add_circle_quadrant(&mut points, pos2(min.x + cr, max.y - cr), cr, 1.0);
add_circle_quadrant(
&mut points,
pos2(min.x + rounding.sw, max.y - rounding.sw),
rounding.sw,
1.0,
);
}
if interaction.left {
points.push(pos2(min.x, max.y - cr));
points.push(pos2(min.x, min.y + cr));
points.push(pos2(min.x, max.y - rounding.sw));
points.push(pos2(min.x, min.y + rounding.nw));
}
if interaction.left && interaction.top {
add_circle_quadrant(&mut points, pos2(min.x + cr, min.y + cr), cr, 2.0);
add_circle_quadrant(
&mut points,
pos2(min.x + rounding.nw, min.y + rounding.nw),
rounding.nw,
2.0,
);
}
if interaction.top {
points.push(pos2(min.x + cr, min.y));
points.push(pos2(max.x - cr, min.y));
points.push(pos2(min.x + rounding.nw, min.y));
points.push(pos2(max.x - rounding.ne, min.y));
}
if interaction.right && interaction.top {
add_circle_quadrant(&mut points, pos2(max.x - cr, min.y + cr), cr, 3.0);
points.push(pos2(max.x, min.y + cr));
points.push(pos2(max.x, max.y - cr));
add_circle_quadrant(
&mut points,
pos2(max.x - rounding.ne, min.y + rounding.ne),
rounding.ne,
3.0,
);
points.push(pos2(max.x, min.y + rounding.ne));
points.push(pos2(max.x, max.y - rounding.se));
}
ui.painter().add(Shape::line(points, visuals.bg_stroke));
}
@ -656,24 +805,32 @@ fn paint_frame_interaction(
// ----------------------------------------------------------------------------
struct TitleBar {
/// A title Id used for dragging windows
id: Id,
title_label: Label,
title_galley: Galley,
/// Prepared text in the title
title_galley: WidgetTextGalley,
/// Size of the title bar in a collapsed state (if window is collapsible),
/// which includes all necessary space for showing the expand button, the
/// title and the close button.
min_rect: Rect,
/// Size of the title bar in an expanded state. This size become known only
/// after expanding window and painting its content
rect: Rect,
}
fn show_title_bar(
ui: &mut Ui,
title_label: Label,
title: WidgetText,
show_close_button: bool,
collapsing_id: Id,
collapsing: &mut collapsing_header::State,
collapsing: &mut CollapsingState,
collapsible: bool,
) -> TitleBar {
let inner_response = ui.horizontal(|ui| {
let height = title_label
.font_height(ui.fonts(), ui.style())
let height = ui
.fonts(|fonts| title.font_height(fonts, ui.style()))
.max(ui.spacing().interact_size.y);
ui.set_min_height(height);
@ -683,31 +840,23 @@ fn show_title_bar(
let pad = (height - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical)
if collapsible {
ui.advance_cursor(pad);
let (_id, rect) = ui.allocate_space(button_size);
let collapse_button_response = ui.interact(rect, collapsing_id, Sense::click());
if collapse_button_response.clicked() {
collapsing.toggle(ui);
}
let openness = collapsing.openness(ui.ctx(), collapsing_id);
collapsing_header::paint_icon(ui, openness, &collapse_button_response);
ui.add_space(pad);
collapsing.show_default_button_with_size(ui, button_size);
}
let title_galley = title_label.layout(ui);
let title_galley = title.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Heading);
let minimum_width = if collapsible || show_close_button {
// If at least one button is shown we make room for both buttons (since title is centered):
2.0 * (pad + button_size.x + item_spacing.x) + title_galley.size.x
2.0 * (pad + button_size.x + item_spacing.x) + title_galley.size().x
} else {
pad + title_galley.size.x + pad
pad + title_galley.size().x + pad
};
let min_rect = Rect::from_min_size(ui.min_rect().min, vec2(minimum_width, height));
let id = ui.advance_cursor_after_rect(min_rect);
TitleBar {
id,
title_label,
title_galley,
min_rect,
rect: Rect::NAN, // Will be filled in later
@ -721,13 +870,27 @@ fn show_title_bar(
}
impl TitleBar {
/// Finishes painting of the title bar when the window content size already known.
///
/// # Parameters
///
/// - `ui`:
/// - `outer_rect`:
/// - `content_response`: if `None`, window is collapsed at this frame, otherwise contains
/// a result of rendering the window content
/// - `open`: if `None`, no "Close" button will be rendered, otherwise renders and processes
/// the "Close" button and writes a `false` if window was closed
/// - `collapsing`: holds the current expanding state. Can be changed by double click on the
/// title if `collapsible` is `true`
/// - `collapsible`: if `true`, double click on the title bar will be handled for a change
/// of `collapsing` state
fn ui(
mut self,
ui: &mut Ui,
outer_rect: Rect,
content_response: &Option<Response>,
open: Option<&mut bool>,
collapsing: &mut collapsing_header::State,
collapsing: &mut CollapsingState,
collapsible: bool,
) {
if let Some(content_response) = &content_response {
@ -742,32 +905,30 @@ impl TitleBar {
}
}
// Always have inactive style for the window.
// It is VERY annoying to e.g. change it when moving the window.
let style = ui.visuals().widgets.inactive;
self.title_label = self.title_label.text_color(style.fg_stroke.color);
let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range());
let text_pos = math::align::center_size_in_rect(self.title_galley.size, full_top_rect);
let text_pos = text_pos.left_top() - 1.5 * Vec2::Y; // HACK: center on x-height of text (looks better)
self.title_label
.paint_galley(ui, text_pos, self.title_galley);
let text_pos =
emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top();
let text_pos = text_pos - self.title_galley.galley().rect.min.to_vec2();
let text_pos = text_pos - 1.5 * Vec2::Y; // HACK: center on x-height of text (looks better)
self.title_galley.paint_with_fallback_color(
ui.painter(),
text_pos,
ui.visuals().text_color(),
);
if let Some(content_response) = &content_response {
// paint separator between title and content:
let left = outer_rect.left();
let right = outer_rect.right();
let y = content_response.rect.top() + ui.spacing().item_spacing.y * 0.5;
// let y = lerp(self.rect.bottom()..=content_response.rect.top(), 0.5);
ui.painter().line_segment(
[pos2(left, y), pos2(right, y)],
ui.visuals().widgets.noninteractive.bg_stroke,
);
let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
ui.painter().hline(outer_rect.x_range(), y, stroke);
}
// Don't cover the close- and collapse buttons:
let double_click_rect = self.rect.shrink2(vec2(32.0, 0.0));
if ui
.interact(self.rect, self.id, Sense::click())
.interact(double_click_rect, self.id, Sense::click())
.double_clicked()
&& collapsible
{
@ -775,6 +936,11 @@ impl TitleBar {
}
}
/// Paints the "Close" button at the right side of the title bar
/// and processes clicks on it.
///
/// The button is square and its size is determined by the
/// [`crate::style::Spacing::icon_width`] setting.
fn close_button_ui(&self, ui: &mut Ui) -> Response {
let button_size = Vec2::splat(ui.spacing().icon_width);
let pad = (self.rect.height() - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical)
@ -790,6 +956,16 @@ impl TitleBar {
}
}
/// Paints the "Close" button of the window and processes clicks on it.
///
/// The close button is just an `X` symbol painted by a current stroke
/// for foreground elements (such as a label text).
///
/// # Parameters
/// - `ui`:
/// - `rect`: The rectangular area to fit the button in
///
/// Returns the result of a click on a button if it was pressed
fn close_button(ui: &mut Ui, rect: Rect) -> Response {
let close_id = ui.auto_id_with("window_close_button");
let response = ui.interact(rect, close_id, Sense::click());
@ -798,9 +974,9 @@ fn close_button(ui: &mut Ui, rect: Rect) -> Response {
let visuals = ui.style().interact(&response);
let rect = rect.shrink(2.0).expand(visuals.expansion);
let stroke = visuals.fg_stroke;
ui.painter()
ui.painter() // paints \
.line_segment([rect.left_top(), rect.right_bottom()], stroke);
ui.painter()
ui.painter() // paints /
.line_segment([rect.right_top(), rect.left_bottom()], stroke);
response
}

1792
crates/egui/src/context.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,910 @@
//! The input needed by egui.
use crate::emath::*;
/// What the integrations provides to egui at the start of each frame.
///
/// Set the values that make sense, leave the rest at their `Default::default()`.
///
/// You can check if `egui` is using the inputs using
/// [`crate::Context::wants_pointer_input`] and [`crate::Context::wants_keyboard_input`].
///
/// All coordinates are in points (logical pixels) with origin (0, 0) in the top left corner.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RawInput {
/// Position and size of the area that egui should use, in points.
/// Usually you would set this to
///
/// `Some(Rect::from_pos_size(Default::default(), screen_size_in_points))`.
///
/// but you could also constrain egui to some smaller portion of your window if you like.
///
/// `None` will be treated as "same as last frame", with the default being a very big area.
pub screen_rect: Option<Rect>,
/// Also known as device pixel ratio, > 1 for high resolution screens.
/// If text looks blurry you probably forgot to set this.
/// Set this the first frame, whenever it changes, or just on every frame.
pub pixels_per_point: Option<f32>,
/// Maximum size of one side of the font texture.
///
/// Ask your graphics drivers about this. This corresponds to `GL_MAX_TEXTURE_SIZE`.
///
/// The default is a very small (but very portable) 2048.
pub max_texture_side: Option<usize>,
/// Monotonically increasing time, in seconds. Relative to whatever. Used for animations.
/// If `None` is provided, egui will assume a time delta of `predicted_dt` (default 1/60 seconds).
pub time: Option<f64>,
/// Should be set to the expected time between frames when painting at vsync speeds.
/// The default for this is 1/60.
/// Can safely be left at its default value.
pub predicted_dt: f32,
/// Which modifier keys are down at the start of the frame?
pub modifiers: Modifiers,
/// In-order events received this frame.
///
/// There is currently no way to know if egui handles a particular event,
/// but you can check if egui is using the keyboard with [`crate::Context::wants_keyboard_input`]
/// and/or the pointer (mouse/touch) with [`crate::Context::is_using_pointer`].
pub events: Vec<Event>,
/// Dragged files hovering over egui.
pub hovered_files: Vec<HoveredFile>,
/// Dragged files dropped into egui.
///
/// Note: when using `eframe` on Windows you need to enable
/// drag-and-drop support using `eframe::NativeOptions`.
pub dropped_files: Vec<DroppedFile>,
/// The window has the keyboard focus (i.e. is receiving key presses).
pub has_focus: bool,
}
impl Default for RawInput {
fn default() -> Self {
Self {
screen_rect: None,
pixels_per_point: None,
max_texture_side: None,
time: None,
predicted_dt: 1.0 / 60.0,
modifiers: Modifiers::default(),
events: vec![],
hovered_files: Default::default(),
dropped_files: Default::default(),
has_focus: true, // integrations opt into global focus tracking
}
}
}
impl RawInput {
/// Helper: move volatile (deltas and events), clone the rest.
///
/// * [`Self::hovered_files`] is cloned.
/// * [`Self::dropped_files`] is moved.
pub fn take(&mut self) -> RawInput {
RawInput {
screen_rect: self.screen_rect.take(),
pixels_per_point: self.pixels_per_point.take(),
max_texture_side: self.max_texture_side.take(),
time: self.time.take(),
predicted_dt: self.predicted_dt,
modifiers: self.modifiers,
events: std::mem::take(&mut self.events),
hovered_files: self.hovered_files.clone(),
dropped_files: std::mem::take(&mut self.dropped_files),
has_focus: self.has_focus,
}
}
/// Add on new input.
pub fn append(&mut self, newer: Self) {
let Self {
screen_rect,
pixels_per_point,
max_texture_side,
time,
predicted_dt,
modifiers,
mut events,
mut hovered_files,
mut dropped_files,
has_focus,
} = newer;
self.screen_rect = screen_rect.or(self.screen_rect);
self.pixels_per_point = pixels_per_point.or(self.pixels_per_point);
self.max_texture_side = max_texture_side.or(self.max_texture_side);
self.time = time; // use latest time
self.predicted_dt = predicted_dt; // use latest dt
self.modifiers = modifiers; // use latest
self.events.append(&mut events);
self.hovered_files.append(&mut hovered_files);
self.dropped_files.append(&mut dropped_files);
self.has_focus = has_focus;
}
}
/// A file about to be dropped into egui.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct HoveredFile {
/// Set by the `egui-winit` backend.
pub path: Option<std::path::PathBuf>,
/// With the `eframe` web backend, this is set to the mime-type of the file (if available).
pub mime: String,
}
/// A file dropped into egui.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct DroppedFile {
/// Set by the `egui-winit` backend.
pub path: Option<std::path::PathBuf>,
/// Name of the file. Set by the `eframe` web backend.
pub name: String,
/// Set by the `eframe` web backend.
pub last_modified: Option<std::time::SystemTime>,
/// Set by the `eframe` web backend.
pub bytes: Option<std::sync::Arc<[u8]>>,
}
/// An input event generated by the integration.
///
/// This only covers events that egui cares about.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Event {
/// The integration detected a "copy" event (e.g. Cmd+C).
Copy,
/// The integration detected a "cut" event (e.g. Cmd+X).
Cut,
/// The integration detected a "paste" event (e.g. Cmd+V).
Paste(String),
/// Text input, e.g. via keyboard.
///
/// When the user presses enter/return, do not send a [`Text`](Event::Text) (just [`Key::Enter`]).
Text(String),
/// A key was pressed or released.
Key {
key: Key,
/// Was it pressed or released?
pressed: bool,
/// If this is a `pressed` event, is it a key-repeat?
///
/// On many platforms, holding down a key produces many repeated "pressed" events for it, so called key-repeats.
/// Sometimes you will want to ignore such events, and this lets you do that.
///
/// egui will automatically detect such repeat events and mark them as such here.
/// Therefore, if you are writing an egui integration, you do not need to set this (just set it to `false`).
repeat: bool,
/// The state of the modifier keys at the time of the event.
modifiers: Modifiers,
},
/// The mouse or touch moved to a new place.
PointerMoved(Pos2),
/// A mouse button was pressed or released (or a touch started or stopped).
PointerButton {
/// Where is the pointer?
pos: Pos2,
/// What mouse button? For touches, use [`PointerButton::Primary`].
button: PointerButton,
/// Was it the button/touch pressed this frame, or released?
pressed: bool,
/// The state of the modifier keys at the time of the event.
modifiers: Modifiers,
},
/// The mouse left the screen, or the last/primary touch input disappeared.
///
/// This means there is no longer a cursor on the screen for hovering etc.
///
/// On touch-up first send `PointerButton{pressed: false, …}` followed by `PointerLeft`.
PointerGone,
/// How many points (logical pixels) the user scrolled.
///
/// The direction of the vector indicates how to move the _content_ that is being viewed.
/// So if you get positive values, the content being viewed should move to the right and down,
/// revealing new things to the left and up.
///
/// A positive X-value indicates the content is being moved right,
/// as when swiping right on a touch-screen or track-pad with natural scrolling.
///
/// A positive Y-value indicates the content is being moved down,
/// as when swiping down on a touch-screen or track-pad with natural scrolling.
///
/// Shift-scroll should result in horizontal scrolling (it is up to the integrations to do this).
Scroll(Vec2),
/// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture).
/// * `zoom = 1`: no change.
/// * `zoom < 1`: pinch together
/// * `zoom > 1`: pinch spread
Zoom(f32),
/// IME composition start.
CompositionStart,
/// A new IME candidate is being suggested.
CompositionUpdate(String),
/// IME composition ended with this final result.
CompositionEnd(String),
/// On touch screens, report this *in addition to*
/// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`]
Touch {
/// Hashed device identifier (if available; may be zero).
/// Can be used to separate touches from different devices.
device_id: TouchDeviceId,
/// Unique identifier of a finger/pen. Value is stable from touch down
/// to lift-up
id: TouchId,
/// One of: start move end cancel.
phase: TouchPhase,
/// Position of the touch (or where the touch was last detected)
pos: Pos2,
/// Describes how hard the touch device was pressed. May always be `0` if the platform does
/// not support pressure sensitivity.
/// The value is in the range from 0.0 (no pressure) to 1.0 (maximum pressure).
force: f32,
},
/// An assistive technology (e.g. screen reader) requested an action.
#[cfg(feature = "accesskit")]
AccessKitActionRequest(accesskit::ActionRequest),
}
/// Mouse button (or similar for touch input)
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum PointerButton {
/// The primary mouse button is usually the left one.
Primary = 0,
/// The secondary mouse button is usually the right one,
/// and most often used for context menus or other optional things.
Secondary = 1,
/// The tertiary mouse button is usually the middle mouse button (e.g. clicking the scroll wheel).
Middle = 2,
/// The first extra mouse button on some mice. In web typically corresponds to the Browser back button.
Extra1 = 3,
/// The second extra mouse button on some mice. In web typically corresponds to the Browser forward button.
Extra2 = 4,
}
/// Number of pointer buttons supported by egui, i.e. the number of possible states of [`PointerButton`].
pub const NUM_POINTER_BUTTONS: usize = 5;
/// State of the modifier keys. These must be fed to egui.
///
/// The best way to compare [`Modifiers`] is by using [`Modifiers::matches`].
///
/// NOTE: For cross-platform uses, ALT+SHIFT is a bad combination of modifiers
/// as on mac that is how you type special characters,
/// so those key presses are usually not reported to egui.
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Modifiers {
/// Either of the alt keys are down (option ⌥ on Mac).
pub alt: bool,
/// Either of the control keys are down.
/// When checking for keyboard shortcuts, consider using [`Self::command`] instead.
pub ctrl: bool,
/// Either of the shift keys are down.
pub shift: bool,
/// The Mac ⌘ Command key. Should always be set to `false` on other platforms.
pub mac_cmd: bool,
/// On Windows and Linux, set this to the same value as `ctrl`.
/// On Mac, this should be set whenever one of the ⌘ Command keys are down (same as `mac_cmd`).
/// This is so that egui can, for instance, select all text by checking for `command + A`
/// and it will work on both Mac and Windows.
pub command: bool,
}
impl Modifiers {
pub const NONE: Self = Self {
alt: false,
ctrl: false,
shift: false,
mac_cmd: false,
command: false,
};
pub const ALT: Self = Self {
alt: true,
ctrl: false,
shift: false,
mac_cmd: false,
command: false,
};
pub const CTRL: Self = Self {
alt: false,
ctrl: true,
shift: false,
mac_cmd: false,
command: false,
};
pub const SHIFT: Self = Self {
alt: false,
ctrl: false,
shift: true,
mac_cmd: false,
command: false,
};
#[deprecated = "Use `Modifiers::ALT | Modifiers::SHIFT` instead"]
pub const ALT_SHIFT: Self = Self {
alt: true,
ctrl: false,
shift: true,
mac_cmd: false,
command: false,
};
/// The Mac ⌘ Command key
pub const MAC_CMD: Self = Self {
alt: false,
ctrl: false,
shift: false,
mac_cmd: true,
command: false,
};
/// On Mac: ⌘ Command key, elsewhere: Ctrl key
pub const COMMAND: Self = Self {
alt: false,
ctrl: false,
shift: false,
mac_cmd: false,
command: true,
};
/// ```
/// # use egui::Modifiers;
/// assert_eq!(
/// Modifiers::CTRL | Modifiers::ALT,
/// Modifiers { ctrl: true, alt: true, ..Default::default() }
/// );
/// assert_eq!(
/// Modifiers::ALT.plus(Modifiers::CTRL),
/// Modifiers::CTRL.plus(Modifiers::ALT),
/// );
/// assert_eq!(
/// Modifiers::CTRL | Modifiers::ALT,
/// Modifiers::CTRL.plus(Modifiers::ALT),
/// );
/// ```
#[inline]
pub const fn plus(self, rhs: Self) -> Self {
Self {
alt: self.alt | rhs.alt,
ctrl: self.ctrl | rhs.ctrl,
shift: self.shift | rhs.shift,
mac_cmd: self.mac_cmd | rhs.mac_cmd,
command: self.command | rhs.command,
}
}
#[inline]
pub fn is_none(&self) -> bool {
self == &Self::default()
}
#[inline]
pub fn any(&self) -> bool {
!self.is_none()
}
/// Is shift the only pressed button?
#[inline]
pub fn shift_only(&self) -> bool {
self.shift && !(self.alt || self.command)
}
/// true if only [`Self::ctrl`] or only [`Self::mac_cmd`] is pressed.
#[inline]
pub fn command_only(&self) -> bool {
!self.alt && !self.shift && self.command
}
/// Check for equality but with proper handling of [`Self::command`].
///
/// ```
/// # use egui::Modifiers;
/// assert!(Modifiers::CTRL.matches(Modifiers::CTRL));
/// assert!(!Modifiers::CTRL.matches(Modifiers::CTRL | Modifiers::SHIFT));
/// assert!(!(Modifiers::CTRL | Modifiers::SHIFT).matches(Modifiers::CTRL));
/// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches(Modifiers::CTRL));
/// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches(Modifiers::COMMAND));
/// assert!((Modifiers::MAC_CMD | Modifiers::COMMAND).matches(Modifiers::COMMAND));
/// assert!(!Modifiers::COMMAND.matches(Modifiers::MAC_CMD));
/// ```
pub fn matches(&self, pattern: Modifiers) -> bool {
// alt and shift must always match the pattern:
if pattern.alt != self.alt || pattern.shift != self.shift {
return false;
}
if pattern.mac_cmd {
// Mac-specific match:
if !self.mac_cmd {
return false;
}
if pattern.ctrl != self.ctrl {
return false;
}
return true;
}
if !pattern.ctrl && !pattern.command {
// the pattern explicitly doesn't want any ctrl/command:
return !self.ctrl && !self.command;
}
// if the pattern is looking for command, then `ctrl` may or may not be set depending on platform.
// if the pattern is looking for `ctrl`, then `command` may or may not be set depending on platform.
if pattern.ctrl && !self.ctrl {
return false;
}
if pattern.command && !self.command {
return false;
}
true
}
}
impl std::ops::BitOr for Modifiers {
type Output = Self;
#[inline]
fn bitor(self, rhs: Self) -> Self {
self.plus(rhs)
}
}
// ----------------------------------------------------------------------------
/// Names of different modifier keys.
///
/// Used to name modifiers.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ModifierNames<'a> {
pub is_short: bool,
pub alt: &'a str,
pub ctrl: &'a str,
pub shift: &'a str,
pub mac_cmd: &'a str,
/// What goes between the names
pub concat: &'a str,
}
impl ModifierNames<'static> {
/// ⌥ ^ ⇧ ⌘ - NOTE: not supported by the default egui font.
pub const SYMBOLS: Self = Self {
is_short: true,
alt: "",
ctrl: "^",
shift: "",
mac_cmd: "",
concat: "",
};
/// Alt, Ctrl, Shift, Cmd
pub const NAMES: Self = Self {
is_short: false,
alt: "Alt",
ctrl: "Ctrl",
shift: "Shift",
mac_cmd: "Cmd",
concat: "+",
};
}
impl<'a> ModifierNames<'a> {
pub fn format(&self, modifiers: &Modifiers, is_mac: bool) -> String {
let mut s = String::new();
let mut append_if = |modifier_is_active, modifier_name| {
if modifier_is_active {
if !s.is_empty() {
s += self.concat;
}
s += modifier_name;
}
};
if is_mac {
append_if(modifiers.ctrl, self.ctrl);
append_if(modifiers.shift, self.shift);
append_if(modifiers.alt, self.alt);
append_if(modifiers.mac_cmd || modifiers.command, self.mac_cmd);
} else {
append_if(modifiers.ctrl || modifiers.command, self.ctrl);
append_if(modifiers.alt, self.alt);
append_if(modifiers.shift, self.shift);
}
s
}
}
// ----------------------------------------------------------------------------
/// Keyboard keys.
///
/// Includes all keys egui is interested in (such as `Home` and `End`)
/// plus a few that are useful for detecting keyboard shortcuts.
///
/// Many keys are omitted because they are not always physical keys (depending on keyboard language), e.g. `;` and `§`,
/// and are therefore unsuitable as keyboard shortcuts if you want your app to be portable.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Key {
ArrowDown,
ArrowLeft,
ArrowRight,
ArrowUp,
Escape,
Tab,
Backspace,
Enter,
Space,
Insert,
Delete,
Home,
End,
PageUp,
PageDown,
/// The virtual keycode for the Minus key.
Minus,
/// The virtual keycode for the Plus/Equals key.
PlusEquals,
/// Either from the main row or from the numpad.
Num0,
/// Either from the main row or from the numpad.
Num1,
/// Either from the main row or from the numpad.
Num2,
/// Either from the main row or from the numpad.
Num3,
/// Either from the main row or from the numpad.
Num4,
/// Either from the main row or from the numpad.
Num5,
/// Either from the main row or from the numpad.
Num6,
/// Either from the main row or from the numpad.
Num7,
/// Either from the main row or from the numpad.
Num8,
/// Either from the main row or from the numpad.
Num9,
A, // Used for cmd+A (select All)
B,
C, // |CMD COPY|
D, // |CMD BOOKMARK|
E, // |CMD SEARCH|
F, // |CMD FIND firefox & chrome|
G, // |CMD FIND chrome|
H, // |CMD History|
I, // italics
J, // |CMD SEARCH firefox/DOWNLOAD chrome|
K, // Used for ctrl+K (delete text after cursor)
L,
M,
N,
O, // |CMD OPEN|
P, // |CMD PRINT|
Q,
R, // |CMD REFRESH|
S, // |CMD SAVE|
T, // |CMD TAB|
U, // Used for ctrl+U (delete text before cursor)
V, // |CMD PASTE|
W, // Used for ctrl+W (delete previous word)
X, // |CMD CUT|
Y,
Z, // |CMD UNDO|
// The function keys:
F1,
F2,
F3,
F4,
F5, // |CMD REFRESH|
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
}
impl Key {
/// Emoji or name representing the key
pub fn symbol_or_name(self) -> &'static str {
// TODO(emilk): add support for more unicode symbols (see for instance https://wincent.com/wiki/Unicode_representations_of_modifier_keys).
// Before we do we must first make sure they are supported in `Fonts` though,
// so perhaps this functions needs to take a `supports_character: impl Fn(char) -> bool` or something.
match self {
Key::ArrowDown => "",
Key::ArrowLeft => "",
Key::ArrowRight => "",
Key::ArrowUp => "",
Key::Minus => "-",
Key::PlusEquals => "+",
_ => self.name(),
}
}
/// Human-readable English name.
pub fn name(self) -> &'static str {
match self {
Key::ArrowDown => "Down",
Key::ArrowLeft => "Left",
Key::ArrowRight => "Right",
Key::ArrowUp => "Up",
Key::Escape => "Escape",
Key::Tab => "Tab",
Key::Backspace => "Backspace",
Key::Enter => "Enter",
Key::Space => "Space",
Key::Insert => "Insert",
Key::Delete => "Delete",
Key::Home => "Home",
Key::End => "End",
Key::PageUp => "PageUp",
Key::PageDown => "PageDown",
Key::Minus => "Minus",
Key::PlusEquals => "Plus",
Key::Num0 => "0",
Key::Num1 => "1",
Key::Num2 => "2",
Key::Num3 => "3",
Key::Num4 => "4",
Key::Num5 => "5",
Key::Num6 => "6",
Key::Num7 => "7",
Key::Num8 => "8",
Key::Num9 => "9",
Key::A => "A",
Key::B => "B",
Key::C => "C",
Key::D => "D",
Key::E => "E",
Key::F => "F",
Key::G => "G",
Key::H => "H",
Key::I => "I",
Key::J => "J",
Key::K => "K",
Key::L => "L",
Key::M => "M",
Key::N => "N",
Key::O => "O",
Key::P => "P",
Key::Q => "Q",
Key::R => "R",
Key::S => "S",
Key::T => "T",
Key::U => "U",
Key::V => "V",
Key::W => "W",
Key::X => "X",
Key::Y => "Y",
Key::Z => "Z",
Key::F1 => "F1",
Key::F2 => "F2",
Key::F3 => "F3",
Key::F4 => "F4",
Key::F5 => "F5",
Key::F6 => "F6",
Key::F7 => "F7",
Key::F8 => "F8",
Key::F9 => "F9",
Key::F10 => "F10",
Key::F11 => "F11",
Key::F12 => "F12",
Key::F13 => "F13",
Key::F14 => "F14",
Key::F15 => "F15",
Key::F16 => "F16",
Key::F17 => "F17",
Key::F18 => "F18",
Key::F19 => "F19",
Key::F20 => "F20",
}
}
}
// ----------------------------------------------------------------------------
/// A keyboard shortcut, e.g. `Ctrl+Alt+W`.
///
/// Can be used with [`crate::InputState::consume_shortcut`]
/// and [`crate::Context::format_shortcut`].
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct KeyboardShortcut {
pub modifiers: Modifiers,
pub key: Key,
}
impl KeyboardShortcut {
pub const fn new(modifiers: Modifiers, key: Key) -> Self {
Self { modifiers, key }
}
pub fn format(&self, names: &ModifierNames<'_>, is_mac: bool) -> String {
let mut s = names.format(&self.modifiers, is_mac);
if !s.is_empty() {
s += names.concat;
}
if names.is_short {
s += self.key.symbol_or_name();
} else {
s += self.key.name();
}
s
}
}
#[test]
fn format_kb_shortcut() {
let cmd_shift_f = KeyboardShortcut::new(Modifiers::COMMAND | Modifiers::SHIFT, Key::F);
assert_eq!(
cmd_shift_f.format(&ModifierNames::NAMES, false),
"Ctrl+Shift+F"
);
assert_eq!(
cmd_shift_f.format(&ModifierNames::NAMES, true),
"Shift+Cmd+F"
);
assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, false), "^⇧F");
assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, true), "⇧⌘F");
}
// ----------------------------------------------------------------------------
impl RawInput {
pub fn ui(&self, ui: &mut crate::Ui) {
let Self {
screen_rect,
pixels_per_point,
max_texture_side,
time,
predicted_dt,
modifiers,
events,
hovered_files,
dropped_files,
has_focus,
} = self;
ui.label(format!("screen_rect: {:?} points", screen_rect));
ui.label(format!("pixels_per_point: {:?}", pixels_per_point))
.on_hover_text(
"Also called HDPI factor.\nNumber of physical pixels per each logical pixel.",
);
ui.label(format!("max_texture_side: {:?}", max_texture_side));
if let Some(time) = time {
ui.label(format!("time: {:.3} s", time));
} else {
ui.label("time: None");
}
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
ui.label(format!("modifiers: {:#?}", modifiers));
ui.label(format!("hovered_files: {}", hovered_files.len()));
ui.label(format!("dropped_files: {}", dropped_files.len()));
ui.label(format!("has_focus: {}", has_focus));
ui.scope(|ui| {
ui.set_min_height(150.0);
ui.label(format!("events: {:#?}", events))
.on_hover_text("key presses etc");
});
}
}
/// this is a `u64` as values of this kind can always be obtained by hashing
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct TouchDeviceId(pub u64);
/// Unique identification of a touch occurrence (finger or pen or …).
/// A Touch ID is valid until the finger is lifted.
/// A new ID is used for the next touch.
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct TouchId(pub u64);
/// In what phase a touch event is in.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum TouchPhase {
/// User just placed a touch point on the touch surface
Start,
/// User moves a touch point along the surface. This event is also sent when
/// any attributes (position, force, …) of the touch point change.
Move,
/// User lifted the finger or pen from the surface, or slid off the edge of
/// the surface
End,
/// Touch operation has been disrupted by something (various reasons are possible,
/// maybe a pop-up alert or any other kind of interruption which may not have
/// been intended by the user)
Cancel,
}
impl From<u64> for TouchId {
fn from(id: u64) -> Self {
Self(id)
}
}
impl From<i32> for TouchId {
fn from(id: i32) -> Self {
Self(id as u64)
}
}
impl From<u32> for TouchId {
fn from(id: u32) -> Self {
Self(id as u64)
}
}

View file

@ -0,0 +1,629 @@
//! All the data egui returns to the backend at the end of each frame.
use crate::WidgetType;
/// What egui emits each frame from [`crate::Context::run`].
///
/// The backend should use this.
#[derive(Clone, Default, PartialEq)]
pub struct FullOutput {
/// Non-rendering related output.
pub platform_output: PlatformOutput,
/// If `Duration::is_zero()`, egui is requesting immediate repaint (i.e. on the next frame).
///
/// This happens for instance when there is an animation, or if a user has called `Context::request_repaint()`.
///
/// If `Duration` is greater than zero, egui wants to be repainted at or before the specified
/// duration elapses. when in reactive mode, egui spends forever waiting for input and only then,
/// will it repaint itself. this can be used to make sure that backend will only wait for a
/// specified amount of time, and repaint egui without any new input.
pub repaint_after: std::time::Duration,
/// Texture changes since last frame (including the font texture).
///
/// The backend needs to apply [`crate::TexturesDelta::set`] _before_ painting,
/// and free any texture in [`crate::TexturesDelta::free`] _after_ painting.
pub textures_delta: epaint::textures::TexturesDelta,
/// What to paint.
///
/// You can use [`crate::Context::tessellate`] to turn this into triangles.
pub shapes: Vec<epaint::ClippedShape>,
}
impl FullOutput {
/// Add on new output.
pub fn append(&mut self, newer: Self) {
let Self {
platform_output,
repaint_after,
textures_delta,
shapes,
} = newer;
self.platform_output.append(platform_output);
self.repaint_after = repaint_after; // if the last frame doesn't need a repaint, then we don't need to repaint
self.textures_delta.append(textures_delta);
self.shapes = shapes; // Only paint the latest
}
}
/// The non-rendering part of what egui emits each frame.
///
/// You can access (and modify) this with [`crate::Context::output`].
///
/// The backend should use this.
#[derive(Default, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct PlatformOutput {
/// Set the cursor to this icon.
pub cursor_icon: CursorIcon,
/// If set, open this url.
pub open_url: Option<OpenUrl>,
/// If set, put this text in the system clipboard. Ignore if empty.
///
/// This is often a response to [`crate::Event::Copy`] or [`crate::Event::Cut`].
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// if ui.button("📋").clicked() {
/// ui.output_mut(|o| o.copied_text = "some_text".to_string());
/// }
/// # });
/// ```
pub copied_text: String,
/// Events that may be useful to e.g. a screen reader.
pub events: Vec<OutputEvent>,
/// Is there a mutable [`TextEdit`](crate::TextEdit) under the cursor?
/// Use by `eframe` web to show/hide mobile keyboard and IME agent.
pub mutable_text_under_cursor: bool,
/// Screen-space position of text edit cursor (used for IME).
pub text_cursor_pos: Option<crate::Pos2>,
#[cfg(feature = "accesskit")]
pub accesskit_update: Option<accesskit::TreeUpdate>,
}
impl PlatformOutput {
/// Open the given url in a web browser.
/// If egui is running in a browser, the same tab will be reused.
pub fn open_url(&mut self, url: impl ToString) {
self.open_url = Some(OpenUrl::same_tab(url));
}
/// This can be used by a text-to-speech system to describe the events (if any).
pub fn events_description(&self) -> String {
// only describe last event:
if let Some(event) = self.events.iter().rev().next() {
match event {
OutputEvent::Clicked(widget_info)
| OutputEvent::DoubleClicked(widget_info)
| OutputEvent::TripleClicked(widget_info)
| OutputEvent::FocusGained(widget_info)
| OutputEvent::TextSelectionChanged(widget_info)
| OutputEvent::ValueChanged(widget_info) => {
return widget_info.description();
}
}
}
Default::default()
}
/// Add on new output.
pub fn append(&mut self, newer: Self) {
let Self {
cursor_icon,
open_url,
copied_text,
mut events,
mutable_text_under_cursor,
text_cursor_pos,
#[cfg(feature = "accesskit")]
accesskit_update,
} = newer;
self.cursor_icon = cursor_icon;
if open_url.is_some() {
self.open_url = open_url;
}
if !copied_text.is_empty() {
self.copied_text = copied_text;
}
self.events.append(&mut events);
self.mutable_text_under_cursor = mutable_text_under_cursor;
self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos);
#[cfg(feature = "accesskit")]
{
// egui produces a complete AccessKit tree for each frame,
// so overwrite rather than appending.
self.accesskit_update = accesskit_update;
}
}
/// Take everything ephemeral (everything except `cursor_icon` currently)
pub fn take(&mut self) -> Self {
let taken = std::mem::take(self);
self.cursor_icon = taken.cursor_icon; // eveything else is ephemeral
taken
}
}
/// What URL to open, and how.
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct OpenUrl {
pub url: String,
/// If `true`, open the url in a new tab.
/// If `false` open it in the same tab.
/// Only matters when in a web browser.
pub new_tab: bool,
}
impl OpenUrl {
#[allow(clippy::needless_pass_by_value)]
pub fn same_tab(url: impl ToString) -> Self {
Self {
url: url.to_string(),
new_tab: false,
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn new_tab(url: impl ToString) -> Self {
Self {
url: url.to_string(),
new_tab: true,
}
}
}
/// A mouse cursor icon.
///
/// egui emits a [`CursorIcon`] in [`PlatformOutput`] each frame as a request to the integration.
///
/// Loosely based on <https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum CursorIcon {
/// Normal cursor icon, whatever that is.
Default,
/// Show no cursor
None,
// ------------------------------------
// Links and status:
/// A context menu is available
ContextMenu,
/// Question mark
Help,
/// Pointing hand, used for e.g. web links
PointingHand,
/// Shows that processing is being done, but that the program is still interactive.
Progress,
/// Not yet ready, try later.
Wait,
// ------------------------------------
// Selection:
/// Hover a cell in a table
Cell,
/// For precision work
Crosshair,
/// Text caret, e.g. "Click here to edit text"
Text,
/// Vertical text caret, e.g. "Click here to edit vertical text"
VerticalText,
// ------------------------------------
// Drag-and-drop:
/// Indicated an alias, e.g. a shortcut
Alias,
/// Indicate that a copy will be made
Copy,
/// Omnidirectional move icon (e.g. arrows in all cardinal directions)
Move,
/// Can't drop here
NoDrop,
/// Forbidden
NotAllowed,
/// The thing you are hovering can be grabbed
Grab,
/// You are grabbing the thing you are hovering
Grabbing,
// ------------------------------------
/// Something can be scrolled in any direction (panned).
AllScroll,
// ------------------------------------
// Resizing in two directions:
/// Horizontal resize `-` to make something wider or more narrow (left to/from right)
ResizeHorizontal,
/// Diagonal resize `/` (right-up to/from left-down)
ResizeNeSw,
/// Diagonal resize `\` (left-up to/from right-down)
ResizeNwSe,
/// Vertical resize `|` (up-down or down-up)
ResizeVertical,
// ------------------------------------
// Resizing in one direction:
/// Resize something rightwards (e.g. when dragging the right-most edge of something)
ResizeEast,
/// Resize something down and right (e.g. when dragging the bottom-right corner of something)
ResizeSouthEast,
/// Resize something downwards (e.g. when dragging the bottom edge of something)
ResizeSouth,
/// Resize something down and left (e.g. when dragging the bottom-left corner of something)
ResizeSouthWest,
/// Resize something leftwards (e.g. when dragging the left edge of something)
ResizeWest,
/// Resize something up and left (e.g. when dragging the top-left corner of something)
ResizeNorthWest,
/// Resize something up (e.g. when dragging the top edge of something)
ResizeNorth,
/// Resize something up and right (e.g. when dragging the top-right corner of something)
ResizeNorthEast,
// ------------------------------------
/// Resize a column
ResizeColumn,
/// Resize a row
ResizeRow,
// ------------------------------------
// Zooming:
/// Enhance!
ZoomIn,
/// Let's get a better overview
ZoomOut,
}
impl CursorIcon {
pub const ALL: [CursorIcon; 35] = [
CursorIcon::Default,
CursorIcon::None,
CursorIcon::ContextMenu,
CursorIcon::Help,
CursorIcon::PointingHand,
CursorIcon::Progress,
CursorIcon::Wait,
CursorIcon::Cell,
CursorIcon::Crosshair,
CursorIcon::Text,
CursorIcon::VerticalText,
CursorIcon::Alias,
CursorIcon::Copy,
CursorIcon::Move,
CursorIcon::NoDrop,
CursorIcon::NotAllowed,
CursorIcon::Grab,
CursorIcon::Grabbing,
CursorIcon::AllScroll,
CursorIcon::ResizeHorizontal,
CursorIcon::ResizeNeSw,
CursorIcon::ResizeNwSe,
CursorIcon::ResizeVertical,
CursorIcon::ResizeEast,
CursorIcon::ResizeSouthEast,
CursorIcon::ResizeSouth,
CursorIcon::ResizeSouthWest,
CursorIcon::ResizeWest,
CursorIcon::ResizeNorthWest,
CursorIcon::ResizeNorth,
CursorIcon::ResizeNorthEast,
CursorIcon::ResizeColumn,
CursorIcon::ResizeRow,
CursorIcon::ZoomIn,
CursorIcon::ZoomOut,
];
}
impl Default for CursorIcon {
fn default() -> Self {
Self::Default
}
}
/// Things that happened during this frame that the integration may be interested in.
///
/// In particular, these events may be useful for accessability, i.e. for screen readers.
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum OutputEvent {
/// A widget was clicked.
Clicked(WidgetInfo),
/// A widget was double-clicked.
DoubleClicked(WidgetInfo),
/// A widget was triple-clicked.
TripleClicked(WidgetInfo),
/// A widget gained keyboard focus (by tab key).
FocusGained(WidgetInfo),
/// Text selection was updated.
TextSelectionChanged(WidgetInfo),
/// A widget's value changed.
ValueChanged(WidgetInfo),
}
impl OutputEvent {
pub fn widget_info(&self) -> &WidgetInfo {
match self {
OutputEvent::Clicked(info)
| OutputEvent::DoubleClicked(info)
| OutputEvent::TripleClicked(info)
| OutputEvent::FocusGained(info)
| OutputEvent::TextSelectionChanged(info)
| OutputEvent::ValueChanged(info) => info,
}
}
}
impl std::fmt::Debug for OutputEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Clicked(wi) => write!(f, "Clicked({:?})", wi),
Self::DoubleClicked(wi) => write!(f, "DoubleClicked({:?})", wi),
Self::TripleClicked(wi) => write!(f, "TripleClicked({:?})", wi),
Self::FocusGained(wi) => write!(f, "FocusGained({:?})", wi),
Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({:?})", wi),
Self::ValueChanged(wi) => write!(f, "ValueChanged({:?})", wi),
}
}
}
/// Describes a widget such as a [`crate::Button`] or a [`crate::TextEdit`].
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct WidgetInfo {
/// The type of widget this is.
pub typ: WidgetType,
/// Whether the widget is enabled.
pub enabled: bool,
/// The text on labels, buttons, checkboxes etc.
pub label: Option<String>,
/// The contents of some editable text (for [`TextEdit`](crate::TextEdit) fields).
pub current_text_value: Option<String>,
/// The previous text value.
pub prev_text_value: Option<String>,
/// The current value of checkboxes and radio buttons.
pub selected: Option<bool>,
/// The current value of sliders etc.
pub value: Option<f64>,
/// Selected range of characters in [`Self::current_text_value`].
pub text_selection: Option<std::ops::RangeInclusive<usize>>,
}
impl std::fmt::Debug for WidgetInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
typ,
enabled,
label,
current_text_value: text_value,
prev_text_value,
selected,
value,
text_selection,
} = self;
let mut s = f.debug_struct("WidgetInfo");
s.field("typ", typ);
s.field("enabled", enabled);
if let Some(label) = label {
s.field("label", label);
}
if let Some(text_value) = text_value {
s.field("text_value", text_value);
}
if let Some(prev_text_value) = prev_text_value {
s.field("prev_text_value", prev_text_value);
}
if let Some(selected) = selected {
s.field("selected", selected);
}
if let Some(value) = value {
s.field("value", value);
}
if let Some(text_selection) = text_selection {
s.field("text_selection", text_selection);
}
s.finish()
}
}
impl WidgetInfo {
pub fn new(typ: WidgetType) -> Self {
Self {
typ,
enabled: true,
label: None,
current_text_value: None,
prev_text_value: None,
selected: None,
value: None,
text_selection: None,
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn labeled(typ: WidgetType, label: impl ToString) -> Self {
Self {
label: Some(label.to_string()),
..Self::new(typ)
}
}
/// checkboxes, radio-buttons etc
#[allow(clippy::needless_pass_by_value)]
pub fn selected(typ: WidgetType, selected: bool, label: impl ToString) -> Self {
Self {
label: Some(label.to_string()),
selected: Some(selected),
..Self::new(typ)
}
}
pub fn drag_value(value: f64) -> Self {
Self {
value: Some(value),
..Self::new(WidgetType::DragValue)
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn slider(value: f64, label: impl ToString) -> Self {
let label = label.to_string();
Self {
label: if label.is_empty() { None } else { Some(label) },
value: Some(value),
..Self::new(WidgetType::Slider)
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn text_edit(prev_text_value: impl ToString, text_value: impl ToString) -> Self {
let text_value = text_value.to_string();
let prev_text_value = prev_text_value.to_string();
let prev_text_value = if text_value == prev_text_value {
None
} else {
Some(prev_text_value)
};
Self {
current_text_value: Some(text_value),
prev_text_value,
..Self::new(WidgetType::TextEdit)
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn text_selection_changed(
text_selection: std::ops::RangeInclusive<usize>,
current_text_value: impl ToString,
) -> Self {
Self {
text_selection: Some(text_selection),
current_text_value: Some(current_text_value.to_string()),
..Self::new(WidgetType::TextEdit)
}
}
/// This can be used by a text-to-speech system to describe the widget.
pub fn description(&self) -> String {
let Self {
typ,
enabled,
label,
current_text_value: text_value,
prev_text_value: _,
selected,
value,
text_selection: _,
} = self;
// TODO(emilk): localization
let widget_type = match typ {
WidgetType::Link => "link",
WidgetType::TextEdit => "text edit",
WidgetType::Button => "button",
WidgetType::Checkbox => "checkbox",
WidgetType::RadioButton => "radio",
WidgetType::SelectableLabel => "selectable",
WidgetType::ComboBox => "combo",
WidgetType::Slider => "slider",
WidgetType::DragValue => "drag value",
WidgetType::ColorButton => "color button",
WidgetType::ImageButton => "image button",
WidgetType::CollapsingHeader => "collapsing header",
WidgetType::Label | WidgetType::Other => "",
};
let mut description = widget_type.to_owned();
if let Some(selected) = selected {
if *typ == WidgetType::Checkbox {
let state = if *selected { "checked" } else { "unchecked" };
description = format!("{} {}", state, description);
} else {
description += if *selected { "selected" } else { "" };
};
}
if let Some(label) = label {
description = format!("{}: {}", label, description);
}
if typ == &WidgetType::TextEdit {
let text;
if let Some(text_value) = text_value {
if text_value.is_empty() {
text = "blank".into();
} else {
text = text_value.to_string();
}
} else {
text = "blank".into();
}
description = format!("{}: {}", text, description);
}
if let Some(value) = value {
description += " ";
description += &value.to_string();
}
if !enabled {
description += ": disabled";
}
description.trim().to_owned()
}
}

View file

@ -0,0 +1,173 @@
use std::ops::RangeInclusive;
use crate::{id::IdSet, *};
#[derive(Clone, Copy, Debug)]
pub(crate) struct TooltipFrameState {
pub common_id: Id,
pub rect: Rect,
pub count: usize,
}
#[cfg(feature = "accesskit")]
#[derive(Clone)]
pub(crate) struct AccessKitFrameState {
pub(crate) node_builders: IdMap<accesskit::NodeBuilder>,
pub(crate) parent_stack: Vec<Id>,
}
/// State that is collected during a frame and then cleared.
/// Short-term (single frame) memory.
#[derive(Clone)]
pub(crate) struct FrameState {
/// All [`Id`]s that were used this frame.
/// Used to debug [`Id`] clashes of widgets.
pub(crate) used_ids: IdMap<Rect>,
/// Starts off as the screen_rect, shrinks as panels are added.
/// The [`CentralPanel`] does not change this.
/// This is the area available to Window's.
pub(crate) available_rect: Rect,
/// Starts off as the screen_rect, shrinks as panels are added.
/// The [`CentralPanel`] retracts from this.
pub(crate) unused_rect: Rect,
/// How much space is used by panels.
pub(crate) used_by_panels: Rect,
/// If a tooltip has been shown this frame, where was it?
/// This is used to prevent multiple tooltips to cover each other.
/// Initialized to `None` at the start of each frame.
pub(crate) tooltip_state: Option<TooltipFrameState>,
/// Set to [`InputState::scroll_delta`] on the start of each frame.
///
/// Cleared by the first [`ScrollArea`] that makes use of it.
pub(crate) scroll_delta: Vec2, // TODO(emilk): move to `InputState` ?
/// horizontal, vertical
pub(crate) scroll_target: [Option<(RangeInclusive<f32>, Option<Align>)>; 2],
#[cfg(feature = "accesskit")]
pub(crate) accesskit_state: Option<AccessKitFrameState>,
/// Highlight these widgets this next frame. Read from this.
pub(crate) highlight_this_frame: IdSet,
/// Highlight these widgets the next frame. Write to this.
pub(crate) highlight_next_frame: IdSet,
}
impl Default for FrameState {
fn default() -> Self {
Self {
used_ids: Default::default(),
available_rect: Rect::NAN,
unused_rect: Rect::NAN,
used_by_panels: Rect::NAN,
tooltip_state: None,
scroll_delta: Vec2::ZERO,
scroll_target: [None, None],
#[cfg(feature = "accesskit")]
accesskit_state: None,
highlight_this_frame: Default::default(),
highlight_next_frame: Default::default(),
}
}
}
impl FrameState {
pub(crate) fn begin_frame(&mut self, input: &InputState) {
let Self {
used_ids,
available_rect,
unused_rect,
used_by_panels,
tooltip_state,
scroll_delta,
scroll_target,
#[cfg(feature = "accesskit")]
accesskit_state,
highlight_this_frame,
highlight_next_frame,
} = self;
used_ids.clear();
*available_rect = input.screen_rect();
*unused_rect = input.screen_rect();
*used_by_panels = Rect::NOTHING;
*tooltip_state = None;
*scroll_delta = input.scroll_delta;
*scroll_target = [None, None];
#[cfg(feature = "accesskit")]
{
*accesskit_state = None;
}
*highlight_this_frame = std::mem::take(highlight_next_frame);
}
/// How much space is still available after panels has been added.
/// This is the "background" area, what egui doesn't cover with panels (but may cover with windows).
/// This is also the area to which windows are constrained.
pub(crate) fn available_rect(&self) -> Rect {
crate::egui_assert!(
self.available_rect.is_finite(),
"Called `available_rect()` before `Context::run()`"
);
self.available_rect
}
/// Shrink `available_rect`.
pub(crate) fn allocate_left_panel(&mut self, panel_rect: Rect) {
crate::egui_assert!(
panel_rect.min.distance(self.available_rect.min) < 0.1,
"Mismatching left panel. You must not create a panel from within another panel."
);
self.available_rect.min.x = panel_rect.max.x;
self.unused_rect.min.x = panel_rect.max.x;
self.used_by_panels = self.used_by_panels.union(panel_rect);
}
/// Shrink `available_rect`.
pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) {
crate::egui_assert!(
panel_rect.max.distance(self.available_rect.max) < 0.1,
"Mismatching right panel. You must not create a panel from within another panel."
);
self.available_rect.max.x = panel_rect.min.x;
self.unused_rect.max.x = panel_rect.min.x;
self.used_by_panels = self.used_by_panels.union(panel_rect);
}
/// Shrink `available_rect`.
pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) {
crate::egui_assert!(
panel_rect.min.distance(self.available_rect.min) < 0.1,
"Mismatching top panel. You must not create a panel from within another panel."
);
self.available_rect.min.y = panel_rect.max.y;
self.unused_rect.min.y = panel_rect.max.y;
self.used_by_panels = self.used_by_panels.union(panel_rect);
}
/// Shrink `available_rect`.
pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) {
crate::egui_assert!(
panel_rect.max.distance(self.available_rect.max) < 0.1,
"Mismatching bottom panel. You must not create a panel from within another panel."
);
self.available_rect.max.y = panel_rect.min.y;
self.unused_rect.max.y = panel_rect.min.y;
self.used_by_panels = self.used_by_panels.union(panel_rect);
}
pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) {
// Note: we do not shrink `available_rect`, because
// we allow windows to cover the CentralPanel.
self.unused_rect = Rect::NOTHING; // Nothing left unused after this
self.used_by_panels = self.used_by_panels.union(panel_rect);
}
}

View file

@ -1,13 +1,23 @@
use crate::*;
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct State {
col_widths: Vec<f32>,
row_heights: Vec<f32>,
}
impl State {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data_mut(|d| d.get_temp(id))
}
pub fn store(self, ctx: &Context, id: Id) {
// We don't persist Grids, because
// A) there are potentially a lot of them, using up a lot of space (and therefore serialization time)
// B) if the code changes, the grid _should_ change, and not remember old sizes
ctx.data_mut(|d| d.insert_temp(id, self));
}
fn set_min_col_width(&mut self, col: usize, width: f32) {
self.col_widths
.resize(self.col_widths.len().max(col + 1), 0.0);
@ -37,41 +47,62 @@ impl State {
// ----------------------------------------------------------------------------
pub(crate) struct GridLayout {
ctx: CtxRef,
ctx: Context,
style: std::sync::Arc<Style>,
id: Id,
/// First frame (no previous know state).
is_first_frame: bool,
/// State previous frame (if any).
/// This can be used to predict future sizes of cells.
prev_state: State,
/// State accumulated during the current frame.
curr_state: State,
initial_available: Rect,
// Options:
num_columns: Option<usize>,
spacing: Vec2,
striped: bool,
initial_x: f32,
min_cell_size: Vec2,
max_cell_size: Vec2,
striped: bool,
// Cursor:
col: usize,
row: usize,
}
impl GridLayout {
pub(crate) fn new(ui: &Ui, id: Id) -> Self {
let prev_state = ui.memory().grid.get(&id).cloned().unwrap_or_default();
pub(crate) fn new(ui: &Ui, id: Id, prev_state: Option<State>) -> Self {
let is_first_frame = prev_state.is_none();
let prev_state = prev_state.unwrap_or_default();
// TODO(emilk): respect current layout
let initial_available = ui.placer().max_rect().intersect(ui.cursor());
crate::egui_assert!(
initial_available.min.x.is_finite(),
"Grid not yet available for right-to-left layouts"
);
ui.ctx().check_for_id_clash(id, initial_available, "Grid");
Self {
ctx: ui.ctx().clone(),
style: ui.style().clone(),
id,
is_first_frame,
prev_state,
curr_state: State::default(),
initial_available,
num_columns: None,
spacing: ui.spacing().item_spacing,
striped: false,
initial_x: ui.cursor().x,
min_cell_size: ui.spacing().interact_size,
max_cell_size: Vec2::INFINITY,
striped: false,
col: 0,
row: 0,
}
@ -84,6 +115,7 @@ impl GridLayout {
.col_width(col)
.unwrap_or(self.min_cell_size.x)
}
fn prev_row_height(&self, row: usize) -> f32 {
self.prev_state
.row_height(row)
@ -95,20 +127,24 @@ impl GridLayout {
}
pub(crate) fn available_rect(&self, region: &Region) -> Rect {
// let mut rect = Rect::from_min_max(region.cursor, region.max_rect.max);
// rect.set_height(rect.height().at_least(self.min_cell_size.y));
// rect
let is_last_column = Some(self.col + 1) == self.num_columns;
// required for putting CollapsingHeader in anything but the last column:
self.available_rect_finite(region)
let width = if is_last_column {
// The first frame we don't really know the widths of the previous columns,
// so returning a big available width here can cause trouble.
if self.is_first_frame {
self.curr_state
.col_width(self.col)
.unwrap_or(self.min_cell_size.x)
} else {
(self.initial_available.right() - region.cursor.left())
.at_most(self.max_cell_size.x)
}
pub(crate) fn available_rect_finite(&self, region: &Region) -> Rect {
let width = if self.max_cell_size.x.is_finite() {
// TODO: should probably heed `prev_state` here too
} else if self.max_cell_size.x.is_finite() {
// TODO(emilk): should probably heed `prev_state` here too
self.max_cell_size.x
} else {
// If we want to allow width-filling widgets like `Separator` in one of the first cells
// If we want to allow width-filling widgets like [`Separator`] in one of the first cells
// then we need to make sure they don't spill out of the first cell:
self.prev_state
.col_width(self.col)
@ -116,23 +152,29 @@ impl GridLayout {
.unwrap_or(self.min_cell_size.x)
};
let height = region.max_rect_finite().max.y - region.cursor.y;
// If something above was wider, we can be wider:
let width = width.max(self.curr_state.col_width(self.col).unwrap_or(0.0));
let available = region.max_rect.intersect(region.cursor);
let height = region.max_rect.max.y - available.top();
let height = height
.at_least(self.min_cell_size.y)
.at_most(self.max_cell_size.y);
Rect::from_min_size(region.cursor, vec2(width, height))
Rect::from_min_size(available.min, vec2(width, height))
}
pub(crate) fn next_cell(&self, cursor: Pos2, child_size: Vec2) -> Rect {
pub(crate) fn next_cell(&self, cursor: Rect, child_size: Vec2) -> Rect {
let width = self.prev_state.col_width(self.col).unwrap_or(0.0);
let height = self.prev_row_height(self.row);
let size = child_size.max(vec2(width, height));
Rect::from_min_size(cursor, size)
Rect::from_min_size(cursor.min, size)
}
#[allow(clippy::unused_self)]
pub(crate) fn align_size_within_rect(&self, size: Vec2, frame: Rect) -> Rect {
// TODO: allow this alignment to be customized
// TODO(emilk): allow this alignment to be customized
Align2::LEFT_CENTER.align_size_within_rect(size, frame)
}
@ -140,9 +182,9 @@ impl GridLayout {
self.align_size_within_rect(size, frame)
}
pub(crate) fn advance(&mut self, cursor: &mut Pos2, frame_rect: Rect, widget_rect: Rect) {
let debug_expand_width = self.style.visuals.debug_expand_width;
let debug_expand_height = self.style.visuals.debug_expand_height;
pub(crate) fn advance(&mut self, cursor: &mut Rect, _frame_rect: Rect, widget_rect: Rect) {
let debug_expand_width = self.style.debug.show_expand_width;
let debug_expand_height = self.style.debug.show_expand_height;
if debug_expand_width || debug_expand_height {
let rect = widget_rect;
let too_wide = rect.width() > self.prev_col_width(self.col);
@ -164,21 +206,22 @@ impl GridLayout {
}
self.curr_state
.set_min_col_width(self.col, widget_rect.width().at_least(self.min_cell_size.x));
self.curr_state.set_min_row_height(
self.row,
widget_rect.height().at_least(self.min_cell_size.y),
);
.set_min_col_width(self.col, widget_rect.width().max(self.min_cell_size.x));
self.curr_state
.set_min_row_height(self.row, widget_rect.height().max(self.min_cell_size.y));
cursor.min.x += self.prev_col_width(self.col) + self.spacing.x;
self.col += 1;
cursor.x += frame_rect.width() + self.spacing.x;
}
pub(crate) fn end_row(&mut self, cursor: &mut Pos2, painter: &Painter) {
let row_height = self.prev_row_height(self.row);
pub(crate) fn end_row(&mut self, cursor: &mut Rect, painter: &Painter) {
cursor.min.x = self.initial_available.min.x;
cursor.min.y += self.spacing.y;
cursor.min.y += self
.curr_state
.row_height(self.row)
.unwrap_or(self.min_cell_size.y);
cursor.x = self.initial_x;
cursor.y += row_height + self.spacing.y;
self.col = 0;
self.row += 1;
@ -186,26 +229,18 @@ impl GridLayout {
if let Some(height) = self.prev_state.row_height(self.row) {
// Paint background for coming row:
let size = Vec2::new(self.prev_state.full_width(self.spacing.x), height);
let rect = Rect::from_min_size(*cursor, size);
let rect = Rect::from_min_size(cursor.min, size);
let rect = rect.expand2(0.5 * self.spacing.y * Vec2::Y);
let rect = rect.expand2(2.0 * Vec2::X); // HACK: just looks better with some spacing on the sides
let color = if self.style.visuals.dark_mode {
Rgba::from_white_alpha(0.0075)
} else {
Rgba::from_black_alpha(0.075)
};
painter.rect_filled(rect, 2.0, color);
painter.rect_filled(rect, 2.0, self.style.visuals.faint_bg_color);
}
}
}
pub(crate) fn save(&self) {
if self.curr_state != self.prev_state {
self.ctx
.memory()
.grid
.insert(self.id, self.curr_state.clone());
self.curr_state.clone().store(&self.ctx, self.id);
self.ctx.request_repaint();
}
}
@ -215,12 +250,14 @@ impl GridLayout {
/// A simple grid layout.
///
/// The contents of each cell be aligned to the left and center.
/// The cells are always layed out left to right, top-down.
/// The contents of each cell will be aligned to the left and center.
///
/// If you want to add multiple widgets to a cell you need to group them with
/// [`Ui::horizontal`], [`Ui::vertical`] etc.
///
/// ```
/// # let ui = &mut egui::Ui::__test();
/// # egui::__run_test_ui(|ui| {
/// egui::Grid::new("some_unique_id").show(ui, |ui| {
/// ui.label("First row, first column");
/// ui.label("First row, second column");
@ -235,14 +272,18 @@ impl GridLayout {
/// ui.label("Third row, second column");
/// ui.end_row();
/// });
/// # });
/// ```
#[must_use = "You should call .show()"]
pub struct Grid {
id_source: Id,
striped: bool,
num_columns: Option<usize>,
striped: Option<bool>,
min_col_width: Option<f32>,
min_row_height: Option<f32>,
max_cell_size: Vec2,
spacing: Option<Vec2>,
start_row: usize,
}
impl Grid {
@ -250,20 +291,28 @@ impl Grid {
pub fn new(id_source: impl std::hash::Hash) -> Self {
Self {
id_source: Id::new(id_source),
striped: false,
num_columns: None,
striped: None,
min_col_width: None,
min_row_height: None,
max_cell_size: Vec2::INFINITY,
spacing: None,
start_row: 0,
}
}
/// Setting this will allow the last column to expand to take up the rest of the space of the parent [`Ui`].
pub fn num_columns(mut self, num_columns: usize) -> Self {
self.num_columns = Some(num_columns);
self
}
/// If `true`, add a subtle background color to every other row.
///
/// This can make a table easier to read.
/// Default: `false`.
/// Default is whatever is in [`crate::Visuals::striped`].
pub fn striped(mut self, striped: bool) -> Self {
self.striped = striped;
self.striped = Some(striped);
self
}
@ -293,34 +342,59 @@ impl Grid {
self.spacing = Some(spacing.into());
self
}
/// Change which row number the grid starts on.
/// This can be useful when you have a large [`Grid`] inside of [`ScrollArea::show_rows`].
pub fn start_row(mut self, start_row: usize) -> Self {
self.start_row = start_row;
self
}
}
impl Grid {
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
self.show_dyn(ui, Box::new(add_contents))
}
fn show_dyn<'c, R>(
self,
ui: &mut Ui,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let Self {
id_source,
num_columns,
striped,
min_col_width,
min_row_height,
max_cell_size,
spacing,
start_row,
} = self;
let striped = striped.unwrap_or(ui.visuals().striped);
let min_col_width = min_col_width.unwrap_or_else(|| ui.spacing().interact_size.x);
let min_row_height = min_row_height.unwrap_or_else(|| ui.spacing().interact_size.y);
let spacing = spacing.unwrap_or_else(|| ui.spacing().item_spacing);
let id = ui.make_persistent_id(id_source);
let prev_state = State::load(ui.ctx(), id);
// Each grid cell is aligned LEFT_CENTER.
// If somebody wants to wrap more things inside a cell,
// then we should pick a default layout that matches that alignment,
// which we do here:
let max_rect = ui.cursor().intersect(ui.max_rect());
ui.allocate_ui_at_rect(max_rect, |ui| {
ui.set_visible(prev_state.is_some()); // Avoid visible first-frame jitter
ui.horizontal(|ui| {
let id = ui.make_persistent_id(id_source);
let grid = GridLayout {
num_columns,
striped,
spacing,
min_cell_size: vec2(min_col_width, min_row_height),
max_cell_size,
..GridLayout::new(ui, id)
spacing,
row: start_row,
..GridLayout::new(ui, id, prev_state)
};
ui.set_grid(grid);
@ -329,5 +403,6 @@ impl Grid {
r
})
.inner
})
}
}

117
crates/egui/src/gui_zoom.rs Normal file
View file

@ -0,0 +1,117 @@
//! Helpers for zooming the whole GUI of an app (changing [`Context::pixels_per_point`].
//!
use crate::*;
/// The suggested keyboard shortcuts for global gui zooming.
pub mod kb_shortcuts {
use super::*;
pub const ZOOM_IN: KeyboardShortcut =
KeyboardShortcut::new(Modifiers::COMMAND, Key::PlusEquals);
pub const ZOOM_OUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Minus);
pub const ZOOM_RESET: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Num0);
}
/// Let the user scale the GUI (change `Context::pixels_per_point`) by pressing
/// Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser.
///
/// When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe), you want to call this as:
/// ```ignore
/// // On web, the browser controls the gui zoom.
/// if !frame.is_web() {
/// egui::gui_zoom::zoom_with_keyboard_shortcuts(
/// ctx,
/// frame.info().native_pixels_per_point,
/// );
/// }
/// ```
pub fn zoom_with_keyboard_shortcuts(ctx: &Context, native_pixels_per_point: Option<f32>) {
if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_RESET)) {
if let Some(native_pixels_per_point) = native_pixels_per_point {
ctx.set_pixels_per_point(native_pixels_per_point);
}
} else {
if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_IN)) {
zoom_in(ctx);
}
if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_OUT)) {
zoom_out(ctx);
}
}
}
const MIN_PIXELS_PER_POINT: f32 = 0.2;
const MAX_PIXELS_PER_POINT: f32 = 4.0;
/// Make everything larger.
pub fn zoom_in(ctx: &Context) {
let mut pixels_per_point = ctx.pixels_per_point();
pixels_per_point += 0.1;
pixels_per_point = pixels_per_point.clamp(MIN_PIXELS_PER_POINT, MAX_PIXELS_PER_POINT);
pixels_per_point = (pixels_per_point * 10.).round() / 10.;
ctx.set_pixels_per_point(pixels_per_point);
}
/// Make everything smaller.
pub fn zoom_out(ctx: &Context) {
let mut pixels_per_point = ctx.pixels_per_point();
pixels_per_point -= 0.1;
pixels_per_point = pixels_per_point.clamp(MIN_PIXELS_PER_POINT, MAX_PIXELS_PER_POINT);
pixels_per_point = (pixels_per_point * 10.).round() / 10.;
ctx.set_pixels_per_point(pixels_per_point);
}
/// Show buttons for zooming the ui.
///
/// This is meant to be called from within a menu (See [`Ui::menu_button`]).
///
/// When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe), you want to call this as:
/// ```ignore
/// // On web, the browser controls the gui zoom.
/// if !frame.is_web() {
/// ui.menu_button("View", |ui| {
/// egui::gui_zoom::zoom_menu_buttons(
/// ui,
/// frame.info().native_pixels_per_point,
/// );
/// });
/// }
/// ```
pub fn zoom_menu_buttons(ui: &mut Ui, native_pixels_per_point: Option<f32>) {
if ui
.add_enabled(
ui.ctx().pixels_per_point() < MAX_PIXELS_PER_POINT,
Button::new("Zoom In").shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_IN)),
)
.clicked()
{
zoom_in(ui.ctx());
ui.close_menu();
}
if ui
.add_enabled(
ui.ctx().pixels_per_point() > MIN_PIXELS_PER_POINT,
Button::new("Zoom Out")
.shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_OUT)),
)
.clicked()
{
zoom_out(ui.ctx());
ui.close_menu();
}
if let Some(native_pixels_per_point) = native_pixels_per_point {
if ui
.add_enabled(
ui.ctx().pixels_per_point() != native_pixels_per_point,
Button::new("Reset Zoom")
.shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_RESET)),
)
.clicked()
{
ui.ctx().set_pixels_per_point(native_pixels_per_point);
ui.close_menu();
}
}
}

175
crates/egui/src/id.rs Normal file
View file

@ -0,0 +1,175 @@
// TODO(emilk): have separate types `PositionId` and `UniqueId`. ?
/// egui tracks widgets frame-to-frame using [`Id`]s.
///
/// For instance, if you start dragging a slider one frame, egui stores
/// the sliders [`Id`] as the current active id so that next frame when
/// you move the mouse the same slider changes, even if the mouse has
/// moved outside the slider.
///
/// For some widgets [`Id`]s are also used to persist some state about the
/// widgets, such as Window position or whether not a collapsing header region is open.
///
/// This implies that the [`Id`]s must be unique.
///
/// For simple things like sliders and buttons that don't have any memory and
/// doesn't move we can use the location of the widget as a source of identity.
/// For instance, a slider only needs a unique and persistent ID while you are
/// dragging the slider. As long as it is still while moving, that is fine.
///
/// For things that need to persist state even after moving (windows, collapsing headers)
/// the location of the widgets is obviously not good enough. For instance,
/// a collapsing region needs to remember whether or not it is open even
/// if the layout next frame is different and the collapsing is not lower down
/// on the screen.
///
/// Then there are widgets that need no identifiers at all, like labels,
/// because they have no state nor are interacted with.
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Id(u64);
impl Id {
/// A special [`Id`], in particular as a key to [`crate::Memory::data`]
/// for when there is no particular widget to attach the data.
///
/// The null [`Id`] is still a valid id to use in all circumstances,
/// though obviously it will lead to a lot of collisions if you do use it!
pub fn null() -> Self {
Self(0)
}
pub(crate) fn background() -> Self {
Self(1)
}
/// Generate a new [`Id`] by hashing some source (e.g. a string or integer).
pub fn new(source: impl std::hash::Hash) -> Id {
use std::hash::{BuildHasher, Hasher};
let mut hasher = epaint::ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
source.hash(&mut hasher);
Id(hasher.finish())
}
/// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument.
pub fn with(self, child: impl std::hash::Hash) -> Id {
use std::hash::{BuildHasher, Hasher};
let mut hasher = epaint::ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
hasher.write_u64(self.0);
child.hash(&mut hasher);
Id(hasher.finish())
}
/// Short and readable summary
pub fn short_debug_format(&self) -> String {
format!("{:04X}", self.0 as u16)
}
#[inline(always)]
pub(crate) fn value(&self) -> u64 {
self.0
}
#[cfg(feature = "accesskit")]
pub(crate) fn accesskit_id(&self) -> accesskit::NodeId {
std::num::NonZeroU64::new(self.0).unwrap().into()
}
}
impl std::fmt::Debug for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:016X}", self.0)
}
}
/// Convenience
impl From<&'static str> for Id {
#[inline]
fn from(string: &'static str) -> Self {
Self::new(string)
}
}
impl From<String> for Id {
#[inline]
fn from(string: String) -> Self {
Self::new(string)
}
}
// ----------------------------------------------------------------------------
// Idea taken from the `nohash_hasher` crate.
#[derive(Default)]
pub struct IdHasher(u64);
impl std::hash::Hasher for IdHasher {
fn write(&mut self, _: &[u8]) {
unreachable!("Invalid use of IdHasher");
}
fn write_u8(&mut self, _n: u8) {
unreachable!("Invalid use of IdHasher");
}
fn write_u16(&mut self, _n: u16) {
unreachable!("Invalid use of IdHasher");
}
fn write_u32(&mut self, _n: u32) {
unreachable!("Invalid use of IdHasher");
}
#[inline(always)]
fn write_u64(&mut self, n: u64) {
self.0 = n;
}
fn write_usize(&mut self, _n: usize) {
unreachable!("Invalid use of IdHasher");
}
fn write_i8(&mut self, _n: i8) {
unreachable!("Invalid use of IdHasher");
}
fn write_i16(&mut self, _n: i16) {
unreachable!("Invalid use of IdHasher");
}
fn write_i32(&mut self, _n: i32) {
unreachable!("Invalid use of IdHasher");
}
fn write_i64(&mut self, _n: i64) {
unreachable!("Invalid use of IdHasher");
}
fn write_isize(&mut self, _n: isize) {
unreachable!("Invalid use of IdHasher");
}
#[inline(always)]
fn finish(&self) -> u64 {
self.0
}
}
#[derive(Copy, Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct BuilIdHasher {}
impl std::hash::BuildHasher for BuilIdHasher {
type Hasher = IdHasher;
#[inline(always)]
fn build_hasher(&self) -> IdHasher {
IdHasher::default()
}
}
/// `IdSet` is a `HashSet<Id>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing.
pub type IdSet = std::collections::HashSet<Id, BuilIdHasher>;
/// `IdMap<V>` is a `HashMap<Id, V>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing.
pub type IdMap<V> = std::collections::HashMap<Id, V, BuilIdHasher>;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,339 @@
use std::{collections::BTreeMap, fmt::Debug};
use crate::{
data::input::TouchDeviceId,
emath::{normalized_angle, Pos2, Vec2},
Event, RawInput, TouchId, TouchPhase,
};
/// All you probably need to know about a multi-touch gesture.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MultiTouchInfo {
/// Point in time when the gesture started.
pub start_time: f64,
/// Position of the pointer at the time the gesture started.
pub start_pos: Pos2,
/// Number of touches (fingers) on the surface. Value is ≥ 2 since for a single touch no
/// [`MultiTouchInfo`] is created.
pub num_touches: usize,
/// Proportional zoom factor (pinch gesture).
/// * `zoom = 1`: no change
/// * `zoom < 1`: pinch together
/// * `zoom > 1`: pinch spread
pub zoom_delta: f32,
/// 2D non-proportional zoom factor (pinch gesture).
///
/// For horizontal pinches, this will return `[z, 1]`,
/// for vertical pinches this will return `[1, z]`,
/// and otherwise this will return `[z, z]`,
/// where `z` is the zoom factor:
/// * `zoom = 1`: no change
/// * `zoom < 1`: pinch together
/// * `zoom > 1`: pinch spread
pub zoom_delta_2d: Vec2,
/// Rotation in radians. Moving fingers around each other will change this value. This is a
/// relative value, comparing the orientation of fingers in the current frame with the previous
/// frame. If all fingers are resting, this value is `0.0`.
pub rotation_delta: f32,
/// Relative movement (comparing previous frame and current frame) of the average position of
/// all touch points. Without movement this value is `Vec2::ZERO`.
///
/// Note that this may not necessarily be measured in screen points (although it _will_ be for
/// most mobile devices). In general (depending on the touch device), touch coordinates cannot
/// be directly mapped to the screen. A touch always is considered to start at the position of
/// the pointer, but touch movement is always measured in the units delivered by the device,
/// and may depend on hardware and system settings.
pub translation_delta: Vec2,
/// Current force of the touch (average of the forces of the individual fingers). This is a
/// value in the interval `[0.0 .. =1.0]`.
///
/// Note 1: A value of `0.0` either indicates a very light touch, or it means that the device
/// is not capable of measuring the touch force at all.
///
/// Note 2: Just increasing the physical pressure without actually moving the finger may not
/// necessarily lead to a change of this value.
pub force: f32,
}
/// The current state (for a specific touch device) of touch events and gestures.
#[derive(Clone)]
pub(crate) struct TouchState {
/// Technical identifier of the touch device. This is used to identify relevant touch events
/// for this [`TouchState`] instance.
device_id: TouchDeviceId,
/// Active touches, if any.
///
/// TouchId is the unique identifier of the touch. It is valid as long as the finger/pen touches the surface. The
/// next touch will receive a new unique ID.
///
/// Refer to [`ActiveTouch`].
active_touches: BTreeMap<TouchId, ActiveTouch>,
/// If a gesture has been recognized (i.e. when exactly two fingers touch the surface), this
/// holds state information
gesture_state: Option<GestureState>,
}
#[derive(Clone, Debug)]
struct GestureState {
start_time: f64,
start_pointer_pos: Pos2,
pinch_type: PinchType,
previous: Option<DynGestureState>,
current: DynGestureState,
}
/// Gesture data that can change over time
#[derive(Clone, Copy, Debug)]
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,
}
/// Describes an individual touch (finger or digitizer) on the touch surface. Instances exist as
/// long as the finger/pen touches the surface.
#[derive(Clone, Copy, Debug)]
struct ActiveTouch {
/// Current position of this touch, in device coordinates (not necessarily screen position)
pos: Pos2,
/// Current force of the touch. A value in the interval [0.0 .. 1.0]
///
/// Note that a value of 0.0 either indicates a very light touch, or it means that the device
/// is not capable of measuring the touch force.
force: f32,
}
impl TouchState {
pub fn new(device_id: TouchDeviceId) -> Self {
Self {
device_id,
active_touches: Default::default(),
gesture_state: None,
}
}
pub fn begin_frame(&mut self, time: f64, new: &RawInput, pointer_pos: Option<Pos2>) {
let mut added_or_removed_touches = false;
for event in &new.events {
match *event {
Event::Touch {
device_id,
id,
phase,
pos,
force,
} if device_id == self.device_id => match phase {
TouchPhase::Start => {
self.active_touches.insert(id, ActiveTouch { pos, force });
added_or_removed_touches = true;
}
TouchPhase::Move => {
if let Some(touch) = self.active_touches.get_mut(&id) {
touch.pos = pos;
touch.force = force;
}
}
TouchPhase::End | TouchPhase::Cancel => {
self.active_touches.remove(&id);
added_or_removed_touches = true;
}
},
_ => (),
}
}
// This needs to be called each frame, even if there are no new touch events.
// Otherwise, we would send the same old delta information multiple times:
self.update_gesture(time, pointer_pos);
if added_or_removed_touches {
// Adding or removing fingers makes the average values "jump". We better forget
// about the previous values, and don't create delta information for this frame:
if let Some(ref mut state) = &mut self.gesture_state {
state.previous = None;
}
}
}
pub fn is_active(&self) -> bool {
self.gesture_state.is_some()
}
pub fn info(&self) -> Option<MultiTouchInfo> {
self.gesture_state.as_ref().map(|state| {
// state.previous can be `None` when the number of simultaneous touches has just
// changed. In this case, we take `current` as `previous`, pretending that there
// was no change for the current frame.
let state_previous = state.previous.unwrap_or(state.current);
let zoom_delta = state.current.avg_distance / state_previous.avg_distance;
let zoom_delta2 = match state.pinch_type {
PinchType::Horizontal => Vec2::new(
state.current.avg_abs_distance2.x / state_previous.avg_abs_distance2.x,
1.0,
),
PinchType::Vertical => Vec2::new(
1.0,
state.current.avg_abs_distance2.y / state_previous.avg_abs_distance2.y,
),
PinchType::Proportional => Vec2::splat(zoom_delta),
};
MultiTouchInfo {
start_time: state.start_time,
start_pos: state.start_pointer_pos,
num_touches: self.active_touches.len(),
zoom_delta,
zoom_delta_2d: zoom_delta2,
rotation_delta: normalized_angle(state.current.heading - state_previous.heading),
translation_delta: state.current.avg_pos - state_previous.avg_pos,
force: state.current.avg_force,
}
})
}
fn update_gesture(&mut self, time: f64, pointer_pos: Option<Pos2>) {
if let Some(dyn_state) = self.calc_dynamic_state() {
if let Some(ref mut state) = &mut self.gesture_state {
// updating an ongoing gesture
state.previous = Some(state.current);
state.current = dyn_state;
} else if let Some(pointer_pos) = pointer_pos {
// starting a new gesture
self.gesture_state = Some(GestureState {
start_time: time,
start_pointer_pos: pointer_pos,
pinch_type: PinchType::classify(&self.active_touches),
previous: None,
current: dyn_state,
});
}
} else {
// the end of a gesture (if there is any)
self.gesture_state = None;
}
}
/// `None` if less than two fingers
fn calc_dynamic_state(&self) -> Option<DynGestureState> {
let num_touches = self.active_touches.len();
if num_touches < 2 {
None
} else {
let mut state = DynGestureState {
avg_distance: 0.0,
avg_abs_distance2: Vec2::ZERO,
avg_pos: Pos2::ZERO,
avg_force: 0.0,
heading: 0.0,
};
let num_touches_recip = 1. / num_touches as f32;
// first pass: calculate force and center of touch positions:
for touch in self.active_touches.values() {
state.avg_force += touch.force;
state.avg_pos.x += touch.pos.x;
state.avg_pos.y += touch.pos.y;
}
state.avg_force *= num_touches_recip;
state.avg_pos.x *= num_touches_recip;
state.avg_pos.y *= num_touches_recip;
// second pass: calculate distances from center:
for touch in self.active_touches.values() {
state.avg_distance += state.avg_pos.distance(touch.pos);
state.avg_abs_distance2.x += (state.avg_pos.x - touch.pos.x).abs();
state.avg_abs_distance2.y += (state.avg_pos.y - touch.pos.y).abs();
}
state.avg_distance *= num_touches_recip;
state.avg_abs_distance2 *= num_touches_recip;
// Calculate the direction from the first touch to the center position.
// This is not the perfect way of calculating the direction if more than two fingers
// are involved, but as long as all fingers rotate more or less at the same angular
// velocity, the shortcomings of this method will not be noticed. One can see the
// issues though, when touching with three or more fingers, and moving only one of them
// (it takes two hands to do this in a controlled manner). A better technique would be
// to store the current and previous directions (with reference to the center) for each
// touch individually, and then calculate the average of all individual changes in
// direction. But this approach cannot be implemented locally in this method, making
// everything a bit more complicated.
let first_touch = self.active_touches.values().next().unwrap();
state.heading = (state.avg_pos - first_touch.pos).angle();
Some(state)
}
}
}
impl TouchState {
pub fn ui(&self, ui: &mut crate::Ui) {
ui.label(format!("{:?}", self));
}
}
impl Debug for TouchState {
// This outputs less clutter than `#[derive(Debug)]`:
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (id, touch) in &self.active_touches {
f.write_fmt(format_args!("#{:?}: {:#?}\n", id, touch))?;
}
f.write_fmt(format_args!("gesture: {:#?}\n", self.gesture_state))?;
Ok(())
}
}
#[derive(Clone, Debug)]
enum PinchType {
Horizontal,
Vertical,
Proportional,
}
impl PinchType {
fn classify(touches: &BTreeMap<TouchId, ActiveTouch>) -> Self {
// For non-proportional 2d zooming:
// If the user is pinching with two fingers that have roughly the same Y coord,
// then the Y zoom is unstable and should be 1.
// Similarly, if the fingers are directly above/below each other,
// we should only zoom on the Y axis.
// If the fingers are roughly on a diagonal, we revert to the proportional zooming.
if touches.len() == 2 {
let mut touches = touches.values();
let t0 = touches.next().unwrap().pos;
let t1 = touches.next().unwrap().pos;
let dx = (t0.x - t1.x).abs();
let dy = (t0.y - t1.y).abs();
if dx > 3.0 * dy {
Self::Horizontal
} else if dy > 3.0 * dx {
Self::Vertical
} else {
Self::Proportional
}
} else {
Self::Proportional
}
}
}

View file

@ -0,0 +1,198 @@
//! Showing UI:s for egui/epaint types.
use crate::*;
pub fn font_family_ui(ui: &mut Ui, font_family: &mut FontFamily) {
let families = ui.fonts(|f| f.families());
ui.horizontal(|ui| {
for alternative in families {
let text = alternative.to_string();
ui.radio_value(font_family, alternative, text);
}
});
}
pub fn font_id_ui(ui: &mut Ui, font_id: &mut FontId) {
let families = ui.fonts(|f| f.families());
ui.horizontal(|ui| {
ui.add(Slider::new(&mut font_id.size, 4.0..=40.0).max_decimals(1));
for alternative in families {
let text = alternative.to_string();
ui.radio_value(&mut font_id.family, alternative, text);
}
});
}
// Show font texture in demo Ui
pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Response {
ui.vertical(|ui| {
let color = if ui.visuals().dark_mode {
Color32::WHITE
} else {
Color32::BLACK
};
ui.label(format!(
"Texture size: {} x {} (hover to zoom)",
width, height
));
if width <= 1 || height <= 1 {
return;
}
let mut size = vec2(width as f32, height as f32);
if size.x > ui.available_width() {
size *= ui.available_width() / size.x;
}
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
let mut mesh = Mesh::default();
mesh.add_rect_with_uv(rect, [pos2(0.0, 0.0), pos2(1.0, 1.0)].into(), color);
ui.painter().add(Shape::mesh(mesh));
let (tex_w, tex_h) = (width as f32, height as f32);
response
.on_hover_cursor(CursorIcon::ZoomIn)
.on_hover_ui_at_pointer(|ui| {
if let Some(pos) = ui.ctx().pointer_latest_pos() {
let (_id, zoom_rect) = ui.allocate_space(vec2(128.0, 128.0));
let u = remap_clamp(pos.x, rect.x_range(), 0.0..=tex_w);
let v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);
let texel_radius = 32.0;
let u = u.at_least(texel_radius).at_most(tex_w - texel_radius);
let v = v.at_least(texel_radius).at_most(tex_h - texel_radius);
let uv_rect = Rect::from_min_max(
pos2((u - texel_radius) / tex_w, (v - texel_radius) / tex_h),
pos2((u + texel_radius) / tex_w, (v + texel_radius) / tex_h),
);
let mut mesh = Mesh::default();
mesh.add_rect_with_uv(zoom_rect, uv_rect, color);
ui.painter().add(Shape::mesh(mesh));
}
});
})
.response
}
impl Widget for &epaint::stats::PaintStats {
fn ui(self, ui: &mut Ui) -> Response {
ui.vertical(|ui| {
ui.label(
"egui generates intermediate level shapes like circles and text. \
These are later tessellated into triangles.",
);
ui.add_space(10.0);
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
let epaint::stats::PaintStats {
shapes,
shape_text,
shape_path,
shape_mesh,
shape_vec,
num_callbacks,
text_shape_vertices,
text_shape_indices,
clipped_primitives,
vertices,
indices,
} = self;
ui.label("Intermediate:");
label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc");
ui.horizontal(|ui| {
label(ui, shape_text, "text");
ui.small("(mostly cached)");
});
label(ui, shape_path, "paths");
label(ui, shape_mesh, "nested meshes");
label(ui, shape_vec, "nested shapes");
ui.label(format!("{:6} callbacks", num_callbacks));
ui.add_space(10.0);
ui.label("Text shapes:");
label(ui, text_shape_vertices, "vertices");
label(ui, text_shape_indices, "indices")
.on_hover_text("Three 32-bit indices per triangles");
ui.add_space(10.0);
ui.label("Tessellated (and culled):");
label(ui, clipped_primitives, "primitives lists")
.on_hover_text("Number of separate clip rectangles");
label(ui, vertices, "vertices");
label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles");
ui.add_space(10.0);
// ui.label("Total:");
// ui.label(self.total().format(""));
})
.response
}
}
fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response {
ui.add(Label::new(alloc_info.format(what)).wrap(false))
}
impl Widget for &mut epaint::TessellationOptions {
fn ui(self, ui: &mut Ui) -> Response {
ui.vertical(|ui| {
let epaint::TessellationOptions {
feathering,
feathering_size_in_pixels,
coarse_tessellation_culling,
prerasterized_discs,
round_text_to_pixels,
debug_paint_clip_rects,
debug_paint_text_rects,
debug_ignore_clip_rects,
bezier_tolerance,
epsilon: _,
} = self;
ui.checkbox(feathering, "Feathering (antialias)")
.on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain.");
let feathering_slider = crate::Slider::new(feathering_size_in_pixels, 0.0..=10.0)
.smallest_positive(0.1)
.logarithmic(true)
.text("Feathering size in pixels");
ui.add_enabled(*feathering, feathering_slider);
ui.checkbox(prerasterized_discs, "Speed up filled circles with pre-rasterization");
ui.add(
crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0)
.logarithmic(true)
.show_value(true)
.text("Spline Tolerance"),
);
ui.collapsing("debug", |ui| {
ui.checkbox(
coarse_tessellation_culling,
"Do coarse culling in the tessellator",
);
ui.checkbox(round_text_to_pixels, "Align text positions to pixel grid")
.on_hover_text("Most text already is, so don't expect to see a large change.");
ui.checkbox(debug_ignore_clip_rects, "Ignore clip rectangles");
ui.checkbox(debug_paint_clip_rects, "Paint clip rectangles");
ui.checkbox(debug_paint_text_rects, "Paint text bounds");
});
})
.response
}
}
impl Widget for &memory::Interaction {
fn ui(self, ui: &mut Ui) -> Response {
ui.vertical(|ui| {
ui.label(format!("click_id: {:?}", self.click_id));
ui.label(format!("drag_id: {:?}", self.drag_id));
ui.label(format!("drag_is_window: {:?}", self.drag_is_window));
ui.label(format!("click_interest: {:?}", self.click_interest));
ui.label(format!("drag_interest: {:?}", self.drag_interest));
})
.response
}
}

View file

@ -1,49 +1,75 @@
//! Handles paint layers, i.e. how things
//! are sometimes painted behind or in front of other things.
use crate::{math::Rect, Id, *};
use epaint::ahash::AHashMap;
use crate::{Id, *};
use epaint::{ClippedShape, Shape};
/// Different layer categories
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Order {
/// Painted behind all floating windows
Background,
/// Special layer between panels and windows
PanelResizeLine,
/// Normal moveable windows that you reorder by click
Middle,
/// Popups, menus etc that should always be painted on top of windows
/// Foreground objects can also have tooltips
Foreground,
/// Things floating on top of everything else, like tooltips.
/// You cannot interact with these.
Tooltip,
/// Debug layer, always painted last / on top
Debug,
}
impl Order {
const COUNT: usize = 5;
const COUNT: usize = 6;
const ALL: [Order; Self::COUNT] = [
Self::Background,
Self::PanelResizeLine,
Self::Middle,
Self::Foreground,
Self::Tooltip,
Self::Debug,
];
pub const TOP: Self = Self::Debug;
#[inline(always)]
pub fn allow_interaction(&self) -> bool {
match self {
Self::Background | Self::Middle | Self::Foreground | Self::Debug => true,
Self::Background
| Self::PanelResizeLine
| Self::Middle
| Self::Foreground
| Self::Debug => true,
Self::Tooltip => false,
}
}
/// Short and readable summary
pub fn short_debug_format(&self) -> &'static str {
match self {
Self::Background => "backg",
Self::PanelResizeLine => "panel",
Self::Middle => "middl",
Self::Foreground => "foreg",
Self::Tooltip => "toolt",
Self::Debug => "debug",
}
}
}
/// An identifier for a paint layer.
/// Also acts as an identifier for [`Area`]:s.
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct LayerId {
pub order: Order,
pub id: Id,
@ -68,13 +94,23 @@ impl LayerId {
}
}
#[inline(always)]
pub fn allow_interaction(&self) -> bool {
self.order.allow_interaction()
}
/// Short and readable summary
pub fn short_debug_format(&self) -> String {
format!(
"{} {}",
self.order.short_debug_format(),
self.id.short_debug_format()
)
}
}
/// A unique identifier of a specific [`Shape`] in a [`PaintList`].
#[derive(Clone, Copy, PartialEq)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ShapeIdx(usize);
/// A list of [`Shape`]s paired with a clip rectangle.
@ -82,31 +118,36 @@ pub struct ShapeIdx(usize);
pub struct PaintList(Vec<ClippedShape>);
impl PaintList {
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Returns the index of the new [`Shape`] that can be used with `PaintList::set`.
#[inline(always)]
pub fn add(&mut self, clip_rect: Rect, shape: Shape) -> ShapeIdx {
let idx = ShapeIdx(self.0.len());
self.0.push(ClippedShape(clip_rect, shape));
idx
}
pub fn extend(&mut self, clip_rect: Rect, mut shapes: Vec<Shape>) {
self.0
.extend(shapes.drain(..).map(|shape| ClippedShape(clip_rect, shape)))
pub fn extend<I: IntoIterator<Item = Shape>>(&mut self, clip_rect: Rect, shapes: I) {
self.0.extend(
shapes
.into_iter()
.map(|shape| ClippedShape(clip_rect, shape)),
);
}
/// Modify an existing [`Shape`].
///
/// Sometimes you want to paint a frame behind some contents, but don't know how large the frame needs to be
/// until the contents have been added, and therefor also painted to the `PaintList`.
/// until the contents have been added, and therefor also painted to the [`PaintList`].
///
/// The solution is to allocate a `Shape` using `let idx = paint_list.add(cr, Shape::Noop);`
/// The solution is to allocate a [`Shape`] using `let idx = paint_list.add(cr, Shape::Noop);`
/// and then later setting it using `paint_list.set(idx, cr, frame);`.
#[inline(always)]
pub fn set(&mut self, idx: ShapeIdx, clip_rect: Rect, shape: Shape) {
assert!(idx.0 < self.0.len());
self.0[idx.0] = ClippedShape(clip_rect, shape);
}
@ -120,7 +161,7 @@ impl PaintList {
}
#[derive(Clone, Default)]
pub(crate) struct GraphicLayers([AHashMap<Id, PaintList>; Order::COUNT]);
pub(crate) struct GraphicLayers([IdMap<PaintList>; Order::COUNT]);
impl GraphicLayers {
pub fn list(&mut self, layer_id: LayerId) -> &mut PaintList {
@ -136,22 +177,22 @@ impl GraphicLayers {
let order_map = &mut self.0[order as usize];
// If a layer is empty at the start of the frame
// the nobody has added to it, and it is old and defunct.
// then nobody has added to it, and it is old and defunct.
// Free it to save memory:
order_map.retain(|_, list| !list.is_empty());
// First do the layers part of area_order:
for layer_id in area_order {
if layer_id.order == order {
if let Some(shapes) = order_map.get_mut(&layer_id.id) {
all_shapes.extend(shapes.0.drain(..));
if let Some(list) = order_map.get_mut(&layer_id.id) {
all_shapes.append(&mut list.0);
}
}
}
// Also draw areas that are missing in `area_order`:
for shapes in order_map.values_mut() {
all_shapes.extend(shapes.0.drain(..));
all_shapes.append(&mut shapes.0);
}
}

841
crates/egui/src/layout.rs Normal file
View file

@ -0,0 +1,841 @@
use crate::{egui_assert, emath::*, Align};
use std::f32::INFINITY;
// ----------------------------------------------------------------------------
/// This describes the bounds and existing contents of an [`Ui`][`crate::Ui`].
/// It is what is used and updated by [`Layout`] when adding new widgets.
#[derive(Clone, Copy, Debug)]
pub(crate) struct Region {
/// This is the minimal size of the [`Ui`](crate::Ui).
/// When adding new widgets, this will generally expand.
///
/// Always finite.
///
/// The bounding box of all child widgets, but not necessarily a tight bounding box
/// since [`Ui`](crate::Ui) can start with a non-zero min_rect size.
pub min_rect: Rect,
/// The maximum size of this [`Ui`](crate::Ui). This is a *soft max*
/// meaning new widgets will *try* not to expand beyond it,
/// but if they have to, they will.
///
/// Text will wrap at `max_rect.right()`.
/// Some widgets (like separator lines) will try to fill the full `max_rect` width of the ui.
///
/// `max_rect` will always be at least the size of `min_rect`.
///
/// If the `max_rect` size is zero, it is a signal that child widgets should be as small as possible.
/// If the `max_rect` size is infinite, it is a signal that child widgets should take up as much room as they want.
pub max_rect: Rect,
/// Where the next widget will be put.
///
/// One side of this will always be infinite: the direction in which new widgets will be added.
/// The opposing side is what is incremented.
/// The crossing sides are initialized to `max_rect`.
///
/// So one can think of `cursor` as a constraint on the available region.
///
/// If something has already been added, this will point to `style.spacing.item_spacing` beyond the latest child.
/// The cursor can thus be `style.spacing.item_spacing` pixels outside of the min_rect.
pub(crate) cursor: Rect,
}
impl Region {
/// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect.
pub fn expand_to_include_rect(&mut self, rect: Rect) {
self.min_rect = self.min_rect.union(rect);
self.max_rect = self.max_rect.union(rect);
}
/// Ensure we are big enough to contain the given X-coordinate.
/// This is sometimes useful to expand an ui to stretch to a certain place.
pub fn expand_to_include_x(&mut self, x: f32) {
self.min_rect.extend_with_x(x);
self.max_rect.extend_with_x(x);
self.cursor.extend_with_x(x);
}
/// Ensure we are big enough to contain the given Y-coordinate.
/// This is sometimes useful to expand an ui to stretch to a certain place.
pub fn expand_to_include_y(&mut self, y: f32) {
self.min_rect.extend_with_y(y);
self.max_rect.extend_with_y(y);
self.cursor.extend_with_y(y);
}
pub fn sanity_check(&self) {
egui_assert!(!self.min_rect.any_nan());
egui_assert!(!self.max_rect.any_nan());
egui_assert!(!self.cursor.any_nan());
}
}
// ----------------------------------------------------------------------------
/// Layout direction, one of [`LeftToRight`](Direction::LeftToRight), [`RightToLeft`](Direction::RightToLeft), [`TopDown`](Direction::TopDown), [`BottomUp`](Direction::BottomUp).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Direction {
LeftToRight,
RightToLeft,
TopDown,
BottomUp,
}
impl Direction {
#[inline(always)]
pub fn is_horizontal(self) -> bool {
match self {
Direction::LeftToRight | Direction::RightToLeft => true,
Direction::TopDown | Direction::BottomUp => false,
}
}
#[inline(always)]
pub fn is_vertical(self) -> bool {
match self {
Direction::LeftToRight | Direction::RightToLeft => false,
Direction::TopDown | Direction::BottomUp => true,
}
}
}
// ----------------------------------------------------------------------------
/// The layout of a [`Ui`][`crate::Ui`], e.g. "vertical & centered".
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
/// ui.label("world!");
/// ui.label("Hello");
/// });
/// # });
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
// #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Layout {
/// Main axis direction
pub main_dir: Direction,
/// If true, wrap around when reading the end of the main direction.
/// For instance, for `main_dir == Direction::LeftToRight` this will
/// wrap to a new row when we reach the right side of the `max_rect`.
pub main_wrap: bool,
/// How to align things on the main axis.
pub main_align: Align,
/// Justify the main axis?
pub main_justify: bool,
/// How to align things on the cross axis.
/// For vertical layouts: put things to left, center or right?
/// For horizontal layouts: put things to top, center or bottom?
pub cross_align: Align,
/// Justify the cross axis?
/// For vertical layouts justify mean all widgets get maximum width.
/// For horizontal layouts justify mean all widgets get maximum height.
pub cross_justify: bool,
}
impl Default for Layout {
fn default() -> Self {
// TODO(emilk): Get from `Style` instead.
Self::top_down(Align::LEFT) // This is a very euro-centric default.
}
}
/// ## Constructors
impl Layout {
/// Place elements horizontally, left to right.
///
/// The `valign` parameter controls how to align elements vertically.
#[inline(always)]
pub fn left_to_right(valign: Align) -> Self {
Self {
main_dir: Direction::LeftToRight,
main_wrap: false,
main_align: Align::Center, // looks best to e.g. center text within a button
main_justify: false,
cross_align: valign,
cross_justify: false,
}
}
/// Place elements horizontally, right to left.
///
/// The `valign` parameter controls how to align elements vertically.
#[inline(always)]
pub fn right_to_left(valign: Align) -> Self {
Self {
main_dir: Direction::RightToLeft,
main_wrap: false,
main_align: Align::Center, // looks best to e.g. center text within a button
main_justify: false,
cross_align: valign,
cross_justify: false,
}
}
/// Place elements vertically, top to bottom.
///
/// Use the provided horizontal alignment.
#[inline(always)]
pub fn top_down(halign: Align) -> Self {
Self {
main_dir: Direction::TopDown,
main_wrap: false,
main_align: Align::Center, // looks best to e.g. center text within a button
main_justify: false,
cross_align: halign,
cross_justify: false,
}
}
/// Top-down layout justified so that buttons etc fill the full available width.
#[inline(always)]
pub fn top_down_justified(halign: Align) -> Self {
Self::top_down(halign).with_cross_justify(true)
}
/// Place elements vertically, bottom up.
///
/// Use the provided horizontal alignment.
#[inline(always)]
pub fn bottom_up(halign: Align) -> Self {
Self {
main_dir: Direction::BottomUp,
main_wrap: false,
main_align: Align::Center, // looks best to e.g. center text within a button
main_justify: false,
cross_align: halign,
cross_justify: false,
}
}
#[inline(always)]
pub fn from_main_dir_and_cross_align(main_dir: Direction, cross_align: Align) -> Self {
Self {
main_dir,
main_wrap: false,
main_align: Align::Center, // looks best to e.g. center text within a button
main_justify: false,
cross_align,
cross_justify: false,
}
}
/// For when you want to add a single widget to a layout, and that widget
/// should use up all available space.
///
/// Only one widget may be added to the inner `Ui`!
#[inline(always)]
pub fn centered_and_justified(main_dir: Direction) -> Self {
Self {
main_dir,
main_wrap: false,
main_align: Align::Center,
main_justify: true,
cross_align: Align::Center,
cross_justify: true,
}
}
/// Wrap widgets when we overflow the main axis?
///
/// For instance, for left-to-right layouts, setting this to `true` will
/// put widgets on a new row if we would overflow the right side of [`crate::Ui::max_rect`].
#[inline(always)]
pub fn with_main_wrap(self, main_wrap: bool) -> Self {
Self { main_wrap, ..self }
}
/// The alignment to use on the main axis.
#[inline(always)]
pub fn with_main_align(self, main_align: Align) -> Self {
Self { main_align, ..self }
}
/// The alignment to use on the cross axis.
///
/// The "cross" axis is the one orthogonal to the main axis.
/// For instance: in left-to-right layout, the main axis is horizontal and the cross axis is vertical.
#[inline(always)]
pub fn with_cross_align(self, cross_align: Align) -> Self {
Self {
cross_align,
..self
}
}
/// Justify widgets on the main axis?
///
/// Justify here means "take up all available space".
#[inline(always)]
pub fn with_main_justify(self, main_justify: bool) -> Self {
Self {
main_justify,
..self
}
}
/// Justify widgets along the cross axis?
///
/// Justify here means "take up all available space".
///
/// The "cross" axis is the one orthogonal to the main axis.
/// For instance: in left-to-right layout, the main axis is horizontal and the cross axis is vertical.
#[inline(always)]
pub fn with_cross_justify(self, cross_justify: bool) -> Self {
Self {
cross_justify,
..self
}
}
}
/// ## Inspectors
impl Layout {
#[inline(always)]
pub fn main_dir(&self) -> Direction {
self.main_dir
}
#[inline(always)]
pub fn main_wrap(&self) -> bool {
self.main_wrap
}
#[inline(always)]
pub fn cross_align(&self) -> Align {
self.cross_align
}
#[inline(always)]
pub fn cross_justify(&self) -> bool {
self.cross_justify
}
#[inline(always)]
pub fn is_horizontal(&self) -> bool {
self.main_dir().is_horizontal()
}
#[inline(always)]
pub fn is_vertical(&self) -> bool {
self.main_dir().is_vertical()
}
pub fn prefer_right_to_left(&self) -> bool {
self.main_dir == Direction::RightToLeft
|| self.main_dir.is_vertical() && self.cross_align == Align::Max
}
/// e.g. for adjusting the placement of something.
/// * in horizontal layout: left or right?
/// * in vertical layout: same as [`Self::horizontal_align`].
pub fn horizontal_placement(&self) -> Align {
match self.main_dir {
Direction::LeftToRight => Align::LEFT,
Direction::RightToLeft => Align::RIGHT,
Direction::TopDown | Direction::BottomUp => self.cross_align,
}
}
/// e.g. for when aligning text within a button.
pub fn horizontal_align(&self) -> Align {
if self.is_horizontal() {
self.main_align
} else {
self.cross_align
}
}
/// e.g. for when aligning text within a button.
pub fn vertical_align(&self) -> Align {
if self.is_vertical() {
self.main_align
} else {
self.cross_align
}
}
/// e.g. for when aligning text within a button.
fn align2(&self) -> Align2 {
Align2([self.horizontal_align(), self.vertical_align()])
}
pub fn horizontal_justify(&self) -> bool {
if self.is_horizontal() {
self.main_justify
} else {
self.cross_justify
}
}
pub fn vertical_justify(&self) -> bool {
if self.is_vertical() {
self.main_justify
} else {
self.cross_justify
}
}
}
/// ## Doing layout
impl Layout {
pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect {
egui_assert!(size.x >= 0.0 && size.y >= 0.0);
egui_assert!(!outer.is_negative());
self.align2().align_size_within_rect(size, outer)
}
fn initial_cursor(&self, max_rect: Rect) -> Rect {
let mut cursor = max_rect;
match self.main_dir {
Direction::LeftToRight => {
cursor.max.x = INFINITY;
}
Direction::RightToLeft => {
cursor.min.x = -INFINITY;
}
Direction::TopDown => {
cursor.max.y = INFINITY;
}
Direction::BottomUp => {
cursor.min.y = -INFINITY;
}
}
cursor
}
pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region {
egui_assert!(!max_rect.any_nan());
egui_assert!(max_rect.is_finite());
let mut region = Region {
min_rect: Rect::NOTHING, // temporary
max_rect,
cursor: self.initial_cursor(max_rect),
};
let seed = self.next_widget_position(&region);
region.min_rect = Rect::from_center_size(seed, Vec2::ZERO);
region
}
pub(crate) fn available_rect_before_wrap(&self, region: &Region) -> Rect {
self.available_from_cursor_max_rect(region.cursor, region.max_rect)
}
/// Amount of space available for a widget.
/// For wrapping layouts, this is the maximum (after wrap).
pub(crate) fn available_size(&self, r: &Region) -> Vec2 {
if self.main_wrap {
if self.main_dir.is_horizontal() {
vec2(r.max_rect.width(), r.cursor.height())
} else {
vec2(r.cursor.width(), r.max_rect.height())
}
} else {
self.available_from_cursor_max_rect(r.cursor, r.max_rect)
.size()
}
}
/// Given the cursor in the region, how much space is available
/// for the next widget?
fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect {
egui_assert!(!cursor.any_nan());
egui_assert!(!max_rect.any_nan());
egui_assert!(max_rect.is_finite());
// NOTE: in normal top-down layout the cursor has moved below the current max_rect,
// but the available shouldn't be negative.
// ALSO: with wrapping layouts, cursor jumps to new row before expanding max_rect.
let mut avail = max_rect;
match self.main_dir {
Direction::LeftToRight => {
avail.min.x = cursor.min.x;
avail.max.x = avail.max.x.max(cursor.min.x);
avail.max.x = avail.max.x.max(avail.min.x);
avail.max.y = avail.max.y.max(avail.min.y);
}
Direction::RightToLeft => {
avail.max.x = cursor.max.x;
avail.min.x = avail.min.x.min(cursor.max.x);
avail.min.x = avail.min.x.min(avail.max.x);
avail.max.y = avail.max.y.max(avail.min.y);
}
Direction::TopDown => {
avail.min.y = cursor.min.y;
avail.max.y = avail.max.y.max(cursor.min.y);
avail.max.x = avail.max.x.max(avail.min.x);
avail.max.y = avail.max.y.max(avail.min.y);
}
Direction::BottomUp => {
avail.max.y = cursor.max.y;
avail.min.y = avail.min.y.min(cursor.max.y);
avail.max.x = avail.max.x.max(avail.min.x);
avail.min.y = avail.min.y.min(avail.max.y);
}
}
// We can use the cursor to restrict the available region.
// For instance, we use this to restrict the available space of a parent Ui
// after adding a panel to it.
// We also use it for wrapping layouts.
avail = avail.intersect(cursor);
// Make sure it isn't negative:
if avail.max.x < avail.min.x {
let x = 0.5 * (avail.min.x + avail.max.x);
avail.min.x = x;
avail.max.x = x;
}
if avail.max.y < avail.min.y {
let y = 0.5 * (avail.min.y + avail.max.y);
avail.min.y = y;
avail.max.y = y;
}
egui_assert!(!avail.any_nan());
avail
}
/// Returns where to put the next widget that is of the given size.
/// The returned `frame_rect` [`Rect`] will always be justified along the cross axis.
/// This is what you then pass to `advance_after_rects`.
/// Use `justify_and_align` to get the inner `widget_rect`.
pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect {
region.sanity_check();
egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
if self.main_wrap {
let available_size = self.available_rect_before_wrap(region).size();
let Region {
mut cursor,
mut max_rect,
min_rect,
} = *region;
match self.main_dir {
Direction::LeftToRight => {
if available_size.x < child_size.x && max_rect.left() < cursor.left() {
// New row
let new_row_height = cursor.height().max(child_size.y);
// let new_top = cursor.bottom() + spacing.y;
let new_top = min_rect.bottom() + spacing.y; // tighter packing
cursor = Rect::from_min_max(
pos2(max_rect.left(), new_top),
pos2(INFINITY, new_top + new_row_height),
);
max_rect.max.y = max_rect.max.y.max(cursor.max.y);
}
}
Direction::RightToLeft => {
if available_size.x < child_size.x && cursor.right() < max_rect.right() {
// New row
let new_row_height = cursor.height().max(child_size.y);
// let new_top = cursor.bottom() + spacing.y;
let new_top = min_rect.bottom() + spacing.y; // tighter packing
cursor = Rect::from_min_max(
pos2(-INFINITY, new_top),
pos2(max_rect.right(), new_top + new_row_height),
);
max_rect.max.y = max_rect.max.y.max(cursor.max.y);
}
}
Direction::TopDown => {
if available_size.y < child_size.y && max_rect.top() < cursor.top() {
// New column
let new_col_width = cursor.width().max(child_size.x);
cursor = Rect::from_min_max(
pos2(cursor.right() + spacing.x, max_rect.top()),
pos2(cursor.right() + spacing.x + new_col_width, INFINITY),
);
max_rect.max.x = max_rect.max.x.max(cursor.max.x);
}
}
Direction::BottomUp => {
if available_size.y < child_size.y && cursor.bottom() < max_rect.bottom() {
// New column
let new_col_width = cursor.width().max(child_size.x);
cursor = Rect::from_min_max(
pos2(cursor.right() + spacing.x, -INFINITY),
pos2(
cursor.right() + spacing.x + new_col_width,
max_rect.bottom(),
),
);
max_rect.max.x = max_rect.max.x.max(cursor.max.x);
}
}
}
// Use the new cursor:
let region = Region {
min_rect,
max_rect,
cursor,
};
self.next_frame_ignore_wrap(&region, child_size)
} else {
self.next_frame_ignore_wrap(region, child_size)
}
}
fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect {
region.sanity_check();
egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
let available_rect = self.available_rect_before_wrap(region);
let mut frame_size = child_size;
if (self.is_vertical() && self.horizontal_align() == Align::Center)
|| self.horizontal_justify()
{
frame_size.x = frame_size.x.max(available_rect.width()); // fill full width
}
if (self.is_horizontal() && self.vertical_align() == Align::Center)
|| self.vertical_justify()
{
frame_size.y = frame_size.y.max(available_rect.height()); // fill full height
}
let align2 = match self.main_dir {
Direction::LeftToRight => Align2([Align::LEFT, self.vertical_align()]),
Direction::RightToLeft => Align2([Align::RIGHT, self.vertical_align()]),
Direction::TopDown => Align2([self.horizontal_align(), Align::TOP]),
Direction::BottomUp => Align2([self.horizontal_align(), Align::BOTTOM]),
};
let mut frame_rect = align2.align_size_within_rect(frame_size, available_rect);
if self.is_horizontal() && frame_rect.top() < region.cursor.top() {
// for horizontal layouts we always want to expand down,
// or we will overlap the row above.
// This is a bit hacky. Maybe we should do it for vertical layouts too.
frame_rect = frame_rect.translate(Vec2::Y * (region.cursor.top() - frame_rect.top()));
}
egui_assert!(!frame_rect.any_nan());
egui_assert!(!frame_rect.is_negative());
frame_rect
}
/// Apply justify (fill width/height) and/or alignment after calling `next_space`.
pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect {
egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
egui_assert!(!frame.is_negative());
if self.horizontal_justify() {
child_size.x = child_size.x.at_least(frame.width()); // fill full width
}
if self.vertical_justify() {
child_size.y = child_size.y.at_least(frame.height()); // fill full height
}
self.align_size_within_rect(child_size, frame)
}
pub(crate) fn next_widget_space_ignore_wrap_justify(
&self,
region: &Region,
size: Vec2,
) -> Rect {
let frame = self.next_frame_ignore_wrap(region, size);
let rect = self.align_size_within_rect(size, frame);
egui_assert!(!rect.any_nan());
egui_assert!(!rect.is_negative());
egui_assert!((rect.width() - size.x).abs() < 1.0 || size.x == f32::INFINITY);
egui_assert!((rect.height() - size.y).abs() < 1.0 || size.y == f32::INFINITY);
rect
}
/// Where would the next tiny widget be centered?
pub(crate) fn next_widget_position(&self, region: &Region) -> Pos2 {
self.next_widget_space_ignore_wrap_justify(region, Vec2::ZERO)
.center()
}
/// Advance the cursor by this many points, and allocate in region.
pub(crate) fn advance_cursor(&self, region: &mut Region, amount: f32) {
match self.main_dir {
Direction::LeftToRight => {
region.cursor.min.x += amount;
region.expand_to_include_x(region.cursor.min.x);
}
Direction::RightToLeft => {
region.cursor.max.x -= amount;
region.expand_to_include_x(region.cursor.max.x);
}
Direction::TopDown => {
region.cursor.min.y += amount;
region.expand_to_include_y(region.cursor.min.y);
}
Direction::BottomUp => {
region.cursor.max.y -= amount;
region.expand_to_include_y(region.cursor.max.y);
}
}
}
/// Advance cursor after a widget was added to a specific rectangle.
///
/// * `frame_rect`: the frame inside which a widget was e.g. centered
/// * `widget_rect`: the actual rect used by the widget
pub(crate) fn advance_after_rects(
&self,
cursor: &mut Rect,
frame_rect: Rect,
widget_rect: Rect,
item_spacing: Vec2,
) {
egui_assert!(!cursor.any_nan());
if self.main_wrap {
if cursor.intersects(frame_rect.shrink(1.0)) {
// make row/column larger if necessary
*cursor = cursor.union(frame_rect);
} else {
// this is a new row or column. We temporarily use NAN for what will be filled in later.
match self.main_dir {
Direction::LeftToRight => {
*cursor = Rect::from_min_max(
pos2(f32::NAN, frame_rect.min.y),
pos2(INFINITY, frame_rect.max.y),
);
}
Direction::RightToLeft => {
*cursor = Rect::from_min_max(
pos2(-INFINITY, frame_rect.min.y),
pos2(f32::NAN, frame_rect.max.y),
);
}
Direction::TopDown => {
*cursor = Rect::from_min_max(
pos2(frame_rect.min.x, f32::NAN),
pos2(frame_rect.max.x, INFINITY),
);
}
Direction::BottomUp => {
*cursor = Rect::from_min_max(
pos2(frame_rect.min.x, -INFINITY),
pos2(frame_rect.max.x, f32::NAN),
);
}
};
}
} else {
// Make sure we also expand where we consider adding things (the cursor):
if self.is_horizontal() {
cursor.min.y = cursor.min.y.min(frame_rect.min.y);
cursor.max.y = cursor.max.y.max(frame_rect.max.y);
} else {
cursor.min.x = cursor.min.x.min(frame_rect.min.x);
cursor.max.x = cursor.max.x.max(frame_rect.max.x);
}
}
match self.main_dir {
Direction::LeftToRight => {
cursor.min.x = widget_rect.max.x + item_spacing.x;
}
Direction::RightToLeft => {
cursor.max.x = widget_rect.min.x - item_spacing.x;
}
Direction::TopDown => {
cursor.min.y = widget_rect.max.y + item_spacing.y;
}
Direction::BottomUp => {
cursor.max.y = widget_rect.min.y - item_spacing.y;
}
};
}
/// Move to the next row in a wrapping layout.
/// Otherwise does nothing.
pub(crate) fn end_row(&mut self, region: &mut Region, spacing: Vec2) {
if self.main_wrap {
match self.main_dir {
Direction::LeftToRight => {
let new_top = region.cursor.bottom() + spacing.y;
region.cursor = Rect::from_min_max(
pos2(region.max_rect.left(), new_top),
pos2(INFINITY, new_top + region.cursor.height()),
);
}
Direction::RightToLeft => {
let new_top = region.cursor.bottom() + spacing.y;
region.cursor = Rect::from_min_max(
pos2(-INFINITY, new_top),
pos2(region.max_rect.right(), new_top + region.cursor.height()),
);
}
Direction::TopDown | Direction::BottomUp => {}
}
}
}
/// Set row height in horizontal wrapping layout.
pub(crate) fn set_row_height(&mut self, region: &mut Region, height: f32) {
if self.main_wrap && self.is_horizontal() {
region.cursor.max.y = region.cursor.min.y + height;
}
}
}
// ----------------------------------------------------------------------------
/// ## Debug stuff
impl Layout {
/// Shows where the next widget is going to be placed
pub(crate) fn paint_text_at_cursor(
&self,
painter: &crate::Painter,
region: &Region,
stroke: epaint::Stroke,
text: impl ToString,
) {
let cursor = region.cursor;
let next_pos = self.next_widget_position(region);
let l = 64.0;
let align = match self.main_dir {
Direction::LeftToRight => {
painter.line_segment([cursor.left_top(), cursor.left_bottom()], stroke);
painter.arrow(next_pos, vec2(l, 0.0), stroke);
Align2([Align::LEFT, self.vertical_align()])
}
Direction::RightToLeft => {
painter.line_segment([cursor.right_top(), cursor.right_bottom()], stroke);
painter.arrow(next_pos, vec2(-l, 0.0), stroke);
Align2([Align::RIGHT, self.vertical_align()])
}
Direction::TopDown => {
painter.line_segment([cursor.left_top(), cursor.right_top()], stroke);
painter.arrow(next_pos, vec2(0.0, l), stroke);
Align2([self.horizontal_align(), Align::TOP])
}
Direction::BottomUp => {
painter.line_segment([cursor.left_bottom(), cursor.right_bottom()], stroke);
painter.arrow(next_pos, vec2(0.0, -l), stroke);
Align2([self.horizontal_align(), Align::BOTTOM])
}
};
painter.debug_text(next_pos, align, stroke.color, text);
}
}

573
crates/egui/src/lib.rs Normal file
View file

@ -0,0 +1,573 @@
//! `egui`: an easy-to-use GUI in pure Rust!
//!
//! Try the live web demo: <https://www.egui.rs/#demo>. Read more about egui at <https://github.com/emilk/egui>.
//!
//! `egui` is in heavy development, with each new version having breaking changes.
//! You need to have rust 1.62.0 or later to use `egui`.
//!
//! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template)
//! which uses [`eframe`](https://docs.rs/eframe).
//!
//! To create a GUI using egui you first need a [`Context`] (by convention referred to by `ctx`).
//! Then you add a [`Window`] or a [`SidePanel`] to get a [`Ui`], which is what you'll be using to add all the buttons and labels that you need.
//!
//!
//! # Using egui
//!
//! To see what is possible to build with egui you can check out the online demo at <https://www.egui.rs/#demo>.
//!
//! If you like the "learning by doing" approach, clone <https://github.com/emilk/eframe_template> and get started using egui right away.
//!
//! ### A simple example
//!
//! Here is a simple counter that can be incremented and decremented using two buttons:
//! ```
//! fn ui_counter(ui: &mut egui::Ui, counter: &mut i32) {
//! // Put the buttons and label on the same row:
//! ui.horizontal(|ui| {
//! if ui.button("-").clicked() {
//! *counter -= 1;
//! }
//! ui.label(counter.to_string());
//! if ui.button("+").clicked() {
//! *counter += 1;
//! }
//! });
//! }
//! ```
//!
//! In some GUI frameworks this would require defining multiple types and functions with callbacks or message handlers,
//! but thanks to `egui` being immediate mode everything is one self-contained function!
//!
//! ### Getting a [`Ui`]
//!
//! Use one of [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`] to
//! get access to an [`Ui`] where you can put widgets. For example:
//!
//! ```
//! # egui::__run_test_ctx(|ctx| {
//! egui::CentralPanel::default().show(&ctx, |ui| {
//! ui.add(egui::Label::new("Hello World!"));
//! ui.label("A shorter and more convenient way to add a label.");
//! if ui.button("Click me").clicked() {
//! // take some action here
//! }
//! });
//! # });
//! ```
//!
//! ### Quick start
//!
//! ```
//! # egui::__run_test_ui(|ui| {
//! # let mut my_string = String::new();
//! # let mut my_boolean = true;
//! # let mut my_f32 = 42.0;
//! ui.label("This is a label");
//! ui.hyperlink("https://github.com/emilk/egui");
//! ui.text_edit_singleline(&mut my_string);
//! if ui.button("Click me").clicked() { }
//! ui.add(egui::Slider::new(&mut my_f32, 0.0..=100.0));
//! ui.add(egui::DragValue::new(&mut my_f32));
//!
//! ui.checkbox(&mut my_boolean, "Checkbox");
//!
//! #[derive(PartialEq)]
//! enum Enum { First, Second, Third }
//! # let mut my_enum = Enum::First;
//! ui.horizontal(|ui| {
//! ui.radio_value(&mut my_enum, Enum::First, "First");
//! ui.radio_value(&mut my_enum, Enum::Second, "Second");
//! ui.radio_value(&mut my_enum, Enum::Third, "Third");
//! });
//!
//! ui.separator();
//!
//! # let my_image = egui::TextureId::default();
//! ui.image(my_image, [640.0, 480.0]);
//!
//! ui.collapsing("Click to see what is hidden!", |ui| {
//! ui.label("Not much, as it turns out");
//! });
//! # });
//! ```
//!
//! ## Conventions
//!
//! Conventions unless otherwise specified:
//!
//! * angles are in radians
//! * `Vec2::X` is right and `Vec2::Y` is down.
//! * `Pos2::ZERO` is left top.
//! * Positions and sizes are measured in _points_. Each point may consist of many physical pixels.
//!
//! # Integrating with egui
//!
//! Most likely you are using an existing `egui` backend/integration such as [`eframe`](https://docs.rs/eframe), [`bevy_egui`](https://docs.rs/bevy_egui),
//! or [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad),
//! but if you want to integrate `egui` into a new game engine, this is the section for you.
//!
//! To write your own integration for egui you need to do this:
//!
//! ``` no_run
//! # fn handle_platform_output(_: egui::PlatformOutput) {}
//! # fn gather_input() -> egui::RawInput { egui::RawInput::default() }
//! # fn paint(textures_detla: egui::TexturesDelta, _: Vec<egui::ClippedPrimitive>) {}
//! let mut ctx = egui::Context::default();
//!
//! // Game loop:
//! loop {
//! let raw_input: egui::RawInput = gather_input();
//!
//! let full_output = ctx.run(raw_input, |ctx| {
//! egui::CentralPanel::default().show(&ctx, |ui| {
//! ui.label("Hello world!");
//! if ui.button("Click me").clicked() {
//! // take some action here
//! }
//! });
//! });
//! handle_platform_output(full_output.platform_output);
//! let clipped_primitives = ctx.tessellate(full_output.shapes); // create triangles to paint
//! paint(full_output.textures_delta, clipped_primitives);
//! }
//! ```
//!
//!
//! # Understanding immediate mode
//!
//! `egui` is an immediate mode GUI library. It is useful to fully grok what "immediate mode" implies.
//!
//! Here is an example to illustrate it:
//!
//! ```
//! # egui::__run_test_ui(|ui| {
//! if ui.button("click me").clicked() {
//! take_action()
//! }
//! # });
//! # fn take_action() {}
//! ```
//!
//! This code is being executed each frame at maybe 60 frames per second.
//! Each frame egui does these things:
//!
//! * lays out the letters `click me` in order to figure out the size of the button
//! * decides where on screen to place the button
//! * check if the mouse is hovering or clicking that location
//! * chose button colors based on if it is being hovered or clicked
//! * add a [`Shape::Rect`] and [`Shape::Text`] to the list of shapes to be painted later this frame
//! * return a [`Response`] with the [`clicked`](`Response::clicked`) member so the user can check for interactions
//!
//! There is no button being created and stored somewhere.
//! The only output of this call is some colored shapes, and a [`Response`].
//!
//! Similarly, consider this code:
//!
//! ```
//! # egui::__run_test_ui(|ui| {
//! # let mut value: f32 = 0.0;
//! ui.add(egui::Slider::new(&mut value, 0.0..=100.0).text("My value"));
//! # });
//! ```
//!
//! Here egui will read `value` to display the slider, then look if the mouse is dragging the slider and if so change the `value`.
//! Note that `egui` does not store the slider value for you - it only displays the current value, and changes it
//! by how much the slider has been dragged in the previous few milliseconds.
//! This means it is responsibility of the egui user to store the state (`value`) so that it persists between frames.
//!
//! It can be useful to read the code for the toggle switch example widget to get a better understanding
//! of how egui works: <https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/demo/toggle_switch.rs>.
//!
//! Read more about the pros and cons of immediate mode at <https://github.com/emilk/egui#why-immediate-mode>.
//!
//! # Misc
//!
//! ## How widgets works
//!
//! ```
//! # egui::__run_test_ui(|ui| {
//! if ui.button("click me").clicked() { take_action() }
//! # });
//! # fn take_action() {}
//! ```
//!
//! is short for
//!
//! ```
//! # egui::__run_test_ui(|ui| {
//! let button = egui::Button::new("click me");
//! if ui.add(button).clicked() { take_action() }
//! # });
//! # fn take_action() {}
//! ```
//!
//! which is short for
//!
//! ```
//! # use egui::Widget;
//! # egui::__run_test_ui(|ui| {
//! let button = egui::Button::new("click me");
//! let response = button.ui(ui);
//! if response.clicked() { take_action() }
//! # });
//! # fn take_action() {}
//! ```
//!
//! [`Button`] uses the builder pattern to create the data required to show it. The [`Button`] is then discarded.
//!
//! [`Button`] implements `trait` [`Widget`], which looks like this:
//! ```
//! # use egui::*;
//! pub trait Widget {
//! /// Allocate space, interact, paint, and return a [`Response`].
//! fn ui(self, ui: &mut Ui) -> Response;
//! }
//! ```
//!
//! ## Auto-sizing panels and windows
//! In egui, all panels and windows auto-shrink to fit the content.
//! If the window or panel is also resizable, this can lead to a weird behavior
//! where you can drag the edge of the panel/window to make it larger, and
//! when you release the panel/window shrinks again.
//! This is an artifact of immediate mode, and here are some alternatives on how to avoid it:
//!
//! 1. Turn off resizing with [`Window::resizable`], [`SidePanel::resizable`], [`TopBottomPanel::resizable`].
//! 2. Wrap your panel contents in a [`ScrollArea`], or use [`Window::vscroll`] and [`Window::hscroll`].
//! 3. Use a justified layout:
//!
//! ```
//! # egui::__run_test_ui(|ui| {
//! ui.with_layout(egui::Layout::top_down_justified(egui::Align::Center), |ui| {
//! ui.button("I am becoming wider as needed");
//! });
//! # });
//! ```
//!
//! 4. Fill in extra space with emptiness:
//!
//! ```
//! # egui::__run_test_ui(|ui| {
//! ui.allocate_space(ui.available_size()); // put this LAST in your panel/window code
//! # });
//! ```
//!
//! ## Sizes
//! You can control the size of widgets using [`Ui::add_sized`].
//!
//! ```
//! # egui::__run_test_ui(|ui| {
//! # let mut my_value = 0.0_f32;
//! ui.add_sized([40.0, 20.0], egui::DragValue::new(&mut my_value));
//! # });
//! ```
//!
//! ## Code snippets
//!
//! ```
//! # egui::__run_test_ui(|ui| {
//! # let mut some_bool = true;
//! // Miscellaneous tips and tricks
//!
//! ui.horizontal_wrapped(|ui| {
//! ui.spacing_mut().item_spacing.x = 0.0; // remove spacing between widgets
//! // `radio_value` also works for enums, integers, and more.
//! ui.radio_value(&mut some_bool, false, "Off");
//! ui.radio_value(&mut some_bool, true, "On");
//! });
//!
//! ui.group(|ui| {
//! ui.label("Within a frame");
//! ui.set_min_height(200.0);
//! });
//!
//! // A `scope` creates a temporary [`Ui`] in which you can change settings:
//! ui.scope(|ui| {
//! ui.visuals_mut().override_text_color = Some(egui::Color32::RED);
//! ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
//! ui.style_mut().wrap = Some(false);
//!
//! ui.label("This text will be red, monospace, and won't wrap to a new line");
//! }); // the temporary settings are reverted here
//! # });
//! ```
//!
//! ## Feature flags
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!
#![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)]
mod animation_manager;
pub mod containers;
mod context;
mod data;
mod frame_state;
pub(crate) mod grid;
pub mod gui_zoom;
mod id;
mod input_state;
pub mod introspection;
pub mod layers;
mod layout;
mod memory;
pub mod menu;
pub mod os;
mod painter;
pub(crate) mod placer;
mod response;
mod sense;
pub mod style;
mod ui;
pub mod util;
pub mod widget_text;
pub mod widgets;
#[cfg(feature = "accesskit")]
pub use accesskit;
pub use epaint;
pub use epaint::ecolor;
pub use epaint::emath;
#[cfg(feature = "color-hex")]
pub use ecolor::hex_color;
pub use ecolor::{Color32, Rgba};
pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2};
pub use epaint::{
mutex,
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
textures::{TextureFilter, TextureOptions, TexturesDelta},
ClippedPrimitive, ColorImage, FontImage, ImageData, Mesh, PaintCallback, PaintCallbackInfo,
Rounding, Shape, Stroke, TextureHandle, TextureId,
};
pub mod text {
pub use crate::text_edit::CCursorRange;
pub use epaint::text::{
cursor::CCursor, FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob,
LayoutSection, TextFormat, TAB_SIZE,
};
}
pub use {
containers::*,
context::Context,
data::{
input::*,
output::{self, CursorIcon, FullOutput, PlatformOutput, WidgetInfo},
},
grid::Grid,
id::{Id, IdMap},
input_state::{InputState, MultiTouchInfo, PointerState},
layers::{LayerId, Order},
layout::*,
memory::{Memory, Options},
painter::Painter,
response::{InnerResponse, Response},
sense::Sense,
style::{FontSelection, Margin, Style, TextStyle, Visuals},
text::{Galley, TextFormat},
ui::Ui,
widget_text::{RichText, WidgetText},
widgets::*,
};
// ----------------------------------------------------------------------------
/// Helper function that adds a label when compiling with debug assertions enabled.
pub fn warn_if_debug_build(ui: &mut crate::Ui) {
if cfg!(debug_assertions) {
ui.label(
RichText::new("⚠ Debug build ⚠")
.small()
.color(ui.visuals().warn_fg_color),
)
.on_hover_text("egui was compiled with debug assertions enabled.");
}
}
// ----------------------------------------------------------------------------
/// Create a [`Hyperlink`](crate::Hyperlink) to the current [`file!()`] (and line) on Github
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// ui.add(egui::github_link_file_line!("https://github.com/YOUR/PROJECT/blob/master/", "(source code)"));
/// # });
/// ```
#[macro_export]
macro_rules! github_link_file_line {
($github_url: expr, $label: expr) => {{
let url = format!("{}{}#L{}", $github_url, file!(), line!());
$crate::Hyperlink::from_label_and_url($label, url)
}};
}
/// Create a [`Hyperlink`](crate::Hyperlink) to the current [`file!()`] on github.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// ui.add(egui::github_link_file!("https://github.com/YOUR/PROJECT/blob/master/", "(source code)"));
/// # });
/// ```
#[macro_export]
macro_rules! github_link_file {
($github_url: expr, $label: expr) => {{
let url = format!("{}{}", $github_url, file!());
$crate::Hyperlink::from_label_and_url($label, url)
}};
}
// ----------------------------------------------------------------------------
/// Show debug info on hover when [`Context::set_debug_on_hover`] has been turned on.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// // Turn on tracing of widgets
/// ui.ctx().set_debug_on_hover(true);
///
/// /// Show [`std::file`], [`std::line`] and argument on hover
/// egui::trace!(ui, "MyWindow");
///
/// /// Show [`std::file`] and [`std::line`] on hover
/// egui::trace!(ui);
/// # });
/// ```
#[macro_export]
macro_rules! trace {
($ui: expr) => {{
$ui.trace_location(format!("{}:{}", file!(), line!()))
}};
($ui: expr, $label: expr) => {{
$ui.trace_location(format!("{} - {}:{}", $label, file!(), line!()))
}};
}
// ----------------------------------------------------------------------------
/// An assert that is only active when `egui` is compiled with the `extra_asserts` feature
/// or with the `extra_debug_asserts` feature in debug builds.
#[macro_export]
macro_rules! egui_assert {
($($arg: tt)*) => {
if cfg!(any(
feature = "extra_asserts",
all(feature = "extra_debug_asserts", debug_assertions),
)) {
assert!($($arg)*);
}
}
}
// ----------------------------------------------------------------------------
/// The default egui fonts supports around 1216 emojis in total.
/// Here are some of the most useful:
/// ∞⊗⎗⎘⎙⏏⏴⏵⏶⏷
/// ⏩⏪⏭⏮⏸⏹⏺■▶📾🔀🔁🔃
/// ☀☁★☆☐☑☜☝☞☟⛃⛶✔
/// ↺↻⟲⟳⬅➡⬆⬇⬈⬉⬊⬋⬌⬍⮨⮩⮪⮫
/// ♡
/// 📅📆
/// 📈📉📊
/// 📋📌📎📤📥🔆
/// 🔈🔉🔊🔍🔎🔗🔘
/// 🕓🖧🖩🖮🖱🖴🖵🖼🗀🗁🗋🗐🗑🗙🚫❓
///
/// NOTE: In egui all emojis are monochrome!
///
/// You can explore them all in the Font Book in [the online demo](https://www.egui.rs/#demo).
///
/// In addition, egui supports a few special emojis that are not part of the unicode standard.
/// This module contains some of them:
pub mod special_emojis {
/// Tux, the Linux penguin.
pub const OS_LINUX: char = '🐧';
/// The Windows logo.
pub const OS_WINDOWS: char = '';
/// The Android logo.
pub const OS_ANDROID: char = '';
/// The Apple logo.
pub const OS_APPLE: char = '';
/// The Github logo.
pub const GITHUB: char = '';
/// The Twitter bird.
pub const TWITTER: char = '';
/// The word `git`.
pub const GIT: char = '';
// I really would like to have ferris here.
}
/// The different types of built-in widgets in egui
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[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.
///
/// If this is something you think should be added, file an issue.
Other,
}
// ----------------------------------------------------------------------------
/// For use in tests; especially doctests.
pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) {
let ctx = Context::default();
ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time)
let _ = ctx.run(Default::default(), |ctx| {
run_ui(ctx);
});
}
/// For use in tests; especially doctests.
pub fn __run_test_ui(mut add_contents: impl FnMut(&mut Ui)) {
let ctx = Context::default();
ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time)
let _ = ctx.run(Default::default(), |ctx| {
crate::CentralPanel::default().show(ctx, |ui| {
add_contents(ui);
});
});
}
#[cfg(feature = "accesskit")]
pub fn accesskit_root_id() -> Id {
Id::new("accesskit_root")
}

647
crates/egui/src/memory.rs Normal file
View file

@ -0,0 +1,647 @@
use crate::{area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style};
// ----------------------------------------------------------------------------
/// The data that egui persists between frames.
///
/// This includes window positions and sizes,
/// how far the user has scrolled in a [`ScrollArea`](crate::ScrollArea) etc.
///
/// If you want this to persist when closing your app you should serialize [`Memory`] and store it.
/// For this you need to enable the `persistence`.
///
/// If you want to store data for your widgets, you should look at [`Memory::data`]
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Memory {
pub options: Options,
/// This map stores some superficial state for all widgets with custom [`Id`]s.
///
/// This includes storing if a [`crate::CollapsingHeader`] is open, how far scrolled a
/// [`crate::ScrollArea`] is, where the cursor in a [`crate::TextEdit`] is, etc.
///
/// This is NOT meant to store any important data. Store that in your own structures!
///
/// Each read clones the data, so keep your values cheap to clone.
/// If you want to store a lot of data you should wrap it in `Arc<Mutex<…>>` so it is cheap to clone.
///
/// This will be saved between different program runs if you use the `persistence` feature.
///
/// To store a state common for all your widgets (a singleton), use [`Id::null`] as the key.
pub data: crate::util::IdTypeMap,
// ------------------------------------------
/// Can be used to cache computations from one frame to another.
///
/// This is for saving CPU when you have something that may take 1-100ms to compute.
/// Things that are very slow (>100ms) should instead be done async (i.e. in another thread)
/// so as not to lock the UI thread.
///
/// ```
/// use egui::util::cache::{ComputerMut, FrameCache};
///
/// #[derive(Default)]
/// struct CharCounter {}
/// impl ComputerMut<&str, usize> for CharCounter {
/// fn compute(&mut self, s: &str) -> usize {
/// s.chars().count() // you probably want to cache something more expensive than this
/// }
/// }
/// type CharCountCache<'a> = FrameCache<usize, CharCounter>;
///
/// # let mut ctx = egui::Context::default();
/// ctx.memory_mut(|mem| {
/// let cache = mem.caches.cache::<CharCountCache<'_>>();
/// assert_eq!(cache.get("hello"), 5);
/// });
/// ```
#[cfg_attr(feature = "persistence", serde(skip))]
pub caches: crate::util::cache::CacheStorage,
// ------------------------------------------
/// new scale that will be applied at the start of the next frame
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) new_pixels_per_point: Option<f32>,
/// new fonts that will be applied at the start of the next frame
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) new_font_definitions: Option<epaint::text::FontDefinitions>,
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) interaction: Interaction,
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) window_interaction: Option<window::WindowInteraction>,
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) drag_value: crate::widgets::drag_value::MonoState,
pub(crate) areas: Areas,
/// Which popup-window is open (if any)?
/// Could be a combo box, color picker, menu etc.
#[cfg_attr(feature = "persistence", serde(skip))]
popup: Option<Id>,
#[cfg_attr(feature = "persistence", serde(skip))]
everything_is_visible: bool,
}
// ----------------------------------------------------------------------------
/// Some global options that you can read and write.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Options {
/// The default style for new [`Ui`](crate::Ui):s.
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) style: std::sync::Arc<Style>,
/// Controls the tessellator.
pub tessellation_options: epaint::TessellationOptions,
/// This is a signal to any backend that we want the [`crate::PlatformOutput::events`] read out loud.
///
/// The only change to egui is that labels can be focused by pressing tab.
///
/// Screen readers is an experimental feature of egui, and not supported on all platforms.
///
/// `eframe` supports it only on web, using the `web_screen_reader` feature flag,
/// but you should consider using [AccessKit](https://github.com/AccessKit/accesskit) instead,
/// which `eframe` supports.
pub screen_reader: bool,
/// If true, the most common glyphs (ASCII) are pre-rendered to the texture atlas.
///
/// Only the fonts in [`Style::text_styles`] will be pre-cached.
///
/// This can lead to fewer texture operations, but may use up the texture atlas quicker
/// if you are changing [`Style::text_styles`], of have a lot of text styles.
pub preload_font_glyphs: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
style: Default::default(),
tessellation_options: Default::default(),
screen_reader: false,
preload_font_glyphs: true,
}
}
}
// ----------------------------------------------------------------------------
/// Say there is a button in a scroll area.
/// If the user clicks the button, the button should click.
/// If the user drags the button we should scroll the scroll area.
/// So what we do is that when the mouse is pressed we register both the button
/// and the scroll area (as `click_id`/`drag_id`).
/// If the user releases the button without moving the mouse we register it as a click on `click_id`.
/// If the cursor moves too much we clear the `click_id` and start passing move events to `drag_id`.
#[derive(Clone, Debug, Default)]
pub(crate) struct Interaction {
/// A widget interested in clicks that has a mouse press on it.
pub click_id: Option<Id>,
/// A widget interested in drags that has a mouse press on it.
pub drag_id: Option<Id>,
pub focus: Focus,
/// HACK: windows have low priority on dragging.
/// This is so that if you drag a slider in a window,
/// the slider will steal the drag away from the window.
/// This is needed because we do window interaction first (to prevent frame delay),
/// and then do content layout.
pub drag_is_window: bool,
/// Any interest in catching clicks this frame?
/// Cleared to false at start of each frame.
pub click_interest: bool,
/// Any interest in catching clicks this frame?
/// Cleared to false at start of each frame.
pub drag_interest: bool,
}
/// Keeps tracks of what widget has keyboard focus
#[derive(Clone, Debug, Default)]
pub(crate) struct Focus {
/// The widget with keyboard focus (i.e. a text input field).
pub(crate) id: Option<Id>,
/// What had keyboard focus previous frame?
id_previous_frame: Option<Id>,
/// Give focus to this widget next frame
id_next_frame: Option<Id>,
#[cfg(feature = "accesskit")]
id_requested_by_accesskit: Option<accesskit::NodeId>,
/// If set, the next widget that is interested in focus will automatically get it.
/// Probably because the user pressed Tab.
give_to_next: bool,
/// The last widget interested in focus.
last_interested: Option<Id>,
/// If `true`, pressing tab will NOT move focus away from the current widget.
is_focus_locked: bool,
/// Set at the beginning of the frame, set to `false` when "used".
pressed_tab: bool,
/// Set at the beginning of the frame, set to `false` when "used".
pressed_shift_tab: bool,
}
impl Interaction {
/// Are we currently clicking or dragging an egui widget?
pub fn is_using_pointer(&self) -> bool {
self.click_id.is_some() || self.drag_id.is_some()
}
fn begin_frame(
&mut self,
prev_input: &crate::input_state::InputState,
new_input: &crate::data::input::RawInput,
) {
self.click_interest = false;
self.drag_interest = false;
if !prev_input.pointer.could_any_button_be_click() {
self.click_id = None;
}
if !prev_input.pointer.any_down() || prev_input.pointer.latest_pos().is_none() {
// pointer button was not down last frame
self.click_id = None;
self.drag_id = None;
}
self.focus.begin_frame(new_input);
}
}
impl Focus {
/// Which widget currently has keyboard focus?
pub fn focused(&self) -> Option<Id> {
self.id
}
fn begin_frame(&mut self, new_input: &crate::data::input::RawInput) {
self.id_previous_frame = self.id;
if let Some(id) = self.id_next_frame.take() {
self.id = Some(id);
}
#[cfg(feature = "accesskit")]
{
self.id_requested_by_accesskit = None;
}
self.pressed_tab = false;
self.pressed_shift_tab = false;
for event in &new_input.events {
if matches!(
event,
crate::Event::Key {
key: crate::Key::Escape,
pressed: true,
modifiers: _,
..
}
) {
self.id = None;
self.is_focus_locked = false;
break;
}
if let crate::Event::Key {
key: crate::Key::Tab,
pressed: true,
modifiers,
..
} = event
{
if !self.is_focus_locked {
if modifiers.shift {
self.pressed_shift_tab = true;
} else {
self.pressed_tab = true;
}
}
}
#[cfg(feature = "accesskit")]
{
if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::Focus,
target,
data: None,
}) = event
{
self.id_requested_by_accesskit = Some(*target);
}
}
}
}
pub(crate) fn end_frame(&mut self, used_ids: &IdMap<Rect>) {
if let Some(id) = self.id {
// Allow calling `request_focus` one frame and not using it until next frame
let recently_gained_focus = self.id_previous_frame != Some(id);
if !recently_gained_focus && !used_ids.contains_key(&id) {
// Dead-mans-switch: the widget with focus has disappeared!
self.id = None;
}
}
}
pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool {
self.id_previous_frame == Some(id)
}
fn interested_in_focus(&mut self, id: Id) {
#[cfg(feature = "accesskit")]
{
if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
self.id = Some(id);
self.id_requested_by_accesskit = None;
self.give_to_next = false;
self.pressed_tab = false;
self.pressed_shift_tab = false;
}
}
if self.give_to_next && !self.had_focus_last_frame(id) {
self.id = Some(id);
self.give_to_next = false;
} else if self.id == Some(id) {
if self.pressed_tab && !self.is_focus_locked {
self.id = None;
self.give_to_next = true;
self.pressed_tab = false;
} else if self.pressed_shift_tab && !self.is_focus_locked {
self.id_next_frame = self.last_interested; // frame-delay so gained_focus works
self.pressed_shift_tab = false;
}
} else if self.pressed_tab && self.id.is_none() && !self.give_to_next {
// nothing has focus and the user pressed tab - give focus to the first widgets that wants it:
self.id = Some(id);
self.pressed_tab = false;
}
self.last_interested = Some(id);
}
}
impl Memory {
pub(crate) fn begin_frame(
&mut self,
prev_input: &crate::input_state::InputState,
new_input: &crate::data::input::RawInput,
) {
self.interaction.begin_frame(prev_input, new_input);
if !prev_input.pointer.any_down() {
self.window_interaction = None;
}
}
pub(crate) fn end_frame(&mut self, input: &InputState, used_ids: &IdMap<Rect>) {
self.caches.update();
self.areas.end_frame();
self.interaction.focus.end_frame(used_ids);
self.drag_value.end_frame(input);
}
/// Top-most layer at the given position.
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<LayerId> {
self.areas.layer_id_at(pos, resize_interact_radius_side)
}
/// An iterator over all layers. Back-to-front. Top is last.
pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
self.areas.order().iter().copied()
}
pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool {
self.interaction.focus.id_previous_frame == Some(id)
}
/// True if the given widget had keyboard focus last frame, but not this one.
pub(crate) fn lost_focus(&self, id: Id) -> bool {
self.had_focus_last_frame(id) && !self.has_focus(id)
}
/// True if the given widget has keyboard focus this frame, but didn't last frame.
pub(crate) fn gained_focus(&self, id: Id) -> bool {
!self.had_focus_last_frame(id) && self.has_focus(id)
}
/// Does this widget have keyboard focus?
///
/// This function does not consider whether the UI as a whole (e.g. window)
/// has the keyboard focus. That makes this function suitable for deciding
/// widget state that should not be disrupted if the user moves away
/// from the window and back.
#[inline(always)]
pub fn has_focus(&self, id: Id) -> bool {
self.interaction.focus.id == Some(id)
}
/// Which widget has keyboard focus?
pub fn focus(&self) -> Option<Id> {
self.interaction.focus.id
}
/// Prevent keyboard focus from moving away from this widget even if users presses the tab key.
/// You must first give focus to the widget before calling this.
pub fn lock_focus(&mut self, id: Id, lock_focus: bool) {
if self.had_focus_last_frame(id) && self.has_focus(id) {
self.interaction.focus.is_focus_locked = lock_focus;
}
}
/// Is the keyboard focus locked on this widget? If so the focus won't move even if the user presses the tab key.
pub fn has_lock_focus(&self, id: Id) -> bool {
if self.had_focus_last_frame(id) && self.has_focus(id) {
self.interaction.focus.is_focus_locked
} else {
false
}
}
/// Give keyboard focus to a specific widget.
/// See also [`crate::Response::request_focus`].
#[inline(always)]
pub fn request_focus(&mut self, id: Id) {
self.interaction.focus.id = Some(id);
self.interaction.focus.is_focus_locked = false;
}
/// Surrender keyboard focus for a specific widget.
/// See also [`crate::Response::surrender_focus`].
#[inline(always)]
pub fn surrender_focus(&mut self, id: Id) {
if self.interaction.focus.id == Some(id) {
self.interaction.focus.id = None;
self.interaction.focus.is_focus_locked = false;
}
}
/// Register this widget as being interested in getting keyboard focus.
/// This will allow the user to select it with tab and shift-tab.
/// This is normally done automatically when handling interactions,
/// but it is sometimes useful to pre-register interest in focus,
/// e.g. before deciding which type of underlying widget to use,
/// as in the [`crate::DragValue`] widget, so a widget can be focused
/// and rendered correctly in a single frame.
#[inline(always)]
pub fn interested_in_focus(&mut self, id: Id) {
self.interaction.focus.interested_in_focus(id);
}
/// Stop editing of active [`TextEdit`](crate::TextEdit) (if any).
#[inline(always)]
pub fn stop_text_input(&mut self) {
self.interaction.focus.id = None;
}
#[inline(always)]
pub fn is_anything_being_dragged(&self) -> bool {
self.interaction.drag_id.is_some()
}
#[inline(always)]
pub fn is_being_dragged(&self, id: Id) -> bool {
self.interaction.drag_id == Some(id)
}
#[inline(always)]
pub fn set_dragged_id(&mut self, id: Id) {
self.interaction.drag_id = Some(id);
}
/// Forget window positions, sizes etc.
/// Can be used to auto-layout windows.
pub fn reset_areas(&mut self) {
self.areas = Default::default();
}
}
/// ## Popups
/// Popups are things like combo-boxes, color pickers, menus etc.
/// Only one can be be open at a time.
impl Memory {
pub fn is_popup_open(&self, popup_id: Id) -> bool {
self.popup == Some(popup_id) || self.everything_is_visible()
}
pub fn any_popup_open(&self) -> bool {
self.popup.is_some() || self.everything_is_visible()
}
pub fn open_popup(&mut self, popup_id: Id) {
self.popup = Some(popup_id);
}
pub fn close_popup(&mut self) {
self.popup = None;
}
pub fn toggle_popup(&mut self, popup_id: Id) {
if self.is_popup_open(popup_id) {
self.close_popup();
} else {
self.open_popup(popup_id);
}
}
/// If true, all windows, menus, tooltips etc are to be visible at once.
///
/// This is useful for testing, benchmarking, pre-caching, etc.
///
/// Experimental feature!
#[inline(always)]
pub fn everything_is_visible(&self) -> bool {
self.everything_is_visible
}
/// If true, all windows, menus, tooltips etc are to be visible at once.
///
/// This is useful for testing, benchmarking, pre-caching, etc.
///
/// Experimental feature!
pub fn set_everything_is_visible(&mut self, value: bool) {
self.everything_is_visible = value;
}
}
// ----------------------------------------------------------------------------
/// Keeps track of [`Area`](crate::containers::area::Area)s, which are free-floating [`Ui`](crate::Ui)s.
/// These [`Area`](crate::containers::area::Area)s can be in any [`Order`](crate::Order).
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Areas {
areas: IdMap<area::State>,
/// Back-to-front. Top is last.
order: Vec<LayerId>,
visible_last_frame: ahash::HashSet<LayerId>,
visible_current_frame: ahash::HashSet<LayerId>,
/// When an area want to be on top, it is put in here.
/// At the end of the frame, this is used to reorder the layers.
/// This means if several layers want to be on top, they will keep their relative order.
/// So if you close three windows and then reopen them all in one frame,
/// they will all be sent to the top, but keep their previous internal order.
wants_to_be_on_top: ahash::HashSet<LayerId>,
}
impl Areas {
pub(crate) fn count(&self) -> usize {
self.areas.len()
}
pub(crate) fn get(&self, id: Id) -> Option<&area::State> {
self.areas.get(&id)
}
/// Back-to-front. Top is last.
pub(crate) fn order(&self) -> &[LayerId] {
&self.order
}
pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::State) {
self.visible_current_frame.insert(layer_id);
self.areas.insert(layer_id.id, state);
if !self.order.iter().any(|x| *x == layer_id) {
self.order.push(layer_id);
}
}
/// Top-most layer at the given position.
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<LayerId> {
for layer in self.order.iter().rev() {
if self.is_visible(layer) {
if let Some(state) = self.areas.get(&layer.id) {
let mut rect = state.rect();
if state.interactable {
// Allow us to resize by dragging just outside the window:
rect = rect.expand(resize_interact_radius_side);
if rect.contains(pos) {
return Some(*layer);
}
}
}
}
}
None
}
pub fn visible_last_frame(&self, layer_id: &LayerId) -> bool {
self.visible_last_frame.contains(layer_id)
}
pub fn is_visible(&self, layer_id: &LayerId) -> bool {
self.visible_last_frame.contains(layer_id) || self.visible_current_frame.contains(layer_id)
}
pub fn visible_layer_ids(&self) -> ahash::HashSet<LayerId> {
self.visible_last_frame
.iter()
.copied()
.chain(self.visible_current_frame.iter().copied())
.collect()
}
pub(crate) fn visible_windows(&self) -> Vec<&area::State> {
self.visible_layer_ids()
.iter()
.filter(|layer| layer.order == crate::Order::Middle)
.filter_map(|layer| self.get(layer.id))
.collect()
}
pub fn move_to_top(&mut self, layer_id: LayerId) {
self.visible_current_frame.insert(layer_id);
self.wants_to_be_on_top.insert(layer_id);
if !self.order.iter().any(|x| *x == layer_id) {
self.order.push(layer_id);
}
}
pub(crate) fn end_frame(&mut self) {
let Self {
visible_last_frame,
visible_current_frame,
order,
wants_to_be_on_top,
..
} = self;
std::mem::swap(visible_last_frame, visible_current_frame);
visible_current_frame.clear();
order.sort_by_key(|layer| (layer.order, wants_to_be_on_top.contains(layer)));
wants_to_be_on_top.clear();
}
}
// ----------------------------------------------------------------------------
#[test]
fn memory_impl_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Memory>();
}

690
crates/egui/src/menu.rs Normal file
View file

@ -0,0 +1,690 @@
//! Menu bar functionality (very basic so far).
//!
//! Usage:
//! ```
//! fn show_menu(ui: &mut egui::Ui) {
//! use egui::{menu, Button};
//!
//! menu::bar(ui, |ui| {
//! ui.menu_button("File", |ui| {
//! if ui.button("Open").clicked() {
//! // …
//! }
//! });
//! });
//! }
//! ```
use super::{
style::WidgetVisuals, Align, Context, Id, InnerResponse, PointerState, Pos2, Rect, Response,
Sense, TextStyle, Ui, Vec2,
};
use crate::{widgets::*, *};
use epaint::mutex::RwLock;
use std::sync::Arc;
/// What is saved between frames.
#[derive(Clone, Default)]
pub(crate) struct BarState {
open_menu: MenuRootManager,
}
impl BarState {
fn load(ctx: &Context, bar_id: Id) -> Self {
ctx.data_mut(|d| d.get_temp::<Self>(bar_id).unwrap_or_default())
}
fn store(self, ctx: &Context, bar_id: Id) {
ctx.data_mut(|d| d.insert_temp(bar_id, self));
}
/// Show a menu at pointer if primary-clicked response.
/// Should be called from [`Context`] on a [`Response`]
pub fn bar_menu<R>(
&mut self,
response: &Response,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
MenuRoot::stationary_click_interaction(response, &mut self.open_menu, response.id);
self.open_menu.show(response, add_contents)
}
}
impl std::ops::Deref for BarState {
type Target = MenuRootManager;
fn deref(&self) -> &Self::Target {
&self.open_menu
}
}
impl std::ops::DerefMut for BarState {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.open_menu
}
}
fn set_menu_style(style: &mut Style) {
style.spacing.button_padding = vec2(2.0, 0.0);
style.visuals.widgets.active.bg_stroke = Stroke::NONE;
style.visuals.widgets.hovered.bg_stroke = Stroke::NONE;
style.visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
style.visuals.widgets.inactive.bg_stroke = Stroke::NONE;
}
/// The menu bar goes well in a [`TopBottomPanel::top`],
/// but can also be placed in a [`Window`].
/// In the latter case you may want to wrap it in [`Frame`].
pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
ui.horizontal(|ui| {
set_menu_style(ui.style_mut());
// Take full width and fixed height:
let height = ui.spacing().interact_size.y;
ui.set_min_size(vec2(ui.available_width(), height));
add_contents(ui)
})
}
/// Construct a top level menu in a menu bar. This would be e.g. "File", "Edit" etc.
///
/// Responds to primary clicks.
///
/// Returns `None` if the menu is not open.
pub fn menu_button<R>(
ui: &mut Ui,
title: impl Into<WidgetText>,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<Option<R>> {
stationary_menu_impl(ui, title, Box::new(add_contents))
}
/// Construct a top level menu with an image in a menu bar. This would be e.g. "File", "Edit" etc.
///
/// Responds to primary clicks.
///
/// Returns `None` if the menu is not open.
pub fn menu_image_button<R>(
ui: &mut Ui,
texture_id: TextureId,
image_size: impl Into<Vec2>,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<Option<R>> {
stationary_menu_image_impl(ui, texture_id, image_size, Box::new(add_contents))
}
/// Construct a nested sub menu in another menu.
///
/// Opens on hover.
///
/// Returns `None` if the menu is not open.
pub(crate) fn submenu_button<R>(
ui: &mut Ui,
parent_state: Arc<RwLock<MenuState>>,
title: impl Into<WidgetText>,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<Option<R>> {
SubMenu::new(parent_state, title).show(ui, add_contents)
}
/// wrapper for the contents of every menu.
pub(crate) fn menu_ui<'c, R>(
ctx: &Context,
menu_id: impl Into<Id>,
menu_state_arc: &Arc<RwLock<MenuState>>,
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
) -> InnerResponse<R> {
let pos = {
let mut menu_state = menu_state_arc.write();
menu_state.entry_count = 0;
menu_state.rect.min
};
let area = Area::new(menu_id)
.order(Order::Foreground)
.constrain(true)
.fixed_pos(pos)
.interactable(true)
.drag_bounds(ctx.screen_rect());
let inner_response = area.show(ctx, |ui| {
set_menu_style(ui.style_mut());
Frame::menu(ui.style())
.show(ui, |ui| {
const DEFAULT_MENU_WIDTH: f32 = 150.0; // TODO(emilk): add to ui.spacing
ui.set_max_width(DEFAULT_MENU_WIDTH);
ui.set_menu_state(Some(menu_state_arc.clone()));
ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents)
.inner
})
.inner
});
menu_state_arc.write().rect = inner_response.response.rect;
inner_response
}
/// Build a top level menu with a button.
///
/// Responds to primary clicks.
fn stationary_menu_impl<'c, R>(
ui: &mut Ui,
title: impl Into<WidgetText>,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<Option<R>> {
let title = title.into();
let bar_id = ui.id();
let menu_id = bar_id.with(title.text());
let mut bar_state = BarState::load(ui.ctx(), bar_id);
let mut button = Button::new(title);
if bar_state.open_menu.is_menu_open(menu_id) {
button = button.fill(ui.visuals().widgets.open.weak_bg_fill);
button = button.stroke(ui.visuals().widgets.open.bg_stroke);
}
let button_response = ui.add(button);
let inner = bar_state.bar_menu(&button_response, add_contents);
bar_state.store(ui.ctx(), bar_id);
InnerResponse::new(inner.map(|r| r.inner), button_response)
}
/// Build a top level menu with an image button.
///
/// Responds to primary clicks.
fn stationary_menu_image_impl<'c, R>(
ui: &mut Ui,
texture_id: TextureId,
image_size: impl Into<Vec2>,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<Option<R>> {
let bar_id = ui.id();
let mut bar_state = BarState::load(ui.ctx(), bar_id);
let button_response = ui.add(ImageButton::new(texture_id, image_size));
let inner = bar_state.bar_menu(&button_response, add_contents);
bar_state.store(ui.ctx(), bar_id);
InnerResponse::new(inner.map(|r| r.inner), button_response)
}
/// Response to secondary clicks (right-clicks) by showing the given menu.
pub(crate) fn context_menu(
response: &Response,
add_contents: impl FnOnce(&mut Ui),
) -> Option<InnerResponse<()>> {
let menu_id = Id::new("__egui::context_menu");
let mut bar_state = BarState::load(&response.ctx, menu_id);
MenuRoot::context_click_interaction(response, &mut bar_state, response.id);
let inner_response = bar_state.show(response, add_contents);
bar_state.store(&response.ctx, menu_id);
inner_response
}
/// Stores the state for the context menu.
#[derive(Clone, Default)]
pub(crate) struct MenuRootManager {
inner: Option<MenuRoot>,
}
impl MenuRootManager {
/// Show a menu at pointer if right-clicked response.
/// Should be called from [`Context`] on a [`Response`]
pub fn show<R>(
&mut self,
response: &Response,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
if let Some(root) = self.inner.as_mut() {
let (menu_response, inner_response) = root.show(response, add_contents);
if MenuResponse::Close == menu_response {
self.inner = None;
}
inner_response
} else {
None
}
}
fn is_menu_open(&self, id: Id) -> bool {
self.inner.as_ref().map(|m| m.id) == Some(id)
}
}
impl std::ops::Deref for MenuRootManager {
type Target = Option<MenuRoot>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for MenuRootManager {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
/// Menu root associated with an Id from a Response
#[derive(Clone)]
pub(crate) struct MenuRoot {
pub menu_state: Arc<RwLock<MenuState>>,
pub id: Id,
}
impl MenuRoot {
pub fn new(position: Pos2, id: Id) -> Self {
Self {
menu_state: Arc::new(RwLock::new(MenuState::new(position))),
id,
}
}
pub fn show<R>(
&mut self,
response: &Response,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> (MenuResponse, Option<InnerResponse<R>>) {
if self.id == response.id {
let inner_response =
MenuState::show(&response.ctx, &self.menu_state, self.id, add_contents);
let mut menu_state = self.menu_state.write();
menu_state.rect = inner_response.response.rect;
if menu_state.response.is_close() {
return (MenuResponse::Close, Some(inner_response));
}
}
(MenuResponse::Stay, None)
}
/// Interaction with a stationary menu, i.e. fixed in another Ui.
///
/// Responds to primary clicks.
fn stationary_interaction(
response: &Response,
root: &mut MenuRootManager,
id: Id,
) -> MenuResponse {
if (response.clicked() && root.is_menu_open(id))
|| response.ctx.input(|i| i.key_pressed(Key::Escape))
{
// menu open and button clicked or esc pressed
return MenuResponse::Close;
} else if (response.clicked() && !root.is_menu_open(id))
|| (response.hovered() && root.is_some())
{
// menu not open and button clicked
// or button hovered while other menu is open
let mut pos = response.rect.left_bottom();
if let Some(root) = root.inner.as_mut() {
let menu_rect = root.menu_state.read().rect;
let screen_rect = response.ctx.input(|i| i.screen_rect);
if pos.y + menu_rect.height() > screen_rect.max.y {
pos.y = screen_rect.max.y - menu_rect.height() - response.rect.height();
}
if pos.x + menu_rect.width() > screen_rect.max.x {
pos.x = screen_rect.max.x - menu_rect.width();
}
}
return MenuResponse::Create(pos, id);
} else if response
.ctx
.input(|i| i.pointer.any_pressed() && i.pointer.primary_down())
{
if let Some(pos) = response.ctx.input(|i| i.pointer.interact_pos()) {
if let Some(root) = root.inner.as_mut() {
if root.id == id {
// pressed somewhere while this menu is open
let menu_state = root.menu_state.read();
let in_menu = menu_state.area_contains(pos);
if !in_menu {
return MenuResponse::Close;
}
}
}
}
}
MenuResponse::Stay
}
/// Interaction with a context menu (secondary clicks).
fn context_interaction(
response: &Response,
root: &mut Option<MenuRoot>,
id: Id,
) -> MenuResponse {
let response = response.interact(Sense::click());
response.ctx.input(|input| {
let pointer = &input.pointer;
if pointer.any_pressed() {
if let Some(pos) = pointer.interact_pos() {
let mut destroy = false;
let mut in_old_menu = false;
if let Some(root) = root {
let menu_state = root.menu_state.read();
in_old_menu = menu_state.area_contains(pos);
destroy = root.id == response.id;
}
if !in_old_menu {
if response.hovered() && pointer.secondary_down() {
return MenuResponse::Create(pos, id);
} else if (response.hovered() && pointer.primary_down()) || destroy {
return MenuResponse::Close;
}
}
}
}
MenuResponse::Stay
})
}
fn handle_menu_response(root: &mut MenuRootManager, menu_response: MenuResponse) {
match menu_response {
MenuResponse::Create(pos, id) => {
root.inner = Some(MenuRoot::new(pos, id));
}
MenuResponse::Close => root.inner = None,
MenuResponse::Stay => {}
}
}
/// Respond to secondary (right) clicks.
pub fn context_click_interaction(response: &Response, root: &mut MenuRootManager, id: Id) {
let menu_response = Self::context_interaction(response, root, id);
Self::handle_menu_response(root, menu_response);
}
// Responds to primary clicks.
pub fn stationary_click_interaction(response: &Response, root: &mut MenuRootManager, id: Id) {
let menu_response = Self::stationary_interaction(response, root, id);
Self::handle_menu_response(root, menu_response);
}
}
#[derive(Copy, Clone, PartialEq)]
pub(crate) enum MenuResponse {
Close,
Stay,
Create(Pos2, Id),
}
impl MenuResponse {
pub fn is_close(&self) -> bool {
*self == Self::Close
}
}
pub struct SubMenuButton {
text: WidgetText,
icon: WidgetText,
index: usize,
}
impl SubMenuButton {
/// The `icon` can be an emoji (e.g. `⏵` right arrow), shown right of the label
fn new(text: impl Into<WidgetText>, icon: impl Into<WidgetText>, index: usize) -> Self {
Self {
text: text.into(),
icon: icon.into(),
index,
}
}
fn visuals<'a>(
ui: &'a Ui,
response: &'_ Response,
menu_state: &'_ MenuState,
sub_id: Id,
) -> &'a WidgetVisuals {
if menu_state.is_open(sub_id) {
&ui.style().visuals.widgets.open
} else {
ui.style().interact(response)
}
}
pub fn icon(mut self, icon: impl Into<WidgetText>) -> Self {
self.icon = icon.into();
self
}
pub(crate) fn show(self, ui: &mut Ui, menu_state: &MenuState, sub_id: Id) -> Response {
let SubMenuButton { text, icon, .. } = self;
let text_style = TextStyle::Button;
let sense = Sense::click();
let button_padding = ui.spacing().button_padding;
let total_extra = button_padding + button_padding;
let text_available_width = ui.available_width() - total_extra.x;
let text_galley =
text.into_galley(ui, Some(true), text_available_width, text_style.clone());
let icon_available_width = text_available_width - text_galley.size().x;
let icon_galley = icon.into_galley(ui, Some(true), icon_available_width, text_style);
let text_and_icon_size = Vec2::new(
text_galley.size().x + icon_galley.size().x,
text_galley.size().y.max(icon_galley.size().y),
);
let mut desired_size = text_and_icon_size + 2.0 * button_padding;
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
let (rect, response) = ui.allocate_at_least(desired_size, sense);
response.widget_info(|| {
crate::WidgetInfo::labeled(crate::WidgetType::Button, text_galley.text())
});
if ui.is_rect_visible(rect) {
let visuals = Self::visuals(ui, &response, menu_state, sub_id);
let text_pos = Align2::LEFT_CENTER
.align_size_within_rect(text_galley.size(), rect.shrink2(button_padding))
.min;
let icon_pos = Align2::RIGHT_CENTER
.align_size_within_rect(icon_galley.size(), rect.shrink2(button_padding))
.min;
if ui.visuals().button_frame {
ui.painter().rect_filled(
rect.expand(visuals.expansion),
visuals.rounding,
visuals.weak_bg_fill,
);
}
let text_color = visuals.text_color();
text_galley.paint_with_fallback_color(ui.painter(), text_pos, text_color);
icon_galley.paint_with_fallback_color(ui.painter(), icon_pos, text_color);
}
response
}
}
pub struct SubMenu {
button: SubMenuButton,
parent_state: Arc<RwLock<MenuState>>,
}
impl SubMenu {
fn new(parent_state: Arc<RwLock<MenuState>>, text: impl Into<WidgetText>) -> Self {
let index = parent_state.write().next_entry_index();
Self {
button: SubMenuButton::new(text, "", index),
parent_state,
}
}
pub fn show<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<Option<R>> {
let sub_id = ui.id().with(self.button.index);
let button = self.button.show(ui, &self.parent_state.read(), sub_id);
self.parent_state
.write()
.submenu_button_interaction(ui, sub_id, &button);
let inner = self
.parent_state
.write()
.show_submenu(ui.ctx(), sub_id, add_contents);
InnerResponse::new(inner, button)
}
}
pub(crate) struct MenuState {
/// The opened sub-menu and its [`Id`]
sub_menu: Option<(Id, Arc<RwLock<MenuState>>)>,
/// Bounding box of this menu (without the sub-menu)
pub rect: Rect,
/// Used to check if any menu in the tree wants to close
pub response: MenuResponse,
/// Used to hash different [`Id`]s for sub-menus
entry_count: usize,
}
impl MenuState {
pub fn new(position: Pos2) -> Self {
Self {
rect: Rect::from_min_size(position, Vec2::ZERO),
sub_menu: None,
response: MenuResponse::Stay,
entry_count: 0,
}
}
/// Close menu hierarchy.
pub fn close(&mut self) {
self.response = MenuResponse::Close;
}
pub fn show<R>(
ctx: &Context,
menu_state: &Arc<RwLock<Self>>,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
crate::menu::menu_ui(ctx, id, menu_state, add_contents)
}
fn show_submenu<R>(
&mut self,
ctx: &Context,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
let (sub_response, response) = self.submenu(id).map(|sub| {
let inner_response = Self::show(ctx, sub, id, add_contents);
(sub.read().response, inner_response.inner)
})?;
self.cascade_close_response(sub_response);
Some(response)
}
/// Check if position is in the menu hierarchy's area.
pub fn area_contains(&self, pos: Pos2) -> bool {
self.rect.contains(pos)
|| self
.sub_menu
.as_ref()
.map_or(false, |(_, sub)| sub.read().area_contains(pos))
}
fn next_entry_index(&mut self) -> usize {
self.entry_count += 1;
self.entry_count - 1
}
/// Sense button interaction opening and closing submenu.
fn submenu_button_interaction(&mut self, ui: &mut Ui, sub_id: Id, button: &Response) {
let pointer = ui.input(|i| i.pointer.clone());
let open = self.is_open(sub_id);
if self.moving_towards_current_submenu(&pointer) {
// ensure to repaint once even when pointer is not moving
ui.ctx().request_repaint();
} else if !open && button.hovered() {
let pos = button.rect.right_top();
self.open_submenu(sub_id, pos);
} else if open && !button.hovered() && !self.hovering_current_submenu(&pointer) {
self.close_submenu();
}
}
/// Check if `dir` points from `pos` towards left side of `rect`.
fn points_at_left_of_rect(pos: Pos2, dir: Vec2, rect: Rect) -> bool {
let vel_a = dir.angle();
let top_a = (rect.left_top() - pos).angle();
let bottom_a = (rect.left_bottom() - pos).angle();
bottom_a - vel_a >= 0.0 && top_a - vel_a <= 0.0
}
/// Check if pointer is moving towards current submenu.
fn moving_towards_current_submenu(&self, pointer: &PointerState) -> bool {
if pointer.is_still() {
return false;
}
if let Some(sub_menu) = self.current_submenu() {
if let Some(pos) = pointer.hover_pos() {
return Self::points_at_left_of_rect(pos, pointer.velocity(), sub_menu.read().rect);
}
}
false
}
/// Check if pointer is hovering current submenu.
fn hovering_current_submenu(&self, pointer: &PointerState) -> bool {
if let Some(sub_menu) = self.current_submenu() {
if let Some(pos) = pointer.hover_pos() {
return sub_menu.read().area_contains(pos);
}
}
false
}
/// Cascade close response to menu root.
fn cascade_close_response(&mut self, response: MenuResponse) {
if response.is_close() {
self.response = response;
}
}
fn is_open(&self, id: Id) -> bool {
self.sub_id() == Some(id)
}
fn sub_id(&self) -> Option<Id> {
self.sub_menu.as_ref().map(|(id, _)| *id)
}
fn current_submenu(&self) -> Option<&Arc<RwLock<MenuState>>> {
self.sub_menu.as_ref().map(|(_, sub)| sub)
}
fn submenu(&mut self, id: Id) -> Option<&Arc<RwLock<MenuState>>> {
self.sub_menu
.as_ref()
.and_then(|(k, sub)| if id == *k { Some(sub) } else { None })
}
/// Open submenu at position, if not already open.
fn open_submenu(&mut self, id: Id, pos: Pos2) {
if !self.is_open(id) {
self.sub_menu = Some((id, Arc::new(RwLock::new(MenuState::new(pos)))));
}
}
fn close_submenu(&mut self) {
self.sub_menu = None;
}
}

76
crates/egui/src/os.rs Normal file
View file

@ -0,0 +1,76 @@
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum OperatingSystem {
/// Unknown OS - could be wasm
Unknown,
/// Android OS.
Android,
/// Apple iPhone OS.
IOS,
/// Linux or Unix other than Android.
Nix,
/// MacOS.
Mac,
/// Windows.
Windows,
}
impl Default for OperatingSystem {
fn default() -> Self {
Self::from_target_os()
}
}
impl OperatingSystem {
pub const fn from_target_os() -> Self {
if cfg!(target_arch = "wasm32") {
Self::Unknown
} else if cfg!(target_os = "android") {
Self::Android
} else if cfg!(target_os = "ios") {
Self::IOS
} else if cfg!(target_os = "macos") {
Self::Mac
} else if cfg!(target_os = "windows") {
Self::Android
} else if cfg!(target_os = "linux")
|| cfg!(target_os = "dragonfly")
|| cfg!(target_os = "freebsd")
|| cfg!(target_os = "netbsd")
|| cfg!(target_os = "openbsd")
{
Self::Nix
} else {
Self::Unknown
}
}
/// Helper: try to guess from the user-agent of a browser.
pub fn from_user_agent(user_agent: &str) -> Self {
if user_agent.contains("Android") {
Self::Android
} else if user_agent.contains("like Mac") {
Self::IOS
} else if user_agent.contains("Win") {
Self::Windows
} else if user_agent.contains("Mac") {
Self::Mac
} else if user_agent.contains("Linux")
|| user_agent.contains("X11")
|| user_agent.contains("Unix")
{
Self::Nix
} else {
#[cfg(feature = "tracing")]
tracing::warn!(
"egui: Failed to guess operating system from User-Agent {:?}. Please file an issue at https://github.com/emilk/egui/issues",
user_agent);
Self::Unknown
}
}
}

455
crates/egui/src/painter.rs Normal file
View file

@ -0,0 +1,455 @@
use std::ops::RangeInclusive;
use std::sync::Arc;
use crate::{
emath::{Align2, Pos2, Rect, Vec2},
layers::{LayerId, PaintList, ShapeIdx},
Color32, Context, FontId,
};
use epaint::{
text::{Fonts, Galley},
CircleShape, RectShape, Rounding, Shape, Stroke,
};
/// Helper to paint shapes and text to a specific region on a specific layer.
///
/// All coordinates are screen coordinates in the unit points (one point can consist of many physical pixels).
#[derive(Clone)]
pub struct Painter {
/// Source of fonts and destination of shapes
ctx: Context,
/// Where we paint
layer_id: LayerId,
/// Everything painted in this [`Painter`] will be clipped against this.
/// This means nothing outside of this rectangle will be visible on screen.
clip_rect: Rect,
/// If set, all shapes will have their colors modified to be closer to this.
/// This is used to implement grayed out interfaces.
fade_to_color: Option<Color32>,
}
impl Painter {
/// Create a painter to a specific layer within a certain clip rectangle.
pub fn new(ctx: Context, layer_id: LayerId, clip_rect: Rect) -> Self {
Self {
ctx,
layer_id,
clip_rect,
fade_to_color: None,
}
}
/// Redirect where you are painting.
#[must_use]
pub fn with_layer_id(self, layer_id: LayerId) -> Self {
Self {
ctx: self.ctx,
layer_id,
clip_rect: self.clip_rect,
fade_to_color: None,
}
}
/// Create a painter for a sub-region of this [`Painter`].
///
/// The clip-rect of the returned [`Painter`] will be the intersection
/// of the given rectangle and the `clip_rect()` of the parent [`Painter`].
pub fn with_clip_rect(&self, rect: Rect) -> Self {
Self {
ctx: self.ctx.clone(),
layer_id: self.layer_id,
clip_rect: rect.intersect(self.clip_rect),
fade_to_color: self.fade_to_color,
}
}
/// Redirect where you are painting.
pub fn set_layer_id(&mut self, layer_id: LayerId) {
self.layer_id = layer_id;
}
/// If set, colors will be modified to look like this
pub(crate) fn set_fade_to_color(&mut self, fade_to_color: Option<Color32>) {
self.fade_to_color = fade_to_color;
}
pub(crate) fn is_visible(&self) -> bool {
self.fade_to_color != Some(Color32::TRANSPARENT)
}
/// If `false`, nothing added to the painter will be visible
pub(crate) fn set_invisible(&mut self) {
self.fade_to_color = Some(Color32::TRANSPARENT);
}
#[deprecated = "Use Painter::with_clip_rect"] // Deprecated in 2022-04-18, before egui 0.18
pub fn sub_region(&self, rect: Rect) -> Self {
Self {
ctx: self.ctx.clone(),
layer_id: self.layer_id,
clip_rect: rect.intersect(self.clip_rect),
fade_to_color: self.fade_to_color,
}
}
}
/// ## Accessors etc
impl Painter {
/// Get a reference to the parent [`Context`].
#[inline(always)]
pub fn ctx(&self) -> &Context {
&self.ctx
}
/// Read-only access to the shared [`Fonts`].
///
/// See [`Context`] documentation for how locks work.
#[inline(always)]
pub fn fonts<R>(&self, reader: impl FnOnce(&Fonts) -> R) -> R {
self.ctx.fonts(reader)
}
/// Where we paint
#[inline(always)]
pub fn layer_id(&self) -> LayerId {
self.layer_id
}
/// Everything painted in this [`Painter`] will be clipped against this.
/// This means nothing outside of this rectangle will be visible on screen.
#[inline(always)]
pub fn clip_rect(&self) -> Rect {
self.clip_rect
}
/// Everything painted in this [`Painter`] will be clipped against this.
/// This means nothing outside of this rectangle will be visible on screen.
#[inline(always)]
pub fn set_clip_rect(&mut self, clip_rect: Rect) {
self.clip_rect = clip_rect;
}
/// Useful for pixel-perfect rendering.
#[inline(always)]
pub fn round_to_pixel(&self, point: f32) -> f32 {
self.ctx().round_to_pixel(point)
}
/// Useful for pixel-perfect rendering.
#[inline(always)]
pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
self.ctx().round_vec_to_pixels(vec)
}
/// Useful for pixel-perfect rendering.
#[inline(always)]
pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
self.ctx().round_pos_to_pixels(pos)
}
}
/// ## Low level
impl Painter {
#[inline]
fn paint_list<R>(&self, writer: impl FnOnce(&mut PaintList) -> R) -> R {
self.ctx.graphics_mut(|g| writer(g.list(self.layer_id)))
}
fn transform_shape(&self, shape: &mut Shape) {
if let Some(fade_to_color) = self.fade_to_color {
tint_shape_towards(shape, fade_to_color);
}
}
/// It is up to the caller to make sure there is room for this.
/// Can be used for free painting.
/// NOTE: all coordinates are screen coordinates!
pub fn add(&self, shape: impl Into<Shape>) -> ShapeIdx {
if self.fade_to_color == Some(Color32::TRANSPARENT) {
self.paint_list(|l| l.add(self.clip_rect, Shape::Noop))
} else {
let mut shape = shape.into();
self.transform_shape(&mut shape);
self.paint_list(|l| l.add(self.clip_rect, shape))
}
}
/// Add many shapes at once.
///
/// Calling this once is generally faster than calling [`Self::add`] multiple times.
pub fn extend<I: IntoIterator<Item = Shape>>(&self, shapes: I) {
if self.fade_to_color == Some(Color32::TRANSPARENT) {
return;
}
if self.fade_to_color.is_some() {
let shapes = shapes.into_iter().map(|mut shape| {
self.transform_shape(&mut shape);
shape
});
self.paint_list(|l| l.extend(self.clip_rect, shapes));
} else {
self.paint_list(|l| l.extend(self.clip_rect, shapes));
};
}
/// Modify an existing [`Shape`].
pub fn set(&self, idx: ShapeIdx, shape: impl Into<Shape>) {
if self.fade_to_color == Some(Color32::TRANSPARENT) {
return;
}
let mut shape = shape.into();
self.transform_shape(&mut shape);
self.paint_list(|l| l.set(idx, self.clip_rect, shape));
}
}
/// ## Debug painting
impl Painter {
#[allow(clippy::needless_pass_by_value)]
pub fn debug_rect(&self, rect: Rect, color: Color32, text: impl ToString) {
self.rect(
rect,
0.0,
color.additive().linear_multiply(0.015),
(1.0, color),
);
self.text(
rect.min,
Align2::LEFT_TOP,
text.to_string(),
FontId::monospace(12.0),
color,
);
}
pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect {
let color = self.ctx.style().visuals.error_fg_color;
self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {}", text))
}
/// text with a background
#[allow(clippy::needless_pass_by_value)]
pub fn debug_text(
&self,
pos: Pos2,
anchor: Align2,
color: Color32,
text: impl ToString,
) -> Rect {
let galley = self.layout_no_wrap(text.to_string(), FontId::monospace(12.0), color);
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size()));
let frame_rect = rect.expand(2.0);
self.add(Shape::rect_filled(
frame_rect,
0.0,
Color32::from_black_alpha(150),
));
self.galley(rect.min, galley);
frame_rect
}
}
/// # Paint different primitives
impl Painter {
/// Paints a line from the first point to the second.
pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<Stroke>) {
self.add(Shape::LineSegment {
points,
stroke: stroke.into(),
});
}
/// Paints a horizontal line.
pub fn hline(&self, x: RangeInclusive<f32>, y: f32, stroke: impl Into<Stroke>) {
self.add(Shape::hline(x, y, stroke));
}
/// Paints a vertical line.
pub fn vline(&self, x: f32, y: RangeInclusive<f32>, stroke: impl Into<Stroke>) {
self.add(Shape::vline(x, y, stroke));
}
pub fn circle(
&self,
center: Pos2,
radius: f32,
fill_color: impl Into<Color32>,
stroke: impl Into<Stroke>,
) {
self.add(CircleShape {
center,
radius,
fill: fill_color.into(),
stroke: stroke.into(),
});
}
pub fn circle_filled(&self, center: Pos2, radius: f32, fill_color: impl Into<Color32>) {
self.add(CircleShape {
center,
radius,
fill: fill_color.into(),
stroke: Default::default(),
});
}
pub fn circle_stroke(&self, center: Pos2, radius: f32, stroke: impl Into<Stroke>) {
self.add(CircleShape {
center,
radius,
fill: Default::default(),
stroke: stroke.into(),
});
}
pub fn rect(
&self,
rect: Rect,
rounding: impl Into<Rounding>,
fill_color: impl Into<Color32>,
stroke: impl Into<Stroke>,
) {
self.add(RectShape {
rect,
rounding: rounding.into(),
fill: fill_color.into(),
stroke: stroke.into(),
});
}
pub fn rect_filled(
&self,
rect: Rect,
rounding: impl Into<Rounding>,
fill_color: impl Into<Color32>,
) {
self.add(RectShape {
rect,
rounding: rounding.into(),
fill: fill_color.into(),
stroke: Default::default(),
});
}
pub fn rect_stroke(
&self,
rect: Rect,
rounding: impl Into<Rounding>,
stroke: impl Into<Stroke>,
) {
self.add(RectShape {
rect,
rounding: rounding.into(),
fill: Default::default(),
stroke: stroke.into(),
});
}
/// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`.
pub fn arrow(&self, origin: Pos2, vec: Vec2, stroke: Stroke) {
use crate::emath::*;
let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0);
let tip_length = vec.length() / 4.0;
let tip = origin + vec;
let dir = vec.normalized();
self.line_segment([origin, tip], stroke);
self.line_segment([tip, tip - tip_length * (rot * dir)], stroke);
self.line_segment([tip, tip - tip_length * (rot.inverse() * dir)], stroke);
}
/// An image at the given position.
///
/// `uv` should normally be `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`
/// unless you want to crop or flip the image.
///
/// `tint` is a color multiplier. Use [`Color32::WHITE`] if you don't want to tint the image.
pub fn image(&self, texture_id: epaint::TextureId, rect: Rect, uv: Rect, tint: Color32) {
self.add(Shape::image(texture_id, rect, uv, tint));
}
}
/// ## Text
impl Painter {
/// Lay out and paint some text.
///
/// To center the text at the given position, use `Align2::CENTER_CENTER`.
///
/// To find out the size of text before painting it, use
/// [`Self::layout`] or [`Self::layout_no_wrap`].
///
/// Returns where the text ended up.
#[allow(clippy::needless_pass_by_value)]
pub fn text(
&self,
pos: Pos2,
anchor: Align2,
text: impl ToString,
font_id: FontId,
text_color: Color32,
) -> Rect {
let galley = self.layout_no_wrap(text.to_string(), font_id, text_color);
let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size()));
self.galley(rect.min, galley);
rect
}
/// Will wrap text at the given width and line break at `\n`.
///
/// Paint the results with [`Self::galley`].
#[inline(always)]
pub fn layout(
&self,
text: String,
font_id: FontId,
color: crate::Color32,
wrap_width: f32,
) -> Arc<Galley> {
self.fonts(|f| f.layout(text, font_id, color, wrap_width))
}
/// Will line break at `\n`.
///
/// Paint the results with [`Self::galley`].
#[inline(always)]
pub fn layout_no_wrap(
&self,
text: String,
font_id: FontId,
color: crate::Color32,
) -> Arc<Galley> {
self.fonts(|f| f.layout(text, font_id, color, f32::INFINITY))
}
/// Paint text that has already been layed out in a [`Galley`].
///
/// You can create the [`Galley`] with [`Self::layout`].
///
/// If you want to change the color of the text, use [`Self::galley_with_color`].
#[inline(always)]
pub fn galley(&self, pos: Pos2, galley: Arc<Galley>) {
if !galley.is_empty() {
self.add(Shape::galley(pos, galley));
}
}
/// Paint text that has already been layed out in a [`Galley`].
///
/// You can create the [`Galley`] with [`Self::layout`].
///
/// The text color in the [`Galley`] will be replaced with the given color.
#[inline(always)]
pub fn galley_with_color(&self, pos: Pos2, galley: Arc<Galley>, text_color: Color32) {
if !galley.is_empty() {
self.add(Shape::galley_with_color(pos, galley, text_color));
}
}
}
fn tint_shape_towards(shape: &mut Shape, target: Color32) {
epaint::shape_transform::adjust_colors(shape, &|color| {
*color = crate::ecolor::tint_color_towards(*color, target);
});
}

View file

@ -17,6 +17,7 @@ impl Placer {
}
}
#[inline(always)]
pub(crate) fn set_grid(&mut self, grid: grid::GridLayout) {
self.grid = Some(grid);
}
@ -27,41 +28,50 @@ impl Placer {
}
}
#[inline(always)]
pub(crate) fn grid(&self) -> Option<&grid::GridLayout> {
self.grid.as_ref()
}
#[inline(always)]
pub(crate) fn is_grid(&self) -> bool {
self.grid.is_some()
}
#[inline(always)]
pub(crate) fn layout(&self) -> &Layout {
&self.layout
}
#[inline(always)]
pub(crate) fn prefer_right_to_left(&self) -> bool {
self.layout.prefer_right_to_left()
}
#[inline(always)]
pub(crate) fn min_rect(&self) -> Rect {
self.region.min_rect
}
#[inline(always)]
pub(crate) fn max_rect(&self) -> Rect {
self.region.max_rect
}
pub(crate) fn max_rect_finite(&self) -> Rect {
self.region.max_rect_finite()
}
#[inline(always)]
pub(crate) fn force_set_min_rect(&mut self, min_rect: Rect) {
self.region.min_rect = min_rect;
}
pub(crate) fn cursor(&self) -> Pos2 {
#[inline(always)]
pub(crate) fn cursor(&self) -> Rect {
self.region.cursor
}
#[inline(always)]
pub(crate) fn set_cursor(&mut self, cursor: Rect) {
self.region.cursor = cursor;
}
}
impl Placer {
@ -81,14 +91,6 @@ impl Placer {
}
}
pub(crate) fn available_rect_before_wrap_finite(&self) -> Rect {
if let Some(grid) = &self.grid {
grid.available_rect_finite(&self.region)
} else {
self.layout.available_rect_before_wrap_finite(&self.region)
}
}
/// Amount of space available for a widget.
/// For wrapping layouts, this is the maximum (after wrap).
pub(crate) fn available_size(&self) -> Vec2 {
@ -104,16 +106,30 @@ impl Placer {
/// This is what you then pass to `advance_after_rects`.
/// Use `justify_and_align` to get the inner `widget_rect`.
pub(crate) fn next_space(&self, child_size: Vec2, item_spacing: Vec2) -> Rect {
egui_assert!(child_size.is_finite() && child_size.x >= 0.0 && child_size.y >= 0.0);
self.region.sanity_check();
if let Some(grid) = &self.grid {
grid.next_cell(self.region.cursor, child_size)
} else {
self.layout
.next_space(&self.region, child_size, item_spacing)
.next_frame(&self.region, child_size, item_spacing)
}
}
/// Where do we expect a zero-sized widget to be placed?
pub(crate) fn next_widget_position(&self) -> Pos2 {
if let Some(grid) = &self.grid {
grid.next_cell(self.region.cursor, Vec2::ZERO).center()
} else {
self.layout.next_widget_position(&self.region)
}
}
/// Apply justify or alignment after calling `next_space`.
pub(crate) fn justify_and_align(&self, rect: Rect, child_size: Vec2) -> Rect {
crate::egui_assert!(!rect.any_nan());
crate::egui_assert!(!child_size.any_nan());
if let Some(grid) = &self.grid {
grid.justify_and_align(rect, child_size)
} else {
@ -124,14 +140,11 @@ impl Placer {
/// Advance the cursor by this many points.
/// [`Self::min_rect`] will expand to contain the cursor.
pub(crate) fn advance_cursor(&mut self, amount: f32) {
debug_assert!(
crate::egui_assert!(
self.grid.is_none(),
"You cannot advance the cursor when in a grid layout"
);
self.layout.advance_cursor(&mut self.region.cursor, amount);
self.region
.expand_to_include_rect(Rect::from_min_size(self.cursor(), Vec2::ZERO));
self.layout.advance_cursor(&mut self.region, amount);
}
/// Advance cursor after a widget was added to a specific rectangle
@ -145,28 +158,40 @@ impl Placer {
widget_rect: Rect,
item_spacing: Vec2,
) {
egui_assert!(!frame_rect.any_nan());
egui_assert!(!widget_rect.any_nan());
self.region.sanity_check();
if let Some(grid) = &mut self.grid {
grid.advance(&mut self.region.cursor, frame_rect, widget_rect)
grid.advance(&mut self.region.cursor, frame_rect, widget_rect);
} else {
self.layout.advance_after_rects(
&mut self.region.cursor,
frame_rect,
widget_rect,
item_spacing,
)
);
}
self.region.expand_to_include_rect(widget_rect);
self.expand_to_include_rect(frame_rect); // e.g. for centered layouts: pretend we used whole frame
self.region.sanity_check();
}
/// Move to the next row in a grid layout or wrapping layout.
/// Otherwise does nothing.
pub(crate) fn end_row(&mut self, item_spacing: Vec2, painter: &Painter) {
if let Some(grid) = &mut self.grid {
grid.end_row(&mut self.region.cursor, painter)
grid.end_row(&mut self.region.cursor, painter);
} else {
self.layout.end_row(&mut self.region, item_spacing)
self.layout.end_row(&mut self.region, item_spacing);
}
}
/// Set row height in horizontal wrapping layout.
pub(crate) fn set_row_height(&mut self, height: f32) {
self.layout.set_row_height(&mut self.region, height);
}
}
impl Placer {
@ -180,81 +205,75 @@ impl Placer {
self.region.expand_to_include_x(x);
}
/// Expand the `min_rect` and `max_rect` of this ui to include a child at the given y-coordinate.
pub(crate) fn expand_to_include_y(&mut self, y: f32) {
self.region.expand_to_include_y(y);
}
fn next_widget_space_ignore_wrap_justify(&self, size: Vec2) -> Rect {
self.layout
.next_widget_space_ignore_wrap_justify(&self.region, size)
}
/// Set the maximum width of the ui.
/// You won't be able to shrink it below the current minimum size.
pub(crate) fn set_max_width(&mut self, width: f32) {
#![allow(clippy::float_cmp)]
let Self { layout, region, .. } = self;
if layout.main_dir() == Direction::RightToLeft {
debug_assert_eq!(region.min_rect.max.x, region.max_rect.max.x);
region.max_rect.min.x = region.max_rect.max.x - width.at_least(region.min_rect.width());
} else {
debug_assert_eq!(region.min_rect.min.x, region.max_rect.min.x);
region.max_rect.max.x = region.max_rect.min.x + width.at_least(region.min_rect.width());
}
let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0));
let region = &mut self.region;
region.max_rect.min.x = rect.min.x;
region.max_rect.max.x = rect.max.x;
region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much
region.cursor.min.x = region.max_rect.min.x;
region.cursor.max.x = region.max_rect.max.x;
region.sanity_check();
}
/// Set the maximum height of the ui.
/// You won't be able to shrink it below the current minimum size.
pub(crate) fn set_max_height(&mut self, height: f32) {
#![allow(clippy::float_cmp)]
let Self { layout, region, .. } = self;
if layout.main_dir() == Direction::BottomUp {
debug_assert_eq!(region.min_rect.max.y, region.max_rect.max.y);
region.max_rect.min.y =
region.max_rect.max.y - height.at_least(region.min_rect.height());
} else {
debug_assert_eq!(region.min_rect.min.y, region.max_rect.min.y);
region.max_rect.max.y =
region.max_rect.min.y + height.at_least(region.min_rect.height());
}
let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height));
let region = &mut self.region;
region.max_rect.min.y = rect.min.y;
region.max_rect.max.y = rect.max.y;
region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much
region.cursor.min.y = region.max_rect.min.y;
region.cursor.max.y = region.max_rect.max.y;
region.sanity_check();
}
/// Set the minimum width of the ui.
/// This can't shrink the ui, only make it larger.
pub(crate) fn set_min_width(&mut self, width: f32) {
#![allow(clippy::float_cmp)]
let Self { layout, region, .. } = self;
if layout.main_dir() == Direction::RightToLeft {
debug_assert_eq!(region.min_rect.max.x, region.max_rect.max.x);
let min_rect = &mut region.min_rect;
min_rect.min.x = min_rect.min.x.min(min_rect.max.x - width);
} else {
debug_assert_eq!(region.min_rect.min.x, region.max_rect.min.x);
let min_rect = &mut region.min_rect;
min_rect.max.x = min_rect.max.x.max(min_rect.min.x + width);
}
region.max_rect = region.max_rect.union(region.min_rect);
let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0));
self.region.expand_to_include_x(rect.min.x);
self.region.expand_to_include_x(rect.max.x);
}
/// Set the minimum height of the ui.
/// This can't shrink the ui, only make it larger.
pub(crate) fn set_min_height(&mut self, height: f32) {
#![allow(clippy::float_cmp)]
let Self { layout, region, .. } = self;
if layout.main_dir() == Direction::BottomUp {
debug_assert_eq!(region.min_rect.max.y, region.max_rect.max.y);
let min_rect = &mut region.min_rect;
min_rect.min.y = min_rect.min.y.min(min_rect.max.y - height);
} else {
debug_assert_eq!(region.min_rect.min.y, region.max_rect.min.y);
let min_rect = &mut region.min_rect;
min_rect.max.y = min_rect.max.y.max(min_rect.min.y + height);
}
region.max_rect = region.max_rect.union(region.min_rect);
let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height));
self.region.expand_to_include_y(rect.min.y);
self.region.expand_to_include_y(rect.max.y);
}
}
impl Placer {
pub(crate) fn debug_paint_cursor(&self, painter: &crate::Painter) {
let color = Color32::GREEN;
let stroke = Stroke::new(2.0, color);
pub(crate) fn debug_paint_cursor(&self, painter: &crate::Painter, text: impl ToString) {
let stroke = Stroke::new(1.0, Color32::DEBUG_COLOR);
if let Some(grid) = &self.grid {
painter.rect_stroke(grid.next_cell(self.cursor(), Vec2::splat(0.0)), 1.0, stroke)
let rect = grid.next_cell(self.cursor(), Vec2::splat(0.0));
painter.rect_stroke(rect, 1.0, stroke);
let align = Align2::CENTER_CENTER;
painter.debug_text(align.pos_in_rect(&rect), align, stroke.color, text);
} else {
self.layout
.debug_paint_cursor(&self.region, stroke, painter)
.paint_text_at_cursor(painter, &self.region, stroke, text);
}
}
}

819
crates/egui/src/response.rs Normal file
View file

@ -0,0 +1,819 @@
use crate::{
emath::{Align, Pos2, Rect, Vec2},
menu, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetText,
NUM_POINTER_BUTTONS,
};
// ----------------------------------------------------------------------------
/// The result of adding a widget to a [`Ui`].
///
/// A [`Response`] lets you know whether or not a widget is being hovered, clicked or dragged.
/// It also lets you easily show a tooltip on hover.
///
/// Whenever something gets added to a [`Ui`], a [`Response`] object is returned.
/// [`ui.add`] returns a [`Response`], as does [`ui.button`], and all similar shortcuts.
// TODO(emilk): we should be using bit sets instead of so many bools
#[derive(Clone)]
pub struct Response {
// CONTEXT:
/// Used for optionally showing a tooltip and checking for more interactions.
pub ctx: Context,
// IN:
/// Which layer the widget is part of.
pub layer_id: LayerId,
/// The [`Id`] of the widget/area this response pertains.
pub id: Id,
/// The area of the screen we are talking about.
pub rect: Rect,
/// The senses (click and/or drag) that the widget was interested in (if any).
pub sense: Sense,
/// Was the widget enabled?
/// If `false`, there was no interaction attempted (not even hover).
#[doc(hidden)]
pub enabled: bool,
// OUT:
/// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
#[doc(hidden)]
pub hovered: bool,
/// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`].
#[doc(hidden)]
pub highlighted: bool,
/// The pointer clicked this thing this frame.
#[doc(hidden)]
pub clicked: [bool; NUM_POINTER_BUTTONS],
// TODO(emilk): `released` for sliders
/// The thing was double-clicked.
#[doc(hidden)]
pub double_clicked: [bool; NUM_POINTER_BUTTONS],
/// The thing was triple-clicked.
pub triple_clicked: [bool; NUM_POINTER_BUTTONS],
/// The widgets is being dragged
#[doc(hidden)]
pub dragged: bool,
/// The widget was being dragged, but now it has been released.
#[doc(hidden)]
pub drag_released: bool,
/// Is the pointer button currently down on this widget?
/// This is true if the pointer is pressing down or dragging a widget
#[doc(hidden)]
pub is_pointer_button_down_on: bool,
/// Where the pointer (mouse/touch) were when when this widget was clicked or dragged.
/// `None` if the widget is not being interacted with.
#[doc(hidden)]
pub interact_pointer_pos: Option<Pos2>,
/// What the underlying data changed?
///
/// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc.
/// Always `false` for something like a [`Button`](crate::Button).
#[doc(hidden)]
pub changed: bool,
}
impl std::fmt::Debug for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
ctx: _,
layer_id,
id,
rect,
sense,
enabled,
hovered,
highlighted,
clicked,
double_clicked,
triple_clicked,
dragged,
drag_released,
is_pointer_button_down_on,
interact_pointer_pos,
changed,
} = self;
f.debug_struct("Response")
.field("layer_id", layer_id)
.field("id", id)
.field("rect", rect)
.field("sense", sense)
.field("enabled", enabled)
.field("hovered", hovered)
.field("highlighted", highlighted)
.field("clicked", clicked)
.field("double_clicked", double_clicked)
.field("triple_clicked", triple_clicked)
.field("dragged", dragged)
.field("drag_released", drag_released)
.field("is_pointer_button_down_on", is_pointer_button_down_on)
.field("interact_pointer_pos", interact_pointer_pos)
.field("changed", changed)
.finish()
}
}
impl Response {
/// Returns true if this widget was clicked this frame by the primary button.
///
/// A click is registered when the mouse or touch is released within
/// a certain amount of time and distance from when and where it was pressed.
///
/// Note that the widget must be sensing clicks with [`Sense::click`].
/// [`crate::Button`] senses clicks; [`crate::Label`] does not (unless you call [`crate::Label::sense`]).
///
/// You can use [`Self::interact`] to sense more things *after* adding a widget.
#[inline(always)]
pub fn clicked(&self) -> bool {
self.clicked[PointerButton::Primary as usize]
}
/// Returns true if this widget was clicked this frame by the given button.
pub fn clicked_by(&self, button: PointerButton) -> bool {
self.clicked[button as usize]
}
/// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button).
pub fn secondary_clicked(&self) -> bool {
self.clicked[PointerButton::Secondary as usize]
}
/// Returns true if this widget was clicked this frame by the middle mouse button.
pub fn middle_clicked(&self) -> bool {
self.clicked[PointerButton::Middle as usize]
}
/// Returns true if this widget was double-clicked this frame by the primary button.
pub fn double_clicked(&self) -> bool {
self.double_clicked[PointerButton::Primary as usize]
}
/// Returns true if this widget was triple-clicked this frame by the primary button.
pub fn triple_clicked(&self) -> bool {
self.triple_clicked[PointerButton::Primary as usize]
}
/// Returns true if this widget was double-clicked this frame by the given button.
pub fn double_clicked_by(&self, button: PointerButton) -> bool {
self.double_clicked[button as usize]
}
/// Returns true if this widget was triple-clicked this frame by the given button.
pub fn triple_clicked_by(&self, button: PointerButton) -> bool {
self.triple_clicked[button as usize]
}
/// `true` if there was a click *outside* this widget this frame.
pub fn clicked_elsewhere(&self) -> bool {
// We do not use self.clicked(), because we want to catch all clicks within our frame,
// even if we aren't clickable (or even enabled).
// This is important for windows and such that should close then the user clicks elsewhere.
self.ctx.input(|i| {
let pointer = &i.pointer;
if pointer.any_click() {
// We detect clicks/hover on a "interact_rect" that is slightly larger than
// self.rect. See Context::interact.
// This means we can be hovered and clicked even though `!self.rect.contains(pos)` is true,
// hence the extra complexity here.
if self.hovered() {
false
} else if let Some(pos) = pointer.interact_pos() {
!self.rect.contains(pos)
} else {
false // clicked without a pointer, weird
}
} else {
false
}
})
}
/// Was the widget enabled?
/// If false, there was no interaction attempted
/// and the widget should be drawn in a gray disabled look.
#[inline(always)]
pub fn enabled(&self) -> bool {
self.enabled
}
/// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
///
/// Note that this is slightly different from checking `response.rect.contains(pointer_pos)`.
/// For one, the hover rectangle is slightly larger, by half of the current item spacing
/// (to make it easier to click things). But `hovered` also checks that no other area
/// is covering this response rectangle.
#[inline(always)]
pub fn hovered(&self) -> bool {
self.hovered
}
/// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`].
#[doc(hidden)]
pub fn highlighted(&self) -> bool {
self.highlighted
}
/// This widget has the keyboard focus (i.e. is receiving key presses).
///
/// This function only returns true if the UI as a whole (e.g. window)
/// also has the keyboard focus. That makes this function suitable
/// for style choices, e.g. a thicker border around focused widgets.
pub fn has_focus(&self) -> bool {
self.ctx.input(|i| i.raw.has_focus) && self.ctx.memory(|mem| mem.has_focus(self.id))
}
/// True if this widget has keyboard focus this frame, but didn't last frame.
pub fn gained_focus(&self) -> bool {
self.ctx.memory(|mem| mem.gained_focus(self.id))
}
/// The widget had keyboard focus and lost it,
/// either because the user pressed tab or clicked somewhere else,
/// or (in case of a [`crate::TextEdit`]) because the user pressed enter.
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// # let mut my_text = String::new();
/// # fn do_request(_: &str) {}
/// let response = ui.text_edit_singleline(&mut my_text);
/// if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
/// do_request(&my_text);
/// }
/// # });
/// ```
pub fn lost_focus(&self) -> bool {
self.ctx.memory(|mem| mem.lost_focus(self.id))
}
/// Request that this widget get keyboard focus.
pub fn request_focus(&self) {
self.ctx.memory_mut(|mem| mem.request_focus(self.id));
}
/// Surrender keyboard focus for this widget.
pub fn surrender_focus(&self) {
self.ctx.memory_mut(|mem| mem.surrender_focus(self.id));
}
/// The widgets is being dragged.
///
/// To find out which button(s), query [`crate::PointerState::button_down`]
/// (`ui.input(|i| i.pointer.button_down(…))`).
///
/// Note that the widget must be sensing drags with [`Sense::drag`].
/// [`crate::DragValue`] senses drags; [`crate::Label`] does not (unless you call [`crate::Label::sense`]).
///
/// You can use [`Self::interact`] to sense more things *after* adding a widget.
#[inline(always)]
pub fn dragged(&self) -> bool {
self.dragged
}
pub fn dragged_by(&self, button: PointerButton) -> bool {
self.dragged() && self.ctx.input(|i| i.pointer.button_down(button))
}
/// Did a drag on this widgets begin this frame?
pub fn drag_started(&self) -> bool {
self.dragged && self.ctx.input(|i| i.pointer.any_pressed())
}
/// Did a drag on this widgets by the button begin this frame?
pub fn drag_started_by(&self, button: PointerButton) -> bool {
self.drag_started() && self.ctx.input(|i| i.pointer.button_pressed(button))
}
/// The widget was being dragged, but now it has been released.
pub fn drag_released(&self) -> bool {
self.drag_released
}
/// The widget was being dragged by the button, but now it has been released.
pub fn drag_released_by(&self, button: PointerButton) -> bool {
self.drag_released() && self.ctx.input(|i| i.pointer.button_released(button))
}
/// If dragged, how many points were we dragged and in what direction?
pub fn drag_delta(&self) -> Vec2 {
if self.dragged() {
self.ctx.input(|i| i.pointer.delta())
} else {
Vec2::ZERO
}
}
/// Where the pointer (mouse/touch) were when when this widget was clicked or dragged.
/// `None` if the widget is not being interacted with.
pub fn interact_pointer_pos(&self) -> Option<Pos2> {
self.interact_pointer_pos
}
/// If it is a good idea to show a tooltip, where is pointer?
/// None if the pointer is outside the response area.
pub fn hover_pos(&self) -> Option<Pos2> {
if self.hovered() {
self.ctx.input(|i| i.pointer.hover_pos())
} else {
None
}
}
/// Is the pointer button currently down on this widget?
/// This is true if the pointer is pressing down or dragging a widget
#[inline(always)]
pub fn is_pointer_button_down_on(&self) -> bool {
self.is_pointer_button_down_on
}
/// What the underlying data changed?
///
/// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc.
/// Always `false` for something like a [`Button`](crate::Button).
///
/// Can sometimes be `true` even though the data didn't changed
/// (e.g. if the user entered a character and erased it the same frame).
///
/// This is not set if the *view* of the data was changed.
/// For instance, moving the cursor in a [`TextEdit`](crate::TextEdit) does not set this to `true`.
#[inline(always)]
pub fn changed(&self) -> bool {
self.changed
}
/// Report the data shown by this widget changed.
///
/// This must be called by widgets that represent some mutable data,
/// e.g. checkboxes, sliders etc.
///
/// This should be called when the *content* changes, but not when the view does.
/// So we call this when the text of a [`crate::TextEdit`], but not when the cursors changes.
#[inline(always)]
pub fn mark_changed(&mut self) {
self.changed = true;
}
/// Show this UI if the widget was hovered (i.e. a tooltip).
///
/// The text will not be visible if the widget is not enabled.
/// For that, use [`Self::on_disabled_hover_ui`] instead.
///
/// If you call this multiple times the tooltips will stack underneath the previous ones.
#[doc(alias = "tooltip")]
pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
if self.should_show_hover_ui() {
crate::containers::show_tooltip_for(
&self.ctx,
self.id.with("__tooltip"),
&self.rect,
add_contents,
);
}
self
}
/// Show this UI when hovering if the widget is disabled.
pub fn on_disabled_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
if !self.enabled && self.ctx.rect_contains_pointer(self.layer_id, self.rect) {
crate::containers::show_tooltip_for(
&self.ctx,
self.id.with("__tooltip"),
&self.rect,
add_contents,
);
}
self
}
/// Like `on_hover_ui`, but show the ui next to cursor.
pub fn on_hover_ui_at_pointer(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
if self.should_show_hover_ui() {
crate::containers::show_tooltip_at_pointer(
&self.ctx,
self.id.with("__tooltip"),
add_contents,
);
}
self
}
/// Was the tooltip open last frame?
pub fn is_tooltip_open(&self) -> bool {
crate::popup::was_tooltip_open_last_frame(&self.ctx, self.id.with("__tooltip"))
}
fn should_show_hover_ui(&self) -> bool {
if self.ctx.memory(|mem| mem.everything_is_visible()) {
return true;
}
if !self.hovered || !self.ctx.input(|i| i.pointer.has_pointer()) {
return false;
}
if self.ctx.style().interaction.show_tooltips_only_when_still {
// We only show the tooltip when the mouse pointer is still,
// but once shown we keep showing it until the mouse leaves the parent.
if !self.ctx.input(|i| i.pointer.is_still()) && !self.is_tooltip_open() {
// wait for mouse to stop
self.ctx.request_repaint();
return false;
}
}
// We don't want tooltips of things while we are dragging them,
// but we do want tooltips while holding down on an item on a touch screen.
if self
.ctx
.input(|i| i.pointer.any_down() && i.pointer.has_moved_too_much_for_a_click)
{
return false;
}
true
}
/// Like `on_hover_text`, but show the text next to cursor.
#[doc(alias = "tooltip")]
pub fn on_hover_text_at_pointer(self, text: impl Into<WidgetText>) -> Self {
self.on_hover_ui_at_pointer(|ui| {
ui.add(crate::widgets::Label::new(text));
})
}
/// Show this text if the widget was hovered (i.e. a tooltip).
///
/// The text will not be visible if the widget is not enabled.
/// For that, use [`Self::on_disabled_hover_text`] instead.
///
/// If you call this multiple times the tooltips will stack underneath the previous ones.
#[doc(alias = "tooltip")]
pub fn on_hover_text(self, text: impl Into<WidgetText>) -> Self {
self.on_hover_ui(|ui| {
ui.add(crate::widgets::Label::new(text));
})
}
/// Highlight this widget, to make it look like it is hovered, even if it isn't.
///
/// The highlight takes on frame to take effect if you call this after the widget has been fully rendered.
///
/// See also [`Context::highlight_widget`].
pub fn highlight(mut self) -> Self {
self.ctx.highlight_widget(self.id);
self.highlighted = true;
self
}
/// Show this text when hovering if the widget is disabled.
pub fn on_disabled_hover_text(self, text: impl Into<WidgetText>) -> Self {
self.on_disabled_hover_ui(|ui| {
ui.add(crate::widgets::Label::new(text));
})
}
/// When hovered, use this icon for the mouse cursor.
pub fn on_hover_cursor(self, cursor: CursorIcon) -> Self {
if self.hovered() {
self.ctx.set_cursor_icon(cursor);
}
self
}
/// When hovered or dragged, use this icon for the mouse cursor.
pub fn on_hover_and_drag_cursor(self, cursor: CursorIcon) -> Self {
if self.hovered() || self.dragged() {
self.ctx.set_cursor_icon(cursor);
}
self
}
/// Check for more interactions (e.g. sense clicks on a [`Response`] returned from a label).
///
/// Note that this call will not add any hover-effects to the widget, so when possible
/// it is better to give the widget a [`Sense`] instead, e.g. using [`crate::Label::sense`].
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// let response = ui.label("hello");
/// assert!(!response.clicked()); // labels don't sense clicks by default
/// let response = response.interact(egui::Sense::click());
/// if response.clicked() { /* … */ }
/// # });
/// ```
#[must_use]
pub fn interact(&self, sense: Sense) -> Self {
self.ctx.interact_with_hovered(
self.layer_id,
self.id,
self.rect,
sense,
self.enabled,
self.hovered,
)
}
/// Adjust the scroll position until this UI becomes visible.
///
/// If `align` is `None`, it'll scroll enough to bring the UI into view.
///
/// See also: [`Ui::scroll_to_cursor`], [`Ui::scroll_to_rect`]. [`Ui::scroll_with_delta`].
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// egui::ScrollArea::vertical().show(ui, |ui| {
/// for i in 0..1000 {
/// let response = ui.button("Scroll to me");
/// if response.clicked() {
/// response.scroll_to_me(Some(egui::Align::Center));
/// }
/// }
/// });
/// # });
/// ```
pub fn scroll_to_me(&self, align: Option<Align>) {
self.ctx.frame_state_mut(|state| {
state.scroll_target[0] = Some((self.rect.x_range(), align));
state.scroll_target[1] = Some((self.rect.y_range(), align));
});
}
/// For accessibility.
///
/// Call after interacting and potential calls to [`Self::mark_changed`].
pub fn widget_info(&self, make_info: impl Fn() -> crate::WidgetInfo) {
use crate::output::OutputEvent;
let event = if self.clicked() {
Some(OutputEvent::Clicked(make_info()))
} else if self.double_clicked() {
Some(OutputEvent::DoubleClicked(make_info()))
} else if self.triple_clicked() {
Some(OutputEvent::TripleClicked(make_info()))
} else if self.gained_focus() {
Some(OutputEvent::FocusGained(make_info()))
} else if self.changed {
Some(OutputEvent::ValueChanged(make_info()))
} else {
None
};
if let Some(event) = event {
self.output_event(event);
} else {
#[cfg(feature = "accesskit")]
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_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, 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 {
builder.add_action(accesskit::Action::Focus);
}
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,
builder: &mut accesskit::NodeBuilder,
info: crate::WidgetInfo,
) {
use crate::WidgetType;
use accesskit::{CheckedState, Role};
self.fill_accesskit_node_common(builder);
builder.set_role(match info.typ {
WidgetType::Label => Role::StaticText,
WidgetType::Link => Role::Link,
WidgetType::TextEdit => Role::TextField,
WidgetType::Button | WidgetType::ImageButton | WidgetType::CollapsingHeader => {
Role::Button
}
WidgetType::Checkbox => Role::CheckBox,
WidgetType::RadioButton => Role::RadioButton,
WidgetType::SelectableLabel => Role::ToggleButton,
WidgetType::ComboBox => Role::PopupButton,
WidgetType::Slider => Role::Slider,
WidgetType::DragValue => Role::SpinButton,
WidgetType::ColorButton => Role::ColorWell,
WidgetType::Other => Role::Unknown,
});
if let Some(label) = info.label {
builder.set_name(label);
}
if let Some(value) = info.current_text_value {
builder.set_value(value);
}
if let Some(value) = info.value {
builder.set_numeric_value(value);
}
if let Some(selected) = info.selected {
builder.set_checked_state(if selected {
CheckedState::True
} else {
CheckedState::False
});
}
}
/// Associate a label with a control for accessibility.
///
/// # Example
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// # let mut text = "Arthur".to_string();
/// ui.horizontal(|ui| {
/// let label = ui.label("Your name: ");
/// ui.text_edit_singleline(&mut text).labelled_by(label.id);
/// });
/// # });
/// ```
pub fn labelled_by(self, id: Id) -> Self {
#[cfg(feature = "accesskit")]
self.ctx.accesskit_node_builder(self.id, |builder| {
builder.push_labelled_by(id.accesskit_id());
});
#[cfg(not(feature = "accesskit"))]
{
let _ = id;
}
self
}
/// Response to secondary clicks (right-clicks) by showing the given menu.
///
/// ```
/// # use egui::{Label, Sense};
/// # egui::__run_test_ui(|ui| {
/// let response = ui.add(Label::new("Right-click me!").sense(Sense::click()));
/// response.context_menu(|ui| {
/// if ui.button("Close the menu").clicked() {
/// ui.close_menu();
/// }
/// });
/// # });
/// ```
///
/// See also: [`Ui::menu_button`] and [`Ui::close_menu`].
pub fn context_menu(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
menu::context_menu(&self, add_contents);
self
}
}
impl Response {
/// A logical "or" operation.
/// For instance `a.union(b).hovered` means "was either a or b hovered?".
///
/// The resulting [`Self::id`] will come from the first (`self`) argument.
pub fn union(&self, other: Self) -> Self {
assert!(self.ctx == other.ctx);
crate::egui_assert!(
self.layer_id == other.layer_id,
"It makes no sense to combine Responses from two different layers"
);
Self {
ctx: other.ctx,
layer_id: self.layer_id,
id: self.id,
rect: self.rect.union(other.rect),
sense: self.sense.union(other.sense),
enabled: self.enabled || other.enabled,
hovered: self.hovered || other.hovered,
highlighted: self.highlighted || other.highlighted,
clicked: [
self.clicked[0] || other.clicked[0],
self.clicked[1] || other.clicked[1],
self.clicked[2] || other.clicked[2],
self.clicked[3] || other.clicked[3],
self.clicked[4] || other.clicked[4],
],
double_clicked: [
self.double_clicked[0] || other.double_clicked[0],
self.double_clicked[1] || other.double_clicked[1],
self.double_clicked[2] || other.double_clicked[2],
self.double_clicked[3] || other.double_clicked[3],
self.double_clicked[4] || other.double_clicked[4],
],
triple_clicked: [
self.triple_clicked[0] || other.triple_clicked[0],
self.triple_clicked[1] || other.triple_clicked[1],
self.triple_clicked[2] || other.triple_clicked[2],
self.triple_clicked[3] || other.triple_clicked[3],
self.triple_clicked[4] || other.triple_clicked[4],
],
dragged: self.dragged || other.dragged,
drag_released: self.drag_released || other.drag_released,
is_pointer_button_down_on: self.is_pointer_button_down_on
|| other.is_pointer_button_down_on,
interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos),
changed: self.changed || other.changed,
}
}
}
impl Response {
/// Returns a response with a modified [`Self::rect`].
pub fn with_new_rect(self, rect: Rect) -> Self {
Self { rect, ..self }
}
}
/// To summarize the response from many widgets you can use this pattern:
///
/// ```
/// use egui::*;
/// fn draw_vec2(ui: &mut Ui, v: &mut Vec2) -> Response {
/// ui.add(DragValue::new(&mut v.x)) | ui.add(DragValue::new(&mut v.y))
/// }
/// ```
///
/// Now `draw_vec2(ui, foo).hovered` is true if either [`DragValue`](crate::DragValue) were hovered.
impl std::ops::BitOr for Response {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
/// To summarize the response from many widgets you can use this pattern:
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// # let (widget_a, widget_b, widget_c) = (egui::Label::new("a"), egui::Label::new("b"), egui::Label::new("c"));
/// let mut response = ui.add(widget_a);
/// response |= ui.add(widget_b);
/// response |= ui.add(widget_c);
/// if response.hovered() { ui.label("You hovered at least one of the widgets"); }
/// # });
/// ```
impl std::ops::BitOrAssign for Response {
fn bitor_assign(&mut self, rhs: Self) {
*self = self.union(rhs);
}
}
// ----------------------------------------------------------------------------
/// Returned when we wrap some ui-code and want to return both
/// the results of the inner function and the ui as a whole, e.g.:
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// let inner_resp = ui.horizontal(|ui| {
/// ui.label("Blah blah");
/// 42
/// });
/// inner_resp.response.on_hover_text("You hovered the horizontal layout");
/// assert_eq!(inner_resp.inner, 42);
/// # });
/// ```
#[derive(Debug)]
pub struct InnerResponse<R> {
/// What the user closure returned.
pub inner: R,
/// The response of the area.
pub response: Response,
}
impl<R> InnerResponse<R> {
#[inline]
pub fn new(inner: R, response: Response) -> Self {
Self { inner, response }
}
}

View file

@ -1,26 +1,38 @@
/// What sort of interaction is a widget sensitive to?
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
// #[cfg_attr(feature = "persistence", derive(serde::Serialize))]
// #[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Sense {
/// buttons, sliders, windows ...
/// buttons, sliders, windows, …
pub click: bool,
/// sliders, windows, scroll bars, scroll areas ...
/// sliders, windows, scroll bars, scroll areas, …
pub drag: bool,
/// this widgets want focus.
/// Anything interactive + labels that can be focused
/// for the benefit of screen readers.
pub focusable: bool,
}
impl Sense {
/// Senses no clicks or drags. Only senses mouse hover.
#[doc(alias = "none")]
pub fn hover() -> Self {
Self {
click: false,
drag: false,
focusable: false,
}
}
#[deprecated = "Use hover()"]
pub fn nothing() -> Self {
Sense::hover()
/// Senses no clicks or drags, but can be focused with the keyboard.
/// Used for labels that can be focused for the benefit of screen readers.
pub fn focusable_noninteractive() -> Self {
Self {
click: false,
drag: false,
focusable: true,
}
}
/// Sense clicks and hover, but not drags.
@ -28,6 +40,7 @@ impl Sense {
Self {
click: true,
drag: false,
focusable: true,
}
}
@ -36,6 +49,7 @@ impl Sense {
Self {
click: false,
drag: true,
focusable: true,
}
}
@ -44,15 +58,22 @@ impl Sense {
Self {
click: true,
drag: true,
focusable: true,
}
}
/// The logical "or" of two `Sense`s.
/// The logical "or" of two [`Sense`]s.
#[must_use]
pub fn union(self, other: Self) -> Self {
Self {
click: self.click | other.click,
drag: self.drag | other.drag,
focusable: self.focusable | other.focusable,
}
}
/// Returns true if we sense either clicks or drags.
pub fn interactive(&self) -> bool {
self.click || self.drag
}
}

1507
crates/egui/src/style.rs Normal file

File diff suppressed because it is too large Load diff

2233
crates/egui/src/ui.rs Normal file

File diff suppressed because it is too large Load diff

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