1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00
Commit Graph

17 Commits

Author SHA1 Message Date
Emil Ernerfeldt
a15a7e6611 Increase widget corner radius to 4 (#8445)
A little more round and friendly!

* See also https://github.com/emilk/egui/pull/8439
2026-08-24 11:07:06 +02:00
Lucas Meurer
e204717b1d Atom support for egui::Window Titlebar (#8154)
* part of #7264 
* based on https://github.com/emilk/egui/pull/8152

The resize fix allows use to really simplify how the Window Titlebar is
rendered. Previously it was using some complex flow to calculate and
allocate the height first and then render it later once we knew the
windows final width.

Since now windows can't shrink past their minimum content widths, I can
just show the titlebar inline with the regular content, just outside of
the `Resize` container so that it is always visible.

This does change what the size of a window means. Before, size was just
the size of the contents, while now size (e.g. via min_height) will
include the Frames margin and outline, title bar and the contents.
Also, the window label now truncates as you shrink the window (meaning
windows can now be smaller than their label allows).

---------

Co-authored-by: lucasmerlin <8009393+lucasmerlin@users.noreply.github.com>
2026-05-13 15:11:15 +02:00
Gautier Cailly
16cad760a5 Integrate harfrust for text shaping (#8031)
* Related to #56 (Improve text — tracking issue)

## Summary

This PR integrates [harfrust](https://crates.io/crates/harfrust) (a
pure-Rust port of HarfBuzz) into epaint's text layout pipeline,
replacing the character-by-character glyph positioning with proper
OpenType text shaping.

### What this enables

- **GPOS kerning**: most modern fonts only ship kerning in GPOS tables
(not the legacy `kern` table). Pairs like "AV", "VA", "AT" are now
properly tightened.
- **GSUB substitutions**: ligatures (fi, fl), contextual alternates, and
other OpenType features.
- **Combining marks**: diacritics (e.g. ɔ̃) are positioned via anchor
tables instead of being rendered as standalone replacement glyphs.

### Before/After

#### Kerning, etc.

<img width="838" height="726" alt="before_main"
src="https://github.com/user-attachments/assets/f0f26d5f-b117-43a6-b39c-ea40d2e73836"
/>

<img width="838" height="726" alt="after_harfrust"
src="https://github.com/user-attachments/assets/d983e5da-486c-4f39-bd4f-5782a90c6b39"
/>

 #### Ligatures

<img width="1117" height="698" alt="before_closeup"
src="https://github.com/user-attachments/assets/7a3b08b4-cf6f-45b7-98ba-07c473cd3b02"
/>

<img width="1117" height="698" alt="after_closeup"
src="https://github.com/user-attachments/assets/6cfc5f21-d32f-4f09-be0c-59c8c553d44f"
/>

### Architecture

The shaping integrates into the existing pipeline without changing the
public API:

1. **`Font::segment_into_runs`** — segments text into contiguous runs by
font face (grapheme-cluster aware, never splits combining sequences)
2. **`FontFace::shape_text`** — calls harfrust to shape each run,
returning glyph IDs + positioned advances/offsets
3. **`layout_shaped_run`** — emits `Glyph` structs from the shaping
output, with NOTDEF fallback to other font faces for missing glyphs
4. **Buffer recycling** — `FontsImpl` pools a `harfrust::UnicodeBuffer`
to avoid per-layout allocations

### Disclaimer

I'm far from being a good Rust programmer. Claude Code did most of the
heavy lifting here. I did my best and used my limited knowledge to avoid
making too many mistakes. If this PR isn't up to quality standards,
please don't hesitate to close it.

## Test plan

- [x] `cargo test -p epaint` — all 18 text tests pass, including 6 new
ones
- [x] `cargo clippy -p epaint --all-features` — clean
- [x] `cargo fmt` — clean
- [ ] Snapshot tests need regeneration (expected: shaping changes glyph
positions)
- New tests added:
- `test_gpos_kerning` — verifies GPOS kerning tightens "AV", "VA", "AT"
pairs
- `test_combining_diacritics` — combining tilde doesn't add extra width
  - `test_shaping_basic_latin` — sanity check for Latin text
  - `test_shaping_empty_string` — empty input doesn't panic
  - `test_shaping_multiple_newlines` — newline splitting works correctly
  - `test_shaping_mixed_font_fallback` — Latin + emoji in same string

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-04-06 14:25:04 +02:00
Emil Ernerfeldt
ca1e76f38b Constrain demo windows to the space left after the panels (#7785) 2025-12-17 21:03:52 +01:00
Emil Ernerfeldt
51493be066 Better framing of demo snapshots (#7787) 2025-12-17 18:06:26 +01:00
valadaptive
609dd2d28e Replace ab_glyph with Skrifa + vello_cpu; enable font hinting (#7694)
<!--
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!
-->

* Closes N/A
* [x] I have followed the instructions in the PR template

I'll probably come back to this and clean it up a bit. This PR
reimplements ab_glyph's functionality on top of Skrifa, a somewhat
lower-level font API that's being used in Chrome now.

Skrifa doesn't perform rasterization itself, so I'm using
[vello_cpu](https://github.com/linebender/vello) from the Linebender
project for rasterization. It's still in its early days, but I believe
it's already quite fast. It also supports color and gradient fills, so
color emoji support will be easier.

Skrifa also supports font hinting, which should make text look a bit
nicer / less blurry.

Here's the current ab_glyph rendering:

<img width="1592" height="1068" alt="image"
src="https://github.com/user-attachments/assets/2385b66e-23f8-4c6e-b8c2-ea90e0eea4e4"
/>

Here's Skrifa *without* hinting--it looks almost identical, but there
are some subpixel differences, probably due to rasterizer behavior:

<img width="1592" height="1068" alt="image"
src="https://github.com/user-attachments/assets/a815f3e9-65ac-4940-bc00-571177bef53d"
/>

Here's Skrifa  *with* hinting:

<img width="1592" height="1068" alt="image"
src="https://github.com/user-attachments/assets/d6cc0669-3537-4377-bba9-ed5ef09664db"
/>

Hinting does make the horizontal strokes look a bit bolder, which makes
me wonder once again about increasing the font weight from "light" to
"regular".

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2025-12-06 16:11:33 +01:00
Emil Ernerfeldt
72b9b9d750 Increase default text size from 12.5 to 13.0 (#7521) 2025-09-09 15:47:24 +02:00
valadaptive
d5b0a6f446 More even text kerning (#7431)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2025-09-08 17:29:41 +02:00
Emil Ernerfeldt
f46926aaf1 Improve texture filtering by doing it in gamma space (#7311)
* Closes https://github.com/emilk/egui/pull/5839

This makes some transparent images look a lot nicer when blended:


![image](https://github.com/user-attachments/assets/7f370aaf-886a-423c-8391-c378849b63ca)

Cursive text will also look nicer.

This unfortunately changes the contract of what
`register_native_texture` expects

---------

Co-authored-by: Adrian Blumer <blumer.adrian@gmail.com>
2025-07-07 17:46:27 +02:00
Emil Ernerfeldt
2058dcb881 Improve text sharpness (#5838)
This improves the sharpness of text slightly, thanks to
<https://hikogui.org/2022/10/24/the-trouble-with-anti-aliasing.html> by
@hikogui (🙏 ).

The difference is very small, but in dark mode (bright text on dark
background) text is sometimes significantly sharper, and also slightly
brighter.

The difference in light mode (dark text on light background) is much
smaller (barely perceptable).

To compare the before/after I suggest you open both in new tabs, then
quickly change between them.

### Low-DPI
#### Before
![Code Editor
old](https://github.com/user-attachments/assets/e10a3cad-932f-48cd-b7d6-5bfe70954c5e)

#### After
![Code
Editor](https://github.com/user-attachments/assets/2e7383fe-8023-4425-91c8-93df3c22c0fe)

#### Before

![old-white](https://github.com/user-attachments/assets/51c41c59-e897-4831-857a-f3ffe17ce7d4)

#### After

![new-white](https://github.com/user-attachments/assets/4ac6f951-8c57-4bcc-a5d5-788cf52ea7d8)

### High-DPI
The difference is starkest on low-DPI screens (like the above
screenshots). On high-DPI screens, the difference is much smaller

#### Before
![widget_gallery
old](https://github.com/user-attachments/assets/f2149a30-aef8-4383-b48c-73d33a03ca02)

#### After

![widget_gallery](https://github.com/user-attachments/assets/c9ceb8be-8a32-490c-9364-2c6562b741f6)
2025-03-21 12:56:47 +01:00
Emil Ernerfeldt
3f731ec794 Fix semi-transparent colors appearing too bright (#5824)
The bug was in `Color32::from_rgba_unmultiplied` and by extension
affects:

* `Color32::from_rgba_unmultiplied`
* `hex_color!`
* `HexColor`
* `ColorImage::from_rgba_unmultiplied`
* All images with transparency (png, webp, …)
* `Color32::from_white_alpha`

The bug caused translucent colors to appear too bright.

## More
Color is hard.

When I started out egui I thought "linear space is objectively better,
for everything!" and then I've been slowly walking that back for various
reasons:

* sRGB textures not available everywhere
* gamma-space is more _perceptually_ even, so it makes sense to use for
anti-aliasing
* other applications do everything in gamma space, so that's what people
expect (this PR)

Similarly, pre-multiplied alpha _makes sense_ for blending colors. It
also enables additive colors, which is nice. But it does complicate
things. Especially when mixed with sRGB/gamma (As @karhu [points
out](https://github.com/emilk/egui/pull/5824#issuecomment-2738099254)).

## Related
* Closes https://github.com/emilk/egui/issues/5751
* Closes https://github.com/emilk/egui/issues/5771 ? (probably; hard to
tell without a repro)
* But not https://github.com/emilk/egui/issues/5810

## TODO
* [x] I broke the RGBA u8 color picker. Fix it

---------

Co-authored-by: Andreas Reich <andreas@rerun.io>
2025-03-21 10:45:25 +01:00
Emil Ernerfeldt
7ec1fbf467 Fix minor glitch in rendering of selected windows (#5678) 2025-02-04 16:05:36 +01:00
Emil Ernerfeldt
c6bda9a38c Make the ends of vline/hline sharper (#5676)
TL;DR: line caps are annoying in two ways:

A) we only add them for lines wider than a pixel
B) they always make the line longer (if added)
2025-02-04 15:31:51 +01:00
Emil Ernerfeldt
3c07e01d08 Improve tessellation quality (#5669)
## Defining what `Rounding` is
This PR defines what `Rounding` means: it is the corner radius of
underlying `RectShape` rectangle. If you use `StrokeKind::Inside`, this
means the rounding is of the outer part of the stroke. Conversely, if
you use `StrokeKind::Outside`, the stroke is outside the rounded
rectangle, so the stroke has an inner radius or `rounding`, and an outer
radius that is larger by `stroke.width`.

This definitions is the same as Figma uses.

## Improving general shape rendering
The rendering of filled shapes (rectangles, circles, paths, bezier) has
been rewritten. Instead of first painting the fill with the stroke on
top, we now paint them as one single mesh with shared vertices at the
border. This has several benefits:

* Less work (faster and with fewer vertices produced)
* No overdraw (nicer rendering of translucent shapes)
* Correct blending of stroke and fill

The logic for rendering thin strokes has also been improved, so that the
width of a stroke of `StrokeKind::Outside` never affects the filled area
(this used to be wrong for thin strokes).

## Improving of rectangle rendering
Rectangles also has specific improvements in how thin rectangles are
painted.
The handling of "Blur width" is also a lot better, and now works for
rectangles with strokes.
There also used to be bugs with specific combinations of corner radius
and stroke width, that are now fixed.

##  But why?
With the new `egui::Scene` we end up with a lot of zoomed out shapes,
with sub-pixel strokes. These need to look good! One thing led to
another, and then I became obsessive 😅

## Tessellation Test
In order to investigate the rendering, I created a Tessellation Test in
the `egui_demo_lib`.

[Try it
here](https://egui-pr-preview.github.io/pr/5669-emilkimprove-tessellator)

![Screenshot 2025-02-04 at 08 45
50](https://github.com/user-attachments/assets/20b47a30-de6a-4ff5-885b-2e2fd6d88321)


![image](https://github.com/user-attachments/assets/e17c50eb-5ae7-48d4-bb0d-4f2165075897)
2025-02-04 11:30:12 +01:00
Emil Ernerfeldt
6607cd95f9 ⚠️ Frame now includes stroke width as part of padding (#5575)
* Part of https://github.com/emilk/egui/issues/4019

`Frame` now includes the width of the stroke as part of its size. From
the new docs:

### `Frame` docs
The total (outer) size of a frame is `content_size + inner_margin +
2*stroke.width + outer_margin`.

Everything within the stroke is filled with the fill color (if any).

```text
+-----------------^-------------------------------------- -+
|                 | outer_margin                           |
|    +------------v----^------------------------------+    |
|    |                 | stroke width                 |    |
|    |    +------------v---^---------------------+    |    |
|    |    |                | inner_margin        |    |    |
|    |    |    +-----------v----------------+    |    |    |
|    |    |    |             ^              |    |    |    |
|    |    |    |             |              |    |    |    |
|    |    |    |<------ content_size ------>|    |    |    |
|    |    |    |             |              |    |    |    |
|    |    |    |             v              |    |    |    |
|    |    |    +------- content_rect -------+    |    |    |
|    |    |                                      |    |    |
|    |    +-------------fill_rect ---------------+    |    |
|    |                                                |    |
|    +----------------- widget_rect ------------------+    |
|                                                          |
+---------------------- outer_rect ------------------------+
```

The four rectangles, from inside to outside, are:
* `content_rect`: the rectangle that is made available to the inner
[`Ui`] or widget.
* `fill_rect`: the rectangle that is filled with the fill color (inside
the stroke, if any).
* `widget_rect`: is the interactive part of the widget (what sense
clicks etc).
* `outer_rect`: what is allocated in the outer [`Ui`], and is what is
returned by [`Response::rect`].

### Notes
This required rewriting a lot of the layout code for `egui::Window`,
which was a massive pain. But now the window margin and stroke width is
properly accounted for everywhere.
2025-01-04 10:29:22 +01:00
Emil Ernerfeldt
d20f93e9bf Make all lines and rectangles crisp (#5518)
* Merge this first: https://github.com/emilk/egui/pull/5517

This aligns all rectangles and (horizontal or vertical) line segments to
the physical pixel grid in the `epaint::Tessellator`, making these
shapes appear crisp everywhere.

* Closes https://github.com/emilk/egui/issues/5164
* Closes https://github.com/emilk/egui/issues/3667

This undoes a lot of the explicit, egui-side aligning added in:
* https://github.com/emilk/egui/pull/4943

The new approach has several benefits over the old one:

* It is done automatically by epaint, so it is applied to everything (no
longer opt-in)
* It is applied after any layer transforms (so it always works)
* It makes line segments crisper on high-DPI screens
* All filled rectangles now has sides that end on pixel boundaries
2024-12-26 21:02:27 +01:00
lucasmerlin
70a01138b7 Add egui testing library (#5166)
- closes #3491 
- closes #3926

This adds a testing library to egui based on
[kittest](https://github.com/rerun-io/kittest). Kittest is a new
[AccessKit](https://github.com/AccessKit/accesskit/)-based testing
library. The api is inspired by the js
[testing-library](https://testing-library.com/) where the idea is also
to query the dom based on accessibility attributes.
We made kittest with egui in mind but it should work with any rust gui
framework with AccessKit support.

It currently has support for:
- running the egui app, frame by frame
- building the AccessKit tree
- ergonomic queries via kittest
  - via e.g. get_by_name, get_by_role
- simulating events based on the accesskit node id
- creating arbitrary events based on Harness::input_mut
- rendering screenshots via wgpu
- snapshot tests with these screenshots

A simple test looks like this: 
```rust
fn main() {
    let mut checked = false;
    let app = |ctx: &Context| {
        CentralPanel::default().show(ctx, |ui| {
            ui.checkbox(&mut checked, "Check me!");
        });
    };

    let mut harness = Harness::builder().with_size(egui::Vec2::new(200.0, 100.0)).build(app);
    
    let checkbox = harness.get_by_name("Check me!");
    assert_eq!(checkbox.toggled(), Some(Toggled::False));
    checkbox.click();
    
    harness.run();

    let checkbox = harness.get_by_name("Check me!");
    assert_eq!(checkbox.toggled(), Some(Toggled::True));

    // You can even render the ui and do image snapshot tests
    #[cfg(all(feature = "wgpu", feature = "snapshot"))]
    egui_kittest::image_snapshot(&egui_kittest::wgpu::TestRenderer::new().render(&harness), "readme_example");
}
```

~Since getting wgpu to run in ci is a hassle, I'm taking another shot at
creating a software renderer for egui (ideally without a huge dependency
like skia)~ (this didn't work as well as I hoped and it turns out in CI
you can just run tests on a mac runner which comes with a real GPU)
 
Here is a example of a failed snapshot test in ci, it will say which
snapshot failed and upload an artifact with the before / after and diff
images:

https://github.com/emilk/egui/actions/runs/11183049487/job/31090724606?pr=5166
2024-10-22 12:39:00 +02:00