3 Popups
dxtr edited this page 2026-10-18 16:23:22 +02:00

Popups

A popup is a window made on another, its parent, for a menu, a list to choose from or a tooltip. It stays above its parent, has no decorations, and is a window like any other, with a size, a scale and events of its own.

Placing a popup

Windows have no places on the screen that a program can know, as Concepts explains. So a popup is placed against a rectangle of its parent, such as the button that opens it, and the window system puts it there, moving it where it would not fit on the screen:

;; A menu below its button, at 20, 10 and 80 by 24 in the window.
(win:create-popup window 160 240 :rectangle '(20 10 80 24) :grab t)

;; A submenu beside its item, in the menu.
(win:create-popup menu 160 120 :rectangle '(0 48 160 24) :anchor :top-right :grab t)

;; A tooltip centred below the pointer, which lets the pointer through.
(win:create-popup window 120 30 :rectangle (list x y 0 0) :anchor :bottom
                                :popup-anchor :top :offset-y 16 :pointer-input nil)
(win:create-popup parent width height
                  &key rectangle anchor popup-anchor offset-x offset-y adjustments
                       grab pointer-input)
=> popup
Argument Meaning Default
width, height The size asked for, in units.
:rectangle (x y width height) in the parent's coordinates, a rectangle. Give a point as a rectangle of size 0. (0 0 0 0)
:anchor The point of the rectangle the popup is put at, a popup-point. :bottom-left
:popup-anchor The point of the popup put there. :top-left
:offset-x, :offset-y How far to move it from there, in units. 0
:adjustments How the window system may move it where it would not fit, a list of popup-adjustments. flip, then slide
:grab Whether it grabs the keyboard, as a menu does. nil
:pointer-input Whether it takes the pointer; nil lets the pointer through, as for a tooltip. t

A popup-point is a corner, :top-left, :top-right, :bottom-left or :bottom-right; the middle of an edge, :top, :bottom, :left or :right; or the :center. By default the popup's top left goes at the rectangle's bottom left, so it opens below the rectangle.

The popup-adjustments are :flip-x and :flip-y, which mirror the popup to the other side of the rectangle; :slide-x and :slide-y, which slide it along until it fits; and :resize-x and :resize-y, which make it narrower or shorter. Flips are tried first, then slides, then resizes.

The rectangle is in whole units, its edges rounded out, and cut to the parent's size: the window system places popups only against rectangles within their parent.

Where it went

The window system places the popup, possibly elsewhere and at another size:

  • popup-position says where it went: its left and top in the parent's coordinates, 0 and 0 until it is placed.
  • A new size comes as a :resize event, as for any window. Draw nothing before window-configured-p is true.
  • popup-placement returns the placement it was last asked for, as create-popup's keyword arguments.

reposition-popup places a popup again, at another size or against another rectangle, with the arguments given and the others as they were:

(win:reposition-popup menu 160 300)                         ; it grew
(win:reposition-popup tooltip 120 30 :rectangle (list x y 0 0))  ; follow the pointer

On Wayland this needs xdg_wm_base version 3 or later, which every current compositor offers; popup-error is signalled otherwise.

Grabbing popups

A popup made with :grab t gets the keyboard while it is open, as a menu does. The window system dismisses it when the user clicks outside the program's windows or switches to another program: the popup gets a :close event, popup-dismissed-p is then true, and the program should destroy it.

  • Make a grabbing popup in answer to a button or key press: the window system grants the grab for one. A popup whose grab is refused is dismissed at once, as on a Wayland compositor without a seat, such as headless Weston.
  • Grabbing popups form one chain. Each is made on the newest one that grabs, or on a top-level window while none grabs; popup-error is signalled otherwise. So close a menu before opening another elsewhere.
  • Popups that do not grab, such as tooltips, may be made on any window.

Popups a program draws itself

A program that draws its popups itself, inside one of its windows, as a game in fullscreen draws its menus, places them by the same rules with place-rectangle, within the window rather than the screen:

(win:place-rectangle 200 300 '(0 0 640 480) :rectangle '(100 450 80 24))
;; => 100, 150, 200, 300: no room below the rectangle, so above it

It takes create-popup's placement arguments, and the bounds to fit within, (x y width height), and returns where the popup goes: its x, y, width and height, in whole units. The rectangle is rounded out to whole units and cut to the bounds, and the size rounded up.

Destroying

destroy-window destroys a window's popups first, the newest first, as Wayland requires, and then the window. window-popups lists the popups of a window that are alive, oldest first. popup-parent, popup-grab-p and popup-pointer-input-p describe a popup.

The parent must have been drawn before a popup is made on it. A popup is never fullscreen.

On each platform

  • Wayland: popups are xdg-shell popups, placed by the compositor. A popup that grabs takes the grab with the serial of the latest key or button press.
  • Windows: popups are windows without frames owned by their parent's window, never activated. The backend places them within the work area of the display, as place-popup says, and places them again as their parent moves. A popup that grabs gets the keys typed while the program has the focus, and is dismissed when the focus goes elsewhere or the user presses a button on a title bar or border. Input methods do not compose in popups on Windows.