From b6fe24481072222da4f88758c987397517a440f5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 6 Jan 2024 23:51:51 +0100 Subject: [PATCH] Fix: apply edited `DragValue` when it looses focus (#3776) * Closes https://github.com/emilk/egui/issues/2877 * Closes https://github.com/emilk/egui/pull/3451 --- crates/egui/src/context.rs | 3 +- crates/egui/src/memory.rs | 10 +-- crates/egui/src/util/id_type_map.rs | 11 +++- crates/egui/src/widgets/drag_value.rs | 91 ++++++++++++--------------- crates/egui_extras/src/table.rs | 7 +-- 5 files changed, 56 insertions(+), 66 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 02d58390..b1b96ebc 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1608,8 +1608,7 @@ impl ContextImpl { viewport.repaint.frame_nr += 1; - self.memory - .end_frame(&viewport.input, &viewport.frame_state.used_ids); + self.memory.end_frame(&viewport.frame_state.used_ids); if let Some(fonts) = self.fonts.get(&pixels_per_point.into()) { let tex_mngr = &mut self.tex_manager.0.write(); diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index a96d8d97..8aca0d2d 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -5,8 +5,7 @@ use epaint::{emath::Rangef, vec2, Vec2}; use crate::{ area, window::{self, WindowInteraction}, - EventFilter, Id, IdMap, InputState, LayerId, Pos2, Rect, Style, ViewportId, ViewportIdMap, - ViewportIdSet, + EventFilter, Id, IdMap, LayerId, Pos2, Rect, Style, ViewportId, ViewportIdMap, ViewportIdSet, }; // ---------------------------------------------------------------------------- @@ -79,9 +78,6 @@ pub struct Memory { #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) viewport_id: ViewportId, - #[cfg_attr(feature = "persistence", serde(skip))] - pub(crate) drag_value: crate::widgets::drag_value::MonoState, - /// Which popup-window is open (if any)? /// Could be a combo box, color picker, menu etc. #[cfg_attr(feature = "persistence", serde(skip))] @@ -111,7 +107,6 @@ impl Default for Memory { interactions: Default::default(), viewport_id: Default::default(), window_interactions: Default::default(), - drag_value: Default::default(), areas: Default::default(), popup: Default::default(), everything_is_visible: Default::default(), @@ -587,11 +582,10 @@ impl Memory { } } - pub(crate) fn end_frame(&mut self, input: &InputState, used_ids: &IdMap) { + pub(crate) fn end_frame(&mut self, used_ids: &IdMap) { self.caches.update(); self.areas_mut().end_frame(); self.interaction_mut().focus.end_frame(used_ids); - self.drag_value.end_frame(input); } pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) { diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index d7af233a..694d1de3 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -476,13 +476,22 @@ impl IdTypeMap { } } - /// Remove the state of this type an id. + /// Remove the state of this type and id. #[inline] pub fn remove(&mut self, id: Id) { let hash = hash(TypeId::of::(), id); self.map.remove(&hash); } + /// Remove and fetch the state of this type and id. + #[inline] + pub fn remove_temp(&mut self, id: Id) -> Option { + let hash = hash(TypeId::of::(), id); + self.map + .remove(&hash) + .and_then(|element| element.get_temp().cloned()) + } + /// Note all state of the given type. pub fn remove_by_type(&mut self) { let key = TypeId::of::(); diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index d9368e22..c4834a03 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -6,28 +6,6 @@ use crate::*; // ---------------------------------------------------------------------------- -/// Same state for all [`DragValue`]s. -#[derive(Clone, Debug, Default)] -pub(crate) struct MonoState { - last_dragged_id: Option, - last_dragged_value: Option, - - /// For temporary edit of a [`DragValue`] value. - /// Couples with the current focus id. - edit_string: Option, -} - -impl MonoState { - pub(crate) fn end_frame(&mut self, input: &InputState) { - if input.pointer.any_pressed() || input.pointer.any_released() { - self.last_dragged_id = None; - self.last_dragged_value = None; - } - } -} - -// ---------------------------------------------------------------------------- - type NumFormatter<'a> = Box) -> String>; type NumParser<'a> = Box Option>; @@ -402,13 +380,13 @@ impl<'a> Widget for DragValue<'a> { // screen readers. let is_kb_editing = ui.memory_mut(|mem| { mem.interested_in_focus(id); - let is_kb_editing = mem.has_focus(id); - if mem.gained_focus(id) { - mem.drag_value.edit_string = None; - } - is_kb_editing + mem.has_focus(id) }); + if ui.memory_mut(|mem| mem.gained_focus(id)) { + ui.data_mut(|data| data.remove::(id)); + } + let old_value = get(&mut get_set_value); let mut value = old_value; let aim_rad = ui.input(|i| i.aim_radius() as f64); @@ -467,7 +445,7 @@ impl<'a> Widget for DragValue<'a> { value = clamp_to_range(value, clamp_range.clone()); if old_value != value { set(&mut get_set_value, value); - ui.memory_mut(|mem| mem.drag_value.edit_string = None); + ui.data_mut(|data| data.remove::(id)); } let value_text = match custom_formatter { @@ -483,11 +461,27 @@ impl<'a> Widget for DragValue<'a> { let text_style = ui.style().drag_value_text_style.clone(); + if ui.memory(|mem| mem.lost_focus(id)) { + let value_text = ui.data_mut(|data| data.remove_temp::(id)); + if let Some(value_text) = value_text { + // We were editing the value as text last frame, but lost focus. + // Make sure we applied the last text value: + let parsed_value = match &custom_parser { + Some(parser) => parser(&value_text), + None => value_text.parse().ok(), + }; + if let Some(parsed_value) = parsed_value { + let parsed_value = clamp_to_range(parsed_value, clamp_range.clone()); + set(&mut get_set_value, parsed_value); + } + } + } + // some clones below are redundant if AccessKit is disabled #[allow(clippy::redundant_clone)] let mut response = if is_kb_editing { let mut value_text = ui - .memory_mut(|mem| mem.drag_value.edit_string.take()) + .data_mut(|data| data.remove_temp::(id)) .unwrap_or_else(|| value_text.clone()); let response = ui.add( TextEdit::singleline(&mut value_text) @@ -509,7 +503,7 @@ impl<'a> Widget for DragValue<'a> { response.lost_focus() }; if update { - let parsed_value = match custom_parser { + let parsed_value = match &custom_parser { Some(parser) => parser(&value_text), None => value_text.parse().ok(), }; @@ -518,7 +512,7 @@ impl<'a> Widget for DragValue<'a> { set(&mut get_set_value, parsed_value); } } - ui.memory_mut(|mem| mem.drag_value.edit_string = Some(value_text)); + ui.data_mut(|data| data.insert_temp(id, value_text)); response } else { let button = Button::new( @@ -533,7 +527,7 @@ impl<'a> Widget for DragValue<'a> { let mut response = response.on_hover_cursor(CursorIcon::ResizeHorizontal); if ui.style().explanation_tooltips { - response = response .on_hover_text(format!( + response = response.on_hover_text(format!( "{}{}{}\nDrag to edit or click to enter a value.\nPress 'Shift' while dragging for better control.", prefix, value as f32, // Show full precision value on-hover. TODO(emilk): figure out f64 vs f32 @@ -541,15 +535,18 @@ impl<'a> Widget for DragValue<'a> { )); } + if ui.input(|i| i.pointer.any_pressed() || i.pointer.any_released()) { + // Reset memory of preciely dagged value. + ui.data_mut(|data| data.remove::(id)); + } + if response.clicked() { - ui.memory_mut(|mem| { - mem.drag_value.edit_string = None; - mem.request_focus(id); - }); + ui.data_mut(|data| data.remove::(id)); + ui.memory_mut(|mem| mem.request_focus(id)); let mut state = TextEdit::load_state(ui.ctx(), id).unwrap_or_default(); state.set_ccursor_range(Some(text::CCursorRange::two( - epaint::text::cursor::CCursor::default(), - epaint::text::cursor::CCursor::new(value_text.chars().count()), + text::CCursor::default(), + text::CCursor::new(value_text.chars().count()), ))); state.store(ui.ctx(), response.id); } else if response.dragged() { @@ -563,28 +560,22 @@ impl<'a> Widget for DragValue<'a> { let delta_value = delta_points as f64 * speed; if delta_value != 0.0 { - let mut drag_state = ui.memory_mut(|mem| std::mem::take(&mut mem.drag_value)); - // Since we round the value being dragged, we need to store the full precision value in memory: - let stored_value = (drag_state.last_dragged_id == Some(response.id)) - .then_some(drag_state.last_dragged_value) - .flatten(); - let stored_value = stored_value.unwrap_or(value); - let stored_value = stored_value + delta_value; + let precise_value = ui.data_mut(|data| data.get_temp::(id)); + let precise_value = precise_value.unwrap_or(value); + let precise_value = precise_value + delta_value; let aim_delta = aim_rad * speed; let rounded_new_value = emath::smart_aim::best_in_range_f64( - stored_value - aim_delta, - stored_value + aim_delta, + precise_value - aim_delta, + precise_value + aim_delta, ); let rounded_new_value = emath::round_to_decimals(rounded_new_value, auto_decimals); let rounded_new_value = clamp_to_range(rounded_new_value, clamp_range.clone()); set(&mut get_set_value, rounded_new_value); - drag_state.last_dragged_id = Some(response.id); - drag_state.last_dragged_value = Some(stored_value); - ui.memory_mut(|mem| mem.drag_value = drag_state); + ui.data_mut(|data| data.insert_temp::(id, precise_value)); } } diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 2e6655c7..510f593c 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -634,11 +634,8 @@ impl<'a> Table<'a> { // Hide first-frame-jitters when auto-sizing. ui.add_visible_ui(!first_frame_auto_size_columns, |ui| { let hovered_row_index_id = self.state_id.with("__table_hovered_row"); - let hovered_row_index = ui.memory_mut(|w| { - let hovered_row = w.data.get_temp(hovered_row_index_id); - w.data.remove::(hovered_row_index_id); - hovered_row - }); + let hovered_row_index = + ui.data_mut(|data| data.remove_temp::(hovered_row_index_id)); let layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout, sense);