# How Telegram Desktop Renders Chat Messages with Reactions and Replies in history_view_message.cpp

> Discover how Telegram Desktop renders messages with reactions and replies in history_view_message.cpp. Learn about geometry calculation and the painting order for a clear chat history view.

- Repository: [Telegram Desktop/tdesktop](https://github.com/telegramdesktop/tdesktop)
- Tags: internals
- Published: 2026-04-05

---

**Telegram Desktop renders chat messages with reactions and replies by first calculating geometry in `performCountOptimalSize()`, then painting components in a strict order inside `Message::draw()`—replies first, followed by text and media, with reactions either embedded inside or drawn beneath the bubble depending on the message layout.**

The [`history_view_message.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_message.cpp) file in the **telegramdesktop/tdesktop** repository contains the core rendering logic for individual chat items. When a message contains quoted replies or emoji reactions, the `HistoryView::Message` class coordinates multiple sub-components—including `HistoryView::Reactions` and `HistoryView::Reply`—to composite the final visual output. Understanding this pipeline requires examining both the layout calculation phase and the actual painting routines that execute during every frame refresh.

## Layout Calculation: Positioning Reactions and Replies

Before any pixels are drawn, `Message::performCountOptimalSize()` (approximately lines 1200–1300) determines the spatial relationships between the bubble, content, and interactive elements. This method decides whether reactions appear inside or outside the bubble and reserves vertical space for reply metadata.

**Embedding reactions** depends on the boolean result of `embedReactionsInBubble()`:

```cpp
const auto reactionsInBubble = _reactions && embedReactionsInBubble();

if (reactionsInBubble) {
    const auto reactionsMaxWidth = st::msgPadding.left()
        + _reactions->maxWidth()
        + st::msgPadding.right();
    accumulate_max(maxWidth, std::min(st::msgMaxWidth, reactionsMaxWidth));
}

```

When `reactionsInBubble` is **false** (the default for standard messages), the reaction block height is subtracted from the main geometry and painted separately below the bubble.

**Reply blocks** are handled through component flags:

```cpp
if (replyData && !_hideReply) {
    AddComponents(Reply::Bit());
} else {
    RemoveComponents(Reply::Bit());
}

```

Enabling the `Reply` component reserves `reply->height()` pixels above the main text area, ensuring the quoted message preview renders before the message body.

## The Rendering Pipeline in Message::draw()

The `Message::draw()` method (lines 1080–1150) serves as the orchestration point, executing painting operations in a specific Z-order to ensure proper layering. The method retrieves the message geometry via `countGeometry()`, then proceeds through distinct rendering phases.

The general execution flow follows this sequence:

1. **Paint reactions below the bubble** (if not embedded)
2. **Paint the bubble background** via `Ui::PaintBubble`
3. **Paint the reply block** via `paintReplyInfo()`
4. **Paint text and media** content

### Rendering Replies with paintReplyInfo()

Reply information renders as a distinct quoted block positioned at the top of the message content area. The `Message::paintReplyInfo()` method (lines 2070–2075) delegates to the `Reply` component:

```cpp
void Message::paintReplyInfo(Painter &p,
                             QRect &trect,
                             const PaintContext &context) const {
    if (const auto reply = Get<Reply>()) {
        reply->paint(p, this, context,
                     trect.x(), trect.y(), trect.width(), true);
        trect.setY(trect.y() + reply->height());
    }
}

```

The `Reply` component—implemented in [`history_view_reply.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_reply.cpp)—draws a rounded rectangle containing the original sender's name, a preview of the quoted text, and a clickable "reply-to" link. By advancing `trect.setY()` after painting, the method ensures subsequent content (text and media) begins below the reply block.

### Rendering Reactions Below the Bubble

For the default non-embedded case, reactions are painted after the main bubble but appear visually beneath it. The code (lines 1090–1120) calculates positioning and adjusts the available content rectangle:

```cpp
if (_reactions && !reactionsInBubble) {
    const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
    const auto reactionsLeft = (!bubble && mediaDisplayed)
        ? media->contentRectForReactions().x()
        : 0;
    
    g.setHeight(g.height() - reactionsHeight);
    
    const auto reactionsPosition = QPoint(
        reactionsLeft + g.left(),
        g.top() + g.height() + st::mediaInBubbleSkip);
    
    p.translate(reactionsPosition);
    _reactions->paint(p, context, g.width(),
        context.clip.translated(-reactionsPosition));
    p.translate(-reactionsPosition);
}

```

This logic shrinks the message rectangle `g` to prevent text overlap, then translates the painter to the calculated position before invoking `_reactions->paint()`.

### Embedding Reactions Inside the Bubble

When `embedReactionsInBubble()` returns true, reactions render at the bottom of the bubble's interior (lines 1160–1175):

```cpp
if (reactionsInBubble) {
    const auto reactionsTop = (reactionsInBubble && !_viewButton)
        ? st::mediaInBubbleSkip
        : 0;
    const auto reactionsHeight = reactionsTop + _reactions->height();
    
    trect.setHeight(trect.height() - reactionsHeight);
    const auto reactionsPosition = QPoint(
        trect.left(),
        trect.top() + trect.height() + reactionsTop);
    
    p.translate(reactionsPosition);
    _reactions->paint(p, context, g.width(),
        context.clip.translated(-reactionsPosition));
    p.translate(-reactionsPosition);
}

```

Here, `trect` represents the inner content area of the bubble. The code reserves space at the bottom, then paints the reaction row within the bubble boundaries.

## The Reactions and Reply Components

The actual drawing of emoji reactions resides in **[`history_view_reactions.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_reactions.cpp)**, not within the main message file. The `Reactions::paint()` method iterates through stored reaction data to render individual emoji icons and count labels:

```cpp
void Reactions::paint(Painter &p,
                      const PaintContext &context,
                      int width,
                      const QRect &clip) const {
    for (const auto &r : _list) {
        r.icon.paint(p, x, y, width);
        p.drawText(x + iconWidth, y, countString);
        if (r.ripple) r.ripple->paint(p, ...);
    }
}

```

Each `Reaction` object contains the emoji graphic (or custom emoji), the user count, and an optional ripple animation state. Similarly, the `Reply` component in [`history_view_reply.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_reply.cpp) encapsulates all logic for drawing quoted message previews, including text elision and author name styling.

## Handling User Interactions and Ripples

Both reactions and reply blocks support press animations via the `Ui::RippleAnimation` system. When a user presses a reaction, the event propagates through `Message::clickHandlerPressedChanged()` (lines 1095–1105), which forwards to the reactions component:

```cpp
// Inside Message::clickHandlerPressedChanged
if (_reactions) {
    _reactions->clickHandlerPressedChanged(...);
}

```

For reply blocks, `Message::toggleReplyRipple()` (lines 2590–2599) explicitly manages ripple state:

```cpp
void Message::toggleReplyRipple() {
    if (const auto reply = Get<Reply>()) {
        reply->toggleRipple(...);
    }
}

```

These ripples overlay the painted content during subsequent frame renders, providing visual feedback without altering the underlying message geometry.

## Summary

- **Layout precedes painting**: `performCountOptimalSize()` determines whether reactions fit inside the bubble or require space below it, while the `Reply` component reserves vertical space for quoted content.
- **Strict rendering order**: `Message::draw()` executes in Z-order sequence—reactions below (if applicable), bubble background, reply block, then text and media.
- **Component architecture**: Actual drawing logic is delegated to specialized classes in [`history_view_reactions.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_reactions.cpp) and [`history_view_reply.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_reply.cpp), keeping [`history_view_message.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_message.cpp) focused on orchestration.
- **Interactive feedback**: Both reactions and replies utilize `Ui::RippleAnimation` for press effects, managed through dedicated toggle methods in the `Message` class.

## Frequently Asked Questions

### How does Telegram Desktop decide whether to put reactions inside or below the message bubble?

The decision occurs in `performCountOptimalSize()` based on the `embedReactionsInBubble()` method. When this returns **true**, the layout reserves space within the bubble's bottom padding and the reactions render inside. By default, most messages return **false**, causing the geometry calculator to subtract reaction height from the main rectangle and paint the reaction row beneath the bubble with additional spacing (`st::mediaInBubbleSkip`).

### What is the exact painting order for a message containing both a reply and reactions?

According to the source in `Message::draw()`, the painting order is: (1) reactions below the bubble (if not embedded), (2) bubble background and outline, (3) reply block via `paintReplyInfo()`, (4) main message text and media, and (5) embedded reactions (if enabled). The reply block always precedes text rendering because `paintReplyInfo()` advances the text rectangle's Y-coordinate before returning control to the main draw loop.

### Where is the actual code that draws the emoji reaction icons?

The emoji rendering implementation lives in [`history_view_reactions.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_reactions.cpp), specifically within the `Reactions::paint()` method. This component receives a `Painter` object and the available width from `Message::draw()`, then iterates through its internal `_list` of reaction data to paint each emoji icon and its associated count. The `Message` class only calculates positioning and delegates the actual icon drawing to this specialized component.

### How does the reply block handle click events and visual feedback?

The reply block uses the `Reply` component defined in [`history_view_reply.h`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_reply.h) and implemented in [`history_view_reply.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_reply.cpp). When users press the reply area, `Message::toggleReplyRipple()` (lines 2590–2599) creates a `Ui::RippleAnimation` that paints over the reply background. Click handling routes through the `Reply` component's `clickHandler`, which navigates to the original message being quoted.