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

Improve Sense vs drag-and-drop #3841

Closed
emilk opened this issue Jan 19, 2024 · 3 comments · Fixed by #3860
Closed

Improve Sense vs drag-and-drop #3841

emilk opened this issue Jan 19, 2024 · 3 comments · Fixed by #3860
Assignees
Labels
egui rerun Desired for Rerun.io

Comments

@emilk
Copy link
Owner

emilk commented Jan 19, 2024

When dragging a widget (e.g. a slider), and letting the cursor move outside the slider, egui should not highlight that other widget:

hover-while-dragging

egui implements this by never setting response.hovered for a widget if another widget is being dragged.

There is a weird exception to this though: if the other widget is not interactive (doesn't sense any clicks or drags), then it will still have response.hovered(). This usually doesn't matter, because a widget that that is not interactive has no hover-effect anyway.

This has implications for drag-and-drop: if the drop-target area has Sense::hover() then it will sense hovers while the user is dragging something else, so the user can do if drag_target_response.hovered() { … to highlight the drop-area. But if the drop-area itself is sensitive to clicks and drags, .hovered() will always be false while something is being dragged. This means it is really tricky to implement drag-and-drop reordering of a list, because usually you'd want to implement that by having each list item sense drags and be a drop-target, to sense how to reorder the items.

A work-around it to use ui.rect_contains_pointer instead of response.hovered(), but this feels hacky.

So: we should figure out:

  • Should response.hovered() be true when something else is being dragged or not?
  • Should we have a special drop-target Sense?
@emilk emilk added egui rerun Desired for Rerun.io labels Jan 19, 2024
@rustbasic
Copy link
Contributor

rustbasic commented Jan 19, 2024

  1. If the button is down somewhere else, shouldn't hovered be false?
  2. There may be a way to create a function that stores and returns two separate hovered states.
    ex) hovered() and uncondition_hovered()

@abey79
Copy link
Collaborator

abey79 commented Jan 19, 2024

I have a list of custom widgets that I want to be able to reorder (for context, these widgets handle spacing themselves, so they are drawn with no margin/spacing between them). Here is the proto-code I need to use to have the ability to drag and drop. I think it's a good illustration of where the drag-and-drop API could be improved.

fn some_ui(ui: &mut egui::Ui) {
    let mut source_item_pos = None;
    let mut target_item_pos = None;

    for (i, widget) in list_items_to_display
    {
        let response = widget.show(ui);

        // TODO(emilk/egui#3841): very tempting to use `response.dragged()` here, but it
        // doesn't work. By the time `i.pointer.any_released()` is true, `response.dragged()`
        // is false. So both condition never happen at the same time.
        if ui.memory(|mem| mem.is_being_dragged(response.id)) {
            source_item_pos = Some(i);
        }

        // TODO(emilk/egui#3841): very tempting to use `response.hovered()` here, but widgets
        // that sense more than hover never get `response.hovered() == true` while another
        // widget is being dragged.
        if ui.memory(|mem| mem.is_anything_being_dragged())
            && ui.rect_contains_pointer(response.rect)
        {
            ui.painter().hline(
                ui.cursor().x_range(),
                ui.cursor().top(),
                (2.0, egui::Color32::WHITE),
            );

            // TODO(emilk/egui#3841): it would be nice to have a drag specific API for that,
            // for discoverability
            if ui.input(|i| i.pointer.any_released()) {
                target_item_pos = Some(i);
            }
        }
    }


    if let (Some(source), Some(target)) =
        (source_item_pos, target_item_pos)
    {
        /* swap `source` and `target` in `list_items_to_display` */
    }
}

@emilk
Copy link
Owner Author

emilk commented Jan 22, 2024

I think rustbasic is on the right track.

We'll keep hovered to be false if dragging something else, and add a contains_pointer method that ignores what the mouse is doing, and just focus on where it is.

Btw, this also affects the drag-to-select text work in #3816

emilk added a commit that referenced this issue Jan 22, 2024
@emilk emilk self-assigned this Jan 22, 2024
emilk added a commit that referenced this issue Jan 22, 2024
…3860)

* Closes #3841

⚠️ Breaking change!

This defines `response.hovered()` to never be true if any other widget
is being dragged.

If you want to detect if a widget is being hovered while another widget
is being dragged, use the new `response.contains_pointer` instead. This
is necessaert for things like drag-and-drop targets.

Overall, this removes special-casing of non-interactive widgets.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
egui rerun Desired for Rerun.io
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants