mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 14:49:06 -04:00
Merge branch 'main' into lucas/experiments/measure-widget-size
# Conflicts: # crates/egui/src/atomics/atom.rs # crates/egui/src/atomics/atom_kind.rs # crates/egui/src/atomics/atom_layout.rs # crates/egui/src/containers/sides.rs # crates/egui/src/placer.rs # crates/egui/src/widgets/label.rs # crates/egui/src/widgets/selected_label.rs # crates/egui_demo_lib/src/demo/dancing_strings.rs # crates/egui_extras/src/layout.rs # crates/epaint/src/text/text_layout_types.rs
This commit is contained in:
2
.github/workflows/deploy_web_demo.yml
vendored
2
.github/workflows/deploy_web_demo.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
with:
|
||||
profile: minimal
|
||||
target: wasm32-unknown-unknown
|
||||
toolchain: 1.84.0
|
||||
toolchain: 1.85.0
|
||||
override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
2
.github/workflows/preview_build.yml
vendored
2
.github/workflows/preview_build.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.84.0
|
||||
toolchain: 1.85.0
|
||||
targets: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
|
||||
14
.github/workflows/rust.yml
vendored
14
.github/workflows/rust.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.84.0
|
||||
toolchain: 1.85.0
|
||||
|
||||
- name: Install packages (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.84.0
|
||||
toolchain: 1.85.0
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
rust-version: "1.84.0"
|
||||
rust-version: "1.85.0"
|
||||
log-level: error
|
||||
command: check
|
||||
arguments: --target ${{ matrix.target }}
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.84.0
|
||||
toolchain: 1.85.0
|
||||
targets: aarch64-linux-android
|
||||
|
||||
- name: Set up cargo cache
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.84.0
|
||||
toolchain: 1.85.0
|
||||
targets: aarch64-apple-ios
|
||||
|
||||
- name: Set up cargo cache
|
||||
@@ -210,7 +210,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.84.0
|
||||
toolchain: 1.85.0
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -234,7 +234,7 @@ jobs:
|
||||
lfs: true
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.84.0
|
||||
toolchain: 1.85.0
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
128
.typos.toml
128
.typos.toml
@@ -18,5 +18,133 @@ teselation = "tessellation"
|
||||
tessalation = "tessellation"
|
||||
tesselation = "tessellation"
|
||||
|
||||
|
||||
# Use the more common spelling
|
||||
adaptor = "adapter"
|
||||
adaptors = "adapters"
|
||||
|
||||
# For consistency we prefer American English:
|
||||
aeroplane = "airplane"
|
||||
analogue = "analog"
|
||||
analyse = "analyze"
|
||||
appetiser = "appetizer"
|
||||
arbour = "arbor"
|
||||
ardour = "arbor"
|
||||
armour = "armor"
|
||||
artefact = "artifact"
|
||||
authorise = "authorize"
|
||||
behaviour = "behavior"
|
||||
behavioural = "behavioral"
|
||||
British = "American"
|
||||
calibre = "caliber"
|
||||
# cancelled = "canceled" # winit uses this :(
|
||||
candour = "candor"
|
||||
capitalise = "capitalize"
|
||||
catalogue = "catalog"
|
||||
centre = "center"
|
||||
characterise = "characterize"
|
||||
chequerboard = "checkerboard"
|
||||
chequered = "checkered"
|
||||
civilise = "civilize"
|
||||
clamour = "clamor"
|
||||
colonise = "colonize"
|
||||
colour = "color"
|
||||
coloured = "colored"
|
||||
cosy = "cozy"
|
||||
criticise = "criticize"
|
||||
defence = "defense"
|
||||
demeanour = "demeanor"
|
||||
dialogue = "dialog"
|
||||
distil = "distill"
|
||||
doughnut = "donut"
|
||||
dramatise = "dramatize"
|
||||
draught = "draft"
|
||||
emphasise = "emphasize"
|
||||
endeavour = "endeavor"
|
||||
enrol = "enroll"
|
||||
epilogue = "epilog"
|
||||
equalise = "equalize"
|
||||
favour = "favor"
|
||||
favourite = "favorite"
|
||||
fibre = "fiber"
|
||||
flavour = "flavor"
|
||||
fulfil = "fufill"
|
||||
gaol = "jail"
|
||||
grey = "gray"
|
||||
greys = "grays"
|
||||
greyscale = "grayscale"
|
||||
harbour = "habor"
|
||||
honour = "honor"
|
||||
humour = "humor"
|
||||
instalment = "installment"
|
||||
instil = "instill"
|
||||
jewellery = "jewelry"
|
||||
kerb = "curb"
|
||||
labour = "labor"
|
||||
litre = "liter"
|
||||
lustre = "luster"
|
||||
meagre = "meager"
|
||||
metre = "meter"
|
||||
mobilise = "mobilize"
|
||||
monologue = "monolog"
|
||||
naturalise = "naturalize"
|
||||
neighbour = "neighbor"
|
||||
neighbourhood = "neighborhood"
|
||||
normalise = "normalize"
|
||||
normalised = "normalized"
|
||||
odour = "odor"
|
||||
offence = "offense"
|
||||
organise = "organize"
|
||||
parlour = "parlor"
|
||||
plough = "plow"
|
||||
popularise = "popularize"
|
||||
pretence = "pretense"
|
||||
programme = "program"
|
||||
prologue = "prolog"
|
||||
rancour = "rancor"
|
||||
realise = "realize"
|
||||
recognise = "recognize"
|
||||
recognised = "recognized"
|
||||
rigour = "rigor"
|
||||
rumour = "rumor"
|
||||
sabre = "saber"
|
||||
satirise = "satirize"
|
||||
saviour = "savior"
|
||||
savour = "savor"
|
||||
sceptical = "skeptical"
|
||||
sceptre = "scepter"
|
||||
sepulchre = "sepulcher"
|
||||
serialisation = "serialization"
|
||||
serialise = "serialize"
|
||||
serialised = "serialized"
|
||||
skilful = "skillful"
|
||||
sombre = "somber"
|
||||
specialisation = "specialization"
|
||||
specialise = "specialize"
|
||||
specialised = "specialized"
|
||||
splendour = "splendor"
|
||||
standardise = "standardize"
|
||||
sulphur = "sulfur"
|
||||
symbolise = "symbolize"
|
||||
theatre = "theater"
|
||||
tonne = "ton"
|
||||
travelogue = "travelog"
|
||||
tumour = "tumor"
|
||||
valour = "valor"
|
||||
vaporise = "vaporize"
|
||||
vigour = "vigor"
|
||||
|
||||
# null-terminated is the name of the wikipedia article!
|
||||
# https://en.wikipedia.org/wiki/Null-terminated_string
|
||||
nullterminated = "null-terminated"
|
||||
zeroterminated = "null-terminated"
|
||||
zero-terminated = "null-terminated"
|
||||
|
||||
|
||||
[files]
|
||||
extend-exclude = ["web_demo/egui_demo_app.js"] # auto-generated
|
||||
|
||||
[default]
|
||||
extend-ignore-re = [
|
||||
"#\\[doc\\(alias = .*", # We suggest "grey" in some doc
|
||||
]
|
||||
|
||||
265
CHANGELOG.md
265
CHANGELOG.md
@@ -14,6 +14,271 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.32.0 - 2025-07-10 - Atoms, popups, and better SVG support
|
||||
This is a big egui release, with several exciting new features!
|
||||
|
||||
* _Atoms_ are new layout primitives in egui, for text and images
|
||||
* Popups, tooltips and menus have undergone a complete rewrite
|
||||
* Much improved SVG support
|
||||
* Crisper graphics (especially text!)
|
||||
|
||||
Let's dive in!
|
||||
|
||||
### ⚛️ Atoms
|
||||
|
||||
`egui::Atom` is the new, indivisible building blocks of egui (hence their name).
|
||||
An `Atom` is an `enum` that can be either `WidgetText`, `Image`, or `Custom`.
|
||||
|
||||
The new `AtomLayout` can be used within widgets to do basic layout.
|
||||
The initial implementation is as minimal as possible, doing just enough to implement what `Button` could do before.
|
||||
There is a new `IntoAtoms` trait that works with tuples of `Atom`s. Each atom can be customized with the `AtomExt` trait
|
||||
which works on everything that implements `Into<Atom>`, so e.g. `RichText` or `Image`.
|
||||
So to create a `Button` with text and image you can now do:
|
||||
```rs
|
||||
let image = include_image!("my_icon.png").atom_size(Vec2::splat(12.0));
|
||||
ui.button((image, "Click me!"));
|
||||
```
|
||||
|
||||
Anywhere you see `impl IntoAtoms` you can add any number of images and text, in any order.
|
||||
|
||||
As of 0.32, we have ported the `Button`, `Checkbox`, `RadioButton` to use atoms
|
||||
(meaning they support adding Atoms and are built on top of `AtomLayout`).
|
||||
The `Button` implementation is not only more powerful now, but also much simpler, removing ~130 lines of layout math.
|
||||
|
||||
In combination with `ui.read_response`, custom widgets are really simple now, here is a minimal button implementation:
|
||||
|
||||
```rs
|
||||
pub struct ALButton<'a> {
|
||||
al: AtomLayout<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ALButton<'a> {
|
||||
pub fn new(content: impl IntoAtoms<'a>) -> Self {
|
||||
Self {
|
||||
al: AtomLayout::new(content.into_atoms()).sense(Sense::click()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for ALButton<'a> {
|
||||
fn ui(mut self, ui: &mut Ui) -> Response {
|
||||
let Self { al } = self;
|
||||
let response = ui.ctx().read_response(ui.next_auto_id());
|
||||
|
||||
let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
|
||||
ui.style().interact(&response)
|
||||
});
|
||||
|
||||
let al = al.frame(
|
||||
Frame::new()
|
||||
.inner_margin(ui.style().spacing.button_padding)
|
||||
.fill(visuals.bg_fill)
|
||||
.stroke(visuals.bg_stroke)
|
||||
.corner_radius(visuals.corner_radius),
|
||||
);
|
||||
|
||||
al.show(ui).response
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can even use `Atom::custom` to add custom content to Widgets. Here is a button in a button:
|
||||
|
||||
https://github.com/user-attachments/assets/8c649784-dcc5-4979-85f8-e735b9cdd090
|
||||
|
||||
```rs
|
||||
let custom_button_id = Id::new("custom_button");
|
||||
let response = Button::new((
|
||||
Atom::custom(custom_button_id, Vec2::splat(18.0)),
|
||||
"Look at my mini button!",
|
||||
))
|
||||
.atom_ui(ui);
|
||||
if let Some(rect) = response.rect(custom_button_id) {
|
||||
ui.put(rect, Button::new("🔎").frame_when_inactive(false));
|
||||
}
|
||||
```
|
||||
Currently, you need to use `atom_ui` to get a `AtomResponse` which will have the `Rect` to use, but in the future
|
||||
this could be streamlined, e.g. by adding a `AtomKind::Callback` or by passing the Rects back with `egui::Response`.
|
||||
|
||||
Basing our widgets on `AtomLayout` also allowed us to improve `Response::intrinsic_size`, which will now report the
|
||||
correct size even if widgets are truncated. `intrinsic_size` is the size that a non-wrapped, non-truncated,
|
||||
non-justified version of the widget would have, and can be useful in advanced layout
|
||||
calculations like [egui_flex](https://github.com/lucasmerlin/hello_egui/tree/main/crates/egui_flex).
|
||||
|
||||
##### Details
|
||||
* Add `AtomLayout`, abstracting layouting within widgets [#5830](https://github.com/emilk/egui/pull/5830) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add `Galley::intrinsic_size` and use it in `AtomLayout` [#7146](https://github.com/emilk/egui/pull/7146) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
|
||||
|
||||
### ❕ Improved popups, tooltips, and menus
|
||||
|
||||
Introduces a new `egui::Popup` api. Checkout the new demo on https://egui.rs:
|
||||
|
||||
https://github.com/user-attachments/assets/74e45243-7d05-4fc3-b446-2387e1412c05
|
||||
|
||||
We introduced a new `RectAlign` helper to align a rect relative to an other rect. The `Popup` will by default try to find the best `RectAlign` based on the source widgets position (previously submenus would annoyingly overlap if at the edge of the window):
|
||||
|
||||
https://github.com/user-attachments/assets/0c5adb6b-8310-4e0a-b936-646bb4ec02f7
|
||||
|
||||
`Tooltip` and `menu` have been rewritten based on the new `Popup` api. They are now compatible with each other, meaning you can just show a `ui.menu_button()` in any `Popup` to get a sub menu. There are now customizable `MenuButton` and `SubMenuButton` structs, to help with customizing your menu buttons. This means menus now also support `PopupCloseBehavior` so you can remove your `close_menu` calls from your click handlers!
|
||||
|
||||
The old tooltip and popup apis have been ported to the new api so there should be very little breaking changes. The old menu is still around but deprecated. `ui.menu_button` etc now open the new menu, if you can't update to the new one immediately you can use the old buttons from the deprecated `egui::menu` menu.
|
||||
|
||||
We also introduced `ui.close()` which closes the nearest container. So you can now conveniently close `Window`s, `Collapsible`s, `Modal`s and `Popup`s from within. To use this for your own containers, call `UiBuilder::closable` and then check for closing within that ui via `ui.should_close()`.
|
||||
|
||||
##### Details
|
||||
* Add `Popup` and `Tooltip`, unifying the previous behaviours [#5713](https://github.com/emilk/egui/pull/5713) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add `Ui::close` and `Response::should_close` [#5729](https://github.com/emilk/egui/pull/5729) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* ⚠️ Improved menu based on `egui::Popup` [#5716](https://github.com/emilk/egui/pull/5716) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add a toggle for the compact menu style [#5777](https://github.com/emilk/egui/pull/5777) by [@s-nie](https://github.com/s-nie)
|
||||
* Use the new `Popup` API for the color picker button [#7137](https://github.com/emilk/egui/pull/7137) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* ⚠️ Close popup if `Memory::keep_popup_open` isn't called [#5814](https://github.com/emilk/egui/pull/5814) by [@juancampa](https://github.com/juancampa)
|
||||
* Fix tooltips sometimes changing position each frame [#7304](https://github.com/emilk/egui/pull/7304) by [@emilk](https://github.com/emilk)
|
||||
* Change popup memory to be per-viewport [#6753](https://github.com/emilk/egui/pull/6753) by [@mkalte666](https://github.com/mkalte666)
|
||||
* Deprecate `Memory::popup` API in favor of new `Popup` API [#7317](https://github.com/emilk/egui/pull/7317) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
### ▲ Improved SVG support
|
||||
You can render SVG in egui with
|
||||
|
||||
```rs
|
||||
ui.add(egui::Image::new(egui::include_image!("icon.svg"));
|
||||
```
|
||||
|
||||
(Requires the use of `egui_extras`, with the `svg` feature enabled and a call to [`install_image_loaders`](https://docs.rs/egui_extras/latest/egui_extras/fn.install_image_loaders.html)).
|
||||
|
||||
Previously this would sometimes result in a blurry SVG, epecially if the `Image` was set to be dynamically scale based on the size of the `Ui` that contained it. Now SVG:s are always pixel-perfect, for truly scalable graphics.
|
||||
|
||||

|
||||
|
||||
##### Details
|
||||
* Support text in SVGs [#5979](https://github.com/emilk/egui/pull/5979) by [@cernec1999](https://github.com/cernec1999)
|
||||
* Fix sometimes blurry SVGs [#7071](https://github.com/emilk/egui/pull/7071) by [@emilk](https://github.com/emilk)
|
||||
* Fix incorrect color fringe colors on SVG:s [#7069](https://github.com/emilk/egui/pull/7069) by [@emilk](https://github.com/emilk)
|
||||
* Make `Image::paint_at` pixel-perfect crisp for SVG images [#7078](https://github.com/emilk/egui/pull/7078) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
### ✨ Crisper graphics
|
||||
Non-SVG icons are also rendered better, and text sharpness has been improved, especially in light mode.
|
||||
|
||||

|
||||
|
||||
##### Details
|
||||
* Improve text sharpness [#5838](https://github.com/emilk/egui/pull/5838) by [@emilk](https://github.com/emilk)
|
||||
* Improve text rendering in light mode [#7290](https://github.com/emilk/egui/pull/7290) by [@emilk](https://github.com/emilk)
|
||||
* Improve texture filtering by doing it in gamma space [#7311](https://github.com/emilk/egui/pull/7311) by [@emilk](https://github.com/emilk)
|
||||
* Make text underline and strikethrough pixel perfect crisp [#5857](https://github.com/emilk/egui/pull/5857) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### Migration guide
|
||||
We have some silently breaking changes (code compiles fine but behavior changed) that require special care:
|
||||
|
||||
#### Menus close on click by default
|
||||
- previously menus would only close on click outside
|
||||
- either
|
||||
- remove the `ui.close_menu()` calls from button click handlers since they are obsolete
|
||||
- if the menu should stay open on clicks, change the `PopupCloseBehavior`:
|
||||
```rs
|
||||
// Change this
|
||||
ui.menu_button("Text", |ui| { /* Menu Content */ });
|
||||
// To this:
|
||||
MenuButton::new("Text").config(
|
||||
MenuConfig::default().close_behavior(PopupCloseBehavior::CloseOnClickOutside),
|
||||
).ui(ui, |ui| { /* Menu Content */ });
|
||||
```
|
||||
You can also change the behavior only for a single SubMenu by using `SubMenuButton`, but by default it should be passed to any submenus when using `MenuButton`.
|
||||
|
||||
#### `Memory::is_popup_open` api now requires calls to `Memory::keep_popup_open`
|
||||
- The popup will immediately close if `keep_popup_open` is not called.
|
||||
- It's recommended to use the new `Popup` api which handles this for you.
|
||||
- If you can't switch to the new api for some reason, update the code to call `keep_popup_open`:
|
||||
```rs
|
||||
if ui.memory(|mem| mem.is_popup_open(popup_id)) {
|
||||
ui.memory_mut(|mem| mem.keep_popup_open(popup_id)); // <- add this line
|
||||
let area_response = Area::new(popup_id).show(...)
|
||||
}
|
||||
```
|
||||
|
||||
### ⭐ Other improvements
|
||||
* Add `Label::show_tooltip_when_elided` [#5710](https://github.com/emilk/egui/pull/5710) by [@bryceberger](https://github.com/bryceberger)
|
||||
* Deprecate `Ui::allocate_new_ui` in favor of `Ui::scope_builder` [#5764](https://github.com/emilk/egui/pull/5764) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add `expand_bg` to customize size of text background [#5365](https://github.com/emilk/egui/pull/5365) by [@MeGaGiGaGon](https://github.com/MeGaGiGaGon)
|
||||
* Add assert messages and print bad argument values in asserts [#5216](https://github.com/emilk/egui/pull/5216) by [@bircni](https://github.com/bircni)
|
||||
* Use `TextBuffer` for `layouter` in `TextEdit` instead of `&str` [#5712](https://github.com/emilk/egui/pull/5712) by [@kernelkind](https://github.com/kernelkind)
|
||||
* Add a `Slider::update_while_editing(bool)` API [#5978](https://github.com/emilk/egui/pull/5978) by [@mbernat](https://github.com/mbernat)
|
||||
* Add `Scene::drag_pan_buttons` option. Allows specifying which pointer buttons pan the scene by dragging [#5892](https://github.com/emilk/egui/pull/5892) by [@mitchmindtree](https://github.com/mitchmindtree)
|
||||
* Add `Scene::sense` to customize how `Scene` responds to user input [#5893](https://github.com/emilk/egui/pull/5893) by [@mitchmindtree](https://github.com/mitchmindtree)
|
||||
* Rework `TextEdit` arrow navigation to handle Unicode graphemes [#5812](https://github.com/emilk/egui/pull/5812) by [@MStarha](https://github.com/MStarha)
|
||||
* `ScrollArea` improvements for user configurability [#5443](https://github.com/emilk/egui/pull/5443) by [@MStarha](https://github.com/MStarha)
|
||||
* Add `Response::clicked_with_open_in_background` [#7093](https://github.com/emilk/egui/pull/7093) by [@emilk](https://github.com/emilk)
|
||||
* Add `Modifiers::matches_any` [#7123](https://github.com/emilk/egui/pull/7123) by [@emilk](https://github.com/emilk)
|
||||
* Add `Context::format_modifiers` [#7125](https://github.com/emilk/egui/pull/7125) by [@emilk](https://github.com/emilk)
|
||||
* Add `OperatingSystem::is_mac` [#7122](https://github.com/emilk/egui/pull/7122) by [@emilk](https://github.com/emilk)
|
||||
* Support vertical-only scrolling by holding down Alt [#7124](https://github.com/emilk/egui/pull/7124) by [@emilk](https://github.com/emilk)
|
||||
* Support for back-button on Android [#7073](https://github.com/emilk/egui/pull/7073) by [@ardocrat](https://github.com/ardocrat)
|
||||
* Select all text in DragValue when gaining focus via keyboard [#7107](https://github.com/emilk/egui/pull/7107) by [@Azkellas](https://github.com/Azkellas)
|
||||
* Add `Context::current_pass_index` [#7276](https://github.com/emilk/egui/pull/7276) by [@emilk](https://github.com/emilk)
|
||||
* Add `Context::cumulative_frame_nr` [#7278](https://github.com/emilk/egui/pull/7278) by [@emilk](https://github.com/emilk)
|
||||
* Add `Visuals::text_edit_bg_color` [#7283](https://github.com/emilk/egui/pull/7283) by [@emilk](https://github.com/emilk)
|
||||
* Add `Visuals::weak_text_alpha` and `weak_text_color` [#7285](https://github.com/emilk/egui/pull/7285) by [@emilk](https://github.com/emilk)
|
||||
* Add support for scrolling via accesskit / kittest [#7286](https://github.com/emilk/egui/pull/7286) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Update area struct to allow force resizing [#7114](https://github.com/emilk/egui/pull/7114) by [@blackberryfloat](https://github.com/blackberryfloat)
|
||||
* Add `egui::Sides` `shrink_left` / `shrink_right` [#7295](https://github.com/emilk/egui/pull/7295) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Set intrinsic size for Label [#7328](https://github.com/emilk/egui/pull/7328) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
|
||||
### 🔧 Changed
|
||||
* Raise MSRV to 1.85 [#6848](https://github.com/emilk/egui/pull/6848) by [@torokati44](https://github.com/torokati44), [#7279](https://github.com/emilk/egui/pull/7279) by [@emilk](https://github.com/emilk)
|
||||
* Set `hint_text` in `WidgetInfo` [#5724](https://github.com/emilk/egui/pull/5724) by [@bircni](https://github.com/bircni)
|
||||
* Implement `Default` for `ThemePreference` [#5702](https://github.com/emilk/egui/pull/5702) by [@MichaelGrupp](https://github.com/MichaelGrupp)
|
||||
* Align `available_rect` docs with the new reality after #4590 [#5701](https://github.com/emilk/egui/pull/5701) by [@podusowski](https://github.com/podusowski)
|
||||
* Clarify platform-specific details for `Viewport` positioning [#5715](https://github.com/emilk/egui/pull/5715) by [@aspiringLich](https://github.com/aspiringLich)
|
||||
* Simplify the text cursor API [#5785](https://github.com/emilk/egui/pull/5785) by [@valadaptive](https://github.com/valadaptive)
|
||||
* Bump accesskit to 0.19 [#7040](https://github.com/emilk/egui/pull/7040) by [@valadaptive](https://github.com/valadaptive)
|
||||
* Better define the meaning of `SizeHint` [#7079](https://github.com/emilk/egui/pull/7079) by [@emilk](https://github.com/emilk)
|
||||
* Move all input-related options into `InputOptions` [#7121](https://github.com/emilk/egui/pull/7121) by [@emilk](https://github.com/emilk)
|
||||
* `Button` inherits the `alt_text` of the `Image` in it, if any [#7136](https://github.com/emilk/egui/pull/7136) by [@emilk](https://github.com/emilk)
|
||||
* Change API of `Tooltip` slightly [#7151](https://github.com/emilk/egui/pull/7151) by [@emilk](https://github.com/emilk)
|
||||
* Use Rust edition 2024 [#7280](https://github.com/emilk/egui/pull/7280) by [@emilk](https://github.com/emilk)
|
||||
* Change `ui.disable()` to modify opacity [#7282](https://github.com/emilk/egui/pull/7282) by [@emilk](https://github.com/emilk)
|
||||
* Make the font atlas use a color image [#7298](https://github.com/emilk/egui/pull/7298) by [@valadaptive](https://github.com/valadaptive)
|
||||
* Implement `BitOr` and `BitOrAssign` for `Rect` [#7319](https://github.com/emilk/egui/pull/7319) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
|
||||
### 🔥 Removed
|
||||
* Remove things that have been deprecated for over a year [#7099](https://github.com/emilk/egui/pull/7099) by [@emilk](https://github.com/emilk)
|
||||
* Remove `SelectableLabel` [#7277](https://github.com/emilk/egui/pull/7277) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
|
||||
### 🐛 Fixed
|
||||
* `Scene`: make `scene_rect` full size on reset [#5801](https://github.com/emilk/egui/pull/5801) by [@graydenshand](https://github.com/graydenshand)
|
||||
* `Scene`: `TextEdit` selection when placed in a `Scene` [#5791](https://github.com/emilk/egui/pull/5791) by [@karhu](https://github.com/karhu)
|
||||
* `Scene`: Set transform layer before calling user content [#5884](https://github.com/emilk/egui/pull/5884) by [@mitchmindtree](https://github.com/mitchmindtree)
|
||||
* Fix: transform `TextShape` underline width [#5865](https://github.com/emilk/egui/pull/5865) by [@emilk](https://github.com/emilk)
|
||||
* Fix missing repaint after `consume_key` [#7134](https://github.com/emilk/egui/pull/7134) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Update `emoji-icon-font` with fix for fullwidth latin characters [#7067](https://github.com/emilk/egui/pull/7067) by [@emilk](https://github.com/emilk)
|
||||
* Mark all keys as released if the app loses focus [#5743](https://github.com/emilk/egui/pull/5743) by [@emilk](https://github.com/emilk)
|
||||
* Fix scroll handle extending outside of `ScrollArea` [#5286](https://github.com/emilk/egui/pull/5286) by [@gilbertoalexsantos](https://github.com/gilbertoalexsantos)
|
||||
* Fix `Response::clicked_elsewhere` not returning `true` sometimes [#5798](https://github.com/emilk/egui/pull/5798) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Fix kinetic scrolling on touch devices [#5778](https://github.com/emilk/egui/pull/5778) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Fix `DragValue` expansion when editing [#5809](https://github.com/emilk/egui/pull/5809) by [@MStarha](https://github.com/MStarha)
|
||||
* Fix disabled `DragValue` eating focus, causing focus to reset [#5826](https://github.com/emilk/egui/pull/5826) by [@KonaeAkira](https://github.com/KonaeAkira)
|
||||
* Fix semi-transparent colors appearing too bright [#5824](https://github.com/emilk/egui/pull/5824) by [@emilk](https://github.com/emilk)
|
||||
* Improve drag-to-select text (add margins) [#5797](https://github.com/emilk/egui/pull/5797) by [@hankjordan](https://github.com/hankjordan)
|
||||
* Fix bug in pointer movement detection [#5329](https://github.com/emilk/egui/pull/5329) by [@rustbasic](https://github.com/rustbasic)
|
||||
* Protect against NaN in hit-test code [#6851](https://github.com/emilk/egui/pull/6851) by [@Skgland](https://github.com/Skgland)
|
||||
* Fix image button panicking with tiny `available_space` [#6900](https://github.com/emilk/egui/pull/6900) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Fix links and text selection in horizontal_wrapped layout [#6905](https://github.com/emilk/egui/pull/6905) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Fix `leading_space` sometimes being ignored during paragraph splitting [#7031](https://github.com/emilk/egui/pull/7031) by [@afishhh](https://github.com/afishhh)
|
||||
* Fix typo in deprecation message for `ComboBox::from_id_source` [#7055](https://github.com/emilk/egui/pull/7055) by [@aelmizeb](https://github.com/aelmizeb)
|
||||
* Bug fix: make sure `end_pass` is called for all loaders [#7072](https://github.com/emilk/egui/pull/7072) by [@emilk](https://github.com/emilk)
|
||||
* Report image alt text as text if widget contains no other text [#7142](https://github.com/emilk/egui/pull/7142) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Slider: move by at least the next increment when using fixed_decimals [#7066](https://github.com/emilk/egui/pull/7066) by [@0x53A](https://github.com/0x53A)
|
||||
* Fix crash when using infinite widgets [#7296](https://github.com/emilk/egui/pull/7296) by [@emilk](https://github.com/emilk)
|
||||
* Fix `debug_assert` triggered by `menu`/`intersect_ray` [#7299](https://github.com/emilk/egui/pull/7299) by [@emilk](https://github.com/emilk)
|
||||
* Change `Rect::area` to return zero for negative rectangles [#7305](https://github.com/emilk/egui/pull/7305) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### 🚀 Performance
|
||||
* Optimize editing long text by caching each paragraph [#5411](https://github.com/emilk/egui/pull/5411) by [@afishhh](https://github.com/afishhh)
|
||||
* Make `WidgetText` smaller and faster [#6903](https://github.com/emilk/egui/pull/6903) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
|
||||
|
||||
## 0.31.1 - 2025-03-05
|
||||
* Fix sizing bug in `TextEdit::singleline` [#5640](https://github.com/emilk/egui/pull/5640) by [@IaVashik](https://github.com/IaVashik)
|
||||
* Fix panic when rendering thin textured rectangles [#5692](https://github.com/emilk/egui/pull/5692) by [@PPakalns](https://github.com/PPakalns)
|
||||
|
||||
@@ -36,7 +36,7 @@ There are snapshots test that might need to be updated.
|
||||
Run the tests with `UPDATE_SNAPSHOTS=true cargo test --workspace --all-features` to update all of them.
|
||||
If CI keeps complaining about snapshots (which could happen if you don't use macOS, snapshots in CI are currently
|
||||
rendered with macOS), you can instead run `./scripts/update_snapshots_from_ci.sh` to update your local snapshots from
|
||||
the last CI run of your PR (which will download the `test_results` artefact).
|
||||
the last CI run of your PR (which will download the `test_results` artifact).
|
||||
For more info about the tests see [egui_kittest](./crates/egui_kittest/README.md).
|
||||
Snapshots and other big files are stored with git lfs. See [Working with git lfs](#working-with-git-lfs) for more info.
|
||||
If you see an `InvalidSignature` error when running snapshot tests, it's probably a problem related to git-lfs.
|
||||
|
||||
37
Cargo.lock
37
Cargo.lock
@@ -1197,7 +1197,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
|
||||
|
||||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cint",
|
||||
@@ -1209,7 +1209,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "eframe"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1249,7 +1249,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"ahash",
|
||||
@@ -1269,7 +1269,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-wgpu"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1287,7 +1287,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"accesskit_winit",
|
||||
"ahash",
|
||||
@@ -1308,7 +1308,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_demo_app"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"chrono",
|
||||
@@ -1336,7 +1336,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_demo_lib"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"criterion",
|
||||
@@ -1353,7 +1353,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_extras"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"chrono",
|
||||
@@ -1372,7 +1372,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_glow"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1392,7 +1392,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_kittest"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"dify",
|
||||
"document-features",
|
||||
@@ -1408,7 +1408,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_tests"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"egui",
|
||||
"egui_extras",
|
||||
@@ -1438,7 +1438,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
@@ -1535,7 +1535,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"ahash",
|
||||
@@ -1558,7 +1558,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "epaint_default_fonts"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
@@ -2421,8 +2421,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||
|
||||
[[package]]
|
||||
name = "kittest"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rerun-io/kittest?branch=main#679f9ade828021295c5f86f38275d9271d001004"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c1bfc4cb16136b6f00fb85a281e4b53d026401cf5dff9a427c466bde5891f0b"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
@@ -3235,7 +3236,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||
|
||||
[[package]]
|
||||
name = "popups"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"env_logger",
|
||||
@@ -5480,7 +5481,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.31.1"
|
||||
version = "0.32.0"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
|
||||
34
Cargo.toml
34
Cargo.toml
@@ -21,10 +21,10 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.84"
|
||||
version = "0.31.1"
|
||||
rust-version = "1.85"
|
||||
version = "0.32.0"
|
||||
|
||||
|
||||
[profile.release]
|
||||
@@ -55,18 +55,18 @@ opt-level = 2
|
||||
|
||||
|
||||
[workspace.dependencies]
|
||||
emath = { version = "0.31.1", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.31.1", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.31.1", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.31.1", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.31.1", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.31.1", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.31.1", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.31.1", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.31.1", path = "crates/egui_demo_lib", default-features = false }
|
||||
egui_glow = { version = "0.31.1", path = "crates/egui_glow", default-features = false }
|
||||
egui_kittest = { version = "0.31.1", path = "crates/egui_kittest", default-features = false }
|
||||
eframe = { version = "0.31.1", path = "crates/eframe", default-features = false }
|
||||
emath = { version = "0.32.0", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.32.0", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.32.0", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.32.0", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.32.0", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.32.0", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.32.0", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.32.0", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.32.0", path = "crates/egui_demo_lib", default-features = false }
|
||||
egui_glow = { version = "0.32.0", path = "crates/egui_glow", default-features = false }
|
||||
egui_kittest = { version = "0.32.0", path = "crates/egui_kittest", default-features = false }
|
||||
eframe = { version = "0.32.0", path = "crates/eframe", default-features = false }
|
||||
|
||||
accesskit = "0.19.0"
|
||||
accesskit_winit = "0.27"
|
||||
@@ -85,7 +85,7 @@ glutin = { version = "0.32.0", default-features = false }
|
||||
glutin-winit = { version = "0.5.0", default-features = false }
|
||||
home = "0.5.9"
|
||||
image = { version = "0.25", default-features = false }
|
||||
kittest = { version = "0.1.0", git = "https://github.com/rerun-io/kittest", branch = "main" }
|
||||
kittest = { version = "0.2.0" }
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
mimalloc = "0.1.46"
|
||||
nohash-hasher = "0.2"
|
||||
@@ -171,7 +171,6 @@ fn_params_excessive_bools = "warn"
|
||||
fn_to_numeric_cast_any = "warn"
|
||||
from_iter_instead_of_collect = "warn"
|
||||
get_unwrap = "warn"
|
||||
if_let_mutex = "warn"
|
||||
implicit_clone = "warn"
|
||||
implied_bounds_in_impls = "warn"
|
||||
imprecise_flops = "warn"
|
||||
@@ -193,6 +192,7 @@ large_stack_frames = "warn"
|
||||
large_types_passed_by_value = "warn"
|
||||
let_unit_value = "warn"
|
||||
linkedlist = "warn"
|
||||
literal_string_with_formatting_args = "warn"
|
||||
lossy_float_literal = "warn"
|
||||
macro_use_imports = "warn"
|
||||
manual_assert = "warn"
|
||||
|
||||
59
RELEASES.md
59
RELEASES.md
@@ -22,8 +22,11 @@ We don't update the MSRV in a patch release, unless we really, really need to.
|
||||
|
||||
|
||||
# Release process
|
||||
## Patch release
|
||||
* [ ] Make a branch off of the latest release
|
||||
* [ ] copy this checklist to a new egui issue, called "Release 0.xx.y"
|
||||
* [ ] close all issues in the milestone for this release
|
||||
|
||||
## Special steps for patch release
|
||||
* [ ] make a branch off of the _latest_ release
|
||||
* [ ] cherry-pick what you want to release
|
||||
* [ ] run `cargo semver-checks`
|
||||
|
||||
@@ -46,43 +49,30 @@ We don't update the MSRV in a patch release, unless we really, really need to.
|
||||
|
||||
## Preparation
|
||||
* [ ] make sure there are no important unmerged PRs
|
||||
* [ ] Create a branch called `release-0.xx.0` and open a PR for it
|
||||
* [ ] run `scripts/generate_example_screenshots.sh` if needed
|
||||
* [ ] write a short release note that fits in a bluesky post
|
||||
* [ ] record gif for `CHANGELOG.md` release note (and later bluesky post)
|
||||
* [ ] update changelogs using `scripts/generate_changelog.py --version 0.x.0 --write`
|
||||
* [ ] bump version numbers in workspace `Cargo.toml`
|
||||
* [ ] update changelogs
|
||||
* [ ] run `scripts/generate_changelog.py --version 0.x.0 --write`
|
||||
* [ ] read changelogs and clean them up if needed
|
||||
* [ ] write a good intro with highlight for the main changelog
|
||||
* [ ] run `typos`
|
||||
|
||||
## Actual release
|
||||
I usually do this all on the `main` branch, but doing it in a release branch is also fine, as long as you remember to merge it into `main` later.
|
||||
|
||||
* [ ] Run `typos`
|
||||
* [ ] `git commit -m 'Release 0.x.0 - <release title>'`
|
||||
* [ ] `cargo publish` (see below)
|
||||
* [ ] bump version numbers in workspace `Cargo.toml`
|
||||
* [ ] check that CI for the PR is green
|
||||
* [ ] publish the crates by running `scripts/publish_crates.sh`
|
||||
* [ ] `git tag -a 0.x.0 -m 'Release 0.x.0 - <release title>'`
|
||||
* [ ] `git pull --tags ; git tag -d latest && git tag -a latest -m 'Latest release' && git push --tags origin latest --force ; git push --tags`
|
||||
* [ ] merge release PR or push to `main`
|
||||
* [ ] check that CI is green
|
||||
* [ ] merge release PR as `Release 0.x.0 - <release title>`
|
||||
* [ ] check that CI for `main` is green
|
||||
* [ ] do a GitHub release: https://github.com/emilk/egui/releases/new
|
||||
* Follow the format of the last release
|
||||
* [ ] wait for documentation to build: https://docs.rs/releases/queue
|
||||
* follow the format of the last release
|
||||
* [ ] wait for the documentation build to finish: https://docs.rs/releases/queue
|
||||
* [ ] https://docs.rs/egui/ works
|
||||
* [ ] https://docs.rs/eframe/ works
|
||||
|
||||
### `cargo publish`:
|
||||
```
|
||||
(cd crates/emath && cargo publish --quiet) && echo "✅ emath"
|
||||
(cd crates/ecolor && cargo publish --quiet) && echo "✅ ecolor"
|
||||
(cd crates/epaint_default_fonts && cargo publish --quiet) && echo "✅ epaint_default_fonts"
|
||||
(cd crates/epaint && cargo publish --quiet) && echo "✅ epaint"
|
||||
(cd crates/egui && cargo publish --quiet) && echo "✅ egui"
|
||||
(cd crates/egui-winit && cargo publish --quiet) && echo "✅ egui-winit"
|
||||
(cd crates/egui-wgpu && cargo publish --quiet) && echo "✅ egui-wgpu"
|
||||
(cd crates/eframe && cargo publish --quiet) && echo "✅ eframe"
|
||||
(cd crates/egui_kittest && cargo publish --quiet) && echo "✅ egui_kittest"
|
||||
(cd crates/egui_extras && cargo publish --quiet) && echo "✅ egui_extras"
|
||||
(cd crates/egui_demo_lib && cargo publish --quiet) && echo "✅ egui_demo_lib"
|
||||
(cd crates/egui_glow && cargo publish --quiet) && echo "✅ egui_glow"
|
||||
```
|
||||
|
||||
\<continue with the checklist above\>
|
||||
|
||||
## Announcements
|
||||
* [ ] [Bluesky](https://bsky.app/profile/ernerfeldt.bsky.social)
|
||||
@@ -91,10 +81,17 @@ I usually do this all on the `main` branch, but doing it in a release branch is
|
||||
* [ ] [r/programming](https://www.reddit.com/r/programming/comments/1bocsf6/announcing_egui_027_an_easytouse_crossplatform/)
|
||||
* [ ] [This Week in Rust](https://github.com/rust-lang/this-week-in-rust/pull/5167)
|
||||
|
||||
|
||||
## After release
|
||||
* [ ] publish new `eframe_template`
|
||||
* [ ] update `eframe_template`
|
||||
* [ ] publish new `egui_plot`
|
||||
* [ ] publish new `egui_table`
|
||||
* [ ] publish new `egui_tiles`
|
||||
* [ ] make a PR to `egui_commonmark`
|
||||
* [ ] make a PR to `rerun`
|
||||
|
||||
|
||||
## Finally
|
||||
* [ ] close the milestone
|
||||
* [ ] close this issue
|
||||
* [ ] improve `RELEASES.md` with what you learned this time around
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Section identical to scripts/clippy_wasm/clippy.toml:
|
||||
|
||||
msrv = "1.84"
|
||||
msrv = "1.85"
|
||||
|
||||
allow-unwrap-in-tests = true
|
||||
|
||||
|
||||
@@ -6,6 +6,12 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.32.0 - 2025-07-10
|
||||
* Fix semi-transparent colors appearing too bright [#5824](https://github.com/emilk/egui/pull/5824) by [@emilk](https://github.com/emilk)
|
||||
* Remove things that have been deprecated for over a year [#7099](https://github.com/emilk/egui/pull/7099) by [@emilk](https://github.com/emilk)
|
||||
* Make `Hsva` derive serde [#7132](https://github.com/emilk/egui/pull/7132) by [@bircni](https://github.com/bircni)
|
||||
|
||||
|
||||
## 0.31.1 - 2025-03-05
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{linear_f32_from_linear_u8, linear_u8_from_linear_f32, Color32, Hsva, HsvaGamma, Rgba};
|
||||
use super::{Color32, Hsva, HsvaGamma, Rgba, linear_f32_from_linear_u8, linear_u8_from_linear_f32};
|
||||
use cint::{Alpha, ColorInterop, EncodedSrgb, Hsv, LinearSrgb, PremultipliedAlpha};
|
||||
|
||||
// ---- Color32 ----
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{fast_round, linear_f32_from_linear_u8, Rgba};
|
||||
use crate::{Rgba, fast_round, linear_f32_from_linear_u8};
|
||||
|
||||
/// This format is used for space-efficient color representation (32 bits).
|
||||
///
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_u8_from_linear_f32, Color32, Rgba,
|
||||
Color32, Rgba, gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_u8_from_linear_f32,
|
||||
};
|
||||
|
||||
/// Hue, saturation, value, alpha. All in the range [0, 1].
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{gamma_from_linear, linear_from_gamma, Color32, Hsva, Rgba};
|
||||
use crate::{Color32, Hsva, Rgba, gamma_from_linear, linear_from_gamma};
|
||||
|
||||
/// Like Hsva but with the `v` value (brightness) being gamma corrected
|
||||
/// so that it is somewhat perceptually even.
|
||||
|
||||
@@ -7,6 +7,33 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.32.0 - 2025-07-10
|
||||
### ⭐ Added
|
||||
* Add pointer events and focus handling for apps run in a Shadow DOM [#5627](https://github.com/emilk/egui/pull/5627) by [@xxvvii](https://github.com/xxvvii)
|
||||
* MacOS: Add `movable_by_window_background` option to viewport [#5412](https://github.com/emilk/egui/pull/5412) by [@jim-ec](https://github.com/jim-ec)
|
||||
* Add macOS-specific `has_shadow` and `with_has_shadow` to ViewportBuilder [#6850](https://github.com/emilk/egui/pull/6850) by [@gaelanmcmillan](https://github.com/gaelanmcmillan)
|
||||
* Add external eventloop support [#6750](https://github.com/emilk/egui/pull/6750) by [@wpbrown](https://github.com/wpbrown)
|
||||
|
||||
### 🔧 Changed
|
||||
* Update MSRV to 1.85 [#7279](https://github.com/emilk/egui/pull/7279) by [@emilk](https://github.com/emilk)
|
||||
* Use Rust edition 2024 [#7280](https://github.com/emilk/egui/pull/7280) by [@emilk](https://github.com/emilk)
|
||||
* Rename `should_propagate_event` and add `should_prevent_default` [#5779](https://github.com/emilk/egui/pull/5779) by [@th0rex](https://github.com/th0rex)
|
||||
* Clarify platform-specific details for `Viewport` positioning [#5715](https://github.com/emilk/egui/pull/5715) by [@aspiringLich](https://github.com/aspiringLich)
|
||||
* Enhance stability on Windows [#5723](https://github.com/emilk/egui/pull/5723) by [@rustbasic](https://github.com/rustbasic)
|
||||
* Set `web-sys` min version to `0.3.73` [#5862](https://github.com/emilk/egui/pull/5862) by [@wareya](https://github.com/wareya)
|
||||
* Bump `ron` to `0.10.1` [#6861](https://github.com/emilk/egui/pull/6861) by [@torokati44](https://github.com/torokati44)
|
||||
* Disallow `accesskit` on Android NativeActivity, making `hello_android` working again [#6855](https://github.com/emilk/egui/pull/6855) by [@podusowski](https://github.com/podusowski)
|
||||
* Respect and detect `prefers-color-scheme: no-preference` [#7293](https://github.com/emilk/egui/pull/7293) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### 🐛 Fixed
|
||||
* Mark all keys as up if the app loses focus [#5743](https://github.com/emilk/egui/pull/5743) by [@emilk](https://github.com/emilk)
|
||||
* Fix text input on Android [#5759](https://github.com/emilk/egui/pull/5759) by [@StratusFearMe21](https://github.com/StratusFearMe21)
|
||||
* Fix text distortion on mobile devices/browsers with `glow` backend [#6893](https://github.com/emilk/egui/pull/6893) by [@wareya](https://github.com/wareya)
|
||||
* Workaround libpng crash on macOS by not creating `NSImage` from png data [#7252](https://github.com/emilk/egui/pull/7252) by [@Wumpf](https://github.com/Wumpf)
|
||||
* Fix incorrect window sizes for non-resizable windows on Wayland [#7103](https://github.com/emilk/egui/pull/7103) by [@GoldsteinE](https://github.com/GoldsteinE)
|
||||
* Web: only consume copy/cut events if the canvas has focus [#7270](https://github.com/emilk/egui/pull/7270) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.31.1 - 2025-03-05
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ 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 need to either use `edition = "2024"`, 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/main/crates/egui-wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`.
|
||||
|
||||
|
||||
@@ -574,7 +574,9 @@ impl Default for Renderer {
|
||||
fn default() -> Self {
|
||||
#[cfg(not(feature = "glow"))]
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
compile_error!("eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'");
|
||||
compile_error!(
|
||||
"eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'"
|
||||
);
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
@@ -617,7 +619,9 @@ impl std::str::FromStr for Renderer {
|
||||
#[cfg(feature = "wgpu")]
|
||||
"wgpu" => Ok(Self::Wgpu),
|
||||
|
||||
_ => Err(format!("eframe renderer {name:?} is not available. Make sure that the corresponding eframe feature is enabled."))
|
||||
_ => Err(format!(
|
||||
"eframe renderer {name:?} is not available. Make sure that the corresponding eframe feature is enabled."
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,13 +205,18 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS
|
||||
|
||||
use objc2::ClassType as _;
|
||||
use objc2_app_kit::{NSApplication, NSImage};
|
||||
use objc2_foundation::{NSData, NSString};
|
||||
use objc2_foundation::NSString;
|
||||
|
||||
let png_bytes = if let Some(icon_data) = icon_data {
|
||||
match icon_data.to_png_bytes() {
|
||||
Ok(png_bytes) => Some(png_bytes),
|
||||
// Do NOT use png even though creating `NSImage` from it is much easier than from raw images data!
|
||||
//
|
||||
// Some MacOS versions have a bug where creating an `NSImage` from a png will cause it to load an arbitrary `libpng.dylib`.
|
||||
// If this dylib isn't the right version, the application will crash with SIGBUS.
|
||||
// For details see https://github.com/emilk/egui/issues/7155
|
||||
let image = if let Some(icon_data) = icon_data {
|
||||
match icon_data.to_image() {
|
||||
Ok(image) => Some(image),
|
||||
Err(err) => {
|
||||
log::warn!("Failed to convert IconData to png: {err}");
|
||||
log::warn!("Failed to read icon data: {err}");
|
||||
return AppIconStatus::NotSetIgnored;
|
||||
}
|
||||
}
|
||||
@@ -220,7 +225,7 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS
|
||||
};
|
||||
|
||||
// TODO(madsmtm): Move this into `objc2-app-kit`
|
||||
extern "C" {
|
||||
unsafe extern "C" {
|
||||
static NSApp: Option<&'static NSApplication>;
|
||||
}
|
||||
|
||||
@@ -231,15 +236,41 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS
|
||||
return AppIconStatus::NotSetIgnored;
|
||||
};
|
||||
|
||||
if let Some(png_bytes) = png_bytes {
|
||||
let data = NSData::from_vec(png_bytes);
|
||||
if let Some(image) = image {
|
||||
use objc2_app_kit::{NSBitmapImageRep, NSDeviceRGBColorSpace};
|
||||
use objc2_foundation::NSSize;
|
||||
|
||||
log::trace!("NSImage::initWithData…");
|
||||
let app_icon = NSImage::initWithData(NSImage::alloc(), &data);
|
||||
log::trace!(
|
||||
"NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel"
|
||||
);
|
||||
let Some(image_rep) = NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(
|
||||
NSBitmapImageRep::alloc(),
|
||||
[image.as_raw().as_ptr().cast_mut()].as_mut_ptr(),
|
||||
image.width() as isize,
|
||||
image.height() as isize,
|
||||
8, // bits per sample
|
||||
4, // samples per pixel
|
||||
true, // has alpha
|
||||
false, // is not planar
|
||||
NSDeviceRGBColorSpace,
|
||||
(image.width() * 4) as isize, // bytes per row
|
||||
32 // bits per pixel
|
||||
) else {
|
||||
log::warn!("Failed to create NSBitmapImageRep from app icon data.");
|
||||
return AppIconStatus::NotSetIgnored;
|
||||
};
|
||||
|
||||
log::trace!("NSImage::initWithSize");
|
||||
let app_icon = NSImage::initWithSize(
|
||||
NSImage::alloc(),
|
||||
NSSize::new(image.width() as f64, image.height() as f64),
|
||||
);
|
||||
log::trace!("NSImage::addRepresentation");
|
||||
app_icon.addRepresentation(&image_rep);
|
||||
|
||||
profiling::scope!("setApplicationIconImage_");
|
||||
log::trace!("setApplicationIconImage…");
|
||||
app.setApplicationIconImage(app_icon.as_deref());
|
||||
app.setApplicationIconImage(Some(&app_icon));
|
||||
}
|
||||
|
||||
// Change the title in the top bar - for python processes this would be again "python" otherwise.
|
||||
|
||||
@@ -52,10 +52,10 @@ fn roaming_appdata() -> Option<PathBuf> {
|
||||
use windows_sys::Win32::Foundation::S_OK;
|
||||
use windows_sys::Win32::System::Com::CoTaskMemFree;
|
||||
use windows_sys::Win32::UI::Shell::{
|
||||
FOLDERID_RoamingAppData, SHGetKnownFolderPath, KF_FLAG_DONT_VERIFY,
|
||||
FOLDERID_RoamingAppData, KF_FLAG_DONT_VERIFY, SHGetKnownFolderPath,
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
unsafe extern "C" {
|
||||
fn wcslen(buf: *const u16) -> usize;
|
||||
}
|
||||
let mut path_raw = ptr::null_mut();
|
||||
@@ -72,7 +72,7 @@ fn roaming_appdata() -> Option<PathBuf> {
|
||||
};
|
||||
|
||||
let path = if result == S_OK {
|
||||
// SAFETY: SHGetKnownFolderPath indicated success and is supposed to allocate a nullterminated string for us.
|
||||
// SAFETY: SHGetKnownFolderPath indicated success and is supposed to allocate a null-terminated string for us.
|
||||
let path_slice = unsafe { slice::from_raw_parts(path_raw, wcslen(path_raw)) };
|
||||
Some(PathBuf::from(OsString::from_wide(path_slice)))
|
||||
} else {
|
||||
|
||||
@@ -32,13 +32,13 @@ use egui::{
|
||||
use egui_winit::accesskit_winit;
|
||||
|
||||
use crate::{
|
||||
native::epi_integration::EpiIntegration, App, AppCreator, CreationContext, NativeOptions,
|
||||
Result, Storage,
|
||||
App, AppCreator, CreationContext, NativeOptions, Result, Storage,
|
||||
native::epi_integration::EpiIntegration,
|
||||
};
|
||||
|
||||
use super::{
|
||||
epi_integration, event_loop_context,
|
||||
winit_integration::{create_egui_context, EventResult, UserEvent, WinitApp},
|
||||
winit_integration::{EventResult, UserEvent, WinitApp, create_egui_context},
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -959,7 +959,6 @@ impl GlutinWindowContext {
|
||||
.with_preference(glutin_winit::ApiPreference::FallbackEgl)
|
||||
.with_window_attributes(Some(egui_winit::create_winit_window_attributes(
|
||||
egui_ctx,
|
||||
event_loop,
|
||||
viewport_builder.clone(),
|
||||
)));
|
||||
|
||||
@@ -1016,7 +1015,9 @@ impl GlutinWindowContext {
|
||||
let gl_context = match gl_context_result {
|
||||
Ok(it) => it,
|
||||
Err(err) => {
|
||||
log::warn!("Failed to create context using default context attributes {context_attributes:?} due to error: {err}");
|
||||
log::warn!(
|
||||
"Failed to create context using default context attributes {context_attributes:?} due to error: {err}"
|
||||
);
|
||||
log::debug!(
|
||||
"Retrying with fallback context attributes: {fallback_context_attributes:?}"
|
||||
);
|
||||
@@ -1113,7 +1114,6 @@ impl GlutinWindowContext {
|
||||
log::debug!("Creating a window for viewport {viewport_id:?}");
|
||||
let window_attributes = egui_winit::create_winit_window_attributes(
|
||||
&self.egui_ctx,
|
||||
event_loop,
|
||||
viewport.builder.clone(),
|
||||
);
|
||||
if window_attributes.transparent()
|
||||
|
||||
@@ -10,9 +10,8 @@ use ahash::HashMap;
|
||||
|
||||
use super::winit_integration::{UserEvent, WinitApp};
|
||||
use crate::{
|
||||
epi,
|
||||
Result, epi,
|
||||
native::{event_loop_context, winit_integration::EventResult},
|
||||
Result,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -25,8 +25,8 @@ use egui_winit::accesskit_winit;
|
||||
use winit_integration::UserEvent;
|
||||
|
||||
use crate::{
|
||||
native::{epi_integration::EpiIntegration, winit_integration::EventResult},
|
||||
App, AppCreator, CreationContext, NativeOptions, Result, Storage,
|
||||
native::{epi_integration::EpiIntegration, winit_integration::EventResult},
|
||||
};
|
||||
|
||||
use super::{epi_integration, event_loop_context, winit_integration, winit_integration::WinitApp};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use egui::{TexturesDelta, UserData, ViewportCommand};
|
||||
|
||||
use crate::{epi, App};
|
||||
use crate::{App, epi};
|
||||
|
||||
use super::{now_sec, text_agent::TextAgent, web_painter::WebPainter as _, NeedRepaint};
|
||||
use super::{NeedRepaint, now_sec, text_agent::TextAgent, web_painter::WebPainter as _};
|
||||
|
||||
pub struct AppRunner {
|
||||
#[allow(dead_code, clippy::allow_attributes)]
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::web::string_from_js_value;
|
||||
|
||||
use super::{
|
||||
button_from_mouse_event, location_hash, modifiers_from_kb_event, modifiers_from_mouse_event,
|
||||
modifiers_from_wheel_event, native_pixels_per_point, pos_from_mouse_event,
|
||||
prefers_color_scheme_dark, primary_touch_pos, push_touches, text_from_keyboard_event,
|
||||
theme_from_dark_mode, translate_key, AppRunner, Closure, JsCast as _, JsValue, WebRunner,
|
||||
DEBUG_RESIZE,
|
||||
AppRunner, Closure, DEBUG_RESIZE, JsCast as _, JsValue, WebRunner, button_from_mouse_event,
|
||||
location_hash, modifiers_from_kb_event, modifiers_from_mouse_event, modifiers_from_wheel_event,
|
||||
native_pixels_per_point, pos_from_mouse_event, prefers_color_scheme, primary_touch_pos,
|
||||
push_touches, text_from_keyboard_event, translate_key,
|
||||
};
|
||||
|
||||
use web_sys::{Document, EventTarget, ShadowRoot};
|
||||
@@ -311,13 +310,17 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) {
|
||||
|
||||
fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
|
||||
runner_ref.add_event_listener(target, "paste", |event: web_sys::ClipboardEvent, runner| {
|
||||
if !runner.input.raw.focused {
|
||||
return; // The eframe app is not interested
|
||||
}
|
||||
|
||||
if let Some(data) = event.clipboard_data() {
|
||||
if let Ok(text) = data.get_data("text") {
|
||||
let text = text.replace("\r\n", "\n");
|
||||
|
||||
let mut should_stop_propagation = true;
|
||||
let mut should_prevent_default = true;
|
||||
if !text.is_empty() && runner.input.raw.focused {
|
||||
if !text.is_empty() {
|
||||
let egui_event = egui::Event::Paste(text);
|
||||
should_stop_propagation =
|
||||
(runner.web_options.should_stop_propagation)(&egui_event);
|
||||
@@ -340,17 +343,19 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
|
||||
})?;
|
||||
|
||||
runner_ref.add_event_listener(target, "cut", |event: web_sys::ClipboardEvent, runner| {
|
||||
if runner.input.raw.focused {
|
||||
runner.input.raw.events.push(egui::Event::Cut);
|
||||
|
||||
// In Safari we are only allowed to write to the clipboard during the
|
||||
// event callback, which is why we run the app logic here and now:
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
if !runner.input.raw.focused {
|
||||
return; // The eframe app is not interested
|
||||
}
|
||||
|
||||
runner.input.raw.events.push(egui::Event::Cut);
|
||||
|
||||
// In Safari we are only allowed to write to the clipboard during the
|
||||
// event callback, which is why we run the app logic here and now:
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if (runner.web_options.should_stop_propagation)(&egui::Event::Cut) {
|
||||
event.stop_propagation();
|
||||
@@ -362,17 +367,19 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
|
||||
})?;
|
||||
|
||||
runner_ref.add_event_listener(target, "copy", |event: web_sys::ClipboardEvent, runner| {
|
||||
if runner.input.raw.focused {
|
||||
runner.input.raw.events.push(egui::Event::Copy);
|
||||
|
||||
// In Safari we are only allowed to write to the clipboard during the
|
||||
// event callback, which is why we run the app logic here and now:
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
if !runner.input.raw.focused {
|
||||
return; // The eframe app is not interested
|
||||
}
|
||||
|
||||
runner.input.raw.events.push(egui::Event::Copy);
|
||||
|
||||
// In Safari we are only allowed to write to the clipboard during the
|
||||
// event callback, which is why we run the app logic here and now:
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if (runner.web_options.should_stop_propagation)(&egui::Event::Copy) {
|
||||
event.stop_propagation();
|
||||
@@ -462,16 +469,19 @@ fn install_color_scheme_change_event(
|
||||
runner_ref: &WebRunner,
|
||||
window: &web_sys::Window,
|
||||
) -> Result<(), JsValue> {
|
||||
if let Some(media_query_list) = prefers_color_scheme_dark(window)? {
|
||||
runner_ref.add_event_listener::<web_sys::MediaQueryListEvent>(
|
||||
&media_query_list,
|
||||
"change",
|
||||
|event, runner| {
|
||||
let theme = theme_from_dark_mode(event.matches());
|
||||
runner.input.raw.system_theme = Some(theme);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
},
|
||||
)?;
|
||||
for theme in [egui::Theme::Dark, egui::Theme::Light] {
|
||||
if let Some(media_query_list) = prefers_color_scheme(window, theme)? {
|
||||
runner_ref.add_event_listener::<web_sys::MediaQueryListEvent>(
|
||||
&media_query_list,
|
||||
"change",
|
||||
|_event, runner| {
|
||||
if let Some(theme) = super::system_theme() {
|
||||
runner.input.raw.system_theme = Some(theme);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{canvas_content_rect, AppRunner};
|
||||
use super::{AppRunner, canvas_content_rect};
|
||||
|
||||
pub fn pos_from_mouse_event(
|
||||
canvas: &web_sys::HtmlCanvasElement,
|
||||
|
||||
@@ -40,6 +40,7 @@ pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu;
|
||||
|
||||
pub use backend::*;
|
||||
|
||||
use egui::Theme;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Document, MediaQueryList, Node};
|
||||
|
||||
@@ -113,24 +114,31 @@ pub fn native_pixels_per_point() -> f32 {
|
||||
///
|
||||
/// `None` means unknown.
|
||||
pub fn system_theme() -> Option<egui::Theme> {
|
||||
let dark_mode = prefers_color_scheme_dark(&web_sys::window()?)
|
||||
.ok()??
|
||||
.matches();
|
||||
Some(theme_from_dark_mode(dark_mode))
|
||||
}
|
||||
|
||||
fn prefers_color_scheme_dark(window: &web_sys::Window) -> Result<Option<MediaQueryList>, JsValue> {
|
||||
window.match_media("(prefers-color-scheme: dark)")
|
||||
}
|
||||
|
||||
fn theme_from_dark_mode(dark_mode: bool) -> egui::Theme {
|
||||
if dark_mode {
|
||||
egui::Theme::Dark
|
||||
let window = web_sys::window()?;
|
||||
if does_prefer_color_scheme(&window, Theme::Dark) == Some(true) {
|
||||
Some(Theme::Dark)
|
||||
} else if does_prefer_color_scheme(&window, Theme::Light) == Some(true) {
|
||||
Some(Theme::Light)
|
||||
} else {
|
||||
egui::Theme::Light
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn does_prefer_color_scheme(window: &web_sys::Window, theme: Theme) -> Option<bool> {
|
||||
Some(prefers_color_scheme(window, theme).ok()??.matches())
|
||||
}
|
||||
|
||||
fn prefers_color_scheme(
|
||||
window: &web_sys::Window,
|
||||
theme: Theme,
|
||||
) -> Result<Option<MediaQueryList>, JsValue> {
|
||||
let theme = match theme {
|
||||
Theme::Dark => "dark",
|
||||
Theme::Light => "light",
|
||||
};
|
||||
window.match_media(format!("(prefers-color-scheme: {theme})").as_str())
|
||||
}
|
||||
|
||||
/// Returns the canvas in client coordinates.
|
||||
fn canvas_content_rect(canvas: &web_sys::HtmlCanvasElement) -> egui::Rect {
|
||||
let bounding_rect = canvas.get_bounding_client_rect();
|
||||
|
||||
@@ -126,12 +126,17 @@ fn shorten_file_path(file_path: &str) -> &str {
|
||||
#[test]
|
||||
fn test_shorten_file_path() {
|
||||
for (before, after) in [
|
||||
("/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs", "tokio-1.24.1/src/runtime/runtime.rs"),
|
||||
(
|
||||
"/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs",
|
||||
"tokio-1.24.1/src/runtime/runtime.rs",
|
||||
),
|
||||
("crates/rerun/src/main.rs", "rerun/src/main.rs"),
|
||||
("/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs", "core/src/ops/function.rs"),
|
||||
(
|
||||
"/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs",
|
||||
"core/src/ops/function.rs",
|
||||
),
|
||||
("/weird/path/file.rs", "/weird/path/file.rs"),
|
||||
]
|
||||
{
|
||||
] {
|
||||
assert_eq!(shorten_file_path(before), after);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use super::web_painter::WebPainter;
|
||||
use crate::WebOptions;
|
||||
use egui::{Event, UserData, ViewportId};
|
||||
use egui_wgpu::capture::{capture_channel, CaptureReceiver, CaptureSender, CaptureState};
|
||||
use egui_wgpu::capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel};
|
||||
use egui_wgpu::{RenderState, SurfaceErrorAction};
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
@@ -279,13 +279,6 @@ impl WebPainter for WebPainterWgpu {
|
||||
Some((output_frame, capture_buffer))
|
||||
};
|
||||
|
||||
{
|
||||
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
|
||||
@@ -307,6 +300,16 @@ impl WebPainter for WebPainterWgpu {
|
||||
frame.present();
|
||||
}
|
||||
|
||||
// Free textures marked for destruction **after** queue submit since they might still be used in the current frame.
|
||||
// Calling `wgpu::Texture::destroy` on a texture that is still in use would invalidate the command buffer(s) it is used in.
|
||||
// However, once we called `wgpu::Queue::submit`, it is up for wgpu to determine how long the underlying gpu resource has to live.
|
||||
{
|
||||
let mut renderer = render_state.renderer.write();
|
||||
for id in &textures_delta.free {
|
||||
renderer.free_texture(id);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{epi, App};
|
||||
use crate::{App, epi};
|
||||
|
||||
use super::{
|
||||
AppRunner, PanicHandler,
|
||||
events::{self, ResizeObserverContext},
|
||||
text_agent::TextAgent,
|
||||
AppRunner, PanicHandler,
|
||||
};
|
||||
|
||||
/// This is how `eframe` runs your web application
|
||||
|
||||
@@ -6,6 +6,12 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.32.0 - 2025-07-10
|
||||
* Update to wgpu 25 [#6744](https://github.com/emilk/egui/pull/6744) by [@torokati44](https://github.com/torokati44)
|
||||
* Free textures after submitting queue instead of before with wgpu renderer on Web [#7291](https://github.com/emilk/egui/pull/7291) by [@Wumpf](https://github.com/Wumpf)
|
||||
* Improve texture filtering by doing it in gamma space [#7311](https://github.com/emilk/egui/pull/7311) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.31.1 - 2025-03-05
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use egui::{UserData, ViewportId};
|
||||
use epaint::ColorImage;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::sync::{Arc, mpsc};
|
||||
use wgpu::{BindGroupLayout, MultisampleState, StoreOp};
|
||||
|
||||
/// A texture and a buffer for reading the rendered frame back to the cpu.
|
||||
@@ -196,7 +196,10 @@ impl CaptureState {
|
||||
wgpu::TextureFormat::Rgba8Unorm => [0, 1, 2, 3],
|
||||
wgpu::TextureFormat::Bgra8Unorm => [2, 1, 0, 3],
|
||||
_ => {
|
||||
log::error!("Screen can't be captured unless the surface format is Rgba8Unorm or Bgra8Unorm. Current surface format is {:?}", format);
|
||||
log::error!(
|
||||
"Screen can't be captured unless the surface format is Rgba8Unorm or Bgra8Unorm. Current surface format is {:?}",
|
||||
format
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -97,9 +97,8 @@ fn vs_main(
|
||||
|
||||
@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);
|
||||
// We expect "normal" textures that are NOT sRGB-aware.
|
||||
let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
|
||||
var out_color_gamma = in.color * tex_gamma;
|
||||
// Dither the float color down to eight bits to reduce banding.
|
||||
// This step is optional for egui backends.
|
||||
@@ -115,9 +114,8 @@ fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
|
||||
@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);
|
||||
// We expect "normal" textures that are NOT sRGB-aware.
|
||||
let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
|
||||
var out_color_gamma = in.color * tex_gamma;
|
||||
// Dither the float color down to eight bits to reduce banding.
|
||||
// This step is optional for egui backends.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use std::{borrow::Cow, num::NonZeroU64, ops::Range};
|
||||
|
||||
use ahash::HashMap;
|
||||
use epaint::{emath::NumExt as _, PaintCallbackInfo, Primitive, Vertex};
|
||||
use epaint::{PaintCallbackInfo, Primitive, Vertex, emath::NumExt as _};
|
||||
|
||||
use wgpu::util::DeviceExt as _;
|
||||
|
||||
@@ -564,15 +564,6 @@ impl Renderer {
|
||||
);
|
||||
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"
|
||||
);
|
||||
profiling::scope!("font -> sRGBA");
|
||||
Cow::Owned(image.srgba_pixels(None).collect::<Vec<epaint::Color32>>())
|
||||
}
|
||||
};
|
||||
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
|
||||
|
||||
@@ -638,9 +629,9 @@ impl Renderer {
|
||||
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.
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
|
||||
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
|
||||
})
|
||||
};
|
||||
let origin = wgpu::Origin3d::ZERO;
|
||||
@@ -699,7 +690,7 @@ impl Renderer {
|
||||
///
|
||||
/// 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 [`wgpu::TextureFormat::Rgba8UnormSrgb`].
|
||||
/// the texture format [`wgpu::TextureFormat::Rgba8Unorm`].
|
||||
pub fn register_native_texture(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
@@ -747,7 +738,7 @@ impl Renderer {
|
||||
/// This allows applications to specify individual minification/magnification filters as well as
|
||||
/// custom mipmap and tiling options.
|
||||
///
|
||||
/// The texture must have the format [`wgpu::TextureFormat::Rgba8UnormSrgb`].
|
||||
/// The texture must have the format [`wgpu::TextureFormat::Rgba8Unorm`].
|
||||
/// Any compare function supplied in the [`wgpu::SamplerDescriptor`] will be ignored.
|
||||
#[expect(clippy::needless_pass_by_value)] // false positive
|
||||
pub fn register_native_texture_with_sampler_options(
|
||||
@@ -909,7 +900,11 @@ impl Renderer {
|
||||
);
|
||||
|
||||
let Some(mut index_buffer_staging) = index_buffer_staging else {
|
||||
panic!("Failed to create staging buffer for index data. Index count: {index_count}. Required index buffer size: {required_index_buffer_size}. Actual size {} and capacity: {} (bytes)", self.index_buffer.buffer.size(), self.index_buffer.capacity);
|
||||
panic!(
|
||||
"Failed to create staging buffer for index data. Index count: {index_count}. Required index buffer size: {required_index_buffer_size}. Actual size {} and capacity: {} (bytes)",
|
||||
self.index_buffer.buffer.size(),
|
||||
self.index_buffer.capacity
|
||||
);
|
||||
};
|
||||
|
||||
let mut index_offset = 0;
|
||||
@@ -948,7 +943,11 @@ impl Renderer {
|
||||
);
|
||||
|
||||
let Some(mut vertex_buffer_staging) = vertex_buffer_staging else {
|
||||
panic!("Failed to create staging buffer for vertex data. Vertex count: {vertex_count}. Required vertex buffer size: {required_vertex_buffer_size}. Actual size {} and capacity: {} (bytes)", self.vertex_buffer.buffer.size(), self.vertex_buffer.capacity);
|
||||
panic!(
|
||||
"Failed to create staging buffer for vertex data. Vertex count: {vertex_count}. Required vertex buffer size: {required_vertex_buffer_size}. Actual size {} and capacity: {} (bytes)",
|
||||
self.vertex_buffer.buffer.size(),
|
||||
self.vertex_buffer.capacity
|
||||
);
|
||||
};
|
||||
|
||||
let mut vertex_offset = 0;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::undocumented_unsafe_blocks)]
|
||||
|
||||
use crate::capture::{capture_channel, CaptureReceiver, CaptureSender, CaptureState};
|
||||
use crate::{renderer, RenderState, SurfaceErrorAction, WgpuConfiguration};
|
||||
use crate::capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel};
|
||||
use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer};
|
||||
use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet};
|
||||
use std::{num::NonZeroU32, sync::Arc};
|
||||
|
||||
@@ -220,7 +220,9 @@ impl Painter {
|
||||
} else if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) {
|
||||
wgpu::CompositeAlphaMode::PostMultiplied
|
||||
} else {
|
||||
log::warn!("Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency.");
|
||||
log::warn!(
|
||||
"Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency."
|
||||
);
|
||||
wgpu::CompositeAlphaMode::Auto
|
||||
}
|
||||
} else {
|
||||
@@ -344,7 +346,9 @@ impl Painter {
|
||||
height_in_pixels,
|
||||
);
|
||||
} else {
|
||||
log::warn!("Ignoring window resize notification with no surface created via Painter::set_window()");
|
||||
log::warn!(
|
||||
"Ignoring window resize notification with no surface created via Painter::set_window()"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,14 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.32.0 - 2025-07-10
|
||||
* Mark all keys as released if the app loses focus [#5743](https://github.com/emilk/egui/pull/5743) by [@emilk](https://github.com/emilk)
|
||||
* Fix text input on Android [#5759](https://github.com/emilk/egui/pull/5759) by [@StratusFearMe21](https://github.com/StratusFearMe21)
|
||||
* Add macOS-specific `has_shadow` and `with_has_shadow` to ViewportBuilder [#6850](https://github.com/emilk/egui/pull/6850) by [@gaelanmcmillan](https://github.com/gaelanmcmillan)
|
||||
* Support for back-button on Android [#7073](https://github.com/emilk/egui/pull/7073) by [@ardocrat](https://github.com/ardocrat)
|
||||
* Fix incorrect window sizes for non-resizable windows on Wayland [#7103](https://github.com/emilk/egui/pull/7103) by [@GoldsteinE](https://github.com/GoldsteinE)
|
||||
|
||||
|
||||
## 0.31.1 - 2025-03-05
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -123,7 +123,9 @@ impl Clipboard {
|
||||
return;
|
||||
}
|
||||
|
||||
log::error!("Copying images is not supported. Enable the 'clipboard' feature of `egui-winit` to enable it.");
|
||||
log::error!(
|
||||
"Copying images is not supported. Enable the 'clipboard' feature of `egui-winit` to enable it."
|
||||
);
|
||||
_ = image;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1566,8 +1566,7 @@ pub fn create_window(
|
||||
) -> Result<Window, winit::error::OsError> {
|
||||
profiling::function_scope!();
|
||||
|
||||
let window_attributes =
|
||||
create_winit_window_attributes(egui_ctx, event_loop, viewport_builder.clone());
|
||||
let window_attributes = create_winit_window_attributes(egui_ctx, viewport_builder.clone());
|
||||
let window = event_loop.create_window(window_attributes)?;
|
||||
apply_viewport_builder_to_window(egui_ctx, &window, viewport_builder);
|
||||
Ok(window)
|
||||
@@ -1575,28 +1574,10 @@ pub fn create_window(
|
||||
|
||||
pub fn create_winit_window_attributes(
|
||||
egui_ctx: &egui::Context,
|
||||
event_loop: &ActiveEventLoop,
|
||||
viewport_builder: ViewportBuilder,
|
||||
) -> winit::window::WindowAttributes {
|
||||
profiling::function_scope!();
|
||||
|
||||
// We set sizes and positions in egui:s own ui points, which depends on the egui
|
||||
// zoom_factor and the native pixels per point, so we need to know that here.
|
||||
// We don't know what monitor the window will appear on though, but
|
||||
// we'll try to fix that after the window is created in the call to `apply_viewport_builder_to_window`.
|
||||
let native_pixels_per_point = event_loop
|
||||
.primary_monitor()
|
||||
.or_else(|| event_loop.available_monitors().next())
|
||||
.map_or_else(
|
||||
|| {
|
||||
log::debug!("Failed to find a monitor - assuming native_pixels_per_point of 1.0");
|
||||
1.0
|
||||
},
|
||||
|m| m.scale_factor() as f32,
|
||||
);
|
||||
let zoom_factor = egui_ctx.zoom_factor();
|
||||
let pixels_per_point = zoom_factor * native_pixels_per_point;
|
||||
|
||||
let ViewportBuilder {
|
||||
title,
|
||||
position,
|
||||
@@ -1672,40 +1653,46 @@ pub fn create_winit_window_attributes(
|
||||
})
|
||||
.with_active(active.unwrap_or(true));
|
||||
|
||||
// Here and below: we create `LogicalSize` / `LogicalPosition` taking
|
||||
// zoom factor into account. We don't have a good way to get physical size here,
|
||||
// and trying to do it anyway leads to weird bugs on Wayland, see:
|
||||
// https://github.com/emilk/egui/issues/7095#issuecomment-2920545377
|
||||
// https://github.com/rust-windowing/winit/issues/4266
|
||||
#[expect(
|
||||
clippy::disallowed_types,
|
||||
reason = "zoom factor is manually accounted for"
|
||||
)]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if let Some(size) = inner_size {
|
||||
window_attributes = window_attributes.with_inner_size(PhysicalSize::new(
|
||||
pixels_per_point * size.x,
|
||||
pixels_per_point * size.y,
|
||||
));
|
||||
}
|
||||
{
|
||||
use winit::dpi::{LogicalPosition, LogicalSize};
|
||||
let zoom_factor = egui_ctx.zoom_factor();
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if let Some(size) = min_inner_size {
|
||||
window_attributes = window_attributes.with_min_inner_size(PhysicalSize::new(
|
||||
pixels_per_point * size.x,
|
||||
pixels_per_point * size.y,
|
||||
));
|
||||
}
|
||||
if let Some(size) = inner_size {
|
||||
window_attributes = window_attributes
|
||||
.with_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if let Some(size) = max_inner_size {
|
||||
window_attributes = window_attributes.with_max_inner_size(PhysicalSize::new(
|
||||
pixels_per_point * size.x,
|
||||
pixels_per_point * size.y,
|
||||
));
|
||||
}
|
||||
if let Some(size) = min_inner_size {
|
||||
window_attributes = window_attributes
|
||||
.with_min_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if let Some(pos) = position {
|
||||
window_attributes = window_attributes.with_position(PhysicalPosition::new(
|
||||
pixels_per_point * pos.x,
|
||||
pixels_per_point * pos.y,
|
||||
));
|
||||
if let Some(size) = max_inner_size {
|
||||
window_attributes = window_attributes
|
||||
.with_max_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
|
||||
}
|
||||
|
||||
if let Some(pos) = position {
|
||||
window_attributes = window_attributes.with_position(LogicalPosition::new(
|
||||
zoom_factor * pos.x,
|
||||
zoom_factor * pos.y,
|
||||
));
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
// Unused:
|
||||
_ = egui_ctx;
|
||||
_ = pixels_per_point;
|
||||
_ = position;
|
||||
_ = inner_size;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
emath::{remap_clamp, NumExt as _},
|
||||
Id, IdMap, InputState,
|
||||
emath::{NumExt as _, remap_clamp},
|
||||
};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
||||
@@ -81,7 +81,7 @@ impl<'a> Atom<'a> {
|
||||
wrap_mode = Some(TextWrapMode::Truncate);
|
||||
}
|
||||
|
||||
let (preferred, kind) = self.kind.into_sized(ui, available_size, wrap_mode);
|
||||
let (intrinsic, kind) = self.kind.into_sized(ui, available_size, wrap_mode);
|
||||
|
||||
let size = self
|
||||
.size
|
||||
@@ -89,7 +89,7 @@ impl<'a> Atom<'a> {
|
||||
|
||||
SizedAtom {
|
||||
size,
|
||||
preferred_size: preferred.at_least(size),
|
||||
intrinsic_size: intrinsic.at_least(self.size.unwrap_or_default()),
|
||||
grow: self.grow,
|
||||
kind,
|
||||
}
|
||||
|
||||
@@ -82,22 +82,9 @@ impl<'a> AtomKind<'a> {
|
||||
match self {
|
||||
AtomKind::Text(text) => {
|
||||
let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode());
|
||||
let desired_size = matches!(wrap_mode, TextWrapMode::Truncate).then(|| {
|
||||
text.clone()
|
||||
.into_galley(
|
||||
ui,
|
||||
Some(TextWrapMode::Extend),
|
||||
available_size.x,
|
||||
TextStyle::Button,
|
||||
)
|
||||
.desired_size()
|
||||
});
|
||||
let galley =
|
||||
text.into_galley(ui, Some(wrap_mode), available_size.x, TextStyle::Button);
|
||||
(
|
||||
desired_size.unwrap_or_else(|| galley.desired_size()),
|
||||
SizedAtomKind::Text(galley),
|
||||
)
|
||||
(galley.intrinsic_size(), SizedAtomKind::Text(galley))
|
||||
}
|
||||
AtomKind::Image(image) => {
|
||||
let size = image.load_and_calc_size(ui, available_size);
|
||||
|
||||
@@ -183,10 +183,10 @@ impl<'a> AtomLayout<'a> {
|
||||
|
||||
let mut desired_width = 0.0;
|
||||
|
||||
// Preferred width / height is the ideal size of the widget, e.g. the size where the
|
||||
// intrinsic width / height is the ideal size of the widget, e.g. the size where the
|
||||
// text is not wrapped. Used to set Response::intrinsic_size.
|
||||
let mut preferred_width = 0.0;
|
||||
let mut preferred_height = 0.0;
|
||||
let mut intrinsic_width = 0.0;
|
||||
let mut intrinsic_height = 0.0;
|
||||
|
||||
let mut height: f32 = 0.0;
|
||||
|
||||
@@ -203,7 +203,7 @@ impl<'a> AtomLayout<'a> {
|
||||
if atoms.len() > 1 {
|
||||
let gap_space = gap * (atoms.len() as f32 - 1.0);
|
||||
desired_width += gap_space;
|
||||
preferred_width += gap_space;
|
||||
intrinsic_width += gap_space;
|
||||
}
|
||||
|
||||
for (idx, item) in atoms.into_iter().enumerate() {
|
||||
@@ -224,10 +224,10 @@ impl<'a> AtomLayout<'a> {
|
||||
let size = sized.size;
|
||||
|
||||
desired_width += size.x;
|
||||
preferred_width += sized.preferred_size.x;
|
||||
intrinsic_width += sized.intrinsic_size.x;
|
||||
|
||||
height = height.at_least(size.y);
|
||||
preferred_height = preferred_height.at_least(sized.preferred_size.y);
|
||||
intrinsic_height = intrinsic_height.at_least(sized.intrinsic_size.y);
|
||||
|
||||
sized_items.push(sized);
|
||||
}
|
||||
@@ -243,10 +243,10 @@ impl<'a> AtomLayout<'a> {
|
||||
let size = sized.size;
|
||||
|
||||
desired_width += size.x;
|
||||
preferred_width += sized.preferred_size.x;
|
||||
intrinsic_width += sized.intrinsic_size.x;
|
||||
|
||||
height = height.at_least(size.y);
|
||||
preferred_height = preferred_height.at_least(sized.preferred_size.y);
|
||||
intrinsic_height = intrinsic_height.at_least(sized.intrinsic_size.y);
|
||||
|
||||
sized_items.insert(index, sized);
|
||||
}
|
||||
@@ -256,7 +256,7 @@ impl<'a> AtomLayout<'a> {
|
||||
let frame_size = (desired_size + margin.sum()).at_least(min_size);
|
||||
|
||||
let intrinsic_size =
|
||||
(Vec2::new(preferred_width, preferred_height) + margin.sum()).at_least(min_size);
|
||||
(Vec2::new(intrinsic_width, intrinsic_height) + margin.sum()).at_least(min_size);
|
||||
let (_, rect) = ui.allocate_space(frame_size, intrinsic_size);
|
||||
let mut response = ui.interact(rect, id, sense);
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ pub struct SizedAtom<'a> {
|
||||
/// size.x + gap.
|
||||
pub size: Vec2,
|
||||
|
||||
/// Preferred size of the atom. This is used to calculate `Response::intrinsic_size`.
|
||||
pub preferred_size: Vec2,
|
||||
/// Intrinsic size of the atom. This is used to calculate `Response::intrinsic_size`.
|
||||
pub intrinsic_size: Vec2,
|
||||
|
||||
pub kind: SizedAtomKind<'a>,
|
||||
}
|
||||
|
||||
@@ -204,12 +204,17 @@ fn shorten_source_file_path(path: &std::path::Path) -> String {
|
||||
#[test]
|
||||
fn test_shorten_path() {
|
||||
for (before, after) in [
|
||||
("/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs", "tokio-1.24.1/src/runtime/runtime.rs"),
|
||||
(
|
||||
"/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs",
|
||||
"tokio-1.24.1/src/runtime/runtime.rs",
|
||||
),
|
||||
("crates/rerun/src/main.rs", "rerun/src/main.rs"),
|
||||
("/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs", "core/src/ops/function.rs"),
|
||||
(
|
||||
"/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs",
|
||||
"core/src/ops/function.rs",
|
||||
),
|
||||
("/weird/path/file.rs", "/weird/path/file.rs"),
|
||||
]
|
||||
{
|
||||
] {
|
||||
use std::str::FromStr as _;
|
||||
let before = std::path::PathBuf::from_str(before).unwrap();
|
||||
assert_eq!(shorten_source_file_path(&before), after);
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
use emath::GuiRounding as _;
|
||||
|
||||
use crate::{
|
||||
emath, pos2, Align2, Context, Id, InnerResponse, LayerId, Layout, NumExt as _, Order, Pos2,
|
||||
Rect, Response, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState,
|
||||
Align2, Context, Id, InnerResponse, LayerId, Layout, NumExt as _, Order, Pos2, Rect, Response,
|
||||
Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState, emath, pos2,
|
||||
};
|
||||
|
||||
/// State of an [`Area`] that is persisted between frames.
|
||||
@@ -121,6 +121,7 @@ pub struct Area {
|
||||
new_pos: Option<Pos2>,
|
||||
fade_in: bool,
|
||||
layout: Layout,
|
||||
sizing_pass: bool,
|
||||
}
|
||||
|
||||
impl WidgetWithState for Area {
|
||||
@@ -147,6 +148,7 @@ impl Area {
|
||||
anchor: None,
|
||||
fade_in: true,
|
||||
layout: Layout::default(),
|
||||
sizing_pass: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,6 +359,27 @@ impl Area {
|
||||
self.layout = layout;
|
||||
self
|
||||
}
|
||||
|
||||
/// While true, a sizing pass will be done. This means the area will be invisible
|
||||
/// and the contents will be laid out to estimate the proper containing size of the area.
|
||||
/// If false, there will be no change to the default area behavior. This is useful if the
|
||||
/// area contents area dynamic and you need to need to make sure the area adjusts its size
|
||||
/// accordingly.
|
||||
///
|
||||
/// This should only be set to true during the specific frames you want force a sizing pass.
|
||||
/// Do NOT hard-code this as `.sizing_pass(true)`, as it will cause the area to never be
|
||||
/// visible.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - resize: If true, the area will be resized to fit its contents. False will keep the
|
||||
/// default area resizing behavior.
|
||||
///
|
||||
/// Default: `false`.
|
||||
#[inline]
|
||||
pub fn sizing_pass(mut self, resize: bool) -> Self {
|
||||
self.sizing_pass = resize;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Prepared {
|
||||
@@ -410,6 +433,7 @@ impl Area {
|
||||
constrain_rect,
|
||||
fade_in,
|
||||
layout,
|
||||
sizing_pass: force_sizing_pass,
|
||||
} = self;
|
||||
|
||||
let constrain_rect = constrain_rect.unwrap_or_else(|| ctx.screen_rect());
|
||||
@@ -425,6 +449,10 @@ impl Area {
|
||||
interactable,
|
||||
last_became_visible_at: None,
|
||||
});
|
||||
if force_sizing_pass {
|
||||
sizing_pass = true;
|
||||
state.size = None;
|
||||
}
|
||||
state.pivot = pivot;
|
||||
state.interactable = interactable;
|
||||
if let Some(new_pos) = new_pos {
|
||||
@@ -679,7 +707,7 @@ fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 {
|
||||
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);
|
||||
*current_column_bb |= rect;
|
||||
} else {
|
||||
// new column
|
||||
column_bbs.push(rect);
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
#[expect(unused_imports)]
|
||||
use crate::{Ui, UiBuilder};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
/// A tag to mark a container as closable.
|
||||
///
|
||||
/// Usually set via [`UiBuilder::closable`].
|
||||
///
|
||||
/// [`Ui::close`] will find the closest parent [`ClosableTag`] and set its `close` field to `true`.
|
||||
/// Use [`Ui::should_close`] to check if close has been called.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ClosableTag {
|
||||
pub close: AtomicBool,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::{
|
||||
emath, epaint, pos2, remap, remap_clamp, vec2, Context, Id, InnerResponse, NumExt as _, Rect,
|
||||
Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2,
|
||||
WidgetInfo, WidgetText, WidgetType,
|
||||
Context, Id, InnerResponse, NumExt as _, Rect, Response, Sense, Stroke, TextStyle,
|
||||
TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetText, WidgetType,
|
||||
emath, epaint, pos2, remap, remap_clamp, vec2,
|
||||
};
|
||||
use emath::GuiRounding as _;
|
||||
use epaint::{Shape, StrokeKind};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use epaint::Shape;
|
||||
|
||||
use crate::{
|
||||
epaint, style::StyleModifier, style::WidgetVisuals, vec2, Align2, Context, Id, InnerResponse,
|
||||
NumExt as _, Painter, Popup, PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke,
|
||||
TextStyle, TextWrapMode, Ui, UiBuilder, Vec2, WidgetInfo, WidgetText, WidgetType,
|
||||
Align2, Context, Id, InnerResponse, NumExt as _, Painter, Popup, PopupCloseBehavior, Rect,
|
||||
Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui, UiBuilder, Vec2, WidgetInfo,
|
||||
WidgetText, WidgetType, epaint, style::StyleModifier, style::WidgetVisuals, vec2,
|
||||
};
|
||||
|
||||
#[expect(unused_imports)] // Documentation
|
||||
@@ -293,7 +293,7 @@ impl ComboBox {
|
||||
|
||||
/// Check if the [`ComboBox`] with the given id has its popup menu currently opened.
|
||||
pub fn is_open(ctx: &Context, id: Id) -> bool {
|
||||
ctx.memory(|m| m.is_popup_open(Self::widget_to_popup_id(id)))
|
||||
Popup::is_id_open(ctx, Self::widget_to_popup_id(id))
|
||||
}
|
||||
|
||||
/// Convert a [`ComboBox`] id to the id used to store it's popup state.
|
||||
@@ -315,7 +315,7 @@ fn combo_box_dyn<'c, R>(
|
||||
) -> InnerResponse<Option<R>> {
|
||||
let popup_id = ComboBox::widget_to_popup_id(button_id);
|
||||
|
||||
let is_popup_open = ui.memory(|m| m.is_popup_open(popup_id));
|
||||
let is_popup_open = Popup::is_id_open(ui.ctx(), popup_id);
|
||||
|
||||
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Frame container
|
||||
|
||||
use crate::{
|
||||
epaint, layers::ShapeIdx, InnerResponse, Response, Sense, Style, Ui, UiBuilder, UiKind,
|
||||
UiStackInfo,
|
||||
InnerResponse, Response, Sense, Style, Ui, UiBuilder, UiKind, UiStackInfo, epaint,
|
||||
layers::ShapeIdx,
|
||||
};
|
||||
use epaint::{Color32, CornerRadius, Margin, MarginF32, Rect, Shadow, Shape, Stroke};
|
||||
|
||||
@@ -143,7 +143,8 @@ pub struct Frame {
|
||||
#[test]
|
||||
fn frame_size() {
|
||||
assert_eq!(
|
||||
std::mem::size_of::<Frame>(), 32,
|
||||
std::mem::size_of::<Frame>(),
|
||||
32,
|
||||
"Frame changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
|
||||
);
|
||||
assert!(
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
//! Popup menus, context menus and menu bars.
|
||||
//!
|
||||
//! Show menus via
|
||||
//! - [`Popup::menu`] and [`Popup::context_menu`]
|
||||
//! - [`Ui::menu_button`], [`MenuButton`] and [`SubMenuButton`]
|
||||
//! - [`MenuBar`]
|
||||
//! - [`Response::context_menu`]
|
||||
//!
|
||||
//! See [`MenuBar`] for an example.
|
||||
|
||||
use crate::style::StyleModifier;
|
||||
use crate::{
|
||||
Button, Color32, Context, Frame, Id, InnerResponse, IntoAtoms, Layout, Popup,
|
||||
PopupCloseBehavior, Response, Style, Ui, UiBuilder, UiKind, UiStack, UiStackInfo, Widget as _,
|
||||
};
|
||||
use emath::{vec2, Align, RectAlign, Vec2};
|
||||
use emath::{Align, RectAlign, Vec2, vec2};
|
||||
use epaint::Stroke;
|
||||
|
||||
/// Apply a menu style to the [`Style`].
|
||||
@@ -50,6 +60,7 @@ pub fn is_in_menu(ui: &Ui) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Configuration and style for menus.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MenuConfig {
|
||||
/// Is this a menu bar?
|
||||
@@ -120,8 +131,10 @@ impl MenuConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds the state of the menu.
|
||||
#[derive(Clone)]
|
||||
pub struct MenuState {
|
||||
/// The currently open sub menu in this menu.
|
||||
pub open_item: Option<Id>,
|
||||
last_visible_pass: u64,
|
||||
}
|
||||
@@ -163,13 +176,29 @@ impl MenuState {
|
||||
/// The menu bar goes well in a [`crate::TopBottomPanel::top`],
|
||||
/// but can also be placed in a [`crate::Window`].
|
||||
/// In the latter case you may want to wrap it in [`Frame`].
|
||||
///
|
||||
/// ### Example:
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// egui::MenuBar::new().ui(ui, |ui| {
|
||||
/// ui.menu_button("File", |ui| {
|
||||
/// if ui.button("Quit").clicked() {
|
||||
/// ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
/// }
|
||||
/// });
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Bar {
|
||||
pub struct MenuBar {
|
||||
config: MenuConfig,
|
||||
style: StyleModifier,
|
||||
}
|
||||
|
||||
impl Default for Bar {
|
||||
#[deprecated = "Renamed to `egui::MenuBar`"]
|
||||
pub type Bar = MenuBar;
|
||||
|
||||
impl Default for MenuBar {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
config: MenuConfig::default(),
|
||||
@@ -178,7 +207,7 @@ impl Default for Bar {
|
||||
}
|
||||
}
|
||||
|
||||
impl Bar {
|
||||
impl MenuBar {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
@@ -234,8 +263,8 @@ impl Bar {
|
||||
|
||||
/// A thin wrapper around a [`Button`] that shows a [`Popup::menu`] when clicked.
|
||||
///
|
||||
/// The only thing this does is search for the current menu config (if set via [`Bar`]).
|
||||
/// If your menu button is not in a [`Bar`] it's fine to use [`Ui::button`] and [`Popup::menu`]
|
||||
/// The only thing this does is search for the current menu config (if set via [`MenuBar`]).
|
||||
/// If your menu button is not in a [`MenuBar`] it's fine to use [`Ui::button`] and [`Popup::menu`]
|
||||
/// directly.
|
||||
pub struct MenuButton<'a> {
|
||||
pub button: Button<'a>,
|
||||
@@ -341,6 +370,10 @@ impl<'a> SubMenuButton<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Show a submenu in a menu.
|
||||
///
|
||||
/// Useful if you want to make custom menu buttons.
|
||||
/// Usually, just use [`MenuButton`] or [`SubMenuButton`] instead.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SubMenu {
|
||||
config: Option<MenuConfig>,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! For instance, a [`Frame`] adds a frame and background to some contained UI.
|
||||
|
||||
pub(crate) mod area;
|
||||
pub mod close_tag;
|
||||
mod close_tag;
|
||||
pub mod collapsing_header;
|
||||
mod combo_box;
|
||||
pub mod frame;
|
||||
@@ -21,6 +21,7 @@ pub(crate) mod window;
|
||||
|
||||
pub use {
|
||||
area::{Area, AreaState},
|
||||
close_tag::ClosableTag,
|
||||
collapsing_header::{CollapsingHeader, CollapsingResponse},
|
||||
combo_box::*,
|
||||
frame::Frame,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use emath::{Align2, Vec2};
|
||||
|
||||
use crate::{
|
||||
Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiBuilder, UiKind,
|
||||
};
|
||||
use emath::{Align2, Vec2};
|
||||
|
||||
/// A modal dialog.
|
||||
///
|
||||
@@ -80,13 +81,11 @@ impl Modal {
|
||||
frame,
|
||||
} = self;
|
||||
|
||||
let (is_top_modal, any_popup_open) = ctx.memory_mut(|mem| {
|
||||
let is_top_modal = ctx.memory_mut(|mem| {
|
||||
mem.set_modal_layer(area.layer());
|
||||
(
|
||||
mem.top_modal_layer() == Some(area.layer()),
|
||||
mem.any_popup_open(),
|
||||
)
|
||||
mem.top_modal_layer() == Some(area.layer())
|
||||
});
|
||||
let any_popup_open = crate::Popup::is_any_open(ctx);
|
||||
let InnerResponse {
|
||||
inner: (inner, backdrop_response),
|
||||
response,
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn show_tooltip_at_pointer<R>(
|
||||
widget_id: Id,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
Tooltip::new(ctx.clone(), parent_layer, widget_id, PopupAnchor::Pointer)
|
||||
Tooltip::always_open(ctx.clone(), parent_layer, widget_id, PopupAnchor::Pointer)
|
||||
.gap(12.0)
|
||||
.show(add_contents)
|
||||
.map(|response| response.inner)
|
||||
@@ -78,7 +78,7 @@ pub fn show_tooltip_for<R>(
|
||||
widget_rect: &Rect,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
Tooltip::new(ctx.clone(), parent_layer, widget_id, *widget_rect)
|
||||
Tooltip::always_open(ctx.clone(), parent_layer, widget_id, *widget_rect)
|
||||
.show(add_contents)
|
||||
.map(|response| response.inner)
|
||||
}
|
||||
@@ -94,7 +94,7 @@ pub fn show_tooltip_at<R>(
|
||||
suggested_position: Pos2,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
Tooltip::new(ctx.clone(), parent_layer, widget_id, suggested_position)
|
||||
Tooltip::always_open(ctx.clone(), parent_layer, widget_id, suggested_position)
|
||||
.show(add_contents)
|
||||
.map(|response| response.inner)
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
use emath::GuiRounding as _;
|
||||
|
||||
use crate::{
|
||||
lerp, vec2, Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _,
|
||||
Rangef, Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2,
|
||||
Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef,
|
||||
Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, lerp, vec2,
|
||||
};
|
||||
|
||||
fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use crate::containers::menu::{menu_style, MenuConfig, MenuState};
|
||||
use crate::style::StyleModifier;
|
||||
#![expect(deprecated)] // This is a new, safe wrapper around the old `Memory::popup` API.
|
||||
|
||||
use std::iter::once;
|
||||
|
||||
use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2};
|
||||
|
||||
use crate::{
|
||||
Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order, Response,
|
||||
Sense, Ui, UiKind, UiStackInfo,
|
||||
containers::menu::{MenuConfig, MenuState, menu_style},
|
||||
style::StyleModifier,
|
||||
};
|
||||
use emath::{vec2, Align, Pos2, Rect, RectAlign, Vec2};
|
||||
use std::iter::once;
|
||||
|
||||
/// What should we anchor the popup to?
|
||||
///
|
||||
@@ -64,9 +68,7 @@ impl PopupAnchor {
|
||||
match self {
|
||||
Self::ParentRect(rect) => Some(rect),
|
||||
Self::Pointer => ctx.pointer_hover_pos().map(Rect::from_pos),
|
||||
Self::PointerFixed => ctx
|
||||
.memory(|mem| mem.popup_position(popup_id))
|
||||
.map(Rect::from_pos),
|
||||
Self::PointerFixed => Popup::position_of_id(ctx, popup_id).map(Rect::from_pos),
|
||||
Self::Position(pos) => Some(Rect::from_pos(pos)),
|
||||
}
|
||||
}
|
||||
@@ -122,12 +124,12 @@ enum OpenKind<'a> {
|
||||
|
||||
impl OpenKind<'_> {
|
||||
/// Returns `true` if the popup should be open
|
||||
fn is_open(&self, id: Id, ctx: &Context) -> bool {
|
||||
fn is_open(&self, popup_id: Id, ctx: &Context) -> bool {
|
||||
match self {
|
||||
OpenKind::Open => true,
|
||||
OpenKind::Closed => false,
|
||||
OpenKind::Bool(open) => **open,
|
||||
OpenKind::Memory { .. } => ctx.memory(|mem| mem.is_popup_open(id)),
|
||||
OpenKind::Memory { .. } => Popup::is_id_open(ctx, popup_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,6 +162,8 @@ impl From<PopupKind> for UiKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// A popup container.
|
||||
#[must_use = "Call `.show()` to actually display the popup"]
|
||||
pub struct Popup<'a> {
|
||||
id: Id,
|
||||
ctx: Context,
|
||||
@@ -210,6 +214,57 @@ impl<'a> Popup<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Show a popup relative to some widget.
|
||||
/// The popup will be always open.
|
||||
///
|
||||
/// See [`Self::menu`] and [`Self::context_menu`] for common use cases.
|
||||
pub fn from_response(response: &Response) -> Self {
|
||||
let mut popup = Self::new(
|
||||
Self::default_response_id(response),
|
||||
response.ctx.clone(),
|
||||
response,
|
||||
response.layer_id,
|
||||
);
|
||||
popup.widget_clicked_elsewhere = response.clicked_elsewhere();
|
||||
popup
|
||||
}
|
||||
|
||||
/// Show a popup relative to some widget,
|
||||
/// toggling the open state based on the widget's click state.
|
||||
///
|
||||
/// See [`Self::menu`] and [`Self::context_menu`] for common use cases.
|
||||
pub fn from_toggle_button_response(button_response: &Response) -> Self {
|
||||
Self::from_response(button_response)
|
||||
.open_memory(button_response.clicked().then_some(SetOpenCommand::Toggle))
|
||||
}
|
||||
|
||||
/// Show a popup when the widget was clicked.
|
||||
/// Sets the layout to `Layout::top_down_justified(Align::Min)`.
|
||||
pub fn menu(button_response: &Response) -> Self {
|
||||
Self::from_toggle_button_response(button_response)
|
||||
.kind(PopupKind::Menu)
|
||||
.layout(Layout::top_down_justified(Align::Min))
|
||||
.style(menu_style)
|
||||
.gap(0.0)
|
||||
}
|
||||
|
||||
/// Show a context menu when the widget was secondary clicked.
|
||||
/// Sets the layout to `Layout::top_down_justified(Align::Min)`.
|
||||
/// In contrast to [`Self::menu`], this will open at the pointer position.
|
||||
pub fn context_menu(response: &Response) -> Self {
|
||||
Self::menu(response)
|
||||
.open_memory(if response.secondary_clicked() {
|
||||
Some(SetOpenCommand::Bool(true))
|
||||
} else if response.clicked() {
|
||||
// Explicitly close the menu if the widget was clicked
|
||||
// Without this, the context menu would stay open if the user clicks the widget
|
||||
Some(SetOpenCommand::Bool(false))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.at_pointer_fixed()
|
||||
}
|
||||
|
||||
/// Set the kind of the popup. Used for [`Area::kind`] and [`Area::order`].
|
||||
#[inline]
|
||||
pub fn kind(mut self, kind: PopupKind) -> Self {
|
||||
@@ -242,49 +297,6 @@ impl<'a> Popup<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Show a popup relative to some widget.
|
||||
/// The popup will be always open.
|
||||
///
|
||||
/// See [`Self::menu`] and [`Self::context_menu`] for common use cases.
|
||||
pub fn from_response(response: &Response) -> Self {
|
||||
let mut popup = Self::new(
|
||||
response.id.with("popup"),
|
||||
response.ctx.clone(),
|
||||
response,
|
||||
response.layer_id,
|
||||
);
|
||||
popup.widget_clicked_elsewhere = response.clicked_elsewhere();
|
||||
popup
|
||||
}
|
||||
|
||||
/// Show a popup when the widget was clicked.
|
||||
/// Sets the layout to `Layout::top_down_justified(Align::Min)`.
|
||||
pub fn menu(response: &Response) -> Self {
|
||||
Self::from_response(response)
|
||||
.open_memory(response.clicked().then_some(SetOpenCommand::Toggle))
|
||||
.kind(PopupKind::Menu)
|
||||
.layout(Layout::top_down_justified(Align::Min))
|
||||
.style(menu_style)
|
||||
.gap(0.0)
|
||||
}
|
||||
|
||||
/// Show a context menu when the widget was secondary clicked.
|
||||
/// Sets the layout to `Layout::top_down_justified(Align::Min)`.
|
||||
/// In contrast to [`Self::menu`], this will open at the pointer position.
|
||||
pub fn context_menu(response: &Response) -> Self {
|
||||
Self::menu(response)
|
||||
.open_memory(if response.secondary_clicked() {
|
||||
Some(SetOpenCommand::Bool(true))
|
||||
} else if response.clicked() {
|
||||
// Explicitly close the menu if the widget was clicked
|
||||
// Without this, the context menu would stay open if the user clicks the widget
|
||||
Some(SetOpenCommand::Bool(false))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.at_pointer_fixed()
|
||||
}
|
||||
|
||||
/// Force the popup to be open or closed.
|
||||
#[inline]
|
||||
pub fn open(mut self, open: bool) -> Self {
|
||||
@@ -446,7 +458,7 @@ impl<'a> Popup<'a> {
|
||||
OpenKind::Open => true,
|
||||
OpenKind::Closed => false,
|
||||
OpenKind::Bool(open) => **open,
|
||||
OpenKind::Memory { .. } => self.ctx.memory(|mem| mem.is_popup_open(self.id)),
|
||||
OpenKind::Memory { .. } => Self::is_id_open(&self.ctx, self.id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,12 +496,43 @@ impl<'a> Popup<'a> {
|
||||
self.gap,
|
||||
expected_popup_size,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Show the popup.
|
||||
/// Returns `None` if the popup is not open or anchor is `PopupAnchor::Pointer` and there is
|
||||
/// no pointer.
|
||||
pub fn show<R>(self, content: impl FnOnce(&mut Ui) -> R) -> Option<InnerResponse<R>> {
|
||||
let hover_pos = self.ctx.pointer_hover_pos();
|
||||
|
||||
let id = self.id;
|
||||
if let OpenKind::Memory { set } = self.open_kind {
|
||||
match set {
|
||||
Some(SetOpenCommand::Bool(open)) => {
|
||||
if open {
|
||||
match self.anchor {
|
||||
PopupAnchor::PointerFixed => {
|
||||
self.ctx.memory_mut(|mem| mem.open_popup_at(id, hover_pos));
|
||||
}
|
||||
_ => Popup::open_id(&self.ctx, id),
|
||||
}
|
||||
} else {
|
||||
Self::close_id(&self.ctx, id);
|
||||
}
|
||||
}
|
||||
Some(SetOpenCommand::Toggle) => {
|
||||
Self::toggle_id(&self.ctx, id);
|
||||
}
|
||||
None => {
|
||||
self.ctx.memory_mut(|mem| mem.keep_popup_open(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.open_kind.is_open(self.id, &self.ctx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let best_align = self.get_best_align();
|
||||
|
||||
let Popup {
|
||||
@@ -512,34 +555,6 @@ impl<'a> Popup<'a> {
|
||||
style,
|
||||
} = self;
|
||||
|
||||
let hover_pos = ctx.pointer_hover_pos();
|
||||
if let OpenKind::Memory { set, .. } = open_kind {
|
||||
ctx.memory_mut(|mem| match set {
|
||||
Some(SetOpenCommand::Bool(open)) => {
|
||||
if open {
|
||||
match self.anchor {
|
||||
PopupAnchor::PointerFixed => {
|
||||
mem.open_popup_at(id, hover_pos);
|
||||
}
|
||||
_ => mem.open_popup(id),
|
||||
}
|
||||
} else {
|
||||
mem.close_popup(id);
|
||||
}
|
||||
}
|
||||
Some(SetOpenCommand::Toggle) => {
|
||||
mem.toggle_popup(id);
|
||||
}
|
||||
None => {
|
||||
mem.keep_popup_open(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if !open_kind.is_open(id, &ctx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if kind != PopupKind::Tooltip {
|
||||
ctx.pass_state_mut(|fs| {
|
||||
fs.layers
|
||||
@@ -573,10 +588,9 @@ impl<'a> Popup<'a> {
|
||||
area = area.default_width(width);
|
||||
}
|
||||
|
||||
let frame = frame.unwrap_or_else(|| Frame::popup(&ctx.style()));
|
||||
|
||||
let mut response = area.show(&ctx, |ui| {
|
||||
style.apply(ui.style_mut());
|
||||
let frame = frame.unwrap_or_else(|| Frame::popup(ui.style()));
|
||||
frame.show(ui, content).inner
|
||||
});
|
||||
|
||||
@@ -616,3 +630,65 @@ impl<'a> Popup<'a> {
|
||||
Some(response)
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Static methods
|
||||
impl Popup<'_> {
|
||||
/// The default ID when constructing a popup from the [`Response`] of e.g. a button.
|
||||
pub fn default_response_id(response: &Response) -> Id {
|
||||
response.id.with("popup")
|
||||
}
|
||||
|
||||
/// Is the given popup open?
|
||||
///
|
||||
/// This assumes the use of either:
|
||||
/// * [`Self::open_memory`]
|
||||
/// * [`Self::from_toggle_button_response`]
|
||||
/// * [`Self::menu`]
|
||||
/// * [`Self::context_menu`]
|
||||
///
|
||||
/// The popup id should be the same as either you set with [`Self::id`] or the
|
||||
/// default one from [`Self::default_response_id`].
|
||||
pub fn is_id_open(ctx: &Context, popup_id: Id) -> bool {
|
||||
ctx.memory(|mem| mem.is_popup_open(popup_id))
|
||||
}
|
||||
|
||||
/// Is any popup open?
|
||||
///
|
||||
/// This assumes the egui memory is being used to track the open state of popups.
|
||||
pub fn is_any_open(ctx: &Context) -> bool {
|
||||
ctx.memory(|mem| mem.any_popup_open())
|
||||
}
|
||||
|
||||
/// Open the given popup and close all others.
|
||||
///
|
||||
/// If you are NOT using [`Popup::show`], you must
|
||||
/// also call [`crate::Memory::keep_popup_open`] as long as
|
||||
/// you're showing the popup.
|
||||
pub fn open_id(ctx: &Context, popup_id: Id) {
|
||||
ctx.memory_mut(|mem| mem.open_popup(popup_id));
|
||||
}
|
||||
|
||||
/// Toggle the given popup between closed and open.
|
||||
///
|
||||
/// Note: At most, only one popup can be open at a time.
|
||||
pub fn toggle_id(ctx: &Context, popup_id: Id) {
|
||||
ctx.memory_mut(|mem| mem.toggle_popup(popup_id));
|
||||
}
|
||||
|
||||
/// Close all currently open popups.
|
||||
pub fn close_all(ctx: &Context) {
|
||||
ctx.memory_mut(|mem| mem.close_all_popups());
|
||||
}
|
||||
|
||||
/// Close the given popup, if it is open.
|
||||
///
|
||||
/// See also [`Self::close_all`] if you want to close any / all currently open popups.
|
||||
pub fn close_id(ctx: &Context, popup_id: Id) {
|
||||
ctx.memory_mut(|mem| mem.close_popup(popup_id));
|
||||
}
|
||||
|
||||
/// Get the position for this popup, if it is open.
|
||||
pub fn position_of_id(ctx: &Context, popup_id: Id) -> Option<Pos2> {
|
||||
ctx.memory(|mem| mem.popup_position(popup_id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
pos2, vec2, Align2, Color32, Context, CursorIcon, Id, NumExt as _, Rect, Response, Sense,
|
||||
Shape, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b,
|
||||
Align2, Color32, Context, CursorIcon, Id, NumExt as _, Rect, Response, Sense, Shape, Ui,
|
||||
UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, pos2, vec2,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -3,8 +3,8 @@ use core::f32;
|
||||
use emath::{GuiRounding as _, Pos2};
|
||||
|
||||
use crate::{
|
||||
emath::TSTransform, InnerResponse, LayerId, PointerButton, Rangef, Rect, Response, Sense, Ui,
|
||||
UiBuilder, Vec2,
|
||||
InnerResponse, LayerId, PointerButton, Rangef, Rect, Response, Sense, Ui, UiBuilder, Vec2,
|
||||
emath::TSTransform,
|
||||
};
|
||||
|
||||
/// Creates a transformation that fits a given scene rectangle into the available screen size.
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
use std::ops::{Add, AddAssign, BitOr, BitOrAssign};
|
||||
|
||||
use crate::{
|
||||
emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, Context, CursorIcon, Id,
|
||||
NumExt as _, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b,
|
||||
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind,
|
||||
UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use emath::{Align, Vec2};
|
||||
use emath::{Align, NumExt as _, Vec2};
|
||||
|
||||
use crate::{Layout, Ui, UiBuilder};
|
||||
|
||||
@@ -20,8 +20,13 @@ use crate::{Layout, Ui, UiBuilder};
|
||||
///
|
||||
/// If the parent is not wide enough to fit all widgets, the parent will be expanded to the right.
|
||||
///
|
||||
/// The left widgets are first added to the ui, left-to-right.
|
||||
/// Then the right widgets are added, right-to-left.
|
||||
/// The left widgets are added left-to-right.
|
||||
/// The right widgets are added right-to-left.
|
||||
///
|
||||
/// Which side is first depends on the configuration:
|
||||
/// - [`Sides::extend`] - left widgets are added first
|
||||
/// - [`Sides::shrink_left`] - right widgets are added first
|
||||
/// - [`Sides::shrink_right`] - left widgets are added first
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
@@ -40,6 +45,16 @@ use crate::{Layout, Ui, UiBuilder};
|
||||
pub struct Sides {
|
||||
height: Option<f32>,
|
||||
spacing: Option<f32>,
|
||||
kind: SidesKind,
|
||||
wrap_mode: Option<crate::TextWrapMode>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
enum SidesKind {
|
||||
#[default]
|
||||
Extend,
|
||||
ShrinkLeft,
|
||||
ShrinkRight,
|
||||
}
|
||||
|
||||
impl Sides {
|
||||
@@ -68,63 +83,184 @@ impl Sides {
|
||||
self
|
||||
}
|
||||
|
||||
/// Try to shrink widgets on the left side.
|
||||
///
|
||||
/// Right widgets will be added first. The left [`Ui`]s max rect will be limited to the
|
||||
/// remaining space.
|
||||
#[inline]
|
||||
pub fn shrink_left(mut self) -> Self {
|
||||
self.kind = SidesKind::ShrinkLeft;
|
||||
self
|
||||
}
|
||||
|
||||
/// Try to shrink widgets on the right side.
|
||||
///
|
||||
/// Left widgets will be added first. The right [`Ui`]s max rect will be limited to the
|
||||
/// remaining space.
|
||||
#[inline]
|
||||
pub fn shrink_right(mut self) -> Self {
|
||||
self.kind = SidesKind::ShrinkRight;
|
||||
self
|
||||
}
|
||||
|
||||
/// Extend the left and right sides to fill the available space.
|
||||
///
|
||||
/// This is the default behavior.
|
||||
/// The left widgets will be added first, followed by the right widgets.
|
||||
#[inline]
|
||||
pub fn extend(mut self) -> Self {
|
||||
self.kind = SidesKind::Extend;
|
||||
self
|
||||
}
|
||||
|
||||
/// The text wrap mode for the shrinking side.
|
||||
///
|
||||
/// Does nothing if [`Self::extend`] is used (the default).
|
||||
#[inline]
|
||||
pub fn wrap_mode(mut self, wrap_mode: crate::TextWrapMode) -> Self {
|
||||
self.wrap_mode = Some(wrap_mode);
|
||||
self
|
||||
}
|
||||
|
||||
/// Truncate the text on the shrinking side.
|
||||
///
|
||||
/// This is a shortcut for [`Self::wrap_mode`].
|
||||
/// Does nothing if [`Self::extend`] is used (the default).
|
||||
#[inline]
|
||||
pub fn truncate(mut self) -> Self {
|
||||
self.wrap_mode = Some(crate::TextWrapMode::Truncate);
|
||||
self
|
||||
}
|
||||
|
||||
/// Wrap the text on the shrinking side.
|
||||
///
|
||||
/// This is a shortcut for [`Self::wrap_mode`].
|
||||
/// Does nothing if [`Self::extend`] is used (the default).
|
||||
#[inline]
|
||||
pub fn wrap(mut self) -> Self {
|
||||
self.wrap_mode = Some(crate::TextWrapMode::Wrap);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show<RetL, RetR>(
|
||||
self,
|
||||
ui: &mut Ui,
|
||||
add_left: impl FnOnce(&mut Ui) -> RetL,
|
||||
add_right: impl FnOnce(&mut Ui) -> RetR,
|
||||
) -> (RetL, RetR) {
|
||||
let Self { height, spacing } = self;
|
||||
let Self {
|
||||
height,
|
||||
spacing,
|
||||
mut kind,
|
||||
mut wrap_mode,
|
||||
} = self;
|
||||
let height = height.unwrap_or_else(|| ui.spacing().interact_size.y);
|
||||
let spacing = spacing.unwrap_or_else(|| ui.spacing().item_spacing.x);
|
||||
|
||||
let mut top_rect = ui.available_rect_before_wrap();
|
||||
top_rect.max.y = top_rect.min.y + height;
|
||||
|
||||
let result_left;
|
||||
let result_right;
|
||||
|
||||
let (left_rect, left_preferred_size) = {
|
||||
let left_max_rect = top_rect;
|
||||
let mut left_ui = ui.new_child(
|
||||
UiBuilder::new()
|
||||
.max_rect(left_max_rect)
|
||||
.layout(Layout::left_to_right(Align::Center)),
|
||||
);
|
||||
result_left = add_left(&mut left_ui);
|
||||
(left_ui.min_rect(), left_ui.intrinsic_size())
|
||||
};
|
||||
|
||||
let (right_rect, right_preferred_size) = {
|
||||
let right_max_rect = top_rect.with_min_x(left_rect.max.x);
|
||||
let mut right_ui = ui.new_child(
|
||||
UiBuilder::new()
|
||||
.max_rect(right_max_rect)
|
||||
.layout(Layout::right_to_left(Align::Center)),
|
||||
);
|
||||
result_right = add_right(&mut right_ui);
|
||||
(right_ui.min_rect(), right_ui.intrinsic_size())
|
||||
};
|
||||
|
||||
let mut final_rect = left_rect.union(right_rect);
|
||||
let min_width = left_rect.width() + spacing + right_rect.width();
|
||||
|
||||
if ui.is_sizing_pass() {
|
||||
// Make as small as possible:
|
||||
final_rect.max.x = left_rect.min.x + min_width;
|
||||
} else {
|
||||
// If the rects overlap, make sure we expand the allocated rect so that the parent
|
||||
// ui knows we overflowed, and resizes:
|
||||
final_rect.max.x = final_rect.max.x.max(left_rect.min.x + min_width);
|
||||
kind = SidesKind::Extend;
|
||||
wrap_mode = None;
|
||||
}
|
||||
|
||||
let preferred_size = Vec2::new(
|
||||
left_preferred_size.x + spacing + right_preferred_size.x,
|
||||
left_preferred_size.y.max(right_preferred_size.y),
|
||||
);
|
||||
let intrinsic_size =
|
||||
|left: Vec2, right: Vec2| Vec2::new(left.x + spacing + right.x, left.y.max(right.y));
|
||||
|
||||
ui.advance_cursor_after_rect(final_rect, preferred_size);
|
||||
match kind {
|
||||
SidesKind::ShrinkLeft => {
|
||||
let (right_rect, right_intrinsic, result_right) = Self::create_ui(
|
||||
ui,
|
||||
top_rect,
|
||||
Layout::right_to_left(Align::Center),
|
||||
add_right,
|
||||
None,
|
||||
);
|
||||
let available_width = top_rect.width() - right_rect.width() - spacing;
|
||||
let left_rect_constraint =
|
||||
top_rect.with_max_x(top_rect.min.x + available_width.at_least(0.0));
|
||||
let (left_rect, left_intrinsic, result_left) = Self::create_ui(
|
||||
ui,
|
||||
left_rect_constraint,
|
||||
Layout::left_to_right(Align::Center),
|
||||
add_left,
|
||||
wrap_mode,
|
||||
);
|
||||
|
||||
(result_left, result_right)
|
||||
let intrinsic = intrinsic_size(left_intrinsic, right_intrinsic);
|
||||
|
||||
ui.advance_cursor_after_rect(left_rect | right_rect, intrinsic);
|
||||
(result_left, result_right)
|
||||
}
|
||||
SidesKind::ShrinkRight => {
|
||||
let (left_rect, left_intrinsic, result_left) = Self::create_ui(
|
||||
ui,
|
||||
top_rect,
|
||||
Layout::left_to_right(Align::Center),
|
||||
add_left,
|
||||
None,
|
||||
);
|
||||
let right_rect_constraint = top_rect.with_min_x(left_rect.max.x + spacing);
|
||||
let (right_rect, right_intrinsic, result_right) = Self::create_ui(
|
||||
ui,
|
||||
right_rect_constraint,
|
||||
Layout::right_to_left(Align::Center),
|
||||
add_right,
|
||||
wrap_mode,
|
||||
);
|
||||
|
||||
let intrinsic_size = intrinsic_size(left_intrinsic, right_intrinsic);
|
||||
|
||||
ui.advance_cursor_after_rect(left_rect | right_rect, intrinsic_size);
|
||||
(result_left, result_right)
|
||||
}
|
||||
SidesKind::Extend => {
|
||||
let (left_rect, left_intrinsic, result_left) = Self::create_ui(
|
||||
ui,
|
||||
top_rect,
|
||||
Layout::left_to_right(Align::Center),
|
||||
add_left,
|
||||
None,
|
||||
);
|
||||
let right_max_rect = top_rect.with_min_x(left_rect.max.x);
|
||||
let (right_rect, right_intrinsic, result_right) = Self::create_ui(
|
||||
ui,
|
||||
right_max_rect,
|
||||
Layout::right_to_left(Align::Center),
|
||||
add_right,
|
||||
None,
|
||||
);
|
||||
|
||||
let mut final_rect = left_rect | right_rect;
|
||||
let min_width = left_rect.width() + spacing + right_rect.width();
|
||||
|
||||
if ui.is_sizing_pass() {
|
||||
final_rect.max.x = left_rect.min.x + min_width;
|
||||
} else {
|
||||
final_rect.max.x = final_rect.max.x.max(left_rect.min.x + min_width);
|
||||
}
|
||||
|
||||
let intrinsic = intrinsic_size(left_intrinsic, right_intrinsic);
|
||||
|
||||
ui.advance_cursor_after_rect(final_rect, intrinsic);
|
||||
(result_left, result_right)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_ui<Ret>(
|
||||
ui: &mut Ui,
|
||||
max_rect: emath::Rect,
|
||||
layout: Layout,
|
||||
add_content: impl FnOnce(&mut Ui) -> Ret,
|
||||
wrap_mode: Option<crate::TextWrapMode>,
|
||||
) -> (emath::Rect, emath::Vec2, Ret) {
|
||||
let mut child_ui = ui.new_child(UiBuilder::new().max_rect(max_rect).layout(layout));
|
||||
if let Some(wrap_mode) = wrap_mode {
|
||||
child_ui.style_mut().wrap_mode = Some(wrap_mode);
|
||||
}
|
||||
let result = add_content(&mut child_ui);
|
||||
(child_ui.min_rect(), child_ui.intrinsic_size(), result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,25 @@ pub struct Tooltip<'a> {
|
||||
|
||||
impl Tooltip<'_> {
|
||||
/// Show a tooltip that is always open.
|
||||
#[deprecated = "Use `Tooltip::always_open` instead."]
|
||||
pub fn new(
|
||||
parent_widget: Id,
|
||||
ctx: Context,
|
||||
anchor: impl Into<PopupAnchor>,
|
||||
parent_layer: LayerId,
|
||||
) -> Self {
|
||||
Self {
|
||||
popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer)
|
||||
.kind(PopupKind::Tooltip)
|
||||
.gap(4.0)
|
||||
.sense(Sense::hover()),
|
||||
parent_layer,
|
||||
parent_widget,
|
||||
}
|
||||
}
|
||||
|
||||
/// Show a tooltip that is always open.
|
||||
pub fn always_open(
|
||||
ctx: Context,
|
||||
parent_layer: LayerId,
|
||||
parent_widget: Id,
|
||||
@@ -145,7 +163,7 @@ impl Tooltip<'_> {
|
||||
// The popup might not be shown on at_pointer if there is no pointer.
|
||||
if let Some(response) = &response {
|
||||
state.tooltip_count += 1;
|
||||
state.bounding_rect = state.bounding_rect.union(response.response.rect);
|
||||
state.bounding_rect |= response.response.rect;
|
||||
response
|
||||
.response
|
||||
.ctx
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::collapsing_header::CollapsingState;
|
||||
use crate::*;
|
||||
|
||||
use super::scroll_area::{ScrollBarVisibility, ScrollSource};
|
||||
use super::{area, resize, Area, Frame, Resize, ScrollArea};
|
||||
use super::{Area, Frame, Resize, ScrollArea, area, resize};
|
||||
|
||||
/// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled (off by default).
|
||||
///
|
||||
|
||||
@@ -4,16 +4,23 @@ use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration
|
||||
|
||||
use emath::{GuiRounding as _, OrderedFloat};
|
||||
use epaint::{
|
||||
ClippedPrimitive, ClippedShape, Color32, ImageData, ImageDelta, Pos2, Rect, StrokeKind,
|
||||
TessellationOptions, TextureAtlas, TextureId, Vec2,
|
||||
emath::{self, TSTransform},
|
||||
mutex::RwLock,
|
||||
stats::PaintStats,
|
||||
tessellator,
|
||||
text::{FontInsert, FontPriority, Fonts},
|
||||
vec2, ClippedPrimitive, ClippedShape, Color32, ImageData, ImageDelta, Pos2, Rect, StrokeKind,
|
||||
TessellationOptions, TextureAtlas, TextureId, Vec2,
|
||||
vec2,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport,
|
||||
ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory,
|
||||
ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText,
|
||||
ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder,
|
||||
ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput,
|
||||
Widget as _, WidgetRect, WidgetText,
|
||||
animation_manager::AnimationManager,
|
||||
containers::{self, area::AreaState},
|
||||
data::output::PlatformOutput,
|
||||
@@ -29,12 +36,6 @@ use crate::{
|
||||
resize, response, scroll_area,
|
||||
util::IdTypeMap,
|
||||
viewport::ViewportClass,
|
||||
Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport,
|
||||
ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory,
|
||||
ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText,
|
||||
ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder,
|
||||
ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput,
|
||||
Widget as _, WidgetRect, WidgetText,
|
||||
};
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
@@ -77,7 +78,7 @@ impl Default for WrappedTextureManager {
|
||||
// Will be filled in later
|
||||
let font_id = tex_mngr.alloc(
|
||||
"egui_font_texture".into(),
|
||||
epaint::FontImage::new([0, 0]).into(),
|
||||
epaint::ColorImage::filled([0, 0], Color32::TRANSPARENT).into(),
|
||||
Default::default(),
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -339,6 +340,15 @@ impl RepaintCause {
|
||||
/// Per-viewport state related to repaint scheduling.
|
||||
struct ViewportRepaintInfo {
|
||||
/// Monotonically increasing counter.
|
||||
///
|
||||
/// Incremented at the end of [`Context::run`].
|
||||
/// This can be smaller than [`Self::cumulative_pass_nr`],
|
||||
/// but never larger.
|
||||
cumulative_frame_nr: u64,
|
||||
|
||||
/// Monotonically increasing counter, counting the number of passes.
|
||||
/// This can be larger than [`Self::cumulative_frame_nr`],
|
||||
/// but never smaller.
|
||||
cumulative_pass_nr: u64,
|
||||
|
||||
/// The duration which the backend will poll for new events
|
||||
@@ -369,6 +379,7 @@ struct ViewportRepaintInfo {
|
||||
impl Default for ViewportRepaintInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cumulative_frame_nr: 0,
|
||||
cumulative_pass_nr: 0,
|
||||
|
||||
// We haven't scheduled a repaint yet.
|
||||
@@ -599,6 +610,8 @@ impl ContextImpl {
|
||||
log::trace!("Adding new fonts");
|
||||
}
|
||||
|
||||
let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage;
|
||||
|
||||
let mut is_new = false;
|
||||
|
||||
let fonts = self
|
||||
@@ -613,13 +626,14 @@ impl ContextImpl {
|
||||
Fonts::new(
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
text_alpha_from_coverage,
|
||||
self.font_definitions.clone(),
|
||||
)
|
||||
});
|
||||
|
||||
{
|
||||
profiling::scope!("Fonts::begin_pass");
|
||||
fonts.begin_pass(pixels_per_point, max_texture_side);
|
||||
fonts.begin_pass(pixels_per_point, max_texture_side, text_alpha_from_coverage);
|
||||
}
|
||||
|
||||
if is_new && self.memory.options.preload_font_glyphs {
|
||||
@@ -850,7 +864,10 @@ impl Context {
|
||||
|
||||
if max_passes <= output.platform_output.num_completed_passes {
|
||||
#[cfg(feature = "log")]
|
||||
log::debug!("Ignoring call request_discard, because max_passes={max_passes}. Requested from {:?}", output.platform_output.request_discard_reasons);
|
||||
log::debug!(
|
||||
"Ignoring call request_discard, because max_passes={max_passes}. Requested from {:?}",
|
||||
output.platform_output.request_discard_reasons
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -864,6 +881,7 @@ impl Context {
|
||||
} else {
|
||||
viewport.num_multipass_in_row = 0;
|
||||
}
|
||||
viewport.repaint.cumulative_frame_nr += 1;
|
||||
});
|
||||
|
||||
output
|
||||
@@ -1134,7 +1152,7 @@ impl Context {
|
||||
ID clashes happens when things like Windows or CollapsingHeaders share names,\n\
|
||||
or when things like Plot and Grid:s aren't given unique id_salt:s.\n\n\
|
||||
Sometimes the solution is to use ui.push_id.",
|
||||
if below { "above" } else { "below" })
|
||||
if below { "above" } else { "below" }),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1201,6 +1219,51 @@ impl Context {
|
||||
self.accesskit_node_builder(w.id, |builder| res.fill_accesskit_node_common(builder));
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
self.write(|ctx| {
|
||||
use crate::{Align, pass_state::ScrollTarget, style::ScrollAnimation};
|
||||
let viewport = ctx.viewport_for(ctx.viewport_id());
|
||||
|
||||
viewport
|
||||
.input
|
||||
.consume_accesskit_action_requests(res.id, |request| {
|
||||
// TODO(lucasmerlin): Correctly handle the scroll unit:
|
||||
// https://github.com/AccessKit/accesskit/blob/e639c0e0d8ccbfd9dff302d972fa06f9766d608e/common/src/lib.rs#L2621
|
||||
const DISTANCE: f32 = 100.0;
|
||||
|
||||
match &request.action {
|
||||
accesskit::Action::ScrollIntoView => {
|
||||
viewport.this_pass.scroll_target = [
|
||||
Some(ScrollTarget::new(
|
||||
res.rect.x_range(),
|
||||
Some(Align::Center),
|
||||
ScrollAnimation::none(),
|
||||
)),
|
||||
Some(ScrollTarget::new(
|
||||
res.rect.y_range(),
|
||||
Some(Align::Center),
|
||||
ScrollAnimation::none(),
|
||||
)),
|
||||
];
|
||||
}
|
||||
accesskit::Action::ScrollDown => {
|
||||
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::UP;
|
||||
}
|
||||
accesskit::Action::ScrollUp => {
|
||||
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::DOWN;
|
||||
}
|
||||
accesskit::Action::ScrollLeft => {
|
||||
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::LEFT;
|
||||
}
|
||||
accesskit::Action::ScrollRight => {
|
||||
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::RIGHT;
|
||||
}
|
||||
_ => return false,
|
||||
};
|
||||
true
|
||||
});
|
||||
});
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -1536,9 +1599,34 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// The total number of completed frames.
|
||||
///
|
||||
/// Starts at zero, and is incremented once at the end of each call to [`Self::run`].
|
||||
///
|
||||
/// This is always smaller or equal to [`Self::cumulative_pass_nr`].
|
||||
pub fn cumulative_frame_nr(&self) -> u64 {
|
||||
self.cumulative_frame_nr_for(self.viewport_id())
|
||||
}
|
||||
|
||||
/// The total number of completed frames.
|
||||
///
|
||||
/// Starts at zero, and is incremented once at the end of each call to [`Self::run`].
|
||||
///
|
||||
/// This is always smaller or equal to [`Self::cumulative_pass_nr_for`].
|
||||
pub fn cumulative_frame_nr_for(&self, id: ViewportId) -> u64 {
|
||||
self.read(|ctx| {
|
||||
ctx.viewports
|
||||
.get(&id)
|
||||
.map_or(0, |v| v.repaint.cumulative_frame_nr)
|
||||
})
|
||||
}
|
||||
|
||||
/// The total number of completed passes (usually there is one pass per rendered frame).
|
||||
///
|
||||
/// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once).
|
||||
///
|
||||
/// If you instead want to know which pass index this is within the current frame,
|
||||
/// use [`Self::current_pass_index`].
|
||||
pub fn cumulative_pass_nr(&self) -> u64 {
|
||||
self.cumulative_pass_nr_for(self.viewport_id())
|
||||
}
|
||||
@@ -1554,6 +1642,18 @@ impl Context {
|
||||
})
|
||||
}
|
||||
|
||||
/// The index of the current pass in the current frame, starting at zero.
|
||||
///
|
||||
/// Usually this is zero, but if something called [`Self::request_discard`] to do multi-pass layout,
|
||||
/// then this will be incremented for each pass.
|
||||
///
|
||||
/// This just reads the value of [`PlatformOutput::num_completed_passes`].
|
||||
///
|
||||
/// To know the total number of passes ever completed, use [`Self::cumulative_pass_nr`].
|
||||
pub fn current_pass_index(&self) -> usize {
|
||||
self.output(|o| o.num_completed_passes)
|
||||
}
|
||||
|
||||
/// Call this if there is need to repaint the UI, i.e. if you are showing an animation.
|
||||
///
|
||||
/// If this is called at least once in a frame, then there will be another frame right after this.
|
||||
@@ -1726,7 +1826,7 @@ impl Context {
|
||||
/// This means the first pass will look glitchy, and ideally should not be shown to the user.
|
||||
/// So [`crate::Grid`] calls [`Self::request_discard`] to cover up this glitches.
|
||||
///
|
||||
/// There is a limit to how many passes egui will perform, set by [`Options::max_passes`].
|
||||
/// There is a limit to how many passes egui will perform, set by [`Options::max_passes`] (default=2).
|
||||
/// Therefore, the request might be declined.
|
||||
///
|
||||
/// You can check if the current pass will be discarded with [`Self::will_discard`].
|
||||
@@ -2305,7 +2405,9 @@ impl Context {
|
||||
// If you see this message, it means we've been paying the cost of multi-pass for multiple frames in a row.
|
||||
// This is likely a bug. `request_discard` should only be called in rare situations, when some layout changes.
|
||||
|
||||
let mut warning = format!("egui PERF WARNING: request_discard has been called {num_multipass_in_row} frames in a row");
|
||||
let mut warning = format!(
|
||||
"egui PERF WARNING: request_discard has been called {num_multipass_in_row} frames in a row"
|
||||
);
|
||||
self.viewport(|vp| {
|
||||
for reason in &vp.output.request_discard_reasons {
|
||||
warning += &format!("\n {reason}");
|
||||
@@ -2587,7 +2689,7 @@ impl Context {
|
||||
self.write(|ctx| {
|
||||
let mut used = ctx.viewport().this_pass.used_by_panels;
|
||||
for (_id, window) in ctx.memory.areas().visible_windows() {
|
||||
used = used.union(window.rect());
|
||||
used |= window.rect();
|
||||
}
|
||||
used.round_ui()
|
||||
})
|
||||
@@ -2983,36 +3085,54 @@ impl Context {
|
||||
pub fn inspection_ui(&self, ui: &mut Ui) {
|
||||
use crate::containers::CollapsingHeader;
|
||||
|
||||
ui.label(format!("Is using pointer: {}", self.is_using_pointer()))
|
||||
.on_hover_text(
|
||||
"Is egui currently using the pointer actively (e.g. dragging a slider)?",
|
||||
);
|
||||
ui.label(format!("Wants pointer input: {}", self.wants_pointer_input()))
|
||||
.on_hover_text("Is egui currently interested in the location of the pointer (either because it is in use, or because it is hovering over a window).");
|
||||
ui.label(format!(
|
||||
"Wants keyboard input: {}",
|
||||
self.wants_keyboard_input()
|
||||
))
|
||||
.on_hover_text("Is egui currently listening for text input?");
|
||||
ui.label(format!(
|
||||
"Keyboard focus widget: {}",
|
||||
self.memory(|m| m.focused())
|
||||
.as_ref()
|
||||
.map(Id::short_debug_format)
|
||||
.unwrap_or_default()
|
||||
))
|
||||
.on_hover_text("Is egui currently listening for text input?");
|
||||
crate::Grid::new("egui-inspection-grid")
|
||||
.num_columns(2)
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Total ui frames:");
|
||||
ui.monospace(ui.ctx().cumulative_frame_nr().to_string());
|
||||
ui.end_row();
|
||||
|
||||
let pointer_pos = self
|
||||
.pointer_hover_pos()
|
||||
.map_or_else(String::new, |pos| format!("{pos:?}"));
|
||||
ui.label(format!("Pointer pos: {pointer_pos}"));
|
||||
ui.label("Total ui passes:");
|
||||
ui.monospace(ui.ctx().cumulative_pass_nr().to_string());
|
||||
ui.end_row();
|
||||
|
||||
let top_layer = self
|
||||
.pointer_hover_pos()
|
||||
.and_then(|pos| self.layer_id_at(pos))
|
||||
.map_or_else(String::new, |layer| layer.short_debug_format());
|
||||
ui.label(format!("Top layer under mouse: {top_layer}"));
|
||||
ui.label("Is using pointer")
|
||||
.on_hover_text("Is egui currently using the pointer actively (e.g. dragging a slider)?");
|
||||
ui.monospace(self.is_using_pointer().to_string());
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Wants pointer input")
|
||||
.on_hover_text("Is egui currently interested in the location of the pointer (either because it is in use, or because it is hovering over a window).");
|
||||
ui.monospace(self.wants_pointer_input().to_string());
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Wants keyboard input").on_hover_text("Is egui currently listening for text input?");
|
||||
ui.monospace(self.wants_keyboard_input().to_string());
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Keyboard focus widget").on_hover_text("Is egui currently listening for text input?");
|
||||
ui.monospace(self.memory(|m| m.focused())
|
||||
.as_ref()
|
||||
.map(Id::short_debug_format)
|
||||
.unwrap_or_default());
|
||||
ui.end_row();
|
||||
|
||||
let pointer_pos = self
|
||||
.pointer_hover_pos()
|
||||
.map_or_else(String::new, |pos| format!("{pos:?}"));
|
||||
ui.label("Pointer pos");
|
||||
ui.monospace(pointer_pos);
|
||||
ui.end_row();
|
||||
|
||||
let top_layer = self
|
||||
.pointer_hover_pos()
|
||||
.and_then(|pos| self.layer_id_at(pos))
|
||||
.map_or_else(String::new, |layer| layer.short_debug_format());
|
||||
ui.label("Top layer under mouse");
|
||||
ui.monospace(top_layer);
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.add_space(16.0);
|
||||
|
||||
@@ -3446,6 +3566,7 @@ impl Context {
|
||||
/// Release all memory and textures related to the given image URI.
|
||||
///
|
||||
/// If you attempt to load the image again, it will be reloaded from scratch.
|
||||
/// Also this cancels any ongoing loading of the image.
|
||||
pub fn forget_image(&self, uri: &str) {
|
||||
use load::BytesLoader as _;
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
use epaint::ColorImage;
|
||||
|
||||
use crate::{
|
||||
emath::{Pos2, Rect, Vec2},
|
||||
Key, Theme, ViewportId, ViewportIdMap,
|
||||
emath::{Pos2, Rect, Vec2},
|
||||
};
|
||||
|
||||
/// What the integrations provides to egui at the start of each frame.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! to get callbacks on certain events ([`Context::on_begin_pass`], [`Context::on_end_pass`]).
|
||||
|
||||
use crate::{
|
||||
text, Align, Align2, Color32, Context, FontFamily, FontId, Id, Rect, Shape, Vec2, WidgetText,
|
||||
Align, Align2, Color32, Context, FontFamily, FontId, Id, Rect, Shape, Vec2, WidgetText, text,
|
||||
};
|
||||
|
||||
/// Register this plugin on the given egui context,
|
||||
@@ -102,7 +102,7 @@ impl State {
|
||||
let location_rect =
|
||||
Align2::RIGHT_TOP.anchor_size(pos - 4.0 * Vec2::X, location_galley.size());
|
||||
painter.galley(location_rect.min, location_galley, color);
|
||||
bounding_rect = bounding_rect.union(location_rect);
|
||||
bounding_rect |= location_rect;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -117,7 +117,7 @@ impl State {
|
||||
);
|
||||
let rect = Align2::LEFT_TOP.anchor_size(pos, galley.size());
|
||||
painter.galley(rect.min, galley, color);
|
||||
bounding_rect = bounding_rect.union(rect);
|
||||
bounding_rect |= rect;
|
||||
}
|
||||
|
||||
pos.y = bounding_rect.max.y + 4.0;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use emath::GuiRounding as _;
|
||||
|
||||
use crate::{
|
||||
vec2, Align2, Color32, Context, Id, InnerResponse, NumExt as _, Painter, Rect, Region, Style,
|
||||
Ui, UiBuilder, Vec2,
|
||||
Align2, Color32, Context, Id, InnerResponse, NumExt as _, Painter, Rect, Region, Style, Ui,
|
||||
UiBuilder, Vec2, vec2,
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
@@ -2,7 +2,7 @@ use ahash::HashMap;
|
||||
|
||||
use emath::TSTransform;
|
||||
|
||||
use crate::{ahash, emath, id::IdSet, LayerId, Pos2, Rect, Sense, WidgetRect, WidgetRects};
|
||||
use crate::{LayerId, Pos2, Rect, Sense, WidgetRect, WidgetRects, ahash, emath, id::IdSet};
|
||||
|
||||
/// Result of a hit-test against [`WidgetRects`].
|
||||
///
|
||||
@@ -91,6 +91,8 @@ pub fn hit_test(
|
||||
}
|
||||
}
|
||||
|
||||
close.retain(|rect| !rect.interact_rect.any_nan()); // Protect against bad input and transforms
|
||||
|
||||
// When using layer transforms it is common to stack layers close to each other.
|
||||
// For instance, you may have a resize-separator on a panel, with two
|
||||
// transform-layers on either side.
|
||||
@@ -466,7 +468,7 @@ fn should_prioritize_hits_on_back(back: Rect, front: Rect) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use emath::{pos2, vec2, Rect};
|
||||
use emath::{Rect, pos2, vec2};
|
||||
|
||||
use crate::{Id, Sense};
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
mod touch_state;
|
||||
|
||||
use crate::data::input::{
|
||||
Event, EventFilter, KeyboardShortcut, Modifiers, MouseWheelUnit, PointerButton, RawInput,
|
||||
TouchDeviceId, ViewportInfo, NUM_POINTER_BUTTONS,
|
||||
Event, EventFilter, KeyboardShortcut, Modifiers, MouseWheelUnit, NUM_POINTER_BUTTONS,
|
||||
PointerButton, RawInput, TouchDeviceId, ViewportInfo,
|
||||
};
|
||||
use crate::{
|
||||
emath::{vec2, NumExt as _, Pos2, Rect, Vec2},
|
||||
emath::{NumExt as _, Pos2, Rect, Vec2, vec2},
|
||||
util::History,
|
||||
};
|
||||
use std::{
|
||||
@@ -824,6 +824,23 @@ impl InputState {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn consume_accesskit_action_requests(
|
||||
&mut self,
|
||||
id: crate::Id,
|
||||
mut consume: impl FnMut(&accesskit::ActionRequest) -> bool,
|
||||
) {
|
||||
let accesskit_id = id.accesskit_id();
|
||||
self.events.retain(|event| {
|
||||
if let Event::AccessKitActionRequest(request) = event {
|
||||
if request.target == accesskit_id {
|
||||
return !consume(request);
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn has_accesskit_action_request(&self, id: crate::Id, action: accesskit::Action) -> bool {
|
||||
self.accesskit_action_requests(id, action).next().is_some()
|
||||
@@ -1448,7 +1465,10 @@ impl PointerState {
|
||||
}
|
||||
|
||||
if let Some(pos) = self.hover_pos() {
|
||||
return rect.intersects_ray(pos, self.direction());
|
||||
let dir = self.direction();
|
||||
if dir != Vec2::ZERO {
|
||||
return rect.intersects_ray(pos, self.direction());
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::{collections::BTreeMap, fmt::Debug};
|
||||
|
||||
use crate::{
|
||||
data::input::TouchDeviceId,
|
||||
emath::{normalized_angle, Pos2, Vec2},
|
||||
Event, RawInput, TouchId, TouchPhase,
|
||||
data::input::TouchDeviceId,
|
||||
emath::{Pos2, Vec2, normalized_angle},
|
||||
};
|
||||
|
||||
/// All you probably need to know about a multi-touch gesture.
|
||||
@@ -174,7 +174,7 @@ impl TouchState {
|
||||
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 {
|
||||
if let Some(state) = &mut self.gesture_state {
|
||||
state.previous = None;
|
||||
}
|
||||
}
|
||||
@@ -224,7 +224,7 @@ impl TouchState {
|
||||
|
||||
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 {
|
||||
if let Some(state) = &mut self.gesture_state {
|
||||
// updating an ongoing gesture
|
||||
state.previous = Some(state.current);
|
||||
state.current = dyn_state;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! How mouse and touch interzcts with widgets.
|
||||
|
||||
use crate::{hit_test, id, input_state, memory, Id, InputState, Key, WidgetRects};
|
||||
use crate::{Id, InputState, Key, WidgetRects, hit_test, id, input_state, memory};
|
||||
|
||||
use self::{hit_test::WidgetHits, id::IdSet, input_state::PointerEvent, memory::InteractionState};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Showing UI:s for egui/epaint types.
|
||||
|
||||
use crate::{
|
||||
epaint, memory, pos2, remap_clamp, vec2, Color32, CursorIcon, FontFamily, FontId, Label, Mesh,
|
||||
NumExt as _, Rect, Response, Sense, Shape, Slider, TextStyle, TextWrapMode, Ui, Widget,
|
||||
Color32, CursorIcon, FontFamily, FontId, Label, Mesh, NumExt as _, Rect, Response, Sense,
|
||||
Shape, Slider, TextStyle, TextWrapMode, Ui, Widget, epaint, memory, pos2, remap_clamp, vec2,
|
||||
};
|
||||
use emath::Vec2;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Handles paint layers, i.e. how things
|
||||
//! are sometimes painted behind or in front of other things.
|
||||
|
||||
use crate::{ahash, epaint, Id, IdMap, Rect};
|
||||
use epaint::{emath::TSTransform, ClippedShape, Shape};
|
||||
use crate::{Id, IdMap, Rect, ahash, epaint};
|
||||
use epaint::{ClippedShape, Shape, emath::TSTransform};
|
||||
|
||||
/// Different layer categories
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use emath::GuiRounding as _;
|
||||
|
||||
use crate::{
|
||||
emath::{pos2, vec2, Align2, NumExt as _, Pos2, Rect, Vec2},
|
||||
Align,
|
||||
emath::{Align2, NumExt as _, Pos2, Rect, Vec2, pos2, vec2},
|
||||
};
|
||||
const INFINITY: f32 = f32::INFINITY;
|
||||
|
||||
@@ -54,8 +54,8 @@ pub(crate) struct Region {
|
||||
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);
|
||||
self.min_rect |= rect;
|
||||
self.max_rect |= rect;
|
||||
}
|
||||
|
||||
/// Ensure we are big enough to contain the given X-coordinate.
|
||||
@@ -738,7 +738,7 @@ impl Layout {
|
||||
if self.main_wrap {
|
||||
if cursor.intersects(frame_rect.shrink(1.0)) {
|
||||
// make row/column larger if necessary
|
||||
*cursor = cursor.union(frame_rect);
|
||||
*cursor |= 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 {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! 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.84.0 or later to use `egui`.
|
||||
//! You need to have rust 1.85.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).
|
||||
@@ -161,12 +161,10 @@
|
||||
//!
|
||||
//! * 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`).
|
||||
//! * 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`).
|
||||
//! * Otherwise: gamma-encode the colors before you write them again.
|
||||
//! * egui prefers gamma color spaces for all blending so:
|
||||
//! * Do NOT use an sRGBA-aware texture (NOT `GL_SRGB8_ALPHA8`).
|
||||
//! * Multiply texture and vertex colors in gamma space
|
||||
//! * Turn OFF sRGBA/gamma framebuffer (NO `GL_FRAMEBUFFER_SRGB`).
|
||||
//!
|
||||
//!
|
||||
//! # Understanding immediate mode
|
||||
@@ -463,36 +461,35 @@ pub use epaint::emath;
|
||||
pub use ecolor::hex_color;
|
||||
pub use ecolor::{Color32, Rgba};
|
||||
pub use emath::{
|
||||
lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rangef, Rect, RectAlign,
|
||||
Vec2, Vec2b,
|
||||
Align, Align2, NumExt, Pos2, Rangef, Rect, RectAlign, Vec2, Vec2b, lerp, pos2, remap,
|
||||
remap_clamp, vec2,
|
||||
};
|
||||
pub use epaint::{
|
||||
mutex,
|
||||
ClippedPrimitive, ColorImage, CornerRadius, ImageData, Margin, Mesh, PaintCallback,
|
||||
PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId, mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
|
||||
textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta},
|
||||
ClippedPrimitive, ColorImage, CornerRadius, FontImage, ImageData, Margin, Mesh, PaintCallback,
|
||||
PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId,
|
||||
};
|
||||
|
||||
pub mod text {
|
||||
pub use crate::text_selection::CCursorRange;
|
||||
pub use epaint::text::{
|
||||
cursor::CCursor, FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob,
|
||||
LayoutSection, TextFormat, TextWrapping, TAB_SIZE,
|
||||
FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TAB_SIZE,
|
||||
TextFormat, TextWrapping, cursor::CCursor,
|
||||
};
|
||||
}
|
||||
|
||||
pub use self::{
|
||||
atomics::*,
|
||||
containers::*,
|
||||
containers::{menu::MenuBar, *},
|
||||
context::{Context, RepaintCause, RequestRepaintInfo},
|
||||
data::{
|
||||
Key, UserData,
|
||||
input::*,
|
||||
output::{
|
||||
self, CursorIcon, FullOutput, OpenUrl, OutputCommand, PlatformOutput,
|
||||
UserAttentionType, WidgetInfo,
|
||||
},
|
||||
Key, UserData,
|
||||
},
|
||||
drag_and_drop::DragAndDrop,
|
||||
epaint::text::TextWrapMode,
|
||||
|
||||
@@ -65,7 +65,7 @@ use std::{
|
||||
use ahash::HashMap;
|
||||
|
||||
use emath::{Float as _, OrderedFloat};
|
||||
use epaint::{mutex::Mutex, textures::TextureOptions, ColorImage, TextureHandle, TextureId, Vec2};
|
||||
use epaint::{ColorImage, TextureHandle, TextureId, Vec2, mutex::Mutex, textures::TextureOptions};
|
||||
|
||||
use crate::Context;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
generate_loader_id, Bytes, BytesLoadResult, BytesLoader, BytesPoll, Context, Cow, HashMap,
|
||||
LoadError, Mutex,
|
||||
Bytes, BytesLoadResult, BytesLoader, BytesPoll, Context, Cow, HashMap, LoadError, Mutex,
|
||||
generate_loader_id,
|
||||
};
|
||||
|
||||
/// Maps URI:s to [`Bytes`], e.g. found with `include_bytes!`.
|
||||
|
||||
@@ -6,8 +6,8 @@ use ahash::{HashMap, HashSet};
|
||||
use epaint::emath::TSTransform;
|
||||
|
||||
use crate::{
|
||||
area, vec2, EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style, Vec2,
|
||||
ViewportId, ViewportIdMap, ViewportIdSet,
|
||||
EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style, Vec2, ViewportId,
|
||||
ViewportIdMap, ViewportIdSet, area, vec2,
|
||||
};
|
||||
|
||||
mod theme;
|
||||
@@ -377,8 +377,8 @@ impl Options {
|
||||
reduce_texture_memory,
|
||||
} = self;
|
||||
|
||||
use crate::containers::CollapsingHeader;
|
||||
use crate::Widget as _;
|
||||
use crate::containers::CollapsingHeader;
|
||||
|
||||
CollapsingHeader::new("⚙ Options")
|
||||
.default_open(false)
|
||||
@@ -408,11 +408,11 @@ impl Options {
|
||||
.show(ui, |ui| {
|
||||
theme_preference.radio_buttons(ui);
|
||||
|
||||
std::sync::Arc::make_mut(match theme {
|
||||
let style = std::sync::Arc::make_mut(match theme {
|
||||
Theme::Dark => dark_style,
|
||||
Theme::Light => light_style,
|
||||
})
|
||||
.ui(ui);
|
||||
});
|
||||
style.ui(ui);
|
||||
});
|
||||
|
||||
CollapsingHeader::new("✒ Painting")
|
||||
@@ -1012,11 +1012,11 @@ impl OpenPopup {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Popups
|
||||
/// Popups are things like combo-boxes, color pickers, menus etc.
|
||||
/// Only one can be open at a time.
|
||||
/// ## Deprecated popup API
|
||||
/// Use [`crate::Popup`] instead.
|
||||
impl Memory {
|
||||
/// Is the given popup open?
|
||||
#[deprecated = "Use Popup::is_id_open instead"]
|
||||
pub fn is_popup_open(&self, popup_id: Id) -> bool {
|
||||
self.popups
|
||||
.get(&self.viewport_id)
|
||||
@@ -1025,6 +1025,7 @@ impl Memory {
|
||||
}
|
||||
|
||||
/// Is any popup open?
|
||||
#[deprecated = "Use Popup::is_any_open instead"]
|
||||
pub fn any_popup_open(&self) -> bool {
|
||||
self.popups.contains_key(&self.viewport_id) || self.everything_is_visible()
|
||||
}
|
||||
@@ -1032,6 +1033,7 @@ impl Memory {
|
||||
/// Open the given popup and close all others.
|
||||
///
|
||||
/// Note that you must call `keep_popup_open` on subsequent frames as long as the popup is open.
|
||||
#[deprecated = "Use Popup::open_id instead"]
|
||||
pub fn open_popup(&mut self, popup_id: Id) {
|
||||
self.popups
|
||||
.insert(self.viewport_id, OpenPopup::new(popup_id, None));
|
||||
@@ -1042,6 +1044,7 @@ impl Memory {
|
||||
/// This is needed because in some cases popups can go away without `close_popup` being
|
||||
/// called. For example, when a context menu is open and the underlying widget stops
|
||||
/// being rendered.
|
||||
#[deprecated = "Use Popup::show instead"]
|
||||
pub fn keep_popup_open(&mut self, popup_id: Id) {
|
||||
if let Some(state) = self.popups.get_mut(&self.viewport_id) {
|
||||
if state.id == popup_id {
|
||||
@@ -1051,12 +1054,14 @@ impl Memory {
|
||||
}
|
||||
|
||||
/// Open the popup and remember its position.
|
||||
#[deprecated = "Use Popup with PopupAnchor::Position instead"]
|
||||
pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into<Option<Pos2>>) {
|
||||
self.popups
|
||||
.insert(self.viewport_id, OpenPopup::new(popup_id, pos.into()));
|
||||
}
|
||||
|
||||
/// Get the position for this popup.
|
||||
#[deprecated = "Use Popup::position_of_id instead"]
|
||||
pub fn popup_position(&self, id: Id) -> Option<Pos2> {
|
||||
self.popups
|
||||
.get(&self.viewport_id)
|
||||
@@ -1064,6 +1069,7 @@ impl Memory {
|
||||
}
|
||||
|
||||
/// Close any currently open popup.
|
||||
#[deprecated = "Use Popup::close_all instead"]
|
||||
pub fn close_all_popups(&mut self) {
|
||||
self.popups.clear();
|
||||
}
|
||||
@@ -1071,7 +1077,9 @@ impl Memory {
|
||||
/// Close the given popup, if it is open.
|
||||
///
|
||||
/// See also [`Self::close_all_popups`] if you want to close any / all currently open popups.
|
||||
#[deprecated = "Use Popup::close_id instead"]
|
||||
pub fn close_popup(&mut self, popup_id: Id) {
|
||||
#[expect(deprecated)]
|
||||
if self.is_popup_open(popup_id) {
|
||||
self.popups.remove(&self.viewport_id);
|
||||
}
|
||||
@@ -1080,14 +1088,18 @@ impl Memory {
|
||||
/// Toggle the given popup between closed and open.
|
||||
///
|
||||
/// Note: At most, only one popup can be open at a time.
|
||||
#[deprecated = "Use Popup::toggle_id instead"]
|
||||
pub fn toggle_popup(&mut self, popup_id: Id) {
|
||||
#[expect(deprecated)]
|
||||
if self.is_popup_open(popup_id) {
|
||||
self.close_popup(popup_id);
|
||||
} else {
|
||||
self.open_popup(popup_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// If true, all windows, menus, tooltips, etc., will be visible at once.
|
||||
///
|
||||
/// This is useful for testing, benchmarking, pre-caching, etc.
|
||||
@@ -1250,8 +1262,11 @@ impl Areas {
|
||||
///
|
||||
/// The two layers must have the same [`LayerId::order`].
|
||||
pub fn set_sublayer(&mut self, parent: LayerId, child: LayerId) {
|
||||
debug_assert_eq!(parent.order, child.order,
|
||||
"DEBUG ASSERT: Trying to set sublayers across layers of different order ({:?}, {:?}), which is currently undefined behavior in egui", parent.order, child.order);
|
||||
debug_assert_eq!(
|
||||
parent.order, child.order,
|
||||
"DEBUG ASSERT: Trying to set sublayers across layers of different order ({:?}, {:?}), which is currently undefined behavior in egui",
|
||||
parent.order, child.order
|
||||
);
|
||||
|
||||
self.sublayers.entry(parent).or_default().insert(child);
|
||||
|
||||
|
||||
@@ -30,11 +30,7 @@ impl Theme {
|
||||
|
||||
/// Chooses between [`Self::Dark`] or [`Self::Light`] based on a boolean value.
|
||||
pub fn from_dark_mode(dark_mode: bool) -> Self {
|
||||
if dark_mode {
|
||||
Self::Dark
|
||||
} else {
|
||||
Self::Light
|
||||
}
|
||||
if dark_mode { Self::Dark } else { Self::Light }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,9 +89,32 @@ impl ThemePreference {
|
||||
/// Show radio-buttons to switch between light mode, dark mode and following the system theme.
|
||||
pub fn radio_buttons(&mut self, ui: &mut crate::Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(self, Self::Light, "☀ Light");
|
||||
ui.selectable_value(self, Self::Dark, "🌙 Dark");
|
||||
ui.selectable_value(self, Self::System, "💻 System");
|
||||
let system_theme = ui.ctx().input(|i| i.raw.system_theme);
|
||||
|
||||
ui.selectable_value(self, Self::System, "💻 System")
|
||||
.on_hover_ui(|ui| {
|
||||
ui.label("Follow the system theme preference.");
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
if let Some(system_theme) = system_theme {
|
||||
ui.label(format!(
|
||||
"The current system theme is: {}",
|
||||
match system_theme {
|
||||
Theme::Dark => "dark",
|
||||
Theme::Light => "light",
|
||||
}
|
||||
));
|
||||
} else {
|
||||
ui.label("The system theme is unknown.");
|
||||
}
|
||||
});
|
||||
|
||||
ui.selectable_value(self, Self::Dark, "🌙 Dark")
|
||||
.on_hover_text("Use the dark mode theme");
|
||||
|
||||
ui.selectable_value(self, Self::Light, "☀ Light")
|
||||
.on_hover_text("Use the light mode theme");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,13 @@
|
||||
//! ```
|
||||
|
||||
use super::{
|
||||
style::WidgetVisuals, Align, Context, Id, InnerResponse, PointerState, Pos2, Rect, Response,
|
||||
Sense, TextStyle, Ui, Vec2,
|
||||
Align, Context, Id, InnerResponse, PointerState, Pos2, Rect, Response, Sense, TextStyle, Ui,
|
||||
Vec2, style::WidgetVisuals,
|
||||
};
|
||||
use crate::{
|
||||
epaint, vec2,
|
||||
widgets::{Button, ImageButton},
|
||||
Align2, Area, Color32, Frame, Key, LayerId, Layout, NumExt as _, Order, Stroke, Style,
|
||||
TextWrapMode, UiKind, WidgetText,
|
||||
TextWrapMode, UiKind, WidgetText, epaint, vec2,
|
||||
widgets::{Button, ImageButton},
|
||||
};
|
||||
use epaint::mutex::RwLock;
|
||||
use std::sync::Arc;
|
||||
@@ -88,7 +87,7 @@ fn set_menu_style(style: &mut Style) {
|
||||
/// The menu bar goes well in a [`crate::TopBottomPanel::top`],
|
||||
/// but can also be placed in a [`crate::Window`].
|
||||
/// In the latter case you may want to wrap it in [`Frame`].
|
||||
#[deprecated = "Use `crate::containers::menu::Bar` instead"]
|
||||
#[deprecated = "Use `egui::MenuBar::new().ui(` instead"]
|
||||
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());
|
||||
|
||||
@@ -71,7 +71,8 @@ impl OperatingSystem {
|
||||
#[cfg(feature = "log")]
|
||||
log::warn!(
|
||||
"egui: Failed to guess operating system from User-Agent {:?}. Please file an issue at https://github.com/emilk/egui/issues",
|
||||
user_agent);
|
||||
user_agent
|
||||
);
|
||||
|
||||
Self::Unknown
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ use std::sync::Arc;
|
||||
|
||||
use emath::GuiRounding as _;
|
||||
use epaint::{
|
||||
text::{Fonts, Galley, LayoutJob},
|
||||
CircleShape, ClippedShape, CornerRadius, PathStroke, RectShape, Shape, Stroke, StrokeKind,
|
||||
text::{Fonts, Galley, LayoutJob},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Color32, Context, FontId,
|
||||
emath::{Align2, Pos2, Rangef, Rect, Vec2},
|
||||
layers::{LayerId, PaintList, ShapeIdx},
|
||||
Color32, Context, FontId,
|
||||
};
|
||||
|
||||
/// Helper to paint shapes and text to a specific region on a specific layer.
|
||||
@@ -83,6 +83,7 @@ impl Painter {
|
||||
}
|
||||
|
||||
/// If set, colors will be modified to look like this
|
||||
#[deprecated = "Use `multiply_opacity` instead"]
|
||||
pub fn set_fade_to_color(&mut self, fade_to_color: Option<Color32>) {
|
||||
self.fade_to_color = fade_to_color;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use ahash::HashMap;
|
||||
|
||||
use crate::{id::IdSet, style, Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects};
|
||||
use crate::{Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects, id::IdSet, style};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::{pos2, Align2, Color32, FontId, NumExt as _, Painter};
|
||||
use crate::{Align2, Color32, FontId, NumExt as _, Painter, pos2};
|
||||
|
||||
/// Reset at the start of each frame.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -318,7 +318,7 @@ impl PassState {
|
||||
);
|
||||
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);
|
||||
self.used_by_panels |= panel_rect;
|
||||
}
|
||||
|
||||
/// Shrink `available_rect`.
|
||||
@@ -329,7 +329,7 @@ impl PassState {
|
||||
);
|
||||
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);
|
||||
self.used_by_panels |= panel_rect;
|
||||
}
|
||||
|
||||
/// Shrink `available_rect`.
|
||||
@@ -340,7 +340,7 @@ impl PassState {
|
||||
);
|
||||
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);
|
||||
self.used_by_panels |= panel_rect;
|
||||
}
|
||||
|
||||
/// Shrink `available_rect`.
|
||||
@@ -351,13 +351,13 @@ impl PassState {
|
||||
);
|
||||
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);
|
||||
self.used_by_panels |= 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);
|
||||
self.used_by_panels |= panel_rect;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ impl Placer {
|
||||
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.max_rect |= 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;
|
||||
@@ -275,7 +275,7 @@ impl Placer {
|
||||
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.max_rect |= 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;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
Context, CursorIcon, Id, LayerId, PointerButton, Popup, PopupKind, Sense, Tooltip, Ui,
|
||||
WidgetRect, WidgetText,
|
||||
emath::{Align, Pos2, Rect, Vec2},
|
||||
pass_state, Context, CursorIcon, Id, LayerId, PointerButton, Popup, PopupKind, Sense, Tooltip,
|
||||
Ui, WidgetRect, WidgetText,
|
||||
pass_state,
|
||||
};
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -58,8 +59,8 @@ pub struct Response {
|
||||
|
||||
/// The intrinsic / desired size of the widget.
|
||||
///
|
||||
/// For a button, this will be the size of the label + the frames padding,
|
||||
/// even if the button is laid out in a justified layout and the actual size will be larger.
|
||||
/// This is the size that a non-wrapped, non-truncated, non-justified version of the widget
|
||||
/// would have.
|
||||
///
|
||||
/// If this is `None`, use [`Self::rect`] instead.
|
||||
///
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
#![allow(clippy::if_same_then_else)]
|
||||
|
||||
use emath::Align;
|
||||
use epaint::{text::FontTweak, CornerRadius, Shadow, Stroke};
|
||||
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, text::FontTweak};
|
||||
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
ecolor::Color32,
|
||||
emath::{pos2, vec2, Rangef, Rect, Vec2},
|
||||
ComboBox, CursorIcon, FontFamily, FontId, Grid, Margin, Response, RichText, TextWrapMode,
|
||||
WidgetText,
|
||||
ecolor::Color32,
|
||||
emath::{Rangef, Rect, Vec2, pos2, vec2},
|
||||
reset_button_with,
|
||||
};
|
||||
|
||||
/// How to format numbers in e.g. a [`crate::DragValue`].
|
||||
@@ -920,6 +921,9 @@ pub struct Visuals {
|
||||
/// this is more to provide a convenient summary of the rest of the settings.
|
||||
pub dark_mode: bool,
|
||||
|
||||
/// ADVANCED: Controls how we render text.
|
||||
pub text_alpha_from_coverage: AlphaFromCoverage,
|
||||
|
||||
/// Override default text color for all text.
|
||||
///
|
||||
/// This is great for setting the color of text for any widget.
|
||||
@@ -935,6 +939,17 @@ pub struct Visuals {
|
||||
/// it is disabled, non-interactive, hovered etc.
|
||||
pub override_text_color: Option<Color32>,
|
||||
|
||||
/// How strong "weak" text is.
|
||||
///
|
||||
/// Ignored if [`Self::weak_text_color`] is set.
|
||||
pub weak_text_alpha: f32,
|
||||
|
||||
/// Color of "weak" text.
|
||||
///
|
||||
/// If `None`, the color is [`Self::text_color`]
|
||||
/// multiplied by [`Self::weak_text_alpha`].
|
||||
pub weak_text_color: Option<Color32>,
|
||||
|
||||
/// Visual styles of widgets
|
||||
pub widgets: Widgets,
|
||||
|
||||
@@ -952,6 +967,11 @@ pub struct Visuals {
|
||||
/// that needs to look different from other interactive stuff.
|
||||
pub extreme_bg_color: Color32,
|
||||
|
||||
/// The background color of [`crate::TextEdit`].
|
||||
///
|
||||
/// Defaults to [`Self::extreme_bg_color`].
|
||||
pub text_edit_bg_color: Option<Color32>,
|
||||
|
||||
/// Background color behind code-styled monospaced labels.
|
||||
pub code_bg_color: Color32,
|
||||
|
||||
@@ -1019,6 +1039,9 @@ pub struct Visuals {
|
||||
|
||||
/// How to display numeric color values.
|
||||
pub numeric_color_space: NumericColorSpace,
|
||||
|
||||
/// How much to modify the alpha of a disabled widget.
|
||||
pub disabled_alpha: f32,
|
||||
}
|
||||
|
||||
impl Visuals {
|
||||
@@ -1034,7 +1057,8 @@ impl Visuals {
|
||||
}
|
||||
|
||||
pub fn weak_text_color(&self) -> Color32 {
|
||||
self.gray_out(self.text_color())
|
||||
self.weak_text_color
|
||||
.unwrap_or_else(|| self.text_color().gamma_multiply(self.weak_text_alpha))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -1042,6 +1066,11 @@ impl Visuals {
|
||||
self.widgets.active.text_color()
|
||||
}
|
||||
|
||||
/// The background color of [`crate::TextEdit`].
|
||||
pub fn text_edit_bg_color(&self) -> Color32 {
|
||||
self.text_edit_bg_color.unwrap_or(self.extreme_bg_color)
|
||||
}
|
||||
|
||||
/// Window background color.
|
||||
#[inline(always)]
|
||||
pub fn window_fill(&self) -> Color32 {
|
||||
@@ -1054,17 +1083,32 @@ impl Visuals {
|
||||
}
|
||||
|
||||
/// When fading out things, we fade the colors towards this.
|
||||
// TODO(emilk): replace with an alpha
|
||||
#[inline(always)]
|
||||
#[deprecated = "Use disabled_alpha(). Fading is now handled by modifying the alpha channel."]
|
||||
pub fn fade_out_to_color(&self) -> Color32 {
|
||||
self.widgets.noninteractive.weak_bg_fill
|
||||
}
|
||||
|
||||
/// Returned a "grayed out" version of the given color.
|
||||
/// Disabled widgets have their alpha modified by this.
|
||||
#[inline(always)]
|
||||
pub fn disabled_alpha(&self) -> f32 {
|
||||
self.disabled_alpha
|
||||
}
|
||||
|
||||
/// Returns a "disabled" version of the given color.
|
||||
///
|
||||
/// This function modifies the opcacity of the given color.
|
||||
/// If this is undesirable use [`gray_out`](Self::gray_out).
|
||||
#[inline(always)]
|
||||
pub fn disable(&self, color: Color32) -> Color32 {
|
||||
color.gamma_multiply(self.disabled_alpha())
|
||||
}
|
||||
|
||||
/// Returns a "grayed out" version of the given color.
|
||||
#[doc(alias = "grey_out")]
|
||||
#[inline(always)]
|
||||
pub fn gray_out(&self, color: Color32) -> Color32 {
|
||||
crate::ecolor::tint_color_towards(color, self.fade_out_to_color())
|
||||
crate::ecolor::tint_color_towards(color, self.widgets.noninteractive.weak_bg_fill)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1333,12 +1377,16 @@ impl Visuals {
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
dark_mode: true,
|
||||
text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
|
||||
override_text_color: None,
|
||||
weak_text_alpha: 0.6,
|
||||
weak_text_color: None,
|
||||
widgets: Widgets::default(),
|
||||
selection: Selection::default(),
|
||||
hyperlink_color: Color32::from_rgb(90, 170, 255),
|
||||
faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so
|
||||
extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background
|
||||
text_edit_bg_color: None, // use `extreme_bg_color` by default
|
||||
code_bg_color: Color32::from_gray(64),
|
||||
warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
|
||||
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
||||
@@ -1384,6 +1432,7 @@ impl Visuals {
|
||||
image_loading_spinners: true,
|
||||
|
||||
numeric_color_space: NumericColorSpace::GammaByte,
|
||||
disabled_alpha: 0.5,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1391,6 +1440,7 @@ impl Visuals {
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
dark_mode: false,
|
||||
text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
|
||||
widgets: Widgets::light(),
|
||||
selection: Selection::light(),
|
||||
hyperlink_color: Color32::from_rgb(0, 155, 255),
|
||||
@@ -1557,8 +1607,8 @@ impl Default for Widgets {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
use crate::{
|
||||
widgets::{reset_button, DragValue, Slider, Widget},
|
||||
Ui,
|
||||
widgets::{DragValue, Slider, Widget, reset_button},
|
||||
};
|
||||
|
||||
impl Style {
|
||||
@@ -1680,11 +1730,11 @@ impl Style {
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.collapsing("🔠 Text Styles", |ui| text_styles_ui(ui, text_styles));
|
||||
ui.collapsing("🔠 Text styles", |ui| text_styles_ui(ui, text_styles));
|
||||
ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
|
||||
ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
|
||||
ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
|
||||
ui.collapsing("🔄 Scroll Animation", |ui| scroll_animation.ui(ui));
|
||||
ui.collapsing("🔄 Scroll animation", |ui| scroll_animation.ui(ui));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
|
||||
@@ -2022,13 +2072,17 @@ impl WidgetVisuals {
|
||||
impl Visuals {
|
||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
dark_mode: _,
|
||||
dark_mode,
|
||||
text_alpha_from_coverage,
|
||||
override_text_color: _,
|
||||
weak_text_alpha,
|
||||
weak_text_color,
|
||||
widgets,
|
||||
selection,
|
||||
hyperlink_color,
|
||||
faint_bg_color,
|
||||
extreme_bg_color,
|
||||
text_edit_bg_color,
|
||||
code_bg_color,
|
||||
warn_fg_color,
|
||||
error_fg_color,
|
||||
@@ -2063,44 +2117,115 @@ impl Visuals {
|
||||
image_loading_spinners,
|
||||
|
||||
numeric_color_space,
|
||||
disabled_alpha,
|
||||
} = self;
|
||||
|
||||
ui.collapsing("Background Colors", |ui| {
|
||||
ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons");
|
||||
ui_color(ui, window_fill, "Windows");
|
||||
ui_color(ui, panel_fill, "Panels");
|
||||
ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
|
||||
"Used for faint accentuation of interactive things, like striped grids.",
|
||||
);
|
||||
ui_color(ui, extreme_bg_color, "Extreme")
|
||||
.on_hover_text("Background of plots and paintings");
|
||||
fn ui_optional_color(
|
||||
ui: &mut Ui,
|
||||
color: &mut Option<Color32>,
|
||||
default_value: Color32,
|
||||
label: impl Into<WidgetText>,
|
||||
) -> Response {
|
||||
let label_response = ui.label(label);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let mut set = color.is_some();
|
||||
ui.checkbox(&mut set, "");
|
||||
if set {
|
||||
let color = color.get_or_insert(default_value);
|
||||
ui.color_edit_button_srgba(color);
|
||||
} else {
|
||||
*color = None;
|
||||
};
|
||||
});
|
||||
|
||||
ui.end_row();
|
||||
|
||||
label_response
|
||||
}
|
||||
|
||||
ui.collapsing("Background colors", |ui| {
|
||||
Grid::new("background_colors")
|
||||
.num_columns(2)
|
||||
.show(ui, |ui| {
|
||||
fn ui_color(
|
||||
ui: &mut Ui,
|
||||
color: &mut Color32,
|
||||
label: impl Into<WidgetText>,
|
||||
) -> Response {
|
||||
let label_response = ui.label(label);
|
||||
ui.color_edit_button_srgba(color);
|
||||
ui.end_row();
|
||||
label_response
|
||||
}
|
||||
|
||||
ui_color(ui, &mut widgets.inactive.weak_bg_fill, "Buttons");
|
||||
ui_color(ui, window_fill, "Windows");
|
||||
ui_color(ui, panel_fill, "Panels");
|
||||
ui_color(ui, faint_bg_color, "Faint accent").on_hover_text(
|
||||
"Used for faint accentuation of interactive things, like striped grids.",
|
||||
);
|
||||
ui_color(ui, extreme_bg_color, "Extreme")
|
||||
.on_hover_text("Background of plots and paintings");
|
||||
|
||||
ui_optional_color(ui, text_edit_bg_color, *extreme_bg_color, "TextEdit")
|
||||
.on_hover_text("Background of TextEdit");
|
||||
});
|
||||
});
|
||||
|
||||
ui.collapsing("Text color", |ui| {
|
||||
ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label");
|
||||
ui_text_color(
|
||||
ui,
|
||||
&mut widgets.inactive.fg_stroke.color,
|
||||
"Unhovered button",
|
||||
);
|
||||
ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button");
|
||||
ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button");
|
||||
fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) {
|
||||
ui.label(label.into().color(*color));
|
||||
ui.color_edit_button_srgba(color);
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
ui_text_color(ui, warn_fg_color, RichText::new("Warnings"));
|
||||
ui_text_color(ui, error_fg_color, RichText::new("Errors"));
|
||||
Grid::new("text_color").num_columns(2).show(ui, |ui| {
|
||||
ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label");
|
||||
|
||||
ui_text_color(ui, hyperlink_color, "hyperlink_color");
|
||||
ui_text_color(
|
||||
ui,
|
||||
&mut widgets.inactive.fg_stroke.color,
|
||||
"Unhovered button",
|
||||
);
|
||||
ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button");
|
||||
ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button");
|
||||
|
||||
ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui(
|
||||
|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("For monospaced inlined text ");
|
||||
ui.code("like this");
|
||||
ui.label(".");
|
||||
ui_text_color(ui, warn_fg_color, RichText::new("Warnings"));
|
||||
ui_text_color(ui, error_fg_color, RichText::new("Errors"));
|
||||
|
||||
ui_text_color(ui, hyperlink_color, "hyperlink_color");
|
||||
|
||||
ui.label(RichText::new("Code background").code())
|
||||
.on_hover_ui(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("For monospaced inlined text ");
|
||||
ui.code("like this");
|
||||
ui.label(".");
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.color_edit_button_srgba(code_bg_color);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Weak text alpha");
|
||||
ui.add_enabled(
|
||||
weak_text_color.is_none(),
|
||||
DragValue::new(weak_text_alpha).speed(0.01).range(0.0..=1.0),
|
||||
);
|
||||
ui.end_row();
|
||||
|
||||
ui_optional_color(
|
||||
ui,
|
||||
weak_text_color,
|
||||
widgets.noninteractive.text_color(),
|
||||
"Weak text color",
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
text_alpha_from_coverage_ui(ui, text_alpha_from_coverage);
|
||||
});
|
||||
|
||||
ui.collapsing("Text cursor", |ui| {
|
||||
@@ -2191,12 +2316,60 @@ impl Visuals {
|
||||
ui.label("Color picker type");
|
||||
numeric_color_space.toggle_button_ui(ui);
|
||||
});
|
||||
|
||||
ui.add(Slider::new(disabled_alpha, 0.0..=1.0).text("Disabled element alpha"));
|
||||
});
|
||||
|
||||
ui.vertical_centered(|ui| reset_button(ui, self, "Reset visuals"));
|
||||
let dark_mode = *dark_mode;
|
||||
ui.vertical_centered(|ui| {
|
||||
reset_button_with(
|
||||
ui,
|
||||
self,
|
||||
"Reset visuals",
|
||||
if dark_mode {
|
||||
Self::dark()
|
||||
} else {
|
||||
Self::light()
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) {
|
||||
let mut dark_mode_special =
|
||||
*text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Text rendering:");
|
||||
|
||||
ui.checkbox(&mut dark_mode_special, "Dark-mode special");
|
||||
|
||||
if dark_mode_special {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
} else {
|
||||
let mut gamma = match text_alpha_from_coverage {
|
||||
AlphaFromCoverage::Linear => 1.0,
|
||||
AlphaFromCoverage::Gamma(gamma) => *gamma,
|
||||
AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same
|
||||
};
|
||||
|
||||
ui.add(
|
||||
DragValue::new(&mut gamma)
|
||||
.speed(0.01)
|
||||
.range(0.1..=4.0)
|
||||
.prefix("Gamma: "),
|
||||
);
|
||||
|
||||
if gamma == 1.0 {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::Linear;
|
||||
} else {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl TextCursorStyle {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
let Self {
|
||||
@@ -2310,22 +2483,6 @@ fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive<f32>) -> im
|
||||
}
|
||||
}
|
||||
|
||||
fn ui_color(ui: &mut Ui, color: &mut Color32, label: impl Into<WidgetText>) -> Response {
|
||||
ui.horizontal(|ui| {
|
||||
ui.color_edit_button_srgba(color);
|
||||
ui.label(label);
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
||||
fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) -> Response {
|
||||
ui.horizontal(|ui| {
|
||||
ui.color_edit_button_srgba(color);
|
||||
ui.label(label.into().color(*color));
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
||||
impl HandleShape {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
|
||||
@@ -2,7 +2,7 @@ use emath::TSTransform;
|
||||
|
||||
use crate::{Context, Galley, Id};
|
||||
|
||||
use super::{text_cursor_state::is_word_char, CCursorRange};
|
||||
use super::{CCursorRange, text_cursor_state::is_word_char};
|
||||
|
||||
/// Update accesskit with the current text state.
|
||||
pub fn update_accesskit_for_text_widget(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use epaint::{text::cursor::CCursor, Galley};
|
||||
use epaint::{Galley, text::cursor::CCursor};
|
||||
|
||||
use crate::{os::OperatingSystem, Event, Id, Key, Modifiers};
|
||||
use crate::{Event, Id, Key, Modifiers, os::OperatingSystem};
|
||||
|
||||
use super::text_cursor_state::{ccursor_next_word, ccursor_previous_word, slice_char_range};
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ use std::sync::Arc;
|
||||
use emath::TSTransform;
|
||||
|
||||
use crate::{
|
||||
layers::ShapeIdx, text::CCursor, text_selection::CCursorRange, Context, CursorIcon, Event,
|
||||
Galley, Id, LayerId, Pos2, Rect, Response, Ui,
|
||||
Context, CursorIcon, Event, Galley, Id, LayerId, Pos2, Rect, Response, Ui, layers::ShapeIdx,
|
||||
text::CCursor, text_selection::CCursorRange,
|
||||
};
|
||||
|
||||
use super::{
|
||||
text_cursor_state::cursor_rect,
|
||||
visuals::{paint_text_selection, RowVertexIndices},
|
||||
TextCursorState,
|
||||
text_cursor_state::cursor_rect,
|
||||
visuals::{RowVertexIndices, paint_text_selection},
|
||||
};
|
||||
|
||||
/// Turn on to help debug this
|
||||
@@ -546,7 +546,7 @@ impl LabelSelectionState {
|
||||
|
||||
if let Some(mut cursor_range) = cursor_state.range(galley) {
|
||||
let galley_rect = global_from_galley * Rect::from_min_size(Pos2::ZERO, galley.size());
|
||||
self.selection_bbox_this_frame = self.selection_bbox_this_frame.union(galley_rect);
|
||||
self.selection_bbox_this_frame |= galley_rect;
|
||||
|
||||
if let Some(selection) = &self.selection {
|
||||
if selection.primary.widget_id == response.id {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Text cursor changes/interaction, without modifying the text.
|
||||
|
||||
use epaint::text::{cursor::CCursor, Galley};
|
||||
use epaint::text::{Galley, cursor::CCursor};
|
||||
use unicode_segmentation::UnicodeSegmentation as _;
|
||||
|
||||
use crate::{epaint, NumExt as _, Rect, Response, Ui};
|
||||
use crate::{NumExt as _, Rect, Response, Ui, epaint};
|
||||
|
||||
use super::CCursorRange;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{pos2, vec2, Galley, Painter, Rect, Ui, Visuals};
|
||||
use crate::{Galley, Painter, Rect, Ui, Visuals, pos2, vec2};
|
||||
|
||||
use super::CCursorRange;
|
||||
|
||||
|
||||
@@ -5,11 +5,15 @@ use emath::GuiRounding as _;
|
||||
use epaint::mutex::RwLock;
|
||||
use std::{any::Any, hash::Hash, sync::Arc};
|
||||
|
||||
use crate::close_tag::ClosableTag;
|
||||
use crate::containers::menu;
|
||||
use crate::ClosableTag;
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::Stroke;
|
||||
use crate::containers::menu;
|
||||
use crate::{
|
||||
Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, IntoAtoms,
|
||||
LayerId, Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText,
|
||||
Sense, Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2,
|
||||
WidgetRect, WidgetText,
|
||||
containers::{CollapsingHeader, CollapsingResponse, Frame},
|
||||
ecolor::Hsva,
|
||||
emath, epaint,
|
||||
@@ -22,13 +26,9 @@ use crate::{
|
||||
util::IdTypeMap,
|
||||
vec2, widgets,
|
||||
widgets::{
|
||||
color_picker, Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link,
|
||||
RadioButton, SelectableLabel, Separator, Spinner, TextEdit, Widget,
|
||||
Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link, RadioButton,
|
||||
Separator, Spinner, TextEdit, Widget, color_picker,
|
||||
},
|
||||
Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, IntoAtoms,
|
||||
LayerId, Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText,
|
||||
Sense, Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2,
|
||||
WidgetRect, WidgetText,
|
||||
};
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -522,7 +522,7 @@ impl Ui {
|
||||
self.enabled = false;
|
||||
if self.is_visible() {
|
||||
self.painter
|
||||
.set_fade_to_color(Some(self.visuals().fade_out_to_color()));
|
||||
.multiply_opacity(self.visuals().disabled_alpha());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2101,13 +2101,13 @@ impl Ui {
|
||||
Checkbox::new(checked, atoms).ui(self)
|
||||
}
|
||||
|
||||
/// Acts like a checkbox, but looks like a [`SelectableLabel`].
|
||||
/// Acts like a checkbox, but looks like a [`Button::selectable`].
|
||||
///
|
||||
/// Click to toggle to bool.
|
||||
///
|
||||
/// See also [`Self::checkbox`].
|
||||
pub fn toggle_value(&mut self, selected: &mut bool, text: impl Into<WidgetText>) -> Response {
|
||||
let mut response = self.selectable_label(*selected, text);
|
||||
pub fn toggle_value<'a>(&mut self, selected: &mut bool, atoms: impl IntoAtoms<'a>) -> Response {
|
||||
let mut response = self.selectable_label(*selected, atoms);
|
||||
if response.clicked() {
|
||||
*selected = !*selected;
|
||||
response.mark_changed();
|
||||
@@ -2158,10 +2158,10 @@ impl Ui {
|
||||
|
||||
/// Show a label which can be selected or not.
|
||||
///
|
||||
/// See also [`SelectableLabel`] and [`Self::toggle_value`].
|
||||
/// See also [`Button::selectable`] and [`Self::toggle_value`].
|
||||
#[must_use = "You should check if the user clicked this with `if ui.selectable_label(…).clicked() { … } "]
|
||||
pub fn selectable_label(&mut self, checked: bool, text: impl Into<WidgetText>) -> Response {
|
||||
SelectableLabel::new(checked, text).ui(self)
|
||||
pub fn selectable_label<'a>(&mut self, checked: bool, text: impl IntoAtoms<'a>) -> Response {
|
||||
Button::selectable(checked, text).ui(self)
|
||||
}
|
||||
|
||||
/// Show selectable text. It is selected if `*current_value == selected_value`.
|
||||
@@ -2169,12 +2169,12 @@ impl Ui {
|
||||
///
|
||||
/// Example: `ui.selectable_value(&mut my_enum, Enum::Alternative, "Alternative")`.
|
||||
///
|
||||
/// See also [`SelectableLabel`] and [`Self::toggle_value`].
|
||||
pub fn selectable_value<Value: PartialEq>(
|
||||
/// See also [`Button::selectable`] and [`Self::toggle_value`].
|
||||
pub fn selectable_value<'a, Value: PartialEq>(
|
||||
&mut self,
|
||||
current_value: &mut Value,
|
||||
selected_value: Value,
|
||||
text: impl Into<WidgetText>,
|
||||
text: impl IntoAtoms<'a>,
|
||||
) -> Response {
|
||||
let mut response = self.selectable_label(*current_value == selected_value, text);
|
||||
if response.clicked() && *current_value != selected_value {
|
||||
@@ -2998,8 +2998,8 @@ impl Ui {
|
||||
|
||||
if is_anything_being_dragged && !can_accept_what_is_being_dragged {
|
||||
// When dragging something else, show that it can't be dropped here:
|
||||
fill = self.visuals().gray_out(fill);
|
||||
stroke.color = self.visuals().gray_out(stroke.color);
|
||||
fill = self.visuals().disable(fill);
|
||||
stroke.color = self.visuals().disable(stroke.color);
|
||||
}
|
||||
|
||||
frame.frame.fill = fill;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{hash::Hash, sync::Arc};
|
||||
|
||||
use crate::close_tag::ClosableTag;
|
||||
use crate::ClosableTag;
|
||||
#[expect(unused_imports)] // Used for doclinks
|
||||
use crate::Ui;
|
||||
use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo};
|
||||
|
||||
@@ -438,7 +438,7 @@ impl ViewportBuilder {
|
||||
}
|
||||
|
||||
/// macOS: Set to `true` to allow the window to be moved by dragging the background.
|
||||
/// Enabling this feature can result in unexpected behaviour with draggable UI widgets such as sliders.
|
||||
/// Enabling this feature can result in unexpected behavior with draggable UI widgets such as sliders.
|
||||
#[inline]
|
||||
pub fn with_movable_by_background(mut self, value: bool) -> Self {
|
||||
self.movable_by_window_background = Some(value);
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::fmt::Formatter;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
text::{LayoutJob, TextWrapping},
|
||||
Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, TextWrapMode, Ui, Visuals,
|
||||
text::{LayoutJob, TextWrapping},
|
||||
};
|
||||
|
||||
/// Text and optional style choices for it.
|
||||
|
||||
@@ -29,6 +29,7 @@ pub struct Button<'a> {
|
||||
stroke: Option<Stroke>,
|
||||
small: bool,
|
||||
frame: Option<bool>,
|
||||
frame_when_inactive: bool,
|
||||
min_size: Vec2,
|
||||
corner_radius: Option<CornerRadius>,
|
||||
selected: bool,
|
||||
@@ -44,6 +45,7 @@ impl<'a> Button<'a> {
|
||||
stroke: None,
|
||||
small: false,
|
||||
frame: None,
|
||||
frame_when_inactive: true,
|
||||
min_size: Vec2::ZERO,
|
||||
corner_radius: None,
|
||||
selected: false,
|
||||
@@ -52,6 +54,27 @@ impl<'a> Button<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Show a selectable button.
|
||||
///
|
||||
/// Equivalent to:
|
||||
/// ```rust
|
||||
/// # use egui::{Button, IntoAtoms, __run_test_ui};
|
||||
/// # __run_test_ui(|ui| {
|
||||
/// let selected = true;
|
||||
/// ui.add(Button::new("toggle me").selected(selected).frame_when_inactive(!selected).frame(true));
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// See also:
|
||||
/// - [`Ui::selectable_value`]
|
||||
/// - [`Ui::selectable_label`]
|
||||
pub fn selectable(selected: bool, atoms: impl IntoAtoms<'a>) -> Self {
|
||||
Self::new(atoms)
|
||||
.selected(selected)
|
||||
.frame_when_inactive(selected)
|
||||
.frame(true)
|
||||
}
|
||||
|
||||
/// Creates a button with an image. The size of the image as displayed is defined by the provided size.
|
||||
///
|
||||
/// Note: In contrast to [`Button::new`], this limits the image size to the default font height
|
||||
@@ -138,6 +161,18 @@ impl<'a> Button<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// If `false`, the button will not have a frame when inactive.
|
||||
///
|
||||
/// Default: `true`.
|
||||
///
|
||||
/// Note: When [`Self::frame`] (or `ui.visuals().button_frame`) is `false`, this setting
|
||||
/// has no effect.
|
||||
#[inline]
|
||||
pub fn frame_when_inactive(mut self, frame_when_inactive: bool) -> Self {
|
||||
self.frame_when_inactive = frame_when_inactive;
|
||||
self
|
||||
}
|
||||
|
||||
/// By default, buttons senses clicks.
|
||||
/// Change this to a drag-button with `Sense::drag()`.
|
||||
#[inline]
|
||||
@@ -220,6 +255,7 @@ impl<'a> Button<'a> {
|
||||
stroke,
|
||||
small,
|
||||
frame,
|
||||
frame_when_inactive,
|
||||
mut min_size,
|
||||
corner_radius,
|
||||
selected,
|
||||
@@ -243,9 +279,9 @@ impl<'a> Button<'a> {
|
||||
|
||||
let text = layout.text().map(String::from);
|
||||
|
||||
let has_frame = frame.unwrap_or_else(|| ui.visuals().button_frame);
|
||||
let has_frame_margin = frame.unwrap_or_else(|| ui.visuals().button_frame);
|
||||
|
||||
let mut button_padding = if has_frame {
|
||||
let mut button_padding = if has_frame_margin {
|
||||
ui.spacing().button_padding
|
||||
} else {
|
||||
Vec2::ZERO
|
||||
@@ -262,13 +298,22 @@ impl<'a> Button<'a> {
|
||||
let response = if ui.is_rect_visible(prepared.response.rect) {
|
||||
let visuals = ui.style().interact_selectable(&prepared.response, selected);
|
||||
|
||||
let visible_frame = if frame_when_inactive {
|
||||
has_frame_margin
|
||||
} else {
|
||||
has_frame_margin
|
||||
&& (prepared.response.hovered()
|
||||
|| prepared.response.is_pointer_button_down_on()
|
||||
|| prepared.response.has_focus())
|
||||
};
|
||||
|
||||
if image_tint_follows_text_color {
|
||||
prepared.map_images(|image| image.tint(visuals.text_color()));
|
||||
}
|
||||
|
||||
prepared.fallback_text_color = visuals.text_color();
|
||||
|
||||
if has_frame {
|
||||
if visible_frame {
|
||||
let stroke = stroke.unwrap_or(visuals.bg_stroke);
|
||||
let fill = fill.unwrap_or(visuals.weak_bg_fill);
|
||||
prepared.frame = prepared
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
epaint, pos2, Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui,
|
||||
Vec2, Widget, WidgetInfo, WidgetType,
|
||||
Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, Vec2, Widget,
|
||||
WidgetInfo, WidgetType, epaint, pos2,
|
||||
};
|
||||
|
||||
// TODO(emilk): allow checkbox without a text label
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
use crate::util::fixed_cache::FixedCache;
|
||||
use crate::{
|
||||
epaint, lerp, remap_clamp, Area, Context, DragValue, Frame, Id, Key, Order, Painter, Response,
|
||||
Sense, Ui, UiKind, Widget as _, WidgetInfo, WidgetType,
|
||||
Context, DragValue, Id, Painter, Popup, PopupCloseBehavior, Response, Sense, Ui, Widget as _,
|
||||
WidgetInfo, WidgetType, epaint, lerp, remap_clamp,
|
||||
};
|
||||
use epaint::{
|
||||
Mesh, Rect, Shape, Stroke, StrokeKind, Vec2,
|
||||
ecolor::{Color32, Hsva, HsvaGamma, Rgba},
|
||||
pos2, vec2, Mesh, Rect, Shape, Stroke, StrokeKind, Vec2,
|
||||
pos2, vec2,
|
||||
};
|
||||
|
||||
fn contrast_color(color: impl Into<Rgba>) -> Color32 {
|
||||
@@ -492,41 +493,23 @@ pub fn color_picker_color32(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> b
|
||||
|
||||
pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response {
|
||||
let popup_id = ui.auto_id_with("popup");
|
||||
let open = ui.memory(|mem| mem.is_popup_open(popup_id));
|
||||
let open = Popup::is_id_open(ui.ctx(), popup_id);
|
||||
let mut button_response = color_button(ui, (*hsva).into(), open);
|
||||
if ui.style().explanation_tooltips {
|
||||
button_response = button_response.on_hover_text("Click to edit color");
|
||||
}
|
||||
|
||||
if button_response.clicked() {
|
||||
ui.memory_mut(|mem| mem.toggle_popup(popup_id));
|
||||
}
|
||||
|
||||
const COLOR_SLIDER_WIDTH: f32 = 275.0;
|
||||
|
||||
// TODO(lucasmerlin): Update this to use new Popup struct
|
||||
if ui.memory(|mem| mem.is_popup_open(popup_id)) {
|
||||
ui.memory_mut(|mem| mem.keep_popup_open(popup_id));
|
||||
let area_response = Area::new(popup_id)
|
||||
.kind(UiKind::Picker)
|
||||
.order(Order::Foreground)
|
||||
.fixed_pos(button_response.rect.max)
|
||||
.show(ui.ctx(), |ui| {
|
||||
ui.spacing_mut().slider_width = COLOR_SLIDER_WIDTH;
|
||||
Frame::popup(ui.style()).show(ui, |ui| {
|
||||
if color_picker_hsva_2d(ui, hsva, alpha) {
|
||||
button_response.mark_changed();
|
||||
}
|
||||
});
|
||||
})
|
||||
.response;
|
||||
|
||||
if !button_response.clicked()
|
||||
&& (ui.input(|i| i.key_pressed(Key::Escape)) || area_response.clicked_elsewhere())
|
||||
{
|
||||
ui.memory_mut(|mem| mem.close_popup(popup_id));
|
||||
}
|
||||
}
|
||||
Popup::menu(&button_response)
|
||||
.id(popup_id)
|
||||
.close_behavior(PopupCloseBehavior::CloseOnClickOutside)
|
||||
.show(|ui| {
|
||||
ui.spacing_mut().slider_width = COLOR_SLIDER_WIDTH;
|
||||
if color_picker_hsva_2d(ui, hsva, alpha) {
|
||||
button_response.mark_changed();
|
||||
}
|
||||
});
|
||||
|
||||
button_response
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
use std::{cmp::Ordering, ops::RangeInclusive};
|
||||
|
||||
use crate::{
|
||||
emath, text, Button, CursorIcon, Id, Key, Modifiers, NumExt as _, Response, RichText, Sense,
|
||||
TextEdit, TextWrapMode, Ui, Widget, WidgetInfo, MINUS_CHAR_STR,
|
||||
Button, CursorIcon, Id, Key, MINUS_CHAR_STR, Modifiers, NumExt as _, Response, RichText, Sense,
|
||||
TextEdit, TextWrapMode, Ui, Widget, WidgetInfo, emath, text,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
epaint, text_selection, CursorIcon, Label, Response, Sense, Stroke, Ui, Widget, WidgetInfo,
|
||||
WidgetText, WidgetType,
|
||||
CursorIcon, Label, Response, Sense, Stroke, Ui, Widget, WidgetInfo, WidgetText, WidgetType,
|
||||
epaint, text_selection,
|
||||
};
|
||||
|
||||
use self::text_selection::LabelSelectionState;
|
||||
|
||||
@@ -2,14 +2,15 @@ use std::{borrow::Cow, slice::Iter, sync::Arc, time::Duration};
|
||||
|
||||
use emath::{Align, Float as _, GuiRounding as _, NumExt as _, Rot2};
|
||||
use epaint::{
|
||||
text::{LayoutJob, TextFormat, TextWrapping},
|
||||
RectShape,
|
||||
text::{LayoutJob, TextFormat, TextWrapping},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll},
|
||||
pos2, Color32, Context, CornerRadius, Id, Mesh, Painter, Rect, Response, Sense, Shape, Spinner,
|
||||
Color32, Context, CornerRadius, Id, Mesh, Painter, Rect, Response, Sense, Shape, Spinner,
|
||||
TextStyle, TextureOptions, Ui, Vec2, Widget, WidgetInfo, WidgetType,
|
||||
load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll},
|
||||
pos2,
|
||||
};
|
||||
|
||||
/// A widget which displays an image.
|
||||
@@ -277,7 +278,8 @@ impl<'a> Image<'a> {
|
||||
}
|
||||
|
||||
/// Set alt text for the image. This will be shown when the image fails to load.
|
||||
/// It will also be read to screen readers.
|
||||
///
|
||||
/// It will also be used for accessibility (e.g. read by screen readers).
|
||||
#[inline]
|
||||
pub fn alt_text(mut self, label: impl Into<String>) -> Self {
|
||||
self.alt_text = Some(label.into());
|
||||
@@ -499,7 +501,7 @@ impl ImageSize {
|
||||
|
||||
let point_size = match fit {
|
||||
ImageFit::Original { scale } => {
|
||||
return SizeHint::Scale((pixels_per_point * scale).ord())
|
||||
return SizeHint::Scale((pixels_per_point * scale).ord());
|
||||
}
|
||||
ImageFit::Fraction(fract) => available_size * fract,
|
||||
ImageFit::Exact(size) => size,
|
||||
@@ -671,7 +673,7 @@ pub fn paint_texture_load_result(
|
||||
rect: Rect,
|
||||
show_loading_spinner: Option<bool>,
|
||||
options: &ImageOptions,
|
||||
alt: Option<&str>,
|
||||
alt_text: Option<&str>,
|
||||
) {
|
||||
match tlr {
|
||||
Ok(TexturePoll::Ready { texture }) => {
|
||||
@@ -696,9 +698,9 @@ pub fn paint_texture_load_result(
|
||||
0.0,
|
||||
TextFormat::simple(font_id.clone(), ui.visuals().error_fg_color),
|
||||
);
|
||||
if let Some(alt) = alt {
|
||||
if let Some(alt_text) = alt_text {
|
||||
job.append(
|
||||
alt,
|
||||
alt_text,
|
||||
ui.spacing().item_spacing.x,
|
||||
TextFormat::simple(font_id, ui.visuals().text_color()),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
widgets, Color32, CornerRadius, Image, Rect, Response, Sense, Ui, Vec2, Widget, WidgetInfo,
|
||||
WidgetType,
|
||||
Color32, CornerRadius, Image, Rect, Response, Sense, Ui, Vec2, Widget, WidgetInfo, WidgetType,
|
||||
widgets,
|
||||
};
|
||||
|
||||
/// A clickable image within a frame.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user