Merge pull request #874 from storytold/fix/862-stop-popover-beside-art

Fix #862: the gradient stop popover opens beside the art
This commit is contained in:
BFlat 2026-10-10 10:45:22 +07:00 committed by GitHub
commit 9c0eccb067
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 60 additions and 3 deletions

View File

@ -90,12 +90,19 @@ fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
let screen =
d.fields.get("screen").and_then(Value::as_array).and_then(|a| Some(egui::pos2(a.first()?.as_f64()? as f32, a.get(1)?.as_f64()? as f32)));
let chip = Point::new(d.f64("x", 0.0), d.f64("y", 0.0));
let pos = screen.unwrap_or_else(|| {
app.canvas_rect.zip(app.view().copied()).map_or(ctx.content_rect().center(), |(r, v)| Xf::new(r, &v).to_screen(chip) + vec2(14.0, 14.0))
let area = egui::Id::new("gradient-stop-popover");
let size = ctx.memory(|m| m.area_rect(area)).map_or(POPOVER_SIZE, |r| r.size());
let pos = screen.unwrap_or_else(|| match app.canvas_rect.zip(app.view().copied()) {
Some((canvas, v)) => {
let xf = Xf::new(canvas, &v);
let at = xf.to_screen(chip);
selection_on_screen(app, &xf).map_or(at + vec2(14.0, 14.0), |art| beside_art(at, art, size, canvas))
}
None => ctx.content_rect().center(),
});
let swatches = d.str("tab") == "swatches";
let mut tab = None;
let resp = egui::Area::new(egui::Id::new("gradient-stop-popover"))
let resp = egui::Area::new(area)
.order(egui::Order::Foreground)
.fixed_pos(pos)
.constrain(true)
@ -150,6 +157,34 @@ fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
}
}
/// The popover's size before it has been drawn.
const POPOVER_SIZE: egui::Vec2 = vec2(272.0, 360.0);
/// The selected art's box on the screen.
fn selection_on_screen(app: &VectorcraftApp, xf: &Xf) -> Option<egui::Rect> {
let st = app.session.active()?;
let b = st.doc.bounds_of(&st.selection.objects, false)?;
Some(egui::Rect::from_points(&xf.quad(b)))
}
/// Where the popover opened from a stop's chip at `chip` goes: beside the selected `art` on the
/// first side of it with room for a popover of `size` in `room` (right, left, below, above), so
/// the art it recolours stays in view (#862); else beside the chip.
fn beside_art(chip: egui::Pos2, art: egui::Rect, size: egui::Vec2, room: egui::Rect) -> egui::Pos2 {
const GAP: f32 = 12.0;
let x = (chip.x - size.x / 2.0).clamp(room.left(), (room.right() - size.x).max(room.left()));
let y = (chip.y - size.y / 2.0).clamp(room.top(), (room.bottom() - size.y).max(room.top()));
[
egui::pos2(art.right() + GAP, y),
egui::pos2(art.left() - GAP - size.x, y),
egui::pos2(x, art.bottom() + GAP),
egui::pos2(x, art.top() - GAP - size.y),
]
.into_iter()
.find(|p| room.contains_rect(egui::Rect::from_min_size(*p, size)))
.unwrap_or(chip + vec2(14.0, 14.0))
}
/// Document swatches as tiles: a click gives the stop (or point) that colour.
fn swatch_grid(app: &mut VectorcraftApp, ui: &mut egui::Ui, target: &Target) {
let colors = swatch_colors(app);
@ -248,6 +283,28 @@ mod tests {
app
}
/// #862: the popover goes beside the art, on the first side with room, not over it.
#[test]
fn the_popover_goes_beside_the_art_it_edits() {
let room = egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(1200.0, 800.0));
let size = egui::vec2(272.0, 360.0);
let place = |art: egui::Rect| egui::Rect::from_min_size(beside_art(art.center(), art, size, room), size);
// Room on the right.
let art = egui::Rect::from_min_max(egui::pos2(300.0, 300.0), egui::pos2(500.0, 450.0));
assert!(place(art).left() > art.right() && room.contains_rect(place(art)));
// Against the right edge: on the left.
let art = egui::Rect::from_min_max(egui::pos2(900.0, 300.0), egui::pos2(1150.0, 450.0));
assert!(place(art).right() < art.left());
// As wide as the room: below it, or above it near the bottom.
let art = egui::Rect::from_min_max(egui::pos2(100.0, 20.0), egui::pos2(1100.0, 300.0));
assert!(place(art).top() > art.bottom());
let art = egui::Rect::from_min_max(egui::pos2(100.0, 420.0), egui::pos2(1100.0, 780.0));
assert!(place(art).bottom() < art.top());
// Nowhere to go: beside the chip, as before.
let art = room.shrink(10.0);
assert_eq!(beside_art(art.center(), art, size, room), art.center() + egui::vec2(14.0, 14.0));
}
fn stops(app: &VectorcraftApp) -> Vec<GradientStop> {
match selected(app) {
Some(Target::Stop(stops, _)) => stops,