88#include < layout/Positioner.hpp>
99
1010#include " ../tricks/Tricks.hpp"
11+ #include " element/Element.hpp"
12+ #include " hyprtoolkit/element/Element.hpp"
13+ #include " hyprtoolkit/element/Null.hpp"
14+ #include " hyprtoolkit/element/Rectangle.hpp"
15+ #include " hyprtoolkit/element/RowLayout.hpp"
16+ #include " hyprtoolkit/types/SizeType.hpp"
1117
1218using namespace Hyprtoolkit ;
1319using namespace Hyprutils ::Math;
@@ -25,60 +31,136 @@ TEST(Element, text) {
2531 text.reset ();
2632}
2733
28- // an auto label gets no room hint and its box tracks its own clamped preferred, so the box feeds
29- // back into the next layout. a transient narrow box on first layout (e.g. before an async icon
30- // sibling reflows the row) must not lock the text ellipsized: once the box equals the clamped
31- // preferred the clamp has to release and the text recover. #94 compared the box against a cached
32- // natural size, so the box-feedback condition stayed true and the ellipsis locked forever. this is
33- // the hyprlauncher regression.
34- TEST (Element, textEllipsizeRecoversFromTransientClamp) {
34+ TEST (Element, textPreferredSize) {
3535 Tests::Tricks::createBackendSupport ();
3636
37+ auto text = CTextBuilder::begin ()->text (" Hello World Foo Bar Baz" )->noEllipsize (true )->commence ();
38+
39+ const auto NATURAL_SIZE = text->preferredSize ({0 , 0 });
40+ EXPECT_TRUE (NATURAL_SIZE .has_value ());
41+
42+ const auto WRAPPED_SIZE = text->preferredSize ({ NATURAL_SIZE ->x * 3 / 4 , NATURAL_SIZE ->y * 3 });
43+ EXPECT_TRUE (WRAPPED_SIZE .has_value ());
44+ EXPECT_LE (WRAPPED_SIZE ->x , NATURAL_SIZE ->x * 3 / 4 );
45+ EXPECT_GT (WRAPPED_SIZE ->y , NATURAL_SIZE ->y );
46+
47+ text->rebuild ()->noEllipsize (false )->commence ();
48+ const auto ELLIPSIZED_SIZE = text->preferredSize ({ NATURAL_SIZE ->x * 3 / 4 , NATURAL_SIZE ->y * 3 });
49+ EXPECT_TRUE (ELLIPSIZED_SIZE .has_value ());
50+ EXPECT_LE (ELLIPSIZED_SIZE ->x , NATURAL_SIZE ->x * 3 / 4 );
51+ EXPECT_EQ (ELLIPSIZED_SIZE ->y , NATURAL_SIZE ->y );
52+
53+ const auto FINAL_NATURAL_SIZE = text->preferredSize ({0 , 0 });
54+ EXPECT_TRUE (FINAL_NATURAL_SIZE .has_value ());
55+ EXPECT_EQ (FINAL_NATURAL_SIZE ->x , FINAL_NATURAL_SIZE ->x );
56+ EXPECT_EQ (FINAL_NATURAL_SIZE ->y , FINAL_NATURAL_SIZE ->y );
57+
58+ text.reset ();
59+ }
60+
61+ TEST (Element, textGrowsAgainstNeighbor) {
62+ Tests::Tricks::createBackendSupport ();
63+
64+ const CBox POSITION = { 0 , 0 , 1000 , 100 };
65+
66+ auto layout = CRowLayoutBuilder::begin ()->commence ();
67+ auto text = CTextBuilder::begin ()->text (" Hello World Foo Bar Baz" )->commence ();
68+ auto rect = CRectangleBuilder::begin ()->size ({CDynamicSize::HT_SIZE_AUTO , CDynamicSize::HT_SIZE_ABSOLUTE , {1 , 10 }})->commence ();
69+ rect->setGrow (true );
70+
71+ layout->addChild (text);
72+ layout->addChild (rect);
73+
74+ g_positioner->position (layout, POSITION );
75+
76+ EXPECT_EQ (rect->impl ->position .width + text->impl ->position .width , POSITION .width );
77+ const auto PREVIOUS_TEXT_WIDTH = text->impl ->position .width ;
78+
79+ text->rebuild ()->text (" Hello World Foo Bar Baz but longer" );
80+ EXPECT_EQ (rect->impl ->position .width + text->impl ->position .width , POSITION .width );
81+ EXPECT_GE (text->impl ->position .width , PREVIOUS_TEXT_WIDTH );
82+
83+ text.reset ();
84+ rect.reset ();
85+ layout.reset ();
86+ }
87+
88+ TEST (Element, textGrowsAfterShrinking) {
89+ Tests::Tricks::createBackendSupport ();
90+
91+ const CBox POSITION = { 0 , 0 , 1000 , 100 };
92+
3793 auto text = CTextBuilder::begin ()->text (" Hello World Foo Bar Baz" )->commence ();
3894
39- // natural width: a generous box, no room hint
40- g_positioner->position (text, {{}, {1000 .F , 20 .F }}, {-1 .F , -1 .F });
41- const float NATURAL = text->preferredSize ({}).value_or (Vector2D{}).x ;
42- EXPECT_GT (NATURAL , 40 .F );
95+ g_positioner->position (text, POSITION );
96+ const auto NATURAL_TEXT_WIDTH = text->impl ->position .width ;
4397
44- // a transient narrow box on first layout clamps it
45- g_positioner->position (text, {{}, {NATURAL * 0 .3F , 20 .F }}, {-1 .F , -1 .F });
46- EXPECT_LT (text->preferredSize ({}).value_or (Vector2D{}).x , NATURAL );
98+ g_positioner->position (text, CBox{0 , 0 , NATURAL_TEXT_WIDTH * 3 / 4 , 100 });
99+ EXPECT_LT (text->impl ->position .width , NATURAL_TEXT_WIDTH );
47100
48- // from here the layout hands the auto label a box equal to its own preferred. feed that back a
49- // few frames: it must converge back to NATURAL, not stay locked at the clamped width.
50- for (int i = 0 ; i < 4 ; ++i) {
51- const auto PREF = text->preferredSize ({}).value_or (Vector2D{});
52- g_positioner->position (text, {{}, PREF }, {-1 .F , -1 .F });
53- }
54- EXPECT_FLOAT_EQ (text->preferredSize ({}).value_or (Vector2D{}).x , NATURAL );
101+ g_positioner->position (text, POSITION );
102+ EXPECT_EQ (text->impl ->position .width , NATURAL_TEXT_WIDTH );
55103
56104 text.reset ();
57105}
58106
59- // a stable room hint (e.g. the combobox row layout) clamps to the room and stays put, no per-frame
60- // flicker at the fit/elide boundary.
61- TEST (Element, textEllipsizeStableUnderRoomHint) {
107+ TEST (Element, textSideBySide) {
108+ Tests::Tricks::createBackendSupport ();
109+
110+ const CBox POSITION = { 0 , 0 , 300 , 100 };
111+
112+ auto text1 = CTextBuilder::begin ()
113+ ->size ({CDynamicSize::HT_SIZE_PERCENT , CDynamicSize::HT_SIZE_AUTO , {0.5 , 1.0 }})
114+ ->text (" First longish paragraph goes here. I love Hyprland it is the best." )
115+ ->noEllipsize (true )
116+ ->commence ();
117+ auto text2 = CTextBuilder::begin ()
118+ ->size ({CDynamicSize::HT_SIZE_PERCENT , CDynamicSize::HT_SIZE_AUTO , {0.5 , 1.0 }})
119+ ->text (" Second longish paragraph goes here. Who needs GTK and Qt when you have Hyprtoolkit?" )
120+ ->noEllipsize (true )
121+ ->commence ();
122+ auto layout = CRowLayoutBuilder::begin ()
123+ ->commence ();
124+
125+ const auto NATURAL_HEIGHT = text1->preferredSize ({0 , 0 })->y ;
126+
127+ layout->addChild (text1);
128+ layout->addChild (text2);
129+
130+ g_positioner->position (layout, POSITION );
131+
132+ ASSERT_EQ (layout->impl ->position .width , POSITION .width );
133+ ASSERT_EQ (text1->impl ->position .width , POSITION .width / 2 );
134+ ASSERT_EQ (text2->impl ->position .width , POSITION .width / 2 );
135+ ASSERT_EQ (text1->impl ->position .width , text2->impl ->position .x );
136+ ASSERT_GT (text1->impl ->position .height , NATURAL_HEIGHT );
137+ ASSERT_GT (text2->impl ->position .height , NATURAL_HEIGHT );
138+
139+ text1.reset ();
140+ text2.reset ();
141+ layout.reset ();
142+ }
143+
144+ TEST (Element, textShrinkingThenGrowing) {
62145 Tests::Tricks::createBackendSupport ();
63146
64- auto text = CTextBuilder::begin ()->text (" Hello World Foo Bar Baz" )->commence ();
65- const float NATURAL = text->preferredSize ({}).value_or (Vector2D{}).x ;
66- EXPECT_GT (NATURAL , 20 .F );
147+ // This container is necessary because we need the text to auto size itself
148+ // if we position it directly, the text sizing code is never run
149+ auto container = CNullBuilder::begin ()->commence ();
150+ auto text = CTextBuilder::begin ()
151+ ->text (" First longish paragraph goes here. I love Hyprland it is the best." )
152+ ->commence ();
67153
68- const CBox widebox = {{}, {NATURAL , 20 .F }};
69- const Vector2D tight = {NATURAL * 0 .4F , 20 .F };
154+ container->addChild (text);
70155
71- g_positioner->position (text, widebox, tight);
72- const float CLAMPED = text->preferredSize ({}).value_or (Vector2D{}).x ;
73- EXPECT_LT (CLAMPED , NATURAL );
156+ const auto NATURAL_WIDTH = text->preferredSize ({0 , 0 })->x ;
74157
75- // same room again does not flip the size
76- g_positioner->position (text, widebox, tight);
77- EXPECT_FLOAT_EQ (text->preferredSize ({}).value_or (Vector2D{}).x , CLAMPED );
158+ g_positioner->position (container, { 0 , 0 , NATURAL_WIDTH * 3 / 4 , 100 });
159+ ASSERT_LE (text->impl ->position .width , NATURAL_WIDTH * 3 / 4 );
78160
79- // room grows back: recover to full
80- g_positioner->position (text, widebox, {NATURAL + 80 .F , 20 .F });
81- EXPECT_FLOAT_EQ (text->preferredSize ({}).value_or (Vector2D{}).x , NATURAL );
161+ g_positioner->position (container, { 0 , 0 , NATURAL_WIDTH * 2 , 100 });
162+ ASSERT_EQ (text->impl ->position .width , NATURAL_WIDTH );
82163
164+ container.reset ();
83165 text.reset ();
84- }
166+ }
0 commit comments