Skip to content

Commit

Permalink
Fix: apply edited DragValue when it looses focus (#3776)
Browse files Browse the repository at this point in the history
* Closes #2877
* Closes #3451
  • Loading branch information
emilk authored Jan 6, 2024
1 parent e13cc69 commit b6fe244
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 66 deletions.
3 changes: 1 addition & 2 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
10 changes: 2 additions & 8 deletions crates/egui/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -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))]
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -587,11 +582,10 @@ impl Memory {
}
}

pub(crate) fn end_frame(&mut self, input: &InputState, used_ids: &IdMap<Rect>) {
pub(crate) fn end_frame(&mut self, used_ids: &IdMap<Rect>) {
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) {
Expand Down
11 changes: 10 additions & 1 deletion crates/egui/src/util/id_type_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: 'static>(&mut self, id: Id) {
let hash = hash(TypeId::of::<T>(), id);
self.map.remove(&hash);
}

/// Remove and fetch the state of this type and id.
#[inline]
pub fn remove_temp<T: 'static + Clone>(&mut self, id: Id) -> Option<T> {
let hash = hash(TypeId::of::<T>(), id);
self.map
.remove(&hash)
.and_then(|element| element.get_temp().cloned())
}

/// Note all state of the given type.
pub fn remove_by_type<T: 'static>(&mut self) {
let key = TypeId::of::<T>();
Expand Down
91 changes: 41 additions & 50 deletions crates/egui/src/widgets/drag_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,6 @@ use crate::*;

// ----------------------------------------------------------------------------

/// Same state for all [`DragValue`]s.
#[derive(Clone, Debug, Default)]
pub(crate) struct MonoState {
last_dragged_id: Option<Id>,
last_dragged_value: Option<f64>,

/// For temporary edit of a [`DragValue`] value.
/// Couples with the current focus id.
edit_string: Option<String>,
}

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<dyn 'a + Fn(f64, RangeInclusive<usize>) -> String>;
type NumParser<'a> = Box<dyn 'a + Fn(&str) -> Option<f64>>;

Expand Down Expand Up @@ -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::<String>(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);
Expand Down Expand Up @@ -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::<String>(id));
}

let value_text = match custom_formatter {
Expand All @@ -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::<String>(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::<String>(id))
.unwrap_or_else(|| value_text.clone());
let response = ui.add(
TextEdit::singleline(&mut value_text)
Expand All @@ -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(),
};
Expand All @@ -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(
Expand All @@ -533,23 +527,26 @@ 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
suffix
));
}

if ui.input(|i| i.pointer.any_pressed() || i.pointer.any_released()) {
// Reset memory of preciely dagged value.
ui.data_mut(|data| data.remove::<f64>(id));
}

if response.clicked() {
ui.memory_mut(|mem| {
mem.drag_value.edit_string = None;
mem.request_focus(id);
});
ui.data_mut(|data| data.remove::<String>(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() {
Expand All @@ -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::<f64>(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::<f64>(id, precise_value));
}
}

Expand Down
7 changes: 2 additions & 5 deletions crates/egui_extras/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<usize>(hovered_row_index_id);
hovered_row
});
let hovered_row_index =
ui.data_mut(|data| data.remove_temp::<usize>(hovered_row_index_id));

let layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout, sense);

Expand Down

0 comments on commit b6fe244

Please sign in to comment.