mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
This commit is contained in:
@@ -142,7 +142,9 @@ pub fn dropdown(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
|
||||
let current_user = users.iter().rev().find(|(_, f)| matches(o, f)).map(|(n, _)| n.clone());
|
||||
let can_new = savable(o).is_some();
|
||||
let mut chosen: Option<Choice> = None;
|
||||
let parent_layer = ui.layer_id();
|
||||
egui::ComboBox::from_id_salt("crop-ratio").selected_text(label).width(140.0).height(420.0).icon(crate::widgets::chevron_icon).show_ui(ui, |ui| {
|
||||
crate::press_menu::above_parent_ui(parent_layer, ui);
|
||||
let (before, after): (Vec<_>, Vec<_>) = builtin.iter().partition(|(k, _)| k != crop_size::FRONT);
|
||||
for (k, l) in &before {
|
||||
if ui.selectable_label(current_user.is_none() && *k == o.crop_ratio, tl!(l)).clicked() {
|
||||
|
||||
@@ -74,3 +74,56 @@ fn a_click_still_opens_the_list_for_a_second_click() {
|
||||
h.run_steps(3);
|
||||
assert_eq!(blend(&h), BlendMode::Screen, "a second click picks");
|
||||
}
|
||||
|
||||
/// #7113: on macOS a trackpad tap-to-click delivers `Pressed` and `Released` in the same frame,
|
||||
/// which raises both the modal dialog (`pointer_pressed_on_area`) and the reopened ComboBox popup
|
||||
/// (`!visible_last_frame`) in the same pass. When the popup's static LayerId already sits before
|
||||
/// the dialog's in `Areas::order` (from an earlier open or `ui.ron`), egui's stable sort left the
|
||||
/// dropdown menu behind the dialog window.
|
||||
#[test]
|
||||
fn single_frame_trackpad_tap_keeps_dialog_dropdown_above_modal_and_selects_option() {
|
||||
let mut h = app_with_a_layer();
|
||||
let tap = |h: &mut Harness<'_, PhotocraftApp>, at: egui::Pos2| {
|
||||
h.input_mut().events.push(egui::Event::PointerMoved(at));
|
||||
for pressed in [true, false] {
|
||||
h.input_mut().events.push(egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE });
|
||||
}
|
||||
h.run_steps(3);
|
||||
};
|
||||
|
||||
// First dialog: open the Palette dropdown once so its static popup LayerId enters `Areas::order`,
|
||||
// then close the dialog.
|
||||
let d1 = crate::filter_dialog::open(h.state_mut(), "image.mode.indexedColor").unwrap();
|
||||
h.run_steps(3);
|
||||
let palette_box = blend_box(&h, "Selective").center();
|
||||
tap(&mut h, palette_box);
|
||||
assert!(egui::Popup::is_any_open(&h.ctx));
|
||||
crate::dialogs::cancel(h.state_mut(), d1).unwrap();
|
||||
h.run_steps(2);
|
||||
|
||||
// Second dialog gets a newer `("dialog", d2)` LayerId placed after the popup's static LayerId
|
||||
// in `Areas::order` (the same ordering `ui.ron` restores across launches).
|
||||
let d2 = crate::filter_dialog::open(h.state_mut(), "image.mode.indexedColor").unwrap();
|
||||
h.run_steps(3);
|
||||
let palette_box = blend_box(&h, "Selective").center();
|
||||
let dialog_layer = h.ctx.layer_id_at(palette_box).expect("dialog layer at ComboBox");
|
||||
|
||||
// 1-frame trackpad tap on the Palette dropdown: the list must open IN FRONT OF the dialog.
|
||||
tap(&mut h, palette_box);
|
||||
assert!(egui::Popup::is_any_open(&h.ctx), "tap opens the dropdown");
|
||||
let perceptual_pos = h.get_by_label("Perceptual").rect().center();
|
||||
let top_at_item = h.ctx.layer_id_at(perceptual_pos);
|
||||
assert!(
|
||||
top_at_item.is_some() && top_at_item != Some(dialog_layer),
|
||||
"#7113: dropdown popup layer ({top_at_item:?}) must be above the modal dialog ({dialog_layer:?})"
|
||||
);
|
||||
|
||||
// Second 1-frame trackpad tap on "Perceptual" selects it and closes the list.
|
||||
tap(&mut h, perceptual_pos);
|
||||
assert!(!egui::Popup::is_any_open(&h.ctx), "tap on option closes the dropdown");
|
||||
assert_eq!(
|
||||
h.state().ui.dialogs.iter().find(|d| d.id == d2).and_then(|d| d.fields.get("palette")).and_then(|v| v.as_str()),
|
||||
Some("perceptual"),
|
||||
"tap on Perceptual selected it"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,9 @@ pub(super) fn row(ui: &mut egui::Ui, f: &mut Map<String, Value>, effect: &str, k
|
||||
open = !open;
|
||||
}
|
||||
let mut chosen: Option<&str> = None;
|
||||
let parent_layer = arrow.layer_id;
|
||||
egui::Popup::from_response(&arrow).open_bool(&mut open).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| {
|
||||
crate::press_menu::above_parent_ui(parent_layer, ui);
|
||||
ui.spacing_mut().item_spacing = vec2(4.0, 4.0);
|
||||
egui::Grid::new(("contour-presets", effect, key)).spacing(vec2(4.0, 4.0)).show(ui, |ui| {
|
||||
for (i, (name, _)) in CONTOURS.iter().enumerate() {
|
||||
|
||||
@@ -79,6 +79,22 @@ impl PressMenu {
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep an open popup or dropdown layer above `parent` when `parent` is also in
|
||||
/// [`egui::Order::Foreground`] (a modal dialog or popover, #7113): a 1-frame trackpad tap raises
|
||||
/// both the parent modal (`pointer_pressed_on_area`) and the reopened popup (`!visible_last_frame`)
|
||||
/// in the same pass, and egui's stable `wants_to_be_on_top` sort then leaves a previously used
|
||||
/// popup behind the dialog.
|
||||
pub fn above_parent(ctx: &egui::Context, parent: egui::LayerId, child: egui::LayerId) {
|
||||
if parent.order == child.order && parent != child {
|
||||
ctx.set_sublayer(parent, child);
|
||||
}
|
||||
}
|
||||
|
||||
/// [`above_parent`] for an open popup's `Ui`.
|
||||
pub fn above_parent_ui(parent: egui::LayerId, popup_ui: &egui::Ui) {
|
||||
above_parent(popup_ui.ctx(), parent, popup_ui.layer_id());
|
||||
}
|
||||
|
||||
/// Shows `button`'s menu (an `egui::Popup::menu`) with press-drag-release; `content` chooses its
|
||||
/// items with [`PressMenu::item`].
|
||||
pub fn show<R>(button: &Response, content: impl FnOnce(&mut Ui, &PressMenu) -> R) -> Option<R> {
|
||||
@@ -89,6 +105,9 @@ pub fn show<R>(button: &Response, content: impl FnOnce(&mut Ui, &PressMenu) -> R
|
||||
// close it again.
|
||||
let close = if button.clicked() && menu.owns_gesture(&ctx) { PopupCloseBehavior::IgnoreClicks } else { PopupCloseBehavior::CloseOnClick };
|
||||
let shown = Popup::menu(button).open_memory(open).close_behavior(close).show(|ui| content(ui, &menu)).map(|r| r.inner);
|
||||
if shown.is_some() || Popup::is_id_open(&ctx, menu.popup) {
|
||||
above_parent(&ctx, button.layer_id, egui::LayerId::new(egui::Order::Foreground, menu.popup));
|
||||
}
|
||||
// This menu's gesture ends with the button (its release was handled by the items above).
|
||||
if menu.owns_gesture(&ctx) && ctx.input(|i| i.pointer.primary_released() || !i.pointer.primary_down()) {
|
||||
ctx.data_mut(|d| d.remove::<egui::Id>(gesture_id()));
|
||||
@@ -150,6 +169,9 @@ impl PressCombo {
|
||||
} else if self.owns_gesture(ctx) && ctx.input(|i| i.pointer.primary_released() || !i.pointer.primary_down()) {
|
||||
ctx.data_mut(|d| d.remove::<egui::Id>(gesture_id()));
|
||||
}
|
||||
if Popup::is_id_open(ctx, self.popup) {
|
||||
above_parent(ctx, button.layer_id, egui::LayerId::new(egui::Order::Foreground, self.popup));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -740,7 +740,9 @@ pub fn popup_value_field(ui: &mut Ui, name: &str, value: &mut f32, range: std::o
|
||||
ui.painter().rect_filled(arrow, t.radius_sm, t.field_border.gamma_multiply(0.6));
|
||||
}
|
||||
chevron_icon(ui, arrow, ui.style().interact(&resp), open);
|
||||
let parent_layer = resp.layer_id;
|
||||
popup.show(|ui| {
|
||||
crate::press_menu::above_parent_ui(parent_layer, ui);
|
||||
ui.set_width(POPUP_SLIDER_W);
|
||||
let s = slider(ui, value, range, None);
|
||||
s.widget_info(|| egui::WidgetInfo::slider(ui.is_enabled(), f64::from(*value), &name));
|
||||
@@ -1666,6 +1668,7 @@ pub fn swatch_popup(swatch: &Response) -> egui::Popup<'static> {
|
||||
ctx.data_mut(|d| d.remove::<f32>(key));
|
||||
return popup;
|
||||
}
|
||||
crate::press_menu::above_parent(ctx, swatch.layer_id, egui::LayerId::new(egui::Order::Foreground, popup.get_id()));
|
||||
// Its width is known from the frame after it opened (egui sizes it unseen first).
|
||||
let left = ctx.data(|d| d.get_temp::<f32>(key)).or_else(|| {
|
||||
let width = popup.get_expected_size()?.x;
|
||||
|
||||
Reference in New Issue
Block a user