Architecture of the Layer/Widget System for Modals and Popups in Telegram Desktop

TL;DR: Telegram Desktop implements all modal dialogs, pop-ups, and overlays using a centralized layer stack architecture built on lib_ui primitives, where Ui::LayerStackWidget manages a stack of Ui::LayerWidget objects that handle animation, backdrop rendering, and input blocking, while Ui::BoxContent provides ready-made centered dialogs.

The modal and popup system in Telegram Desktop relies on a sophisticated yet lightweight widget hierarchy defined in the lib_ui submodule. This architecture separates the concerns of layer management, content presentation, and animation lifecycle, allowing any component to spawn full-screen overlays or centered confirmation boxes with minimal boilerplate.

Core Components of the Layer Architecture

Ui::LayerWidget – The Foundation for Overlays

At the base of the system sits Ui::LayerWidget, defined in lib_ui/ui/layers/layer_widget.h. This class serves as the abstract foundation for any full-screen overlay. It encapsulates critical responsibilities including animation state management, semi-transparent black-out background rendering, input event blocking, and lifetime management. Every modal in Telegram Desktop, whether a simple confirmation or a complex media editor, ultimately inherits from or wraps this class.

Ui::LayerStackWidget – The Centralized Stack Manager

The Ui::LayerStackWidget, located in lib_ui/ui/layers/layer_stack_widget.h, acts as the single source of truth for modal state. The MainWindow owns exactly one instance via base::unique_qptr<Ui::LayerStackWidget> _layer. This stack widget guarantees that only the topmost layer receives input events and coordinates the black-out backdrop drawing across the entire window. When layers are pushed or popped, the stack manages the visual hierarchy and ensures proper focus restoration.

Ui::BoxContent and Ui::GenericBox – Ready-Made Dialogs

For standard centered dialogs, the codebase uses Ui::BoxContent (lib_ui/ui/boxes/box_content.h) and its concrete subclass Ui::GenericBox (lib_ui/ui/boxes/generic_box.h). These classes provide ready-to-use layouts with default paddings, scrollable bodies, title bars, and OK/Cancel button rows. Rather than constructing raw LayerWidget instances, most features use GenericBox for settings, confirmations, and form dialogs like "Edit profile" or "Delete chat".

How the Layer Stack Works in Practice

Ownership and Event Routing

The MainWindow instantiates a single layer stack during initialization:

base::unique_qptr<Ui::LayerStackWidget> _layer;

When displaying a modal, the code creates a LayerWidget, attaches content, and pushes it onto this stack:

auto layer = Ui::CreateChild<Ui::LayerWidget>(parent);
layer->setContent(object_ptr<Ui::BoxContent>(...));
_layer->pushLayer(std::move(layer), options);

The LayerStackWidget strictly enforces event isolation: only the topmost layer processes mouse and keyboard input. When that layer closes via popLayer(), the previous layer automatically regains focus and event handling privileges.

Animation and Transitions

Animation behavior is controlled through Ui::LayerOptions, which carries flags such as anim::type::normal, anim::type::instant, and Ui::LayerOption::CloseByClick. The LayerWidget implementation utilizes Qt's QPropertyAnimation to execute slide and fade transitions. These options are passed during the pushLayer() call, ensuring consistent visual behavior across the application.

Implementation Patterns: Boxes vs. Full-Screen Layers

The Box Pattern (Centered Dialogs)

Most user interactions employ the box pattern for small-to-medium dialogs. These appear as centered cards with limited dimensions:

auto box = Ui::CreateChild<Ui::GenericBox>(parent);
box->setTitle(tr::lng_confirm_delete_item(tr::now, lt_item_name, name));
Ui::show(std::move(box), Ui::LayerOption::CloseByClick);

Under the hood, the Ui::show() helper wraps the GenericBox in a LayerWidget, pushes it onto the stack, and applies the requested animation options.

Full-Screen Layer Pattern

For immersive flows like the photo editor, story creation, or login sequences, the code creates full-screen layers with custom content:

auto editor = Ui::CreateChild<Ui::LayerWidget>(parent);
editor->setContent(object_ptr<Editor::LayerWidget>(editor, args));
Ui::showLayer(std::move(editor), Ui::LayerOption::KeepOtherLayers);

Using Ui::LayerOption::KeepOtherLayers allows previous layers to remain visible underneath, which supports "undo" overlays or multi-step workflows without destroying the underlying state.

Helper Functions and Global Access

The lib_ui/ui/layers/show.h header provides convenience wrappers that eliminate the need to manually reference MainWindow:

  • Ui::show() – General-purpose display helper
  • Ui::showLayer() – For full-screen LayerWidget instances
  • Ui::showBox() – Specifically for BoxContent subclasses

These functions resolve the active LayerStackWidget through Ui::MainWidget::layerStack(), enabling any controller or utility class to spawn modals without knowing the window hierarchy. For special cases requiring overlays without the black-out backdrop (such as media previews), MainWindow exposes Ui::showSpecialLayer().

Lifetime Management and Safety

All layers are owned by the LayerStackWidget via base::unique_qptr. This smart pointer implementation ensures automatic deletion once the closing animation completes, preventing memory leaks and guaranteeing deterministic destruction order. The stack deletes layers from top to bottom, ensuring that child widgets are destroyed before their parents.

Programmatic closure is handled through MainWindow methods:

if (window->ui_isLayerShown()) {
    window->ui_hideSettingsAndLayer(anim::type::normal);
}

This invokes LayerStackWidget::popLayer(), triggers the reverse animation, and safely destroys the widget.

Real-World Usage Examples

Showing a Box from a Controller

When the calling code lacks direct access to MainWindow, the global helpers provide clean abstraction:

Ui::showBox(box_ptr, Ui::LayerOption::CloseByClick);

Complex Full-Screen Flows

The iv::Instance (Instant View) and InfoLayerWidget implementations demonstrate how the same infrastructure supports temporary UI flows over the main window. Similarly, Editor::LayerWidget in Telegram/SourceFiles/editor/editor_layer_widget.cpp showcases a full-screen editor built entirely on the layer stack primitives.

Summary

  • Centralized Stack: A single Ui::LayerStackWidget owned by MainWindow manages all modal state.
  • Dual Patterns: Ui::GenericBox provides centered dialogs, while custom Ui::LayerWidget subclasses handle full-screen flows.
  • Automatic Safety: base::unique_qptr ownership ensures layers are deleted automatically after animations complete.
  • Global Access: Helper functions in lib_ui/ui/layers/show.h allow any component to spawn modals without window references.
  • Event Isolation: The stack guarantees only the topmost layer receives input, with previous layers automatically regaining focus on closure.

Frequently Asked Questions

What is the difference between a LayerWidget and a BoxContent in Telegram Desktop?

Ui::LayerWidget is the low-level container that handles full-screen overlays, animations, and backdrops, while Ui::BoxContent is a higher-level abstraction specifically designed for centered, card-style dialogs with standardized layouts. In practice, BoxContent objects are almost always wrapped inside a LayerWidget before being displayed, as seen in the Ui::show() implementation.

How does Telegram Desktop handle multiple simultaneous modals?

The Ui::LayerStackWidget maintains a stack of layers, pushing new modals on top and popping them on close. Only the topmost layer receives input events, and each layer can optionally keep previous layers visible underneath using the KeepOtherLayers option. This stack-based approach prevents focus conflicts and ensures predictable z-ordering.

Where are the layer helper functions like Ui::show defined?

These global helpers are declared in lib_ui/ui/layers/show.h and provide convenience wrappers around the layer stack mechanics. They automatically resolve the active LayerStackWidget through Ui::MainWidget::layerStack(), allowing developers to display modals from anywhere in the codebase without passing MainWindow pointers through the call chain.

How can I programmatically close the topmost modal in Telegram Desktop?

Use the MainWindow inspection methods ui_isLayerShown() to check for active layers, then call ui_hideSettingsAndLayer(anim::type::normal) to trigger the close animation. This internally invokes LayerStackWidget::popLayer(), which runs the exit animation and automatically destroys the widget via base::unique_qptr when finished.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →