docs
Loading...
Searching...
No Matches
Length.h
Go to the documentation of this file.
1///
2/// Copyright (C) 2026 Ultralight, Inc. All rights reserved.
3/// A license is required for commercial use. https://ultralig.ht
4///
5#pragma once
6#include <Ultralight/String.h>
7#include <Ultralight/detail/Css.h>
8
9#include <cstddef>
10#include <cstdint>
11
12namespace ultralight {
13
14class Flex;
15
16///
17/// A distance or size constraint in an AppCore layout.
18///
19/// You use Length to set container padding and gap, or to define minimum and maximum size
20/// constraints on panels and containers.
21///
22/// Numbers and CSS string literals convert to Length automatically, so you can write values
23/// directly in layout options.
24///
25/// This example sets a row's spacing and constrains a child panel:
26///
27/// ```
28/// RefPtr<Container> body =
29/// window->layout()->AddRow({ .gap = 8, .padding = "12px" });
30/// body->AddPanel({ .min_size = "160px", .max_size = "40%" });
31/// ```
32///
33/// ## Supported Units
34///
35/// A Length is measured in one of two units:
36///
37/// - **Logical pixels represent fixed distances.** Bare numbers, Length::Px(), and string literals
38/// with `"px"` specify logical pixels. Container gap and padding accept logical pixels only.
39/// - **Percentages are relative to the container's content box (inside its padding).** Unlike a
40/// Size percent, which is a share of the container's free space, a Length percent uses the whole
41/// box.
42///
43/// Length doesn't accept flex units-- a flex factor isn't a distance, and passing `"fr"` or a Flex
44/// produces a compile error.
45///
46/// ## Literals and Unset Values
47///
48/// String literals and runtime strings follow the same rules as Size (see Size).
49///
50/// The default constructor creates an unset Length, which leaves the receiving field's default in
51/// effect:
52///
53/// - **Size constraints impose no bounds.** An unset `min_size` leaves a node with no minimum size,
54/// and an unset `max_size` leaves it with no maximum size.
55/// - **Container spacing adds no space.** An unset `gap` leaves zero space between adjacent
56/// children, and an unset `padding` applies zero inset around the container.
57///
58/// @see Size, ContainerOptions, LayoutNode::SetMinSize()
59///
60class Length {
61 public:
62 ///
63 /// The unit of a Length.
64 ///
65 enum class Unit : uint8_t {
66 Default = 0, ///< Unset: the receiving field's default applies.
67 Px, ///< Logical pixels.
68 Percent, ///< A percentage of the parent container's content box (inside its padding).
69 };
70
71 ///
72 /// Create an unset Length (the receiving field's default applies).
73 ///
74 constexpr Length() = default;
75
76 ///
77 /// Create a Length in logical pixels (implicit, a bare number means px).
78 ///
79 constexpr Length(double px) : value_(px), unit_(Unit::Px) {}
80
81 ///
82 /// Create a Length from a CSS string literal, parsed and validated at compile time.
83 ///
84 /// Accepted forms are a number with `px`, a number with `%`, and a bare number (logical
85 /// pixels). Any other format produces a compile error that explains the issue.
86 ///
87 /// @note Only string literals bind here. A runtime string must go through TryParse().
88 ///
89 template <size_t N>
90 UL_CSS_CONSTEVAL Length(const char (&s)[N]) {
91 const detail::TrackValue t = detail::ResolveTrack(s, N - 1, false);
92 if (t.status != detail::CssStatus::Ok) {
93 if (std::is_constant_evaluated())
94 detail::RaiseCssError(t.status);
95 invalid_ = true;
96 return;
97 }
98 value_ = t.value;
99 unit_ = (t.unit_code == 1) ? Unit::Percent : Unit::Px;
100 }
101
102 ///
103 /// Length does not accept `fr` units-- a flex factor is not a distance. Use Size instead.
104 ///
105 constexpr Length(Flex) = delete;
106
107 ///
108 /// Create a Length in logical pixels.
109 ///
110 /// @param value The distance, in logical pixels.
111 ///
112 static constexpr Length Px(double value) { return Length(value, Unit::Px); }
113
114 ///
115 /// Create a percentage Length.
116 ///
117 /// @param value The percentage, in percent points (`50` means 50%).
118 ///
119 static constexpr Length Pct(double value) { return Length(value, Unit::Percent); }
120
121 ///
122 /// Parse a runtime string with the same rules as the literal form.
123 ///
124 /// @param text UTF-8 text to parse.
125 ///
126 /// @param length Byte length of `text`.
127 ///
128 /// @param out Receives the parsed value on success (left unchanged on failure).
129 ///
130 /// @return Returns whether or not the text parsed as a valid Length.
131 ///
132 static constexpr bool TryParse(const char* text, size_t length, Length& out) {
133 const detail::TrackValue t = detail::ResolveTrack(text, length, false);
134 if (t.status != detail::CssStatus::Ok)
135 return false;
136 out = Length(t.value, (t.unit_code == 1) ? Unit::Percent : Unit::Px);
137 return true;
138 }
139
140 ///
141 /// Parse a runtime string with the same rules as the literal form.
142 ///
143 /// @param text The text to parse.
144 ///
145 /// @param out Receives the parsed value on success (left unchanged on failure).
146 ///
147 /// @return Returns whether or not the text parsed as a valid Length.
148 ///
149 static bool TryParse(const String& text, Length& out) {
150 const String8& utf8 = text.utf8();
151 return TryParse(utf8.data(), utf8.length(), out);
152 }
153
154 ///
155 /// Get the numeric value (px or percent points, per unit()). Zero when unset.
156 ///
157 constexpr double value() const { return value_; }
158
159 ///
160 /// Get the unit. `Unit::Default` means unset (the field's default applies).
161 ///
162 constexpr Unit unit() const { return unit_; }
163
164 ///
165 /// Whether or not this value was explicitly set (shorthand for `unit() != Unit::Default`).
166 ///
167 constexpr bool is_set() const { return unit_ != Unit::Default; }
168
169 ///
170 /// Whether or not this value came from a malformed literal on a compiler where literal
171 /// validation degrades to a runtime flag.
172 ///
173 /// @note The library treats an invalid value as unset and logs a warning that identifies the
174 /// panel or container it belongs to.
175 ///
176 constexpr bool invalid() const { return invalid_; }
177
178 friend constexpr bool operator==(const Length&, const Length&) = default;
179
180 private:
181 constexpr Length(double value, Unit unit) : value_(value), unit_(unit) {}
182
183 double value_ = 0.0;
184 Unit unit_ = Unit::Default;
185 bool invalid_ = false;
186};
187
188///
189/// A share of a container's free space distributed among siblings.
190///
191/// Sibling Panel%s and Container%s divide whatever space remains along the container's axis after
192/// deducting fixed and percentage sizes, proportional to their declared flex factors.
193///
194/// You'll use Flex when computing a flex factor dynamically in code to pass to a Size field.
195///
196/// You can construct a Size from an explicit Flex factor or use its literal form:
197///
198/// ```
199/// Size wide = Flex(2); // twice the share of a 1fr sibling
200/// Size same = "2fr"; // the literal form
201/// ```
202///
203/// ## Distinguishing Shares from Distances
204///
205/// In layout contexts, a bare number means logical pixels rather than a flex share, so the Flex()
206/// constructor is explicit. You can construct a Flex from a computed number using Flex() or
207/// Flex::Fr().
208///
209/// Layout fields that take a Length (such as `min_size`, `max_size`, `gap`, and `padding`) don't
210/// accept a flex share because a flex factor isn't a distance (see Length).
211///
212/// @see Size, Length
213///
214class Flex {
215 public:
216 ///
217 /// Create a flex factor from a computed number.
218 ///
219 /// The conversion is explicit because a bare number means logical px in layout contexts, never
220 /// a flex factor.
221 ///
222 /// @param factor The share of free space, relative to the sibling flex factors.
223 ///
224 constexpr explicit Flex(double factor) : value_(factor) {}
225
226 ///
227 /// Create a Flex from a CSS string literal, parsed and validated at compile time.
228 ///
229 /// Only the `fr` form is accepted (eg, `"2fr"`). Any other format produces a compile error that
230 /// explains the issue.
231 ///
232 template <size_t N>
233 UL_CSS_CONSTEVAL Flex(const char (&s)[N]) {
234 const detail::CssNumber num = detail::ParseCssNumber(s, N - 1);
235 const detail::CssStatus status
236 = (num.status != detail::CssStatus::Ok)
237 ? num.status
238 : (num.unit == detail::CssUnit::Fr ? detail::CssStatus::Ok
239 : detail::CssStatus::ExpectedFr);
240 if (status != detail::CssStatus::Ok) {
241 if (std::is_constant_evaluated())
242 detail::RaiseCssError(status);
243 invalid_ = true;
244 return;
245 }
246 value_ = num.value;
247 }
248
249 ///
250 /// Create a flex factor.
251 ///
252 /// @param factor The share of free space, relative to the sibling flex factors.
253 ///
254 static constexpr Flex Fr(double factor) { return Flex(factor); }
255
256 ///
257 /// Parse a runtime string with the same rules as the literal form (only the `fr` form is
258 /// accepted).
259 ///
260 /// @param text UTF-8 text to parse.
261 ///
262 /// @param length Byte length of `text`.
263 ///
264 /// @param out Receives the parsed value on success (left unchanged on failure).
265 ///
266 /// @return Returns whether or not the text parsed as a valid Flex.
267 ///
268 static constexpr bool TryParse(const char* text, size_t length, Flex& out) {
269 const detail::CssNumber num = detail::ParseCssNumber(text, length);
270 if (num.status != detail::CssStatus::Ok || num.unit != detail::CssUnit::Fr)
271 return false;
272 out = Flex(num.value);
273 return true;
274 }
275
276 ///
277 /// Parse a runtime string with the same rules as the literal form.
278 ///
279 /// @param text The text to parse.
280 ///
281 /// @param out Receives the parsed value on success (left unchanged on failure).
282 ///
283 /// @return Returns whether or not the text parsed as a valid Flex.
284 ///
285 static bool TryParse(const String& text, Flex& out) {
286 const String8& utf8 = text.utf8();
287 return TryParse(utf8.data(), utf8.length(), out);
288 }
289
290 ///
291 /// Get the flex factor.
292 ///
293 constexpr double value() const { return value_; }
294
295 ///
296 /// Whether or not this value came from a malformed literal on a compiler where literal
297 /// validation degrades to a runtime flag (see Length::invalid()).
298 ///
299 constexpr bool invalid() const { return invalid_; }
300
301 friend constexpr bool operator==(const Flex&, const Flex&) = default;
302
303 private:
304 double value_ = 1.0;
305 bool invalid_ = false;
306};
307
308///
309/// Fixed, percentage, and flexible sizes for panels and containers.
310///
311/// Size specifies how much space a child node occupies inside a row or column. You assign it when
312/// configuring panels and containers in layout options, or update it later with
313/// LayoutNode::SetSize().
314///
315/// This row distributes its space among three panels using different sizing units:
316///
317/// ```
318/// RefPtr<Container> body = window->layout()->AddRow();
319/// body->AddPanel({ .size = "240px" }); // logical pixels
320/// body->AddPanel({ .size = "30%" }); // percent of the free space
321/// body->AddPanel({ .size = "2fr" }); // two flex shares of what's left
322/// ```
323///
324/// ## Layout Units
325///
326/// Panels and containers accept different measurement forms along their parent container's axis:
327///
328/// - **Logical pixels specify a fixed distance.** A bare number or a string with `px` sets the
329/// dimension in display-independent pixels.
330/// - **Percentages claim a fraction of the container's free space.** A string with `%` reserves
331/// that portion of the space remaining after fixed siblings.
332/// - **Flex shares divide whatever space remains.** A string with `fr` takes a proportional cut of
333/// the free space left after fixed and percentage allocations.
334///
335/// ## Computed Sizes
336///
337/// When sizes depend on runtime calculations rather than string constants, use factory methods or
338/// the Flex helper type. Call Size::Fr() or Flex to specify flex shares.
339///
340/// Pass numeric values directly instead of formatting strings:
341///
342/// ```
343/// Size half = Size::Pct(50);
344/// Size wide = Flex(2); // the same as "2fr"
345/// ```
346///
347/// ## Literals and Runtime Strings
348///
349/// String literals passed to Size are parsed and validated at compile time. Any syntax typo or
350/// unsupported unit produces a compiler error.
351///
352/// For strings read at runtime, use Size::TryParse():
353///
354/// ```
355/// Size parsed;
356/// if (Size::TryParse(saved_width, parsed))
357/// sidebar->SetSize(parsed);
358/// ```
359///
360/// ## Default and Unset Sizes
361///
362/// A default-constructed Size has no explicit unit or value. Leaving a size unset instructs the
363/// receiving field to apply its own default, which gives panels and containers in a row or column a
364/// single flex share (`1fr`) and floating panels `100%`.
365///
366/// Setting an explicit size of `0` or `"0px"` collapses the node to zero pixels along the layout
367/// axis instead of expanding it.
368///
369/// @see Length, Flex, PanelOptions, LayoutNode::SetSize()
370///
371class Size {
372 public:
373 ///
374 /// The unit of a Size.
375 ///
376 enum class Unit : uint8_t {
377 Default = 0, ///< Unset: the receiving field's default applies.
378 Px, ///< Logical pixels.
379 Percent, ///< A percentage of the container's free space along the axis.
380 Fr, ///< A flex factor (share of the remaining free space).
381 };
382
383 ///
384 /// Create an unset Size (the receiving field's default applies).
385 ///
386 constexpr Size() = default;
387
388 ///
389 /// Create a Size in logical pixels (implicit, a bare number means px).
390 ///
391 constexpr Size(double px) : value_(px), unit_(Unit::Px) {}
392
393 ///
394 /// Create a Size from a Length (implicit, px stays px and percent stays percent).
395 ///
396 constexpr Size(Length length)
397 : value_(length.value()),
398 unit_(length.unit() == Length::Unit::Percent ? Unit::Percent
399 : length.unit() == Length::Unit::Px ? Unit::Px
400 : Unit::Default),
401 invalid_(length.invalid()) {}
402
403 ///
404 /// Create a Size from a flex factor (implicit).
405 ///
406 constexpr Size(Flex flex) : value_(flex.value()), unit_(Unit::Fr), invalid_(flex.invalid()) {}
407
408 ///
409 /// Create a Size from a CSS string literal, parsed and validated at compile time.
410 ///
411 /// Accepted forms are a number with `px`, `%`, or `fr`, and a bare number (logical pixels).
412 /// Any other format produces a compile error that explains the issue.
413 ///
414 /// @note Only string literals bind here. A runtime string must go through TryParse().
415 ///
416 template <size_t N>
417 UL_CSS_CONSTEVAL Size(const char (&s)[N]) {
418 const detail::TrackValue t = detail::ResolveTrack(s, N - 1, true);
419 if (t.status != detail::CssStatus::Ok) {
420 if (std::is_constant_evaluated())
421 detail::RaiseCssError(t.status);
422 invalid_ = true;
423 return;
424 }
425 value_ = t.value;
426 unit_ = (t.unit_code == 2) ? Unit::Fr : (t.unit_code == 1) ? Unit::Percent : Unit::Px;
427 }
428
429 ///
430 /// Create a Size in logical pixels.
431 ///
432 /// @param value The size, in logical pixels.
433 ///
434 static constexpr Size Px(double value) { return Size(value, Unit::Px); }
435
436 ///
437 /// Create a percent-of-free-space Size.
438 ///
439 /// @param value The percentage, in percent points (`50` means 50%).
440 ///
441 static constexpr Size Pct(double value) { return Size(value, Unit::Percent); }
442
443 ///
444 /// Create a flex-factor Size.
445 ///
446 /// @param value The share of free space, relative to the sibling flex factors.
447 ///
448 static constexpr Size Fr(double value) { return Size(value, Unit::Fr); }
449
450 ///
451 /// Parse a runtime string with the same rules as the literal form.
452 ///
453 /// @param text UTF-8 text to parse.
454 ///
455 /// @param length Byte length of `text`.
456 ///
457 /// @param out Receives the parsed value on success (left unchanged on failure).
458 ///
459 /// @return Returns whether or not the text parsed as a valid Size.
460 ///
461 static constexpr bool TryParse(const char* text, size_t length, Size& out) {
462 const detail::TrackValue t = detail::ResolveTrack(text, length, true);
463 if (t.status != detail::CssStatus::Ok)
464 return false;
465 out = Size(t.value, (t.unit_code == 2) ? Unit::Fr
466 : (t.unit_code == 1) ? Unit::Percent
467 : Unit::Px);
468 return true;
469 }
470
471 ///
472 /// Parse a runtime string with the same rules as the literal form.
473 ///
474 /// @param text The text to parse.
475 ///
476 /// @param out Receives the parsed value on success (left unchanged on failure).
477 ///
478 /// @return Returns whether or not the text parsed as a valid Size.
479 ///
480 static bool TryParse(const String& text, Size& out) {
481 const String8& utf8 = text.utf8();
482 return TryParse(utf8.data(), utf8.length(), out);
483 }
484
485 ///
486 /// Get the numeric value (px, percent points, or flex factor, per unit()). Zero when unset.
487 ///
488 constexpr double value() const { return value_; }
489
490 ///
491 /// Get the unit. `Unit::Default` means unset (the field's default applies).
492 ///
493 constexpr Unit unit() const { return unit_; }
494
495 ///
496 /// Whether or not this value was explicitly set (shorthand for `unit() != Unit::Default`).
497 ///
498 constexpr bool is_set() const { return unit_ != Unit::Default; }
499
500 ///
501 /// Whether or not this value came from a malformed literal on a compiler where literal
502 /// validation degrades to a runtime flag (see Length::invalid()).
503 ///
504 constexpr bool invalid() const { return invalid_; }
505
506 friend constexpr bool operator==(const Size&, const Size&) = default;
507
508 private:
509 constexpr Size(double value, Unit unit) : value_(value), unit_(unit) {}
510
511 double value_ = 0.0;
512 Unit unit_ = Unit::Default;
513 bool invalid_ = false;
514};
515
516} // namespace ultralight
A share of a container's free space distributed among siblings.
Definition Length.h:214
static constexpr Flex Fr(double factor)
Create a flex factor.
Definition Length.h:254
constexpr Flex(double factor)
Create a flex factor from a computed number.
Definition Length.h:224
constexpr double value() const
Get the flex factor.
Definition Length.h:293
static constexpr bool TryParse(const char *text, size_t length, Flex &out)
Parse a runtime string with the same rules as the literal form (only the fr form is accepted).
Definition Length.h:268
static bool TryParse(const String &text, Flex &out)
Parse a runtime string with the same rules as the literal form.
Definition Length.h:285
UL_CSS_CONSTEVAL Flex(const char(&s)[N])
Create a Flex from a CSS string literal, parsed and validated at compile time.
Definition Length.h:233
constexpr bool invalid() const
Whether or not this value came from a malformed literal on a compiler where literal validation degrad...
Definition Length.h:299
friend constexpr bool operator==(const Flex &, const Flex &)=default
A distance or size constraint in an AppCore layout.
Definition Length.h:60
Unit
The unit of a Length.
Definition Length.h:65
@ Default
Unset: the receiving field's default applies.
Definition Length.h:66
@ Percent
A percentage of the parent container's content box (inside its padding).
Definition Length.h:68
@ Px
Logical pixels.
Definition Length.h:67
constexpr Length()=default
Create an unset Length (the receiving field's default applies).
static constexpr Length Px(double value)
Create a Length in logical pixels.
Definition Length.h:112
UL_CSS_CONSTEVAL Length(const char(&s)[N])
Create a Length from a CSS string literal, parsed and validated at compile time.
Definition Length.h:90
constexpr Length(double px)
Create a Length in logical pixels (implicit, a bare number means px).
Definition Length.h:79
constexpr double value() const
Get the numeric value (px or percent points, per unit()).
Definition Length.h:157
static bool TryParse(const String &text, Length &out)
Parse a runtime string with the same rules as the literal form.
Definition Length.h:149
static constexpr Length Pct(double value)
Create a percentage Length.
Definition Length.h:119
static constexpr bool TryParse(const char *text, size_t length, Length &out)
Parse a runtime string with the same rules as the literal form.
Definition Length.h:132
constexpr Length(Flex)=delete
Length does not accept fr units– a flex factor is not a distance.
constexpr bool invalid() const
Whether or not this value came from a malformed literal on a compiler where literal validation degrad...
Definition Length.h:176
constexpr bool is_set() const
Whether or not this value was explicitly set (shorthand for unit() != Unit::Default).
Definition Length.h:167
friend constexpr bool operator==(const Length &, const Length &)=default
constexpr Unit unit() const
Get the unit.
Definition Length.h:162
constexpr Size()=default
Create an unset Size (the receiving field's default applies).
Unit
The unit of a Size.
Definition Length.h:376
@ Default
Unset: the receiving field's default applies.
Definition Length.h:377
@ Percent
A percentage of the container's free space along the axis.
Definition Length.h:379
@ Px
Logical pixels.
Definition Length.h:378
@ Fr
A flex factor (share of the remaining free space).
Definition Length.h:380
constexpr double value() const
Get the numeric value (px, percent points, or flex factor, per unit()).
Definition Length.h:488
static constexpr bool TryParse(const char *text, size_t length, Size &out)
Parse a runtime string with the same rules as the literal form.
Definition Length.h:461
static bool TryParse(const String &text, Size &out)
Parse a runtime string with the same rules as the literal form.
Definition Length.h:480
friend constexpr bool operator==(const Size &, const Size &)=default
constexpr Size(Length length)
Create a Size from a Length (implicit, px stays px and percent stays percent).
Definition Length.h:396
static constexpr Size Px(double value)
Create a Size in logical pixels.
Definition Length.h:434
static constexpr Size Pct(double value)
Create a percent-of-free-space Size.
Definition Length.h:441
constexpr bool invalid() const
Whether or not this value came from a malformed literal on a compiler where literal validation degrad...
Definition Length.h:504
constexpr bool is_set() const
Whether or not this value was explicitly set (shorthand for unit() != Unit::Default).
Definition Length.h:498
static constexpr Size Fr(double value)
Create a flex-factor Size.
Definition Length.h:448
constexpr Size(double px)
Create a Size in logical pixels (implicit, a bare number means px).
Definition Length.h:391
UL_CSS_CONSTEVAL Size(const char(&s)[N])
Create a Size from a CSS string literal, parsed and validated at compile time.
Definition Length.h:417
constexpr Size(Flex flex)
Create a Size from a flex factor (implicit).
Definition Length.h:406
constexpr Unit unit() const
Get the unit.
Definition Length.h:493
A null-terminated UTF-8 string container.
Definition String8.h:17
char * data()
Get raw UTF-8 data.
Definition String8.h:53
size_t length() const
Get length in bytes (not including null terminator).
Definition String8.h:59
Unicode string container with conversions for UTF-8, UTF-16, and UTF-32.
Definition String.h:31
String8 & utf8()
Get native UTF-8 string.
Definition String.h:109
Root namespace for every public Ultralight type, function, and enumeration.
@ Default
The platform's usual corners for this kind of window.
Definition Window.h:138