Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: apply edited DragValue when it looses focus #3776

Merged
merged 8 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading