* 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.
## 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>
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>
<!--
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!
-->
I removed (I hope so) all wildcard imports I found.
For me on my pc this improved the build time:
- for egui -5s
- for eframe -12s
* [x] I have followed the instructions in the PR template
Previously, many labels had non-integer widths. This lead to rounding
errors.
This was most notable for the new `Area` sizing code:
We would run the initial sizing pass, to measure the size of e.g. a
tooltip.
Say the tooltip contains text that was 100.123 ui points wide. With a
16pt border, that becomes 116.123, which is stored in the `Area` state
as the width. The next frame, we use that stored size as the wrapping
width. With perfect precision, we would then tell the label to wrap to
100.123 pts, which the text would _just_ fit in. However, due to
rounding errors we might end up asking it to wrap to 100.12**2** pts,
meaning the last word would now wrap and end up on the next line.
By rounding label sizes to perfect integers, we avoid such rounding
errors, and most ui elements will now end up on perfect integer point
coordinates (and `f32` can precisely express and do arithmetic on all
integers < 2^24).
Visually this has very little impact. Some labels move by a pixel here
and there, mostly for the better.
<!--
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 add commits to your PR.
* Remember to run `cargo fmt` and `cargo cranky`.
* 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!
-->
I had to make a couple types not Copy because closures, but it should'nt
be a massive deal.
I tried my best to make the API change as non breaking as possible.
Anywhere a PathStroke is used, you can just use a normal Stroke instead.
As mentioned above, the bezier paths couldn't be copy anymore, but IMO
that's a minor caveat.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
<!--
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 add commits to your PR.
* Remember to run `cargo fmt` and `cargo cranky`.
* 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!
-->
While breaking a paragraph, it was possible to lose line break
candidates that could've been used on the next line, causing egui to
unnecessarily overrun `wrap.max_width`.
This PR fixes it so that we don't forget about those candidates.
Before:
Note that the window can't resize to the requested width because the
text is not wrapping.
https://github.com/emilk/egui/assets/1410520/6430a334-2995-4b40-bc34-8f01923f9f95
After:
https://github.com/emilk/egui/assets/1410520/225fa4cd-cbbb-4a7e-9580-7f1814c05ee7
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* Add Row::text
* Rename elide_at_width -> truncate_at_width
* Move text layout tests to own module
* Add test to check that elision character is always included
* Include elision character in more circumstances
* Append overflow character if we can't replace
* Always append … when eliding
* Add a secondary text to the text layout demo
* Add `TextFormat::extra_letter_spacing`
* Add control of line height
* Add to text layout demo
* Move the text layout demo to its own window in the demo app
* Fix doclink
* Better document points vs pixels
* Better documentation and code cleanup
* Add option to clip text to wrap width
* Spelling
* Better naming, and report back wether the text was elided
* Improve docstrings
* Simplify
* Fix max_rows with multiple paragraphs
* Add note
* Typos
* fix doclink
* Add `Label::elide`
* Label: show full non-elided text on hover
* Add demo of `Label::elide`
* Call it `Label::truncate`
* Clarify limitations of `break_anywhere`
* Better docstrings
* use font metrics in layout
* properly center scaled fonts
* adjust docs
* fix raised text
* fix easymark viewer small text alignment
caused by variable row heights
Closes https://github.com/emilk/egui/issues/2068
Before this PR, the default font, Ubuntu-Light, was ~11% smaller
than it should have been, and the default monospace font, Hack,
was ~14% smaller. This means that setting the font size `12` in egui
would yield smaller text than using that font size in any other app.
Ooops!
The change is that this PR now takes into account the ttf properties
`units_per_em` and `height_unscaled`.
If your egui application has specified you own font sizes or text styles
you will see the text in your application grow
larger, unless you go in and compensate by dividing all font sizes by
~1.21 for Ubuntu-Light/Proportional and ~1.16 for Hack/Monospace,
and with something else if you are using a custom font!
This effects any use of `FontId`, `RichText::size`, etc.
This PR changes the default `Style::text_styles` to compensate,
so the default egui style should look the same before and after this PR.