Add Response::changed(): see if e.g. text was entered or slider dragged
This commit is contained in:
parent
a859b2a26e
commit
d3fd51d6a4
10 changed files with 83 additions and 17 deletions
|
@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
* Add `Ui::hyperlink_to(label, url)`.
|
* Add `Ui::hyperlink_to(label, url)`.
|
||||||
* Sliders can now have a value prefix and suffix (e.g. the suffix `"°"` works like a unit).
|
* Sliders can now have a value prefix and suffix (e.g. the suffix `"°"` works like a unit).
|
||||||
* `Context::set_pixels_per_point` to control the scale of the UI.
|
* `Context::set_pixels_per_point` to control the scale of the UI.
|
||||||
|
* Add `Response::changed()` to query if e.g. a slider was dragged, text was entered or a checkbox was clicked.
|
||||||
* Add support for all integers in `DragValue` and `Slider` (except 128-bit).
|
* Add support for all integers in `DragValue` and `Slider` (except 128-bit).
|
||||||
|
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
|
|
|
@ -198,6 +198,7 @@ impl CtxRef {
|
||||||
interact_pointer_pos: None,
|
interact_pointer_pos: None,
|
||||||
has_kb_focus,
|
has_kb_focus,
|
||||||
lost_kb_focus,
|
lost_kb_focus,
|
||||||
|
changed: false, // must be set by the widget itself
|
||||||
};
|
};
|
||||||
|
|
||||||
if !enabled || sense == Sense::hover() || !layer_id.allow_interaction() {
|
if !enabled || sense == Sense::hover() || !layer_id.allow_interaction() {
|
||||||
|
|
|
@ -8,8 +8,11 @@ use crate::{CtxRef, Id, LayerId, Sense, Ui};
|
||||||
|
|
||||||
/// The result of adding a widget to a [`Ui`].
|
/// The result of adding a widget to a [`Ui`].
|
||||||
///
|
///
|
||||||
/// This lets you know whether or not a widget has been clicked this frame.
|
/// A `Response` lets you know whether or not a widget is being hovered, clicked or dragged.
|
||||||
/// It also lets you easily show a tooltip on hover.
|
/// It also lets you easily show a tooltip on hover.
|
||||||
|
///
|
||||||
|
/// Whenever something gets added to a `Ui`, a `Response` object is returned.
|
||||||
|
/// [`ui.add`] returns a `Response`, as does [`ui.button`], and all similar shortcuts.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
// CONTEXT:
|
// CONTEXT:
|
||||||
|
@ -62,6 +65,11 @@ pub struct Response {
|
||||||
|
|
||||||
/// The widget had keyboard focus and lost it.
|
/// The widget had keyboard focus and lost it.
|
||||||
pub(crate) lost_kb_focus: bool,
|
pub(crate) lost_kb_focus: bool,
|
||||||
|
|
||||||
|
/// What the underlying data changed?
|
||||||
|
/// e.g. the slider was dragged, text was entered in a `TextEdit` etc.
|
||||||
|
/// Always `false` for something like a `Button`.
|
||||||
|
pub(crate) changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Response {
|
impl std::fmt::Debug for Response {
|
||||||
|
@ -82,6 +90,7 @@ impl std::fmt::Debug for Response {
|
||||||
interact_pointer_pos,
|
interact_pointer_pos,
|
||||||
has_kb_focus,
|
has_kb_focus,
|
||||||
lost_kb_focus,
|
lost_kb_focus,
|
||||||
|
changed,
|
||||||
} = self;
|
} = self;
|
||||||
f.debug_struct("Response")
|
f.debug_struct("Response")
|
||||||
.field("layer_id", layer_id)
|
.field("layer_id", layer_id)
|
||||||
|
@ -98,6 +107,7 @@ impl std::fmt::Debug for Response {
|
||||||
.field("interact_pointer_pos", interact_pointer_pos)
|
.field("interact_pointer_pos", interact_pointer_pos)
|
||||||
.field("has_kb_focus", has_kb_focus)
|
.field("has_kb_focus", has_kb_focus)
|
||||||
.field("lost_kb_focus", lost_kb_focus)
|
.field("lost_kb_focus", lost_kb_focus)
|
||||||
|
.field("changed", changed)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,6 +202,24 @@ impl Response {
|
||||||
self.is_pointer_button_down_on
|
self.is_pointer_button_down_on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What the underlying data changed?
|
||||||
|
///
|
||||||
|
/// e.g. the slider was dragged, text was entered in a `TextEdit` etc.
|
||||||
|
/// Always `false` for something like a `Button`.
|
||||||
|
/// Can sometimes be `true` even though the data didn't changed
|
||||||
|
/// (e.g. if the user entered a character and erased it the same frame).
|
||||||
|
pub fn changed(&self) -> bool {
|
||||||
|
self.changed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Report the data shown by this widget changed.
|
||||||
|
///
|
||||||
|
/// This must be called by widgets that represent some mutable data,
|
||||||
|
/// e.g. checkboxes, sliders etc.
|
||||||
|
pub fn mark_changed(&mut self) {
|
||||||
|
self.changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Show this UI if the item was hovered (i.e. a tooltip).
|
/// Show this UI if the item was hovered (i.e. a tooltip).
|
||||||
/// If you call this multiple times the tooltips will stack underneath the previous ones.
|
/// If you call this multiple times the tooltips will stack underneath the previous ones.
|
||||||
pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
|
pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
|
||||||
|
@ -315,6 +343,7 @@ impl Response {
|
||||||
interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos),
|
interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos),
|
||||||
has_kb_focus: self.has_kb_focus || other.has_kb_focus,
|
has_kb_focus: self.has_kb_focus || other.has_kb_focus,
|
||||||
lost_kb_focus: self.lost_kb_focus || other.lost_kb_focus,
|
lost_kb_focus: self.lost_kb_focus || other.lost_kb_focus,
|
||||||
|
changed: self.changed || other.changed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -897,9 +897,10 @@ impl Ui {
|
||||||
selected_value: Value,
|
selected_value: Value,
|
||||||
text: impl Into<String>,
|
text: impl Into<String>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let response = self.radio(*current_value == selected_value, text);
|
let mut response = self.radio(*current_value == selected_value, text);
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
*current_value = selected_value;
|
*current_value = selected_value;
|
||||||
|
response.mark_changed();
|
||||||
}
|
}
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
@ -920,9 +921,10 @@ impl Ui {
|
||||||
selected_value: Value,
|
selected_value: Value,
|
||||||
text: impl Into<String>,
|
text: impl Into<String>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let response = self.selectable_label(*current_value == selected_value, text);
|
let mut response = self.selectable_label(*current_value == selected_value, text);
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
*current_value = selected_value;
|
*current_value = selected_value;
|
||||||
|
response.mark_changed();
|
||||||
}
|
}
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
@ -938,11 +940,12 @@ impl Ui {
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
|
|
||||||
let mut degrees = radians.to_degrees();
|
let mut degrees = radians.to_degrees();
|
||||||
let response = self.add(DragValue::f32(&mut degrees).speed(1.0).suffix("°"));
|
let mut response = self.add(DragValue::f32(&mut degrees).speed(1.0).suffix("°"));
|
||||||
|
|
||||||
// only touch `*radians` if we actually changed the degree value
|
// only touch `*radians` if we actually changed the degree value
|
||||||
if degrees != radians.to_degrees() {
|
if degrees != radians.to_degrees() {
|
||||||
*radians = degrees.to_radians();
|
*radians = degrees.to_radians();
|
||||||
|
response.changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
|
@ -957,13 +960,14 @@ impl Ui {
|
||||||
use std::f32::consts::TAU;
|
use std::f32::consts::TAU;
|
||||||
|
|
||||||
let mut taus = *radians / TAU;
|
let mut taus = *radians / TAU;
|
||||||
let response = self
|
let mut response = self
|
||||||
.add(DragValue::f32(&mut taus).speed(0.01).suffix("τ"))
|
.add(DragValue::f32(&mut taus).speed(0.01).suffix("τ"))
|
||||||
.on_hover_text("1τ = one turn, 0.5τ = half a turn, etc. 0.25τ = 90°");
|
.on_hover_text("1τ = one turn, 0.5τ = half a turn, etc. 0.25τ = 90°");
|
||||||
|
|
||||||
// only touch `*radians` if we actually changed the value
|
// only touch `*radians` if we actually changed the value
|
||||||
if taus != *radians / TAU {
|
if taus != *radians / TAU {
|
||||||
*radians = taus * TAU;
|
*radians = taus * TAU;
|
||||||
|
response.changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
|
|
|
@ -227,9 +227,10 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
let mut desired_size = total_extra + galley.size;
|
let mut desired_size = total_extra + galley.size;
|
||||||
desired_size = desired_size.at_least(spacing.interact_size);
|
desired_size = desired_size.at_least(spacing.interact_size);
|
||||||
desired_size.y = desired_size.y.max(icon_width);
|
desired_size.y = desired_size.y.max(icon_width);
|
||||||
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
|
let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
*checked = !*checked;
|
*checked = !*checked;
|
||||||
|
response.mark_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
|
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
|
||||||
|
|
|
@ -307,15 +307,22 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsva: &mut HsvaGamma, alpha: Alpha) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) {
|
/// return true on change
|
||||||
|
fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> bool {
|
||||||
let mut hsvag = HsvaGamma::from(*hsva);
|
let mut hsvag = HsvaGamma::from(*hsva);
|
||||||
color_picker_hsvag_2d(ui, &mut hsvag, alpha);
|
color_picker_hsvag_2d(ui, &mut hsvag, alpha);
|
||||||
*hsva = Hsva::from(hsvag);
|
let new_hasva = Hsva::from(hsvag);
|
||||||
|
if *hsva == new_hasva {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
*hsva = new_hasva;
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response {
|
pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response {
|
||||||
let pupup_id = ui.auto_id_with("popup");
|
let pupup_id = ui.auto_id_with("popup");
|
||||||
let button_response = color_button(ui, (*hsva).into()).on_hover_text("Click to edit color");
|
let mut button_response = color_button(ui, (*hsva).into()).on_hover_text("Click to edit color");
|
||||||
|
|
||||||
if button_response.clicked() {
|
if button_response.clicked() {
|
||||||
ui.memory().toggle_popup(pupup_id);
|
ui.memory().toggle_popup(pupup_id);
|
||||||
|
@ -328,7 +335,9 @@ pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Res
|
||||||
.show(ui.ctx(), |ui| {
|
.show(ui.ctx(), |ui| {
|
||||||
ui.spacing_mut().slider_width = 256.0;
|
ui.spacing_mut().slider_width = 256.0;
|
||||||
Frame::popup(ui.style()).show(ui, |ui| {
|
Frame::popup(ui.style()).show(ui, |ui| {
|
||||||
color_picker_hsva_2d(ui, hsva, alpha);
|
if color_picker_hsva_2d(ui, hsva, alpha) {
|
||||||
|
button_response.mark_changed();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -210,7 +210,7 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
let kb_edit_id = ui.auto_id_with("edit");
|
let kb_edit_id = ui.auto_id_with("edit");
|
||||||
let is_kb_editing = ui.memory().has_kb_focus(kb_edit_id);
|
let is_kb_editing = ui.memory().has_kb_focus(kb_edit_id);
|
||||||
|
|
||||||
if is_kb_editing {
|
let mut response = if is_kb_editing {
|
||||||
let button_width = ui.spacing().interact_size.x;
|
let button_width = ui.spacing().interact_size.x;
|
||||||
let mut value_text = ui
|
let mut value_text = ui
|
||||||
.memory()
|
.memory()
|
||||||
|
@ -283,6 +283,13 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response
|
response
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::float_cmp)]
|
||||||
|
{
|
||||||
|
response.changed = get(&mut get_set_value) != value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -396,6 +396,8 @@ impl<'a> Widget for Slider<'a> {
|
||||||
let font = &ui.fonts()[text_style];
|
let font = &ui.fonts()[text_style];
|
||||||
let height = font.row_height().at_least(ui.spacing().interact_size.y);
|
let height = font.row_height().at_least(ui.spacing().interact_size.y);
|
||||||
|
|
||||||
|
let old_value = self.get_value();
|
||||||
|
|
||||||
let inner_response = ui.horizontal(|ui| {
|
let inner_response = ui.horizontal(|ui| {
|
||||||
let slider_response = self.allocate_slider_space(ui, height);
|
let slider_response = self.allocate_slider_space(ui, height);
|
||||||
self.slider_ui(ui, &slider_response);
|
self.slider_ui(ui, &slider_response);
|
||||||
|
@ -410,7 +412,10 @@ impl<'a> Widget for Slider<'a> {
|
||||||
}
|
}
|
||||||
slider_response
|
slider_response
|
||||||
});
|
});
|
||||||
inner_response.inner | inner_response.response
|
|
||||||
|
let mut response = inner_response.inner | inner_response.response;
|
||||||
|
response.changed = self.get_value() != old_value;
|
||||||
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -320,7 +320,7 @@ impl<'t> TextEdit<'t> {
|
||||||
} else {
|
} else {
|
||||||
Sense::hover()
|
Sense::hover()
|
||||||
};
|
};
|
||||||
let response = ui.interact(rect, id, sense);
|
let mut response = ui.interact(rect, id, sense);
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
ui.memory().interested_in_kb_focus(id);
|
ui.memory().interested_in_kb_focus(id);
|
||||||
|
@ -346,6 +346,7 @@ impl<'t> TextEdit<'t> {
|
||||||
primary: galley.from_ccursor(ccursorp.primary),
|
primary: galley.from_ccursor(ccursorp.primary),
|
||||||
secondary: galley.from_ccursor(ccursorp.secondary),
|
secondary: galley.from_ccursor(ccursorp.secondary),
|
||||||
});
|
});
|
||||||
|
response.mark_changed();
|
||||||
} else if response.hovered() && ui.input().pointer.any_pressed() {
|
} else if response.hovered() && ui.input().pointer.any_pressed() {
|
||||||
ui.memory().request_kb_focus(id);
|
ui.memory().request_kb_focus(id);
|
||||||
if ui.input().modifiers.shift {
|
if ui.input().modifiers.shift {
|
||||||
|
@ -357,9 +358,11 @@ impl<'t> TextEdit<'t> {
|
||||||
} else {
|
} else {
|
||||||
state.cursorp = Some(CursorPair::one(cursor_at_pointer));
|
state.cursorp = Some(CursorPair::one(cursor_at_pointer));
|
||||||
}
|
}
|
||||||
|
response.mark_changed();
|
||||||
} else if ui.input().pointer.any_down() && response.is_pointer_button_down_on() {
|
} else if ui.input().pointer.any_down() && response.is_pointer_button_down_on() {
|
||||||
if let Some(cursorp) = &mut state.cursorp {
|
if let Some(cursorp) = &mut state.cursorp {
|
||||||
cursorp.primary = cursor_at_pointer;
|
cursorp.primary = cursor_at_pointer;
|
||||||
|
response.mark_changed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -484,6 +487,8 @@ impl<'t> TextEdit<'t> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(new_ccursorp) = did_mutate_text {
|
if let Some(new_ccursorp) = did_mutate_text {
|
||||||
|
response.mark_changed();
|
||||||
|
|
||||||
// Layout again to avoid frame delay, and to keep `text` and `galley` in sync.
|
// Layout again to avoid frame delay, and to keep `text` and `galley` in sync.
|
||||||
let font = &ui.fonts()[text_style];
|
let font = &ui.fonts()[text_style];
|
||||||
galley = if multiline {
|
galley = if multiline {
|
||||||
|
|
|
@ -29,11 +29,12 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
// 2. Allocating space:
|
// 2. Allocating space:
|
||||||
// This is where we get a region of the screen assigned.
|
// This is where we get a region of the screen assigned.
|
||||||
// We also tell the Ui to sense clicks in the allocated region.
|
// We also tell the Ui to sense clicks in the allocated region.
|
||||||
let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
||||||
|
|
||||||
// 3. Interact: Time to check for clicks!.
|
// 3. Interact: Time to check for clicks!
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
*on = !*on;
|
*on = !*on;
|
||||||
|
response.mark_changed(); // report back that the value changed
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Paint!
|
// 4. Paint!
|
||||||
|
@ -65,8 +66,11 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0);
|
let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0);
|
||||||
let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
||||||
*on ^= response.clicked(); // toggle if clicked
|
if response.clicked() {
|
||||||
|
*on = !*on;
|
||||||
|
response.mark_changed();
|
||||||
|
}
|
||||||
|
|
||||||
let how_on = ui.ctx().animate_bool(response.id, *on);
|
let how_on = ui.ctx().animate_bool(response.id, *on);
|
||||||
let visuals = ui.style().interact_selectable(&response, *on);
|
let visuals = ui.style().interact_selectable(&response, *on);
|
||||||
|
|
Loading…
Reference in a new issue