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

Add Popup and Tooltip, unifying the previous behaviours #5713

Merged
merged 30 commits into from
Feb 18, 2025

Conversation

lucasmerlin
Copy link
Collaborator

@lucasmerlin lucasmerlin commented Feb 12, 2025

This introduces new Tooltip and Popup structs that unify and extend the old popups and tooltips.

Popup handles the positioning and optionally stores state on whether the popup is open (for click based popups like ComboBox, menus, context menus).
Tooltip is based on Popup and handles state of whether the tooltip should be shown (which turns out to be quite complex to handles all the edge cases).

Both Popup and Tooltip can easily be constructed from a Response and then customized via builder methods.

This also introduces PositionAlign, for aligning something outside of a Rect (in contrast to Align2 for aligning inside a Rect). But I don't like the name, any suggestions? Inspired by mui's tooltip positioning.

TODOs:

  • Automatic tooltip positioning based on available space
  • Review / fix / remove all code TODOs
  • Update the helper fns on Response to be consistent in naming and parameters (Some use tooltip, some hover_ui, some take &self, some take self) actually, I think the naming and parameter make sense on second thought
  • Make sure all old code is marked deprecated

For discussion during review:

  • the following check in show_tooltip_for still necessary?:
     let is_touch_screen = ctx.input(|i| i.any_touches());
     let allow_placing_below = !is_touch_screen; // There is a finger below. TODO: Needed?

@lucasmerlin lucasmerlin marked this pull request as draft February 12, 2025 09:56
Copy link

Preview available at https://egui-pr-preview.github.io/pr/5713-lucasunify-tooltip-popup
Note that it might take a couple seconds for the update to show up after the preview_build workflow has completed.

@lucasmerlin lucasmerlin force-pushed the lucas/unify-tooltip-popup branch from 12cf1b7 to 199db96 Compare February 12, 2025 10:27
@lucasmerlin
Copy link
Collaborator Author

While I think it's nice that the ComboBoxes indicate the direction they will open in, it can look somewhat confusing in edge cases:

Screenshot 2025-02-12 at 18 31 35

(the second last combobox is bigger so it will wrap while the last one doesn't yet wrap)

Since this is also difficult to implement with the new Popup positioning, I'll remove this behavior and always point the arrow down.

@lucasmerlin lucasmerlin added feature New feature or request egui labels Feb 12, 2025
Comment on lines 115 to 135
/// Similar to [`Align2`] but for aligning something to the outside of some rect.
/// ```text
/// ┌───────────┐ ┌────────┐ ┌─────────┐
/// │ TOP_START │ │ TOP │ │ TOP_END │
/// └───────────┘ └────────┘ └─────────┘
/// ┌──────────┐ ┌────────────────────────────────────┐ ┌───────────┐
/// │LEFT_START│ │ │ │RIGHT_START│
/// └──────────┘ │ │ └───────────┘
/// ┌──────────┐ │ │ ┌───────────┐
/// │ LEFT │ │ some_rect │ │ RIGHT │
/// └──────────┘ │ │ └───────────┘
/// ┌──────────┐ │ │ ┌───────────┐
/// │ LEFT_END │ │ │ │ RIGHT_END │
/// └──────────┘ └────────────────────────────────────┘ └───────────┘
/// ┌────────────┐ ┌──────┐ ┌──────────┐
/// │BOTTOM_START│ │BOTTOM│ │BOTTOM_END│
/// └────────────┘ └──────┘ └──────────┘
/// ```
/// # egui::__run_test_ui(|ui| {
/// if ui.ui_contains_pointer() {
/// egui::show_tooltip_text(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), "Helpful text");
/// }
/// # });
/// ```
pub fn show_tooltip_text(
ctx: &Context,
parent_layer: LayerId,
widget_id: Id,
text: impl Into<WidgetText>,
) -> Option<()> {
show_tooltip(ctx, parent_layer, widget_id, |ui| {
crate::widgets::Label::new(text).ui(ui);
})
}
// TODO: Find a better name for Position and PositionAlign
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PositionAlign(pub Position, pub Align);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The great gojs library models similar conception as two properties of a Spot type in the widget (which they called GraphObject):

  • alignment
  • alignmentFocus

For examples of positioning see the Panels examples, especially a Panel.Spot type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, that is a nice way to think about it! I've changed my PositionAlign to be based on two Align2, which feels nicer and allows for more alignments. It's called Align4 now 😄

@lucasmerlin lucasmerlin marked this pull request as ready for review February 13, 2025 13:31
@lucasmerlin lucasmerlin force-pushed the lucas/unify-tooltip-popup branch from 34b082b to fc9821e Compare February 13, 2025 13:32
Copy link
Owner

@emilk emilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of code to review here 😅 I've only looked at a small portion of it so far. I think I need a walk-through

crates/egui/src/containers/area.rs Show resolved Hide resolved
crates/egui/src/containers/combo_box.rs Show resolved Hide resolved
crates/emath/src/align.rs Outdated Show resolved Hide resolved
crates/emath/src/align.rs Outdated Show resolved Hide resolved
crates/emath/src/align.rs Outdated Show resolved Hide resolved
crates/egui/src/memory/mod.rs Show resolved Hide resolved
crates/egui/src/containers/old_popup.rs Outdated Show resolved Hide resolved
crates/egui/src/containers/popup.rs Outdated Show resolved Hide resolved
crates/egui/src/containers/popup.rs Outdated Show resolved Hide resolved
crates/egui/src/containers/popup.rs Outdated Show resolved Hide resolved
crates/emath/src/rect_relation.rs Outdated Show resolved Hide resolved
crates/emath/src/rect_relation.rs Outdated Show resolved Hide resolved
crates/emath/src/rect_relation.rs Outdated Show resolved Hide resolved
crates/egui_kittest/tests/popup.rs Show resolved Hide resolved

if parent_ui.input(|i| i.key_pressed(Key::Escape)) || should_close {
parent_ui.memory_mut(|mem| mem.close_popup());
Some(response)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you said: it would be nice to have a response.closed() flag the user can check. That could also be set by Area, CollapsingPanel, ColorPickerButton etc

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created another issue for this: #5727

Comment on lines +501 to 513
match open_kind {
OpenKind::Open | OpenKind::Closed => {}
OpenKind::Bool(open, close_behavior) => {
if should_close(close_behavior) {
*open = false;
}
}
OpenKind::Memory { close_behavior, .. } => {
if should_close(close_behavior) {
ctx.memory_mut(|mem| mem.close_popup());
}
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice with something like

if should_close(close_behavior) {
    open_kind.set_open(false);
}

…thought maybe for a later PR

crates/egui/src/containers/popup.rs Outdated Show resolved Hide resolved
crates/egui/src/containers/tooltip.rs Outdated Show resolved Hide resolved
fs.layers
.entry(parent_layer)
.or_default()
.widget_with_tooltip = Some(widget_id);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could maybe get rid of LayerState::open_popups if we also stored which widgets has a popup open (attached to them). That would allow us to simplify and improve the "don't show a tooltip if this widget has a popup open" check in

let any_open_popups = self.ctx.prev_pass_state(|fs| {
fs.layers
.get(&self.layer_id)
.map_or(false, |layer| !layer.open_popups.is_empty())
});

(can be saved for a future PR though)

@lucasmerlin lucasmerlin merged commit a8e98d3 into master Feb 18, 2025
46 checks passed
@lucasmerlin lucasmerlin deleted the lucas/unify-tooltip-popup branch February 18, 2025 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
egui feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants