emilk / egui

egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native
https://www.egui.rs/
Apache License 2.0
22.04k stars 1.59k forks source link

Add `ui.align_cursor()` to for the cursor to be aligned with physical pixels #4928

Open abey79 opened 2 months ago

abey79 commented 2 months ago

Unaligned cursor lead to various visual glitches. It would be nice to have an API to "fix" the cursor position. This would avoid using code like this:

ui.add_space(-ui.cursor().min.y.fract());

which is not robust to layout orientation and assumes 1:1 physical pixels.

Related:

emilk commented 3 weeks ago

There are three alternatives:

I'd like to understand a bit more what the trade-offs are between these.

Regardless of how/if we round widgets during layout, we still want to round some visual shapes to physical pixels to make them crisper (text, thin lines, etc).

Rounding to physical pixels

Since we need to round some visual shapes to physical pixels anyway, always rounding to physical pixels may produce more even results.

Rounding to logical ui points

All calculations are done in logical ui points, and so if we make them integers we prevent rounding errors.


Unaligned cursor lead to various visual glitches.

I'd like a repro for this

abey79 commented 3 weeks ago

I'd like a repro for this

use eframe::egui;

fn main() -> eframe::Result {
    env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
        ..Default::default()
    };
    eframe::run_native(
        "My egui App",
        options,
        Box::new(|cc| {
            // This gives us image support:
            egui_extras::install_image_loaders(&cc.egui_ctx);

            Ok(Box::new(MyApp))
        }),
    )
}

struct MyApp;

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            // unaligned cursor
            ui.add_space(0.1234);

            ui.spacing_mut().item_spacing.y = 0.0;

            for _ in 0..5 {
                let (rect, _) =
                    ui.allocate_exact_size(egui::vec2(100.0, 20.0), egui::Sense::hover());
                ui.painter()
                    .rect_filled(rect, 0.0, ui.style().visuals.selection.bg_fill);
            }
        });
    }
}
image



Commenting out the mis-alignment line gives the expect result:

image



One notch of zooming-in (cmd-+) brings back the glitch though:

image
abey79 commented 3 weeks ago

There are three alternatives:

  • Round widgets to physical pixels
  • Round widgets to logical ui points
  • Only round when pixels_per_point is an integer
  • No rounding

In light of the repro above, I believe rounding to physical pixels is what's needed, but, ultimately, whatever fixes this 😅

emilk commented 3 weeks ago

The problem there is the feathering (anti-aliasing) of the boxes.

The easy fix there would be to always align boxes to the pixel grid, and turn of feathering for them. The usefulness of feathering on axis-aligned boxes is minimal anyway. If you're animating boxes then it will make them move smoother, but that seems quite niche.

Related:

emilk commented 2 weeks ago

I think the correct thing to do here is to align ui points to integers in order to avoid rounding errors in width calculations and similar (see e.g. https://github.com/emilk/egui/issues/5084).

The problem of rendering rectangles side-by-side without a seem is a purely visual one, and can be solved separately by one of these means:

I've opened two new issues for this: