Skip to content

Commit 1a7eb86

Browse files
Improve performance of text element. (#1309)
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
1 parent c100eab commit 1a7eb86

3 files changed

Lines changed: 53 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ Changelog
44
Next
55
====
66

7+
### Dom
8+
- Performance: `text` computes its requirement once and renders only the
9+
visible lines. This makes scrolling a large text inside a `frame`
10+
significantly faster. Thanks @patlefort. See #1309.
11+
712
7.0.1 (2026-07-14)
813
------------------
914

src/ftxui/dom/selection_test.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,27 @@ TEST(SelectionTest, StyleSelection) {
152152
}
153153
}
154154

155+
// The selection style must not persist on an element rendered again after the
156+
// selection was cleared. See #1309.
157+
TEST(SelectionTest, StyleClearedWhenSelectionCleared) {
158+
auto element = text("Lorem ipsum dolor");
159+
160+
{
161+
auto screen = App::FixedSize(20, 1);
162+
Selection selection(2, 0, 9, 0);
163+
Render(screen, element.get(), selection);
164+
EXPECT_EQ(screen.CellAt(2, 0).inverted, true);
165+
}
166+
167+
{
168+
auto screen = App::FixedSize(20, 1);
169+
Render(screen, element);
170+
for (int i = 0; i < 20; i++) {
171+
EXPECT_EQ(screen.CellAt(i, 0).inverted, false);
172+
}
173+
}
174+
}
175+
155176
TEST(SelectionTest, VBoxSelection) {
156177
auto element = vbox({
157178
text("Lorem ipsum dolor"),

src/ftxui/dom/text.cpp

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,10 @@ using ftxui::Screen;
2525

2626
class Text : public Node {
2727
public:
28-
explicit Text(std::string_view text) : glyphs_(Utf8ToGlyphs(text)) {}
29-
30-
void ComputeRequirement() override {
28+
explicit Text(std::string_view text) : glyphs_(Utf8ToGlyphs(text)) {
3129
int max_width = 0;
3230
int current_width = 0;
3331
int lines_count = 1;
34-
has_selection_ = false;
35-
selection_rows_.clear();
36-
lines_offsets_.clear();
3732
lines_offsets_.push_back(0);
3833

3934
for (size_t i = 0; i < glyphs_.size(); ++i) {
@@ -53,12 +48,18 @@ class Text : public Node {
5348
requirement_.min_y = lines_count;
5449
}
5550

51+
void ComputeRequirement() override {
52+
// The requirement was computed once in the constructor. This hook still
53+
// runs before every frame; use it to clear the selection, which Select()
54+
// re-populates while a selection is active.
55+
selection_rows_.clear();
56+
}
57+
5658
void Select(Selection& selection) override {
5759
if (Box::Intersection(selection.GetBox(), box_).IsEmpty()) {
5860
return;
5961
}
6062

61-
has_selection_ = true;
6263
selection_rows_.assign(lines_offsets_.size() - 1, {-1, -1});
6364

6465
for (size_t i = 0; i < lines_offsets_.size() - 1; ++i) {
@@ -92,43 +93,42 @@ class Text : public Node {
9293
}
9394

9495
void Render(Screen& screen) override {
95-
int x = box_.x_min;
96-
int y = box_.y_min;
97-
size_t line = 0;
98-
99-
if (y > box_.y_max) {
96+
const auto visible_box = Box::Intersection(screen.stencil, box_);
97+
if (visible_box.IsEmpty()) {
10098
return;
10199
}
102100

103-
for (const auto& cell : glyphs_) {
104-
if (cell == "\n") {
105-
y++;
106-
x = box_.x_min;
107-
line++;
108-
if (y > box_.y_max) {
109-
return;
101+
int y = visible_box.y_min;
102+
103+
const size_t first_line = visible_box.y_min - box_.y_min;
104+
const size_t last_line = std::min<size_t>(
105+
visible_box.y_max - box_.y_min + 1, lines_offsets_.size() - 1);
106+
107+
for (size_t line = first_line; line < last_line; ++line, ++y) {
108+
int x = box_.x_min;
109+
110+
for (auto glyph = glyphs_.begin() + lines_offsets_[line];
111+
glyph != glyphs_.end() && *glyph != "\n"; ++glyph, ++x) {
112+
if (x > box_.x_max) {
113+
break;
110114
}
111-
continue;
112-
}
113115

114-
if (x <= box_.x_max) {
115-
screen.CellAt(x, y).character = cell;
116+
auto& cell = screen.CellAt(x, y);
117+
cell.character = *glyph;
116118

117-
if (has_selection_ && line < selection_rows_.size()) {
119+
if (line < selection_rows_.size()) {
118120
const auto& [sel_start, sel_end] = selection_rows_[line];
119121
if (sel_start != -1 && x >= sel_start && x <= sel_end) {
120-
screen.GetSelectionStyle()(screen.CellAt(x, y));
122+
screen.GetSelectionStyle()(cell);
121123
}
122124
}
123125
}
124-
x++;
125126
}
126127
}
127128

128129
private:
129130
std::vector<std::string> glyphs_;
130131
std::vector<int> lines_offsets_;
131-
bool has_selection_ = false;
132132
std::vector<std::pair<int, int>> selection_rows_;
133133
};
134134

0 commit comments

Comments
 (0)