mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 23:01:02 +00:00
#6429: a value typed in a Stroke popup sticks when a click outside closes it
The Properties panel's Stroke link and the Appearance panel's Stroke: row open the Stroke panel as a popup. A click outside closed it without drawing it again, so the dash field (or any other field in it) never saw itself lose the keyboard, and what was typed went back. A popup closing while one of its fields has the keyboard now draws once more first: the field lets go of the keyboard to the click and commits, as it does in a panel. The Appearance popup closes on the press, before it is a click, so for that last frame a press is what lets go of the keyboard. Escape still drops what was typed.
This commit is contained in:
@@ -385,9 +385,23 @@ pub(crate) fn row_popup(app: &mut VectorcraftApp, ctx: &egui::Context, panel: Re
|
||||
let area_id = egui::Id::unique("ap-row-popup");
|
||||
let shown = ctx.memory(|m| m.area_rect(area_id));
|
||||
// A press outside it (not one that closes a dropdown the popup opened) or Escape puts it away.
|
||||
// With a field in it that has the keyboard, a press draws it once more first, so the field lets
|
||||
// go of the keyboard to that press and commits what was typed, as in a panel (#6429). Escape
|
||||
// drops what was typed.
|
||||
let pressed_outside = ctx.input(|i| i.pointer.any_pressed() && i.pointer.interact_pos().is_some_and(|p| shown.is_some_and(|r| !r.contains(p))));
|
||||
if pressed_outside && !egui::Popup::is_any_open(ctx) || ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Escape)) {
|
||||
let mut last = false;
|
||||
if ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Escape)) {
|
||||
return close(app);
|
||||
} else if pressed_outside && !egui::Popup::is_any_open(ctx) {
|
||||
if !widgets::field_focused_in(ctx, egui::LayerId::new(egui::Order::Foreground, area_id)) {
|
||||
return close(app);
|
||||
}
|
||||
last = true;
|
||||
}
|
||||
// Fields let go of the keyboard on a click, which this press isn't yet.
|
||||
let surrender = ctx.options(|o| o.input_options.surrender_focus_on);
|
||||
if last {
|
||||
ctx.options_mut(|o| o.input_options.surrender_focus_on = egui::SurrenderFocusOn::Presses);
|
||||
}
|
||||
let screen = ctx.content_rect();
|
||||
let left = panel.left() - POPUP_GAP - POPUP_W;
|
||||
@@ -415,6 +429,10 @@ pub(crate) fn row_popup(app: &mut VectorcraftApp, ctx: &egui::Context, panel: Re
|
||||
}
|
||||
});
|
||||
});
|
||||
if last {
|
||||
ctx.options_mut(|o| o.input_options.surrender_focus_on = surrender);
|
||||
close(app);
|
||||
}
|
||||
}
|
||||
|
||||
/// A row's name that edits the row when clicked ([`link`]), with the tooltip "Click to edit".
|
||||
|
||||
@@ -1994,3 +1994,31 @@ fn the_drop_buttons_light_up_while_a_row_is_dragged() {
|
||||
assert_eq!(drop_buttons_on(&ctx, &mut app, vec![]), [false, false], "dark again once it is released");
|
||||
assert_eq!(stack_items(&app).len(), 2, "released elsewhere: nothing removed");
|
||||
}
|
||||
|
||||
/// #6429: a dash length typed into the Stroke panel that the Appearance panel's "Stroke:" row or
|
||||
/// the Properties panel's Stroke link opens sticks when a click outside closes the popup, as it
|
||||
/// does when Enter ends the edit. The popup used to close without drawing again, so the field
|
||||
/// never saw itself lose the keyboard and the value went back.
|
||||
#[test]
|
||||
fn a_dash_typed_in_a_stroke_popup_sticks_when_a_click_outside_closes_it() {
|
||||
type Panel = fn(&mut VectorcraftApp, &mut egui::Ui);
|
||||
let hosts: [(&str, Panel); 2] = [("Stroke:", appearance::show), ("Stroke", super::properties::show)];
|
||||
for (link, show) in hosts {
|
||||
let mut app = app_with_rect();
|
||||
run(&mut app, "stroke.set", json!({"dash": [10.0]}));
|
||||
let ctx = egui::Context::default();
|
||||
let texts = frame_events(&ctx, &mut app, vec![], show);
|
||||
click(&ctx, &mut app, text_rect(&texts, link).center(), show);
|
||||
// The popup's first dash field: a click gives it the keyboard with its text selected.
|
||||
let texts = frame_events(&ctx, &mut app, vec![], show);
|
||||
click(&ctx, &mut app, text_rect(&texts, "10").center(), show);
|
||||
frame_events(&ctx, &mut app, vec![egui::Event::Text("4".into())], show);
|
||||
let dash = |app: &VectorcraftApp| current_stroke(app).and_then(|s| s.dash).map(|d| d.pattern);
|
||||
assert_eq!(dash(&app), Some(vec![10.0]), "{link}: nothing set while typing");
|
||||
click(&ctx, &mut app, egui::pos2(5.0, 3000.0), show);
|
||||
frame_events(&ctx, &mut app, vec![], show);
|
||||
assert_eq!(dash(&app).and_then(|p| p.first().copied()), Some(4.0), "{link}: the typed dash stuck: {:?}", dash(&app));
|
||||
let texts = frame_events(&ctx, &mut app, vec![], show);
|
||||
assert!(!texts.iter().any(|(t, _)| t == "Dashed Line"), "{link}: the popup closed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ pub use drag::{
|
||||
use dropdowns::combo_button_id;
|
||||
#[cfg(test)]
|
||||
pub(crate) use dropdowns::menu_floor;
|
||||
pub(crate) use dropdowns::{combo, combo_menu, combo_menu_field, combo_named, popover_is_open};
|
||||
pub(crate) use dropdowns::{combo, combo_menu, combo_menu_field, combo_named, field_focused_in, popover_is_open};
|
||||
pub use dropdowns::{
|
||||
dropdown, dropdown_named, dropdown_names, dropdown_names_named, dropdown_with, dropdown_with_named, list_scroll_style, menu_room, menu_scroll,
|
||||
panel_scroll, popover, set_menu_floor, strip_scroll,
|
||||
|
||||
@@ -234,8 +234,13 @@ pub fn popover<R>(resp: &Response, toggle: bool, content: impl FnOnce(&mut Ui) -
|
||||
&& click_pos.is_some_and(|pos| ctx.layer_id_at(pos).is_none_or(|l| l.order != egui::Order::Foreground));
|
||||
was_open && !clicked_elsewhere
|
||||
};
|
||||
// Closing with a field in it that has the keyboard: it draws once more, so the field lets go of
|
||||
// the keyboard to the click and commits what was typed, as in a panel (#6429).
|
||||
let last = was_open && !open && field_focused_in(ctx, egui::LayerId::new(egui::Order::Foreground, id));
|
||||
let mut shown = open || last;
|
||||
let inner =
|
||||
egui::Popup::menu(resp).id(id).open_bool(&mut open).close_behavior(egui::PopupCloseBehavior::IgnoreClicks).show(content).map(|r| r.inner);
|
||||
egui::Popup::menu(resp).id(id).open_bool(&mut shown).close_behavior(egui::PopupCloseBehavior::IgnoreClicks).show(content).map(|r| r.inner);
|
||||
open = shown && !last;
|
||||
ctx.data_mut(|d| {
|
||||
if open {
|
||||
d.insert_temp(id, frame);
|
||||
@@ -246,6 +251,13 @@ pub fn popover<R>(resp: &Response, toggle: bool, content: impl FnOnce(&mut Ui) -
|
||||
inner
|
||||
}
|
||||
|
||||
/// Whether a field in the popup on `layer` has the keyboard. A popup closing with one draws once
|
||||
/// more before it goes: the field lets go of the keyboard to the click away and commits what was
|
||||
/// typed, as it does in a panel. A popup that closed without drawing again dropped it (#6429).
|
||||
pub(crate) fn field_focused_in(ctx: &egui::Context, layer: egui::LayerId) -> bool {
|
||||
ctx.memory(|m| m.focused()).and_then(|id| ctx.read_response(id)).is_some_and(|r| r.layer_id == layer)
|
||||
}
|
||||
|
||||
/// Whether the popover for this anchor was open in the latest completed frame.
|
||||
pub(crate) fn popover_is_open(ctx: &egui::Context, anchor: egui::Id) -> bool {
|
||||
let frame = ctx.cumulative_frame_nr();
|
||||
|
||||
Reference in New Issue
Block a user