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
This commit is contained in:
parent
e13cc69d76
commit
b6fe244810
|
|
@ -1608,8 +1608,7 @@ impl ContextImpl {
|
||||||
|
|
||||||
viewport.repaint.frame_nr += 1;
|
viewport.repaint.frame_nr += 1;
|
||||||
|
|
||||||
self.memory
|
self.memory.end_frame(&viewport.frame_state.used_ids);
|
||||||
.end_frame(&viewport.input, &viewport.frame_state.used_ids);
|
|
||||||
|
|
||||||
if let Some(fonts) = self.fonts.get(&pixels_per_point.into()) {
|
if let Some(fonts) = self.fonts.get(&pixels_per_point.into()) {
|
||||||
let tex_mngr = &mut self.tex_manager.0.write();
|
let tex_mngr = &mut self.tex_manager.0.write();
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ use epaint::{emath::Rangef, vec2, Vec2};
|
||||||
use crate::{
|
use crate::{
|
||||||
area,
|
area,
|
||||||
window::{self, WindowInteraction},
|
window::{self, WindowInteraction},
|
||||||
EventFilter, Id, IdMap, InputState, LayerId, Pos2, Rect, Style, ViewportId, ViewportIdMap,
|
EventFilter, Id, IdMap, LayerId, Pos2, Rect, Style, ViewportId, ViewportIdMap, ViewportIdSet,
|
||||||
ViewportIdSet,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
@ -79,9 +78,6 @@ pub struct Memory {
|
||||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||||
pub(crate) viewport_id: ViewportId,
|
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)?
|
/// Which popup-window is open (if any)?
|
||||||
/// Could be a combo box, color picker, menu etc.
|
/// Could be a combo box, color picker, menu etc.
|
||||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||||
|
|
@ -111,7 +107,6 @@ impl Default for Memory {
|
||||||
interactions: Default::default(),
|
interactions: Default::default(),
|
||||||
viewport_id: Default::default(),
|
viewport_id: Default::default(),
|
||||||
window_interactions: Default::default(),
|
window_interactions: Default::default(),
|
||||||
drag_value: Default::default(),
|
|
||||||
areas: Default::default(),
|
areas: Default::default(),
|
||||||
popup: Default::default(),
|
popup: Default::default(),
|
||||||
everything_is_visible: 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<Rect>) {
|
pub(crate) fn end_frame(&mut self, used_ids: &IdMap<Rect>) {
|
||||||
self.caches.update();
|
self.caches.update();
|
||||||
self.areas_mut().end_frame();
|
self.areas_mut().end_frame();
|
||||||
self.interaction_mut().focus.end_frame(used_ids);
|
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) {
|
pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) {
|
||||||
|
|
|
||||||
|
|
@ -476,13 +476,22 @@ impl IdTypeMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the state of this type an id.
|
/// Remove the state of this type and id.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove<T: 'static>(&mut self, id: Id) {
|
pub fn remove<T: 'static>(&mut self, id: Id) {
|
||||||
let hash = hash(TypeId::of::<T>(), id);
|
let hash = hash(TypeId::of::<T>(), id);
|
||||||
self.map.remove(&hash);
|
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.
|
/// Note all state of the given type.
|
||||||
pub fn remove_by_type<T: 'static>(&mut self) {
|
pub fn remove_by_type<T: 'static>(&mut self) {
|
||||||
let key = TypeId::of::<T>();
|
let key = TypeId::of::<T>();
|
||||||
|
|
|
||||||
|
|
@ -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 NumFormatter<'a> = Box<dyn 'a + Fn(f64, RangeInclusive<usize>) -> String>;
|
||||||
type NumParser<'a> = Box<dyn 'a + Fn(&str) -> Option<f64>>;
|
type NumParser<'a> = Box<dyn 'a + Fn(&str) -> Option<f64>>;
|
||||||
|
|
||||||
|
|
@ -402,13 +380,13 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
// screen readers.
|
// screen readers.
|
||||||
let is_kb_editing = ui.memory_mut(|mem| {
|
let is_kb_editing = ui.memory_mut(|mem| {
|
||||||
mem.interested_in_focus(id);
|
mem.interested_in_focus(id);
|
||||||
let is_kb_editing = mem.has_focus(id);
|
mem.has_focus(id)
|
||||||
if mem.gained_focus(id) {
|
|
||||||
mem.drag_value.edit_string = None;
|
|
||||||
}
|
|
||||||
is_kb_editing
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 old_value = get(&mut get_set_value);
|
||||||
let mut value = old_value;
|
let mut value = old_value;
|
||||||
let aim_rad = ui.input(|i| i.aim_radius() as f64);
|
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());
|
value = clamp_to_range(value, clamp_range.clone());
|
||||||
if old_value != value {
|
if old_value != value {
|
||||||
set(&mut get_set_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 {
|
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();
|
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
|
// some clones below are redundant if AccessKit is disabled
|
||||||
#[allow(clippy::redundant_clone)]
|
#[allow(clippy::redundant_clone)]
|
||||||
let mut response = if is_kb_editing {
|
let mut response = if is_kb_editing {
|
||||||
let mut value_text = ui
|
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());
|
.unwrap_or_else(|| value_text.clone());
|
||||||
let response = ui.add(
|
let response = ui.add(
|
||||||
TextEdit::singleline(&mut value_text)
|
TextEdit::singleline(&mut value_text)
|
||||||
|
|
@ -509,7 +503,7 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
response.lost_focus()
|
response.lost_focus()
|
||||||
};
|
};
|
||||||
if update {
|
if update {
|
||||||
let parsed_value = match custom_parser {
|
let parsed_value = match &custom_parser {
|
||||||
Some(parser) => parser(&value_text),
|
Some(parser) => parser(&value_text),
|
||||||
None => value_text.parse().ok(),
|
None => value_text.parse().ok(),
|
||||||
};
|
};
|
||||||
|
|
@ -518,7 +512,7 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
set(&mut get_set_value, parsed_value);
|
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
|
response
|
||||||
} else {
|
} else {
|
||||||
let button = Button::new(
|
let button = Button::new(
|
||||||
|
|
@ -533,7 +527,7 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
let mut response = response.on_hover_cursor(CursorIcon::ResizeHorizontal);
|
let mut response = response.on_hover_cursor(CursorIcon::ResizeHorizontal);
|
||||||
|
|
||||||
if ui.style().explanation_tooltips {
|
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.",
|
"{}{}{}\nDrag to edit or click to enter a value.\nPress 'Shift' while dragging for better control.",
|
||||||
prefix,
|
prefix,
|
||||||
value as f32, // Show full precision value on-hover. TODO(emilk): figure out f64 vs f32
|
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::<f64>(id));
|
||||||
|
}
|
||||||
|
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
ui.memory_mut(|mem| {
|
ui.data_mut(|data| data.remove::<String>(id));
|
||||||
mem.drag_value.edit_string = None;
|
ui.memory_mut(|mem| mem.request_focus(id));
|
||||||
mem.request_focus(id);
|
|
||||||
});
|
|
||||||
let mut state = TextEdit::load_state(ui.ctx(), id).unwrap_or_default();
|
let mut state = TextEdit::load_state(ui.ctx(), id).unwrap_or_default();
|
||||||
state.set_ccursor_range(Some(text::CCursorRange::two(
|
state.set_ccursor_range(Some(text::CCursorRange::two(
|
||||||
epaint::text::cursor::CCursor::default(),
|
text::CCursor::default(),
|
||||||
epaint::text::cursor::CCursor::new(value_text.chars().count()),
|
text::CCursor::new(value_text.chars().count()),
|
||||||
)));
|
)));
|
||||||
state.store(ui.ctx(), response.id);
|
state.store(ui.ctx(), response.id);
|
||||||
} else if response.dragged() {
|
} else if response.dragged() {
|
||||||
|
|
@ -563,28 +560,22 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
let delta_value = delta_points as f64 * speed;
|
let delta_value = delta_points as f64 * speed;
|
||||||
|
|
||||||
if delta_value != 0.0 {
|
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:
|
// 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))
|
let precise_value = ui.data_mut(|data| data.get_temp::<f64>(id));
|
||||||
.then_some(drag_state.last_dragged_value)
|
let precise_value = precise_value.unwrap_or(value);
|
||||||
.flatten();
|
let precise_value = precise_value + delta_value;
|
||||||
let stored_value = stored_value.unwrap_or(value);
|
|
||||||
let stored_value = stored_value + delta_value;
|
|
||||||
|
|
||||||
let aim_delta = aim_rad * speed;
|
let aim_delta = aim_rad * speed;
|
||||||
let rounded_new_value = emath::smart_aim::best_in_range_f64(
|
let rounded_new_value = emath::smart_aim::best_in_range_f64(
|
||||||
stored_value - aim_delta,
|
precise_value - aim_delta,
|
||||||
stored_value + aim_delta,
|
precise_value + aim_delta,
|
||||||
);
|
);
|
||||||
let rounded_new_value =
|
let rounded_new_value =
|
||||||
emath::round_to_decimals(rounded_new_value, auto_decimals);
|
emath::round_to_decimals(rounded_new_value, auto_decimals);
|
||||||
let rounded_new_value = clamp_to_range(rounded_new_value, clamp_range.clone());
|
let rounded_new_value = clamp_to_range(rounded_new_value, clamp_range.clone());
|
||||||
set(&mut get_set_value, rounded_new_value);
|
set(&mut get_set_value, rounded_new_value);
|
||||||
|
|
||||||
drag_state.last_dragged_id = Some(response.id);
|
ui.data_mut(|data| data.insert_temp::<f64>(id, precise_value));
|
||||||
drag_state.last_dragged_value = Some(stored_value);
|
|
||||||
ui.memory_mut(|mem| mem.drag_value = drag_state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -634,11 +634,8 @@ impl<'a> Table<'a> {
|
||||||
// Hide first-frame-jitters when auto-sizing.
|
// Hide first-frame-jitters when auto-sizing.
|
||||||
ui.add_visible_ui(!first_frame_auto_size_columns, |ui| {
|
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_id = self.state_id.with("__table_hovered_row");
|
||||||
let hovered_row_index = ui.memory_mut(|w| {
|
let hovered_row_index =
|
||||||
let hovered_row = w.data.get_temp(hovered_row_index_id);
|
ui.data_mut(|data| data.remove_temp::<usize>(hovered_row_index_id));
|
||||||
w.data.remove::<usize>(hovered_row_index_id);
|
|
||||||
hovered_row
|
|
||||||
});
|
|
||||||
|
|
||||||
let layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout, sense);
|
let layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout, sense);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue