egui icon indicating copy to clipboard operation
egui copied to clipboard

`egui::Response` "finished"/"complete" predicate

Open Fuzzyzilla opened this issue 1 year ago • 3 comments

Is your feature request related to a problem? Please describe. Some widgets have a way to detect when the input is "finished," the exact meaning of which is contextual to the widget. Single-line text fields have response.lost_focus(), DragValue has .update_while_editting(false) .... .changed(), buttons have .clicked(), but many do not - Multiline textfields, sliders, and color pickers, for example, and there is no consistent way to construct this from the returned Response without digging into the source for the widget and knowing what each response predicate corresponds to - and there is no guarantee that such info can be derived at all, in the case of the color pickers.

This is useful for when a value-in-flux should be used differently then the crystalized value, for example a cheap preview for a changing value and an expensive full-quality version when the interaction is finished, or submitting a history state only for finished values. (my exact usecases!)

Describe the solution you'd like Response could have an additional .finished()/.completed()/.ended(), ect. predicate to report the end of these contextual interactions, explicitly provided by the widget logic - it is not possible to derive from the other fields of the response.

Describe alternatives you've considered I have written a latch wrapper providing this functionality (and a bit more) manually, however it involves lots of boilerplate for use at the callsite and still requires a way for user code to query whether an interaction is "ongoing" in the context of the relevant widget - not always possible, in the context of the color picker and others, resulting in bad hacks to check (eg, global mouse button state).

Additional Context This isn't always meaningful for every widget with a response, but the response API has such fields already - a label has no concept of "finished" but it also doesn't have a concept of "dragged," alas the latter is still provided.

I would be willing to implement this for existing widgets, if it is agreed that this isn't a horrible idea! :3

Fuzzyzilla avatar Dec 30 '23 03:12 Fuzzyzilla

For an example of the contextual-ness of this problem, here is the correct logic for a Slider i wrote for my latch implementation, which took much trial and error to catch every interaction type (dragging the handle, dragging the text, typing and hitting enter, typing and tabbing out)

let response = todo!(); //add sider
if response.has_focus() {
    return Ongoing;
}
if response.drag_released() || response.lost_focus() {
    return Finished;
}
match (response.changed(), response.dragged()) {
    (_, true) => Ongoing,
    (true, false) => Finished,
    (false, false) => NotInteracting,
}

Fuzzyzilla avatar Dec 30 '23 03:12 Fuzzyzilla

I agree something like this would be very useful!

It's worth noting that changed is the only flag that an egui widget needs to actively set (using Response::mark_changed) - all the others (clicked, lost_focus, drag_released, etc) are provided by egui::Context at interaction time.

So I wonder if we can implement something like this in a way that for most widgets there is no need for extra code?

For instance, the default finished() (or whatever we name it) could be defined as

struct Response {
    …
    finished: Option<bool>
}

impl Response {
    /// Called by a few special widgets
    pub fn set_finished(&mut self, finished: bool) {
        self.finished = Some(finished);
    }
    
    pub fn finished(&self) -> bool {
        self.finished.unwrap_or_else(|| {
            self.drag_released() || self.lost_focused() || (self.changed() && !self.dragged())
        })
    }
}

Would that work for most widgets? What widgets would need to call set_finished?

emilk avatar Dec 30 '23 11:12 emilk

Thanks for cleaning up my over-complex pseudocode for detecting it :sweat_smile:

I gave this a try via an extension trait, to see how a few different widgets behave:

  • Combo boxes never report finished when using the mouse or enter to select
  • Color buttons report finished on every change/every frame when dragging the selector (I don't believe it sets self.dragged, I had tried to detect this before but ended up just needing a "submit" button)
  • Single line textedit, buttons, selectable labels + other button-likes work great!
  • Items with a textbox value (Sliders, DragEdit) work on drag, but report finished on every keystroke in the textbox

I think the contextual nature of what "finished" means makes it hard to write a general polyfill for when the widget doesn't support it unfortunately..

Fuzzyzilla avatar Dec 30 '23 23:12 Fuzzyzilla

This would be very useful in detecting when the user has selected a color and closed the color picker (my current problem). If the user drags the color around a new changed is issued each frame, not what I want when saving a new value on each changed.

technomage avatar Mar 01 '24 21:03 technomage