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)`.
|
||||
* 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.
|
||||
* 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).
|
||||
|
||||
### Changed 🔧
|
||||
|
|
|
@ -198,6 +198,7 @@ impl CtxRef {
|
|||
interact_pointer_pos: None,
|
||||
has_kb_focus,
|
||||
lost_kb_focus,
|
||||
changed: false, // must be set by the widget itself
|
||||
};
|
||||
|
||||
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`].
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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)]
|
||||
pub struct Response {
|
||||
// CONTEXT:
|
||||
|
@ -62,6 +65,11 @@ pub struct Response {
|
|||
|
||||
/// The widget had keyboard focus and lost it.
|
||||
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 {
|
||||
|
@ -82,6 +90,7 @@ impl std::fmt::Debug for Response {
|
|||
interact_pointer_pos,
|
||||
has_kb_focus,
|
||||
lost_kb_focus,
|
||||
changed,
|
||||
} = self;
|
||||
f.debug_struct("Response")
|
||||
.field("layer_id", layer_id)
|
||||
|
@ -98,6 +107,7 @@ impl std::fmt::Debug for Response {
|
|||
.field("interact_pointer_pos", interact_pointer_pos)
|
||||
.field("has_kb_focus", has_kb_focus)
|
||||
.field("lost_kb_focus", lost_kb_focus)
|
||||
.field("changed", changed)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -192,6 +202,24 @@ impl Response {
|
|||
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).
|
||||
/// 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 {
|
||||
|
@ -315,6 +343,7 @@ impl Response {
|
|||
interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos),
|
||||
has_kb_focus: self.has_kb_focus || other.has_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,
|
||||
text: impl Into<String>,
|
||||
) -> Response {
|
||||
let response = self.radio(*current_value == selected_value, text);
|
||||
let mut response = self.radio(*current_value == selected_value, text);
|
||||
if response.clicked() {
|
||||
*current_value = selected_value;
|
||||
response.mark_changed();
|
||||
}
|
||||
response
|
||||
}
|
||||
|
@ -920,9 +921,10 @@ impl Ui {
|
|||
selected_value: Value,
|
||||
text: impl Into<String>,
|
||||
) -> 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() {
|
||||
*current_value = selected_value;
|
||||
response.mark_changed();
|
||||
}
|
||||
response
|
||||
}
|
||||
|
@ -938,11 +940,12 @@ impl Ui {
|
|||
#![allow(clippy::float_cmp)]
|
||||
|
||||
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
|
||||
if degrees != radians.to_degrees() {
|
||||
*radians = degrees.to_radians();
|
||||
response.changed = true;
|
||||
}
|
||||
|
||||
response
|
||||
|
@ -957,13 +960,14 @@ impl Ui {
|
|||
use std::f32::consts::TAU;
|
||||
|
||||
let mut taus = *radians / TAU;
|
||||
let response = self
|
||||
let mut response = self
|
||||
.add(DragValue::f32(&mut taus).speed(0.01).suffix("τ"))
|
||||
.on_hover_text("1τ = one turn, 0.5τ = half a turn, etc. 0.25τ = 90°");
|
||||
|
||||
// only touch `*radians` if we actually changed the value
|
||||
if taus != *radians / TAU {
|
||||
*radians = taus * TAU;
|
||||
response.changed = true;
|
||||
}
|
||||
|
||||
response
|
||||
|
|
|
@ -227,9 +227,10 @@ impl<'a> Widget for Checkbox<'a> {
|
|||
let mut desired_size = total_extra + galley.size;
|
||||
desired_size = desired_size.at_least(spacing.interact_size);
|
||||
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() {
|
||||
*checked = !*checked;
|
||||
response.mark_changed();
|
||||
}
|
||||
|
||||
// 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);
|
||||
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 {
|
||||
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() {
|
||||
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| {
|
||||
ui.spacing_mut().slider_width = 256.0;
|
||||
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 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 mut value_text = ui
|
||||
.memory()
|
||||
|
@ -283,6 +283,13 @@ impl<'a> Widget for DragValue<'a> {
|
|||
}
|
||||
}
|
||||
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 height = font.row_height().at_least(ui.spacing().interact_size.y);
|
||||
|
||||
let old_value = self.get_value();
|
||||
|
||||
let inner_response = ui.horizontal(|ui| {
|
||||
let slider_response = self.allocate_slider_space(ui, height);
|
||||
self.slider_ui(ui, &slider_response);
|
||||
|
@ -410,7 +412,10 @@ impl<'a> Widget for Slider<'a> {
|
|||
}
|
||||
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 {
|
||||
Sense::hover()
|
||||
};
|
||||
let response = ui.interact(rect, id, sense);
|
||||
let mut response = ui.interact(rect, id, sense);
|
||||
|
||||
if enabled {
|
||||
ui.memory().interested_in_kb_focus(id);
|
||||
|
@ -346,6 +346,7 @@ impl<'t> TextEdit<'t> {
|
|||
primary: galley.from_ccursor(ccursorp.primary),
|
||||
secondary: galley.from_ccursor(ccursorp.secondary),
|
||||
});
|
||||
response.mark_changed();
|
||||
} else if response.hovered() && ui.input().pointer.any_pressed() {
|
||||
ui.memory().request_kb_focus(id);
|
||||
if ui.input().modifiers.shift {
|
||||
|
@ -357,9 +358,11 @@ impl<'t> TextEdit<'t> {
|
|||
} else {
|
||||
state.cursorp = Some(CursorPair::one(cursor_at_pointer));
|
||||
}
|
||||
response.mark_changed();
|
||||
} else if ui.input().pointer.any_down() && response.is_pointer_button_down_on() {
|
||||
if let Some(cursorp) = &mut state.cursorp {
|
||||
cursorp.primary = cursor_at_pointer;
|
||||
response.mark_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -484,6 +487,8 @@ impl<'t> TextEdit<'t> {
|
|||
};
|
||||
|
||||
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.
|
||||
let font = &ui.fonts()[text_style];
|
||||
galley = if multiline {
|
||||
|
|
|
@ -29,11 +29,12 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
|||
// 2. Allocating space:
|
||||
// This is where we get a region of the screen assigned.
|
||||
// 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() {
|
||||
*on = !*on;
|
||||
response.mark_changed(); // report back that the value changed
|
||||
}
|
||||
|
||||
// 4. Paint!
|
||||
|
@ -65,8 +66,11 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
|||
#[allow(dead_code)]
|
||||
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 (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
||||
*on ^= response.clicked(); // toggle if clicked
|
||||
let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
||||
if response.clicked() {
|
||||
*on = !*on;
|
||||
response.mark_changed();
|
||||
}
|
||||
|
||||
let how_on = ui.ctx().animate_bool(response.id, *on);
|
||||
let visuals = ui.style().interact_selectable(&response, *on);
|
||||
|
|
Loading…
Reference in a new issue