gpui-ce 0.3.0

A community fork of Zed's GPU-accelerated UI framework
Documentation
use bitflags::bitflags;
use thiserror::Error;

use crate::{AnyWindowHandle, Bounds, Pixels, Point, util::FluentBuilder};

/// Describes where a native popup goes, relative to a rectangle on its parent window.
///
/// Use a popup for menus, comboboxes, context menus and tooltips that should be able to leave
/// the parent window's bounds. Use an in-window element for everything else. Pass the popup to
/// [`WindowKind::AnchoredPopup`](crate::WindowKind::AnchoredPopup) in [`WindowOptions::kind`](crate::WindowOptions::kind).
///
/// The popup is placed relative to `anchor_rect` rather than at an absolute screen position.
/// The platform resolves the final position. That works on Wayland, where the compositor places
/// windows, and on platforms with absolute coordinates.
///
/// The popup's size comes from `window_bounds` in [`WindowOptions`](crate::WindowOptions).
/// Wayland uses only the size, not the origin. All coordinates are in logical pixels.
///
/// Native popups are available on Wayland only. On macOS, Windows, X11 and the web, opening one
/// returns a [`PopupNotSupportedError`] and you can draw the same content in the window instead.
///
/// Pass the parent window and anchor rectangle to [`PopupOptions::new`], then chain setters to
/// configure placement and input grabbing.
///
/// # Examples
///
/// ```
/// # use gpui::{Context, Render, TestApp, Window, div, prelude::*, px, point, size, Bounds};
/// use gpui::popup::{PopupAnchor, PopupGravity, PopupOptions};
/// # struct Root;
/// # impl Render for Root {
/// #     fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement { div() }
/// # }
/// # let mut app = TestApp::new();
/// # let _window = app.open_window(|_, _| Root);
/// # let parent = app.windows()[0];
/// // A dropdown below a button whose bounds are `anchor_rect`.
/// let anchor_rect = Bounds::new(point(px(10.), px(20.)), size(px(80.), px(24.)));
/// let options = PopupOptions::new(parent, anchor_rect)
///     .anchor(PopupAnchor::BottomLeft)
///     .gravity(PopupGravity::BottomRight)
///     .grab(true);
///
/// assert_eq!(options.anchor, PopupAnchor::BottomLeft);
/// assert_eq!(options.gravity, PopupGravity::BottomRight);
/// assert!(options.grab);
/// ```
#[derive(Clone, Debug, PartialEq, Eq, derive_setters::Setters)]
#[setters(into)]
pub struct PopupOptions {
    /// The window the popup is anchored to.
    pub parent: AnyWindowHandle,

    /// The rectangle the popup is positioned relative to, in the parent window's coordinate
    /// space (the same space element bounds are in). For example, a dropdown menu uses the
    /// bounds of the button that opened it.
    pub anchor_rect: Bounds<Pixels>,

    /// Which point of [`Self::anchor_rect`] the popup is anchored to.
    pub anchor: PopupAnchor,

    /// The direction in which the popup extends away from the anchor point. A dropdown that
    /// drops below its button anchors to [`PopupAnchor::BottomLeft`] with a gravity of
    /// [`PopupGravity::BottomRight`] so it grows down and to the right.
    pub gravity: PopupGravity,

    /// How the platform may adjust the popup if the requested placement would put it off-screen.
    pub constraint_adjustment: PopupConstraintAdjustment,

    /// An additional offset applied to the popup after anchoring.
    pub offset: Point<Pixels>,

    /// Whether the popup should take an explicit input grab.
    ///
    /// Grabbing popups behave like menus: they take keyboard focus and are dismissed when the
    /// user clicks outside of them or presses a dismissing key. Use it for menus and comboboxes,
    /// not for tooltips or other passive popups.
    ///
    /// On Wayland, the grab uses the serial of the most recent mouse or key press, so open a
    /// grabbing popup in response to a press. If the window hasn't received a press yet, the
    /// popup opens without a grab.
    ///
    /// Automatic dismissal only covers input aimed at other applications. A click elsewhere in
    /// your own application still reaches it as usual, so closing the popup in that case is up
    /// to you. Nested grabbing popups must be closed in the reverse order they were opened.
    pub grab: bool,
}

impl PopupOptions {
    /// Creates popup options for a parent window and anchor rectangle.
    ///
    /// Defaults to centered placement, zero offset, no constraint adjustments, and no input grab.
    pub fn new(parent: impl Into<AnyWindowHandle>, anchor_rect: Bounds<Pixels>) -> Self {
        Self {
            parent: parent.into(),
            anchor_rect,
            anchor: PopupAnchor::default(),
            gravity: PopupGravity::default(),
            constraint_adjustment: PopupConstraintAdjustment::default(),
            offset: Point::default(),
            grab: false,
        }
    }
}

impl FluentBuilder for PopupOptions {}

/// The point of the anchor rectangle that a popup is anchored to.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum PopupAnchor {
    /// Anchor to the center of the anchor rectangle.
    #[default]
    Center,
    /// Anchor to the center of the top edge.
    Top,
    /// Anchor to the center of the bottom edge.
    Bottom,
    /// Anchor to the center of the left edge.
    Left,
    /// Anchor to the center of the right edge.
    Right,
    /// Anchor to the top-left corner.
    TopLeft,
    /// Anchor to the bottom-left corner.
    BottomLeft,
    /// Anchor to the top-right corner.
    TopRight,
    /// Anchor to the bottom-right corner.
    BottomRight,
}

/// The direction in which a popup extends away from its anchor point.
///
/// For instance, a gravity of [`PopupGravity::BottomRight`] places the popup below and to the
/// right of the anchor point.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum PopupGravity {
    /// The popup is centered over the anchor point.
    #[default]
    Center,
    /// The popup extends upwards from the anchor point.
    Top,
    /// The popup extends downwards from the anchor point.
    Bottom,
    /// The popup extends to the left of the anchor point.
    Left,
    /// The popup extends to the right of the anchor point.
    Right,
    /// The popup extends up and to the left of the anchor point.
    TopLeft,
    /// The popup extends down and to the left of the anchor point.
    BottomLeft,
    /// The popup extends up and to the right of the anchor point.
    TopRight,
    /// The popup extends down and to the right of the anchor point.
    BottomRight,
}

bitflags! {
    /// How a popup may be adjusted by the platform if the requested placement would put it
    /// off-screen. If no flags are set, the popup is placed exactly as requested and may be
    /// clipped.
    #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
    pub struct PopupConstraintAdjustment: u32 {
        /// The popup may be slid horizontally to stay on-screen.
        const SLIDE_X = 1;
        /// The popup may be slid vertically to stay on-screen.
        const SLIDE_Y = 2;
        /// The popup's anchor and gravity may be flipped horizontally to stay on-screen.
        const FLIP_X = 4;
        /// The popup's anchor and gravity may be flipped vertically to stay on-screen.
        const FLIP_Y = 8;
        /// The popup may be shrunk horizontally to stay on-screen.
        const RESIZE_X = 16;
        /// The popup may be shrunk vertically to stay on-screen.
        const RESIZE_Y = 32;
    }
}

/// Returned when a platform can't open a native popup window.
///
/// Opening a [`WindowKind::AnchoredPopup`](crate::WindowKind::AnchoredPopup) window fails with
/// this error on macOS, Windows, X11 and the web. Wayland supports native popups.
///
/// Native popups are separate from gpui's in-window popovers, which are drawn as elements inside
/// an existing window. To show a popup on every platform, catch this error and draw the same
/// content in the parent window instead.
#[derive(Debug, Error)]
#[error("popups are not supported on this platform")]
pub struct PopupNotSupportedError;