Fixes#7378
Includes a regression test that previously failed and now succeeds.
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
* related #7494
Removes the `deadlock_detection` feature, since we now have a more
primitive panic-after-30s deadlock detection which works well enough and
even detects kinds of deadlocks that the `deadlock_detection` feature
never supported.
* Recently CI runs started to hang randomly:
https://github.com/emilk/egui/actions/runs/17427449210/job/49477714447?pr=7359
This fixes the deadlock and adds the basic deadlock detection we also
added to Mutexes in #7468.
Also, interestingly, the more sophisticated deadlock detection (behind
the deadlock_detection feature) didn't catch this for some reason. I
wonder why it exists in the first place, when parking_lot also has built
in deadlock detection? It also seems to make tests slower, widget_tests
usually needs ~30s, with the deadlock detection removed its only ~12s.
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* [x] I have followed the instructions in the PR template
Update some of the core dependencies and run cargo update for selected
dependencies to remove total number and older versions.
I'm trying to debug a suspected deadlock in the CI for
https://github.com/emilk/egui/pull/7467
Since we use our own mutex wrappers, we can just panic if the lock is
too slow. Ugly and effective :)
* Closes <https://github.com/emilk/egui/issues/7397>
* [X] I have followed the instructions in the PR template
I do admit I got a peak NixOS `RequestDeviceError` and deemed it
entirely not worth it to think about that.
https://github.com/emilk/egui/pull/5411 broke rotation of multi-line
`TextShape`s because `PlacedRow::pos` was no longer being rotated, so
let's rotate it.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
* Follow up to #7146
Previously when galleys were splitted, each exept the last had an extra
empty row that had to be removed when they were concated. This changes
it to remove the `\n` from the layout jobs when splitting.
* [x] I have followed the instructions in the PR template
Splitting this out from the Parley work as requested. This removes
`FontImage` and makes the font atlas use a `ColorImage`. It converts
alpha to coverage at glyph-drawing time, not at delta-upload time.
This doesn't do much now, but will allow for color emoji rendering once
we start using Parley.
I've changed things around so that we pass in `text_alpha_to_coverage`
to the `Fonts` the same way we do with `pixels_per_point` and
`max_texture_side`, reusing the existing code to check if the setting
differs and recreating the font atlas if so. I'm not quite sure why this
wasn't done in the first place.
I've left `ImageData` as an enum for now, in case we want to add support
for more texture pixel formats in the future (which I personally think
would be worthwhile). If you'd like, I can just remove that enum
entirely.
Closes#7077.
This fixes the problem shown in #7077 where clearing a `TextEdit`
wouldn't reset its cursor position. I've fixed that by adding back the
`TextCursorState::range` method, which clamps the selection range to
that of the passed `Galley`, and calling it in the same places where it
was called before #5785.
(/cc @juancampa)
* [x] I have followed the instructions in the PR template
This fixes bugs related to how an `Image` follows the size of an SVG.
We track the "source size" of each image, i.e. the original width/height
of the SVG, which can be different from whatever it was rasterized as.
TextShape.visual_bounding_rect was not taking the text rotation into
account. I manually tested drawing the new bounding box on top of the
text for various rotations & anchor settings. For example:
<img width="191" alt="image"
src="https://github.com/user-attachments/assets/56528fc7-7e7d-45af-b92a-c1cd307ff205"
/>
The unit test I added will fail without this patch, but perhaps doesn't
add much value.
* [x] I have followed the instructions in the PR template
Fixes a regression introduced in https://github.com/emilk/egui/pull/5411
(possibly
d74bee536f)
that breaks `leading_space` handling.
I think this is what the condition should be but I haven't touched this
code in a while.
## What
(written by @emilk)
When editing long text (thousands of line), egui would previously
re-layout the entire text on each edit. This could be slow.
With this PR, we instead split the text into paragraphs (split on `\n`)
and then cache each such paragraph. When editing text then, only the
changed paragraph needs to be laid out again.
Still, there is overhead from splitting the text, hashing each
paragraph, and then joining the results, so the runtime complexity is
still O(N).
In our benchmark, editing a 2000 line string goes from ~8ms to ~300 ms,
a speedup of ~25x.
In the future, we could also consider laying out each paragraph in
parallel, to speed up the initial layout of the text.
## Details
This is an ~~almost complete~~ implementation of the approach described
by emilk [in this
comment](<https://github.com/emilk/egui/issues/3086#issuecomment-1724205777>),
excluding CoW semantics for `LayoutJob` (but including them for `Row`).
It supersedes the previous unsuccessful attempt here:
https://github.com/emilk/egui/pull/4000.
Draft because:
- [X] ~~Currently individual rows will have `ends_with_newline` always
set to false.
This breaks selection with Ctrl+A (and probably many other things)~~
- [X] ~~The whole block for doing the splitting and merging should
probably become a function (I'll do that later).~~
- [X] ~~I haven't run the check script, the tests, and haven't made sure
all of the examples build (although I assume they probably don't rely on
Galley internals).~~
- [x] ~~Layout is sometimes incorrect (missing empty lines, wrapping
sometimes makes text overlap).~~
- A lot of text-related code had to be changed so this needs to be
properly tested to ensure no layout issues were introduced, especially
relating to the now row-relative coordinate system of `Row`s. Also this
requires that we're fine making these very breaking changes.
It does significantly improve the performance of rendering large blocks
of text (if they have many newlines), this is the test program I used to
test it (adapted from <https://github.com/emilk/egui/issues/3086>):
<details>
<summary>code</summary>
```rust
use eframe::egui::{self, CentralPanel, TextEdit};
use std::fmt::Write;
fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions {
..Default::default()
};
eframe::run_native(
"editor big file test",
options,
Box::new(|_cc| Ok(Box::<MyApp>::new(MyApp::new()))),
)
}
struct MyApp {
text: String,
}
impl MyApp {
fn new() -> Self {
let mut string = String::new();
for line_bytes in (0..50000).map(|_| (0u8..50)) {
for byte in line_bytes {
write!(string, " {byte:02x}").unwrap();
}
write!(string, "\n").unwrap();
}
println!("total bytes: {}", string.len());
MyApp { text: string }
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
CentralPanel::default().show(ctx, |ui| {
let start = std::time::Instant::now();
egui::ScrollArea::vertical().show(ui, |ui| {
let code_editor = TextEdit::multiline(&mut self.text)
.code_editor()
.desired_width(f32::INFINITY)
.desired_rows(40);
let response = code_editor.show(ui).response;
if response.changed() {
println!("total bytes now: {}", self.text.len());
}
});
let end = std::time::Instant::now();
let time_to_update = end - start;
if time_to_update.as_secs_f32() > 0.5 {
println!("Long update took {:.3}s", time_to_update.as_secs_f32())
}
});
}
}
```
</details>
I think the way to proceed would be to make a new type, something like
`PositionedRow`, that would wrap an `Arc<Row>` but have a separate `pos`
~~and `ends_with_newline`~~ (that would mean `Row` only holds a `size`
instead of a `rect`). This type would of course have getters that would
allow you to easily get a `Rect` from it and probably a `Deref` to the
underlying `Row`.
~~I haven't done this yet because I wanted to get some opinions whether
this would be an acceptable API first.~~ This is now implemented, but of
course I'm still open to discussion about this approach and whether it's
what we want to do.
Breaking changes (currently):
- The `Galley::rows` field has a different type.
- There is now a `PlacedRow` wrapper for `Row`.
- `Row` now uses a coordinate system relative to itself instead of the
`Galley`.
* Closes <https://github.com/emilk/egui/issues/3086>
* [X] I have followed the instructions in the PR template
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Minor bug fix when transforming a `TextShape` with a `underline` (used
for e.g. hyperlinks). Before the underline width would not scale
properly; now it will.
Might want to draw from `interaction.interact_radius` style instead of
hard-coding the margin, but I didn't want to create a breaking change.
If desired, I can follow up with a separate PR to address that concern.
* Closes <https://github.com/emilk/egui/issues/5796>
* [x] I have followed the instructions in the PR template
Enabled the `missing_assert_message` lint
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Lucas Meurer <lucasmeurer96@gmail.com>
This removes the `expand(1.0)` on text background colors, since it makes
translucent background colors have bad looking bleeding.
There is probably a smarter solution than disabling the highlighting
entirely, but I don't see a way to do that while keeping the area
consumed consistent between translucent/solid colors, or adding a decent
step up in complexity.
Since this makes it impossible to tell if selected text is highlighted,
this also adds a blanket `0.5` gamma multiply to the text selection
background color. If that is undesirable because it's a bad arbitrary
number choice, or if it's too much of an unexpected change and just the
default values should be changed, please let me know.
These changes cause the tests that use screenshots with highlighted text
to fail, though I am not sure how to update those tests to match the
changes.
<details>
<summary>Comparison Images</summary>
Current:

After changes:

</details>
<details>
<summary>Code used to make comparison images</summary>
```rs
fn color_text_format(ui: &Ui, color: Color32) -> TextFormat {
TextFormat { font_id: FontId::monospace(ui.text_style_height(&egui::TextStyle::Monospace)), background: color, ..Default::default() }
}
fn color_sequence_galley(ui: &Ui, text: &str, colors: [Color32; 3]) -> Arc<Galley> {
let mut layout_job = LayoutJob::default();
for color in colors {
layout_job.append(text, 0.0, color_text_format(ui, color));
}
ui.fonts(|f| f.layout_job(layout_job))
}
fn color_sequence_row(ui: &mut Ui, label_text: &str, text: &str, colors: [Color32; 3]) {
ui.label(label_text);
ui.label(color_sequence_galley(ui, text, colors));
ui.end_row();
}
egui::Grid::new("comparison display").show(ui, |ui| {
ui.ctx().set_pixels_per_point(2.0);
let transparent = Color32::TRANSPARENT;
let solid = Color32::RED;
let solid_2 = Color32::GREEN;
let translucent_1 = Color32::GRAY.gamma_multiply(0.5);
let translucent_2 = Color32::GREEN.gamma_multiply(0.5);
color_sequence_row(ui, "Transparent to Solid:", " ", [transparent, solid, transparent]);
color_sequence_row(ui, "Translucent to Transparent:", " ", [transparent, translucent_1, transparent]);
color_sequence_row(ui, "Solid to Transparent:", " ", [solid, solid_2, solid]);
color_sequence_row(ui, "Solid to Solid:", " ", [solid, transparent, solid]);
color_sequence_row(ui, "Solid to Translucent:", " ", [solid, translucent_1, solid]);
color_sequence_row(ui, "Translucent to Translucent:", " ", [translucent_1, translucent_2, translucent_1]);
color_sequence_row(ui, "Transparent to Solid:", "a", [transparent, solid, transparent]);
color_sequence_row(ui, "Translucent to Transparent:", "a", [transparent, translucent_1, transparent]);
color_sequence_row(ui, "Solid to Transparent:", "a", [solid, solid_2, solid]);
color_sequence_row(ui, "Solid to Solid:", "a", [solid, transparent, solid]);
color_sequence_row(ui, "Solid to Translucent:", "a", [solid, translucent_1, solid]);
color_sequence_row(ui, "Translucent to Translucent:", "a", [translucent_1, translucent_2, translucent_1]);
})
```
</details>
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* [x] I have followed the instructions in the PR template
This PR implements `AsRef<[u8]>` for `FontData`, allowing it to be
passed into `fontdb`'s
[`Source`](https://docs.rs/fontdb/0.16.2/fontdb/enum.Source.html) type.
This would allow `egui` and `cosmic_text` to share font data with
eachother
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* Closes N/A, but this is part of
https://github.com/emilk/egui/issues/3378
* [x] I have followed the instructions in the PR template
Other text layout libraries in Rust--namely, Parley and Cosmic
Text--have one canonical text cursor type (Parley's is a byte index,
Cosmic Text's also stores the line index). To prepare for migrating egui
to one of those libraries, it should also have only one text cursor
type. I also think simplifying the API is a good idea in and of
itself--having three different cursor types that you have to convert
between (and a `Cursor` struct which contains all three at once) is
confusing.
After a bit of experimentation, I found that the best cursor type to
coalesce around is `CCursor`. In the few places where we need a
paragraph index or row/column position, we can calculate them as
necessary.
I've removed `CursorRange` and `PCursorRange` (the latter appears to
have never been used), merging the functionality with `CCursorRange`. To
preserve the cursor position when navigating row-by-row, `CCursorRange`
now stores the previous horizontal position of the cursor.
I've also removed `PCursor`, and renamed `RowCursor` to `LayoutCursor`
(since it includes not only the row but the column). I have not renamed
either `CCursorRange` or `CCursor` as those names are used in a lot of
places, and I don't want to clutter this PR with a bunch of renames.
I'll leave it for a later PR.
Finally, I've removed the deprecated methods from `TextEditState`--it
made the refactoring easier, and it should be pretty easy to migrate to
the equivalent `TextCursorState` methods.
I'm not sure how many breaking changes people will actually encounter. A
lot of these APIs were technically public, but I don't think many were
useful. The `TextBuffer` trait now takes `&CCursorRange` instead of
`&CursorRange` in a couple of methods, and I renamed
`CCursorRange::sorted` to `CCursorRange::sorted_cursors` to match
`CursorRange`.
I did encounter a couple of apparent minor bugs when testing out text
cursor behavior, but I checked them against the current version of egui
and they're all pre-existing.
Breaking change!
* `Rounding` -> `CornerRadius`
* `rounding` -> `corner_radius`
This is to:
* Clarify
* Conform to other systems (e.g. Figma)
* Avoid confusion with `GuiRounding`