Repository navigation
popover widget - #3544
Merged
Merged
popover widget#3544
popover widget#3544Conversation
A new `popover` widget, an analogue of `tooltip` that appears only when its base is clicked and is dismissed when the user clicks outside of the overlay's bounds. The base is rendered and behaves like a `button`: it is styled with a `Status`-reactive `Style` (including an `Opened` variant, set while the popover is shown), it reports a `Pointer` interaction when hovered, and it toggles the popover on release over the base (a press alone, or a release elsewhere, does not open it) — just as a button fires on release. The popover content is drawn directly, leaving its styling to the caller. Includes a helper function, an example app, and behavioral tests.
The popover no longer owns its open/closed state. Instead, the application tracks whether the popover is open and provides that state (`is_open`) on creation, mirroring the value-based pattern of `checkbox`/`slider` and the app-provided state of `combo_box`. The base is now a plain element (any element) that loses all button-like styling, since the application is the one opening the popover: the `Status`, `Style`, `Catalog`, and related styling API are removed, and the base is a straight passthrough (no padding, no press tracking, no pointer interaction). When the user clicks outside of the popover's bounds, the popover notifies the application of a close request through a new `on_close` handler. It is up to the application to actually close the popover by providing `is_open = false` on the next update. Clicking over the base does not request a close, so the base keeps its own behavior (e.g. a button that toggles the popover).
Delete the `is_open` state altogether. The popover now always displays its overlay; it is up to the application to remove the popover from the view when it is "closed" (e.g. by conditionally including it in the view). The `on_close` handler is still published when the user clicks outside of the popover's bounds, which is typically how the application decides to remove it from the view. The base remains a plain element (any element).
Wrap the example's view in `center`, and build the trigger button once in a variable so it is reused in both cases: standing alone when the popover is closed, and anchoring the popover when it is open.
The base is a plain passthrough element, so its mouse interaction (e.g. the `Pointer` of a `button` base) should be delegated to the base content, just like the other passthrough methods. Without this, hovering the base reported no interaction.
The `popover` argument is now an `Option`: `Some` displays the overlay (open) and `None` hides it (closed). The base is always present, so it keeps a stable identity/state in the view, and the application simply toggles the `Option` to open or close the popover. This replaces the previous "remove the popover from the view" approach: the `diff` now reconciles two children when open and one when closed, and the overlay is only produced when the popup is `Some`.
Pass the `Popover` directly to `center` (which takes `impl Into<Element>`) instead of converting it with a redundant `.into()` and an annotated `let content: Element<_, _>` binding. The intermediate `Element` broke type inference for the `trigger` button, so this also removes the annotation.
The `is_press` function was only used once, so inline the `matches!` expression directly at the call site in the `Overlay`'s `update` method.
Format the popover widget, its tests, and the example with rustfmt.
The `Position` enum gains an `Auto` variant, now the default. When used, the popover is placed on the side of the base with the most available space (`Bottom` is preferred on a tie). A `position` builder method is added so the position can be set explicitly after construction.
The popover's position is set via the `new` argument; only the `Position` itself defaults to `Position::Auto`.
The `position` is now set exclusively through the `position` method, which defaults to `Position::Auto`. This removes the argument from both `Popover::new` and the `popover` helper, and updates the doctests, tests, and example to use the method.
The `Popover` no longer has a `padding` field, `DEFAULT_PADDING`, or a `padding` builder method. The popup is laid out directly and positioned according to the `Position`/`gap`; the application controls the popup's own padding (e.g. by wrapping it in a `container` with padding). The overlay's layout now returns the popup's node translated to its computed position instead of wrapping it in a padded `Node::with_children`.
Since the overlay's `layout` now returns the popup's node directly (rather than wrapping it in a `Node::with_children`), the `Overlay`'s `update`, `draw`, `mouse_interaction`, and `operate` must use the `layout` they are given, not `layout.children().next()`. The latter pointed at the popup's first inner child instead of the popup itself.
…solve` Introduce `widget::overlay::Position`, a shared position type for popups (popovers and tooltips) that combines the popover directions (`Auto`, `Top`, `Bottom`, `Left`, `Right`) with the tooltip's `FollowCursor`. All the positioning logic previously inlined in the `popover` and `tooltip` overlays now lives in `Position::resolve`, which computes the popup's rectangle from the anchored element, the popup size, the gap/padding, the cursor position, and the viewport. `popover::Position` converts into it via `From`, and `tooltip::Position` is now a re-export of `overlay::Position` (so its default is `Position::Auto` rather than `Position::Top`).
Analogous to `popover`, `tooltip` now exposes a `position` builder method and `Tooltip::new` / the `tooltip` helper no longer take a `position` argument — they default to `Position::Auto` (now the default of the shared `overlay::Position`). Examples and doctests were updated to use `.position(…)`.
`tooltip` no longer has `padding`, `style`, or `class` (nor the `container::Catalog` bound they required). Like `popover`, the tooltip's content is laid out and drawn directly, so the application is responsible for its padding and styling (e.g. by wrapping it in a `container`). The overlay layout now returns the tooltip's node translated to its resolved position instead of wrapping it in a padded, styled `Node::with_children`, and `draw` delegates to the tooltip's own layout. Examples were updated to wrap their tooltip content in a styled `container`.
`popover` and `tooltip` no longer apply padding (the application styles the popup), so `Position::resolve`'s `padding` argument was always `0.0`. Remove it and simplify the offset/rectangle math accordingly. Move the positioning test out of `tests/popover.rs` (which drove the full widget/overlay machinery) into a `#[cfg(test)]` module in `overlay::position`, where it exercises `Position::resolve` directly. Add coverage for the fixed directions (`Top`/`Bottom`/`Left`/`Right`) and `FollowCursor` alongside the existing `Auto` case.
Add a new "Tooltips and popovers" screen to the `tour` example that showcases both widgets side by side: a `tooltip` shown on hover, and a `popover` (a floating panel anchored to its base) that opens and closes on click and dismisses when clicking outside. The *Next* button stays disabled until the popover has been opened at least once. With both widgets now demonstrated in the tour, remove the standalone `popover` and `tooltip` examples.
Wrap the tooltip contents in a `container` with padding and the `rounded_box` style, matching how the popover content is styled and the styling shown in the `tooltip` widget documentation.
The `Auto` strategy previously picked the side of the base with the most raw available space, ignoring how large the popup itself was. When the popup was bigger than that space it would later be snapped back into the viewport, landing on top of the base element. Score each side by the space that remains once the popup (and the gap) is placed there, so `Auto` prefers a side where the popup actually fits and avoids overlapping the base. Add a unit test covering the case where the popup does not fit on its previously preferred side.
- Make `overlay::position` a private module in `widget`; `Position` stays public through the existing re-export, and its docs move to the type itself. - Rename the `popover` overlay's `popover_tree` field to `tree` and build the overlay children without `push`. - Rename the popup layout and bounds locals to `layout` and `bounds` in `popover` and `tooltip`. - Use `Point::ORIGIN` for the popover's cursor position.
- `Position::resolve` takes a `snap_within_viewport` argument and clamps the resolved rectangle into the viewport when it is `true`. - `popover` and `tooltip` pass their flag to `resolve` instead of clamping the rectangle themselves; it still sizes the popup's layout limits. - Test the clamping and shifting of `Position::resolve` directly.
- Introduce a private `Side` for the four sides a popup can be placed on, with `available_space`, `offset`, and `with_most_available_space`. - `Position::resolve` now matches each `Position` variant directly, and the side math is exhaustive over `Side`, so no match arm is unreachable.
- `Position::resolve` now takes all its arguments in the same coordinate space (the one the popup is laid out in), and `FollowCursor` no longer computes the space shift. - `popover` and `tooltip` apply the overlay translation up front to the base bounds and, for `tooltip`, the tracked cursor position. - The `is_over_base` check of `popover` now compares positions in the same coordinate space.
The flag can also restore opacity, since the transparent variant keeps the base element intact. Also sort the `popover` module with the others in `lib.rs`.
The feature was only required by the `popover` and `layout` integration tests, which have moved to the `widget` crate.
Master now collects overlays as `Vec<overlay::Element>` and dropped `overlay::Group`, so: - `Popover` returns its overlays as a `Vec` and no longer wraps them in a group; - the `tooltip` overlay's `operate` uses the given layout directly, as the tooltip node is no longer wrapped; - update the `component` and `popover` integration tests accordingly.
# Conflicts: # Cargo.toml # widget/src/helpers.rs # widget/src/tooltip.rs
Bring the popover/tooltip work onto master's rewritten core widget API: - core::Element now lives in core::widget, Layout has no lifetimes, and Widget::overlay receives the window size and no longer requires the Overlay trait to expose layout(). - Port Tooltip to the new API (2-argument new(), builder position(), shared overlay::Position) and Opaque/Popover to generic W/V types. - Popover now keeps its base widget as a plain child and computes the popup layout lazily inside overlay(); outside clicks still notify the application through the on_close handler. - Fold the tooltip example into the tour app and drop the old tooltip example; adapt the editor/tour examples and widget tests to the new APIs.
Drop the bounds() helper and pass the translated viewport directly to the overlay's draw layer and the delegated widget methods, consistent with the tooltip and sticky overlays.
- Apply rustfmt to the popover/tooltip/opaque widget changes - Link the iced logo helper to crate::Element explicitly, since the Element import no longer shadows the name in helpers - Disambiguate the opaque helper links in the popover docs (the name refers to both a function and a module at the crate root)
hecrj
enabled auto-merge
November 12, 2026 12:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds a new
popoverwidget, which is analogous to atooltipbut instead of popping up when hovered, its visibility is controlled inviewlogic.In practice, this should allows user to build all kinds of dropdown menus.
Additionally, the changes here refactor a bunch of other stuff:
opaquewas promoted to its own widget and gained a newcapture_scrollproperty for capturing scroll wheel events.overlay::Positionabstracted away fromtooltip::Position; which gains some more functionality (Autoplacement withSidepreference).tooltipfocuses on pure functionality and its contents are raw now; it no longer supportscontainerstyling. Just provide your owncontainerinstead.Renderer: core::Rendererbounds across the entire codebase have been eliminated.Componentoverlays now invalidate layout on diffing.