28 unit::pixel_density
pixel_density = unit::pixel_density{unit::pixels_per_inch(72.0f), device_type::desktop};
31 theme_mode mode = theme_mode::light;
33 theme() noexcept = default;
36 theme& operator=(
theme const&) noexcept = default;
41 theme(
std::filesystem::path const& path)
44 hi_log_info(
"Parsing theme at {}", path.string());
45 auto const data = parse_JSON(path);
48 throw io_error(std::format(
"{}: Could not load theme.\n{}", path.string(), e.
what()));
54 template<
typename T = hi::margins>
55 [[nodiscard]]
constexpr T
margin() const noexcept
57 if constexpr (std::is_same_v<T, hi::margins>) {
59 }
else if constexpr (std::is_same_v<T, float>) {
62 hi_static_not_implemented();
75 template<
typename T = hi::corner_radii>
78 if constexpr (std::is_same_v<T, hi::corner_radii>) {
79 return T{_rounding_radius};
80 }
else if constexpr (std::is_same_v<T, float>) {
81 return _rounding_radius;
83 hi_static_not_implemented();
89 [[nodiscard]]
constexpr float size() const noexcept
96 [[nodiscard]]
constexpr float large_size() const noexcept
103 [[nodiscard]]
constexpr float icon_size() const noexcept
112 return _large_icon_size;
119 return _label_icon_size;
126 return _baseline_adjustment;
139 [[nodiscard]] theme
transform(unit::pixel_density new_pixel_density)
const noexcept
143 auto delta_scale = new_pixel_density.ppi /
pixel_density.ppi;
144 r.pixel_density = new_pixel_density;
147 r._margin =
std::round(delta_scale * _margin);
148 r._border_width =
std::round(delta_scale * _border_width);
149 r._rounding_radius =
std::round(delta_scale * _rounding_radius);
151 r._large_size =
std::round(delta_scale * _large_size);
152 r._icon_size =
std::round(delta_scale * _icon_size);
153 r._large_icon_size =
std::round(delta_scale * _large_icon_size);
154 r._label_icon_size =
std::round(delta_scale * _label_icon_size);
156 r._baseline_adjustment =
std::round(delta_scale * _baseline_adjustment);
161 [[nodiscard]]
hi::color accent_color(
size_t nesting_level = 0) const noexcept
163 hi_assert(not _accent_colors.empty());
164 return _accent_colors[nesting_level % _accent_colors.size()];
167 [[nodiscard]]
hi::color foreground_color(
size_t nesting_level = 0) const noexcept
169 hi_assert(not _foreground_colors.empty());
170 return _foreground_colors[nesting_level % _foreground_colors.size()];
173 [[nodiscard]] hi::color border_color(
size_t nesting_level = 0) const noexcept
175 hi_assert(not _border_colors.empty());
176 return _border_colors[nesting_level % _border_colors.size()];
179 [[nodiscard]] hi::color fill_color(
size_t nesting_level = 0) const noexcept
181 hi_assert(not _fill_colors.empty());
182 return _fill_colors[nesting_level % _fill_colors.size()];
185 [[nodiscard]] hi::color text_select_color(
size_t nesting_level = 0) const noexcept
187 hi_assert(not _text_select_colors.empty());
188 return _text_select_colors[nesting_level % _text_select_colors.size()];
191 [[nodiscard]] hi::color primary_cursor_color(
size_t nesting_level = 0) const noexcept
193 hi_assert(not _primary_cursor_colors.empty());
194 return _primary_cursor_colors[nesting_level % _primary_cursor_colors.size()];
197 [[nodiscard]] hi::color secondary_cursor_color(
size_t nesting_level = 0) const noexcept
199 hi_assert(not _secondary_cursor_colors.empty());
200 return _secondary_cursor_colors[nesting_level % _secondary_cursor_colors.size()];
203 [[nodiscard]] hi::text_style_set
const &text_style_set() const noexcept
205 return _text_style_set;
208 [[nodiscard]] style::attributes_from_theme_type attributes_from_theme_function() const noexcept
210 return [](style_path
const &path,
style_pseudo_class const &pseudo_class) -> style_attributes {
211 return style_attributes{};
218 float _margin = 5.0f;
222 float _border_width = 1.0f;
226 float _rounding_radius = 4.0f;
234 float _large_size = 19.0f;
238 float _icon_size = 8.0f;
242 float _large_icon_size = 23.0f;
246 float _label_icon_size = 15.0f;
250 float _baseline_adjustment = 9.0f;
252 std::vector<hi::color> _foreground_colors;
253 std::vector<hi::color> _border_colors;
254 std::vector<hi::color> _fill_colors;
255 std::vector<hi::color> _accent_colors;
256 std::vector<hi::color> _text_select_colors;
257 std::vector<hi::color> _primary_cursor_colors;
258 std::vector<hi::color> _secondary_cursor_colors;
260 hi::text_style_set _text_style_set;
262 [[nodiscard]]
float parse_float(datum
const& data,
char const* object_name)
264 if (!data.contains(object_name)) {
265 throw parse_error(std::format(
"Missing '{}'", object_name));
268 auto const object = data[object_name];
269 if (
auto f = get_if<double>(
object)) {
270 return static_cast<float>(*f);
271 }
else if (
auto ll = get_if<long long>(
object)) {
272 return static_cast<float>(*ll);
275 std::format(
"'{}' attribute must be a floating point number, got {}.", object_name,
object.type_name()));
279 [[nodiscard]]
long long parse_long_long(datum
const& data,
char const* object_name)
281 if (!data.contains(object_name)) {
282 throw parse_error(std::format(
"Missing '{}'", object_name));
285 auto const object = data[object_name];
286 if (
auto f = get_if<long long>(
object)) {
287 return static_cast<long long>(*f);
289 throw parse_error(std::format(
"'{}' attribute must be a integer, got {}.", object_name,
object.type_name()));
293 [[nodiscard]]
int parse_int(datum
const& data,
char const* object_name)
295 auto const value = parse_long_long(data, object_name);
297 throw parse_error(std::format(
"'{}' attribute is out of range, got {}.", object_name, value));
299 return narrow_cast<int>(value);
302 [[nodiscard]]
bool parse_bool(datum
const& data,
char const* object_name)
304 if (!data.contains(object_name)) {
305 throw parse_error(std::format(
"Missing '{}'", object_name));
308 auto const object = data[object_name];
309 if (!holds_alternative<bool>(
object)) {
310 throw parse_error(std::format(
"'{}' attribute must be a boolean, got {}.", object_name,
object.type_name()));
313 return to_bool(
object);
316 [[nodiscard]] std::string parse_string(datum
const& data,
char const* object_name)
319 if (!data.contains(object_name)) {
320 throw parse_error(std::format(
"Missing '{}'", object_name));
322 auto const object = data[object_name];
323 if (!holds_alternative<std::string>(
object)) {
324 throw parse_error(std::format(
"'{}' attribute must be a string, got {}.", object_name,
object.type_name()));
326 return static_cast<std::string
>(object);
329 [[nodiscard]] hi::color parse_color_value(datum
const& data)
331 if (holds_alternative<datum::vector_type>(data)) {
332 if (data.size() != 3 && data.size() != 4) {
333 throw parse_error(std::format(
"Expect 3 or 4 values for a color, got {}.", data));
335 auto const r = data[0];
336 auto const g = data[1];
337 auto const b = data[2];
338 auto const a = data.size() == 4 ? data[3] : (holds_alternative<long long>(r) ? datum{255} : datum{1.0});
340 if (holds_alternative<long long>(r) and holds_alternative<long long>(g) and holds_alternative<long long>(b) and
341 holds_alternative<long long>(a)) {
342 auto const r_ = get<long long>(r);
343 auto const g_ = get<long long>(g);
344 auto const b_ = get<long long>(b);
345 auto const a_ = get<long long>(a);
347 hi_check(r_ >= 0 and r_ <= 255,
"integer red-color value not within 0 and 255");
348 hi_check(g_ >= 0 and g_ <= 255,
"integer green-color value not within 0 and 255");
349 hi_check(b_ >= 0 and b_ <= 255,
"integer blue-color value not within 0 and 255");
350 hi_check(a_ >= 0 and a_ <= 255,
"integer alpha-color value not within 0 and 255");
353 static_cast<uint8_t
>(r_),
static_cast<uint8_t
>(g_),
static_cast<uint8_t
>(b_),
static_cast<uint8_t
>(a_));
356 holds_alternative<double>(r) and holds_alternative<double>(g) and holds_alternative<double>(b) and
357 holds_alternative<double>(a)) {
358 auto const r_ =
static_cast<float>(get<double>(r));
359 auto const g_ =
static_cast<float>(get<double>(g));
360 auto const b_ =
static_cast<float>(get<double>(b));
361 auto const a_ =
static_cast<float>(get<double>(a));
363 return hi::color(r_, g_, b_, a_);
366 throw parse_error(std::format(
"Expect all integers or all floating point numbers in a color, got {}.", data));
369 }
else if (
auto const* color_name = get_if<std::string>(data)) {
370 auto const color_name_ = to_lower(*color_name);
371 if (color_name_.starts_with(
"#")) {
373 }
else if (
auto color_ptr = color::find(color_name_)) {
376 throw parse_error(std::format(
"Unable to parse color, got {}.", data));
379 throw parse_error(std::format(
"Unable to parse color, got {}.", data));
383 [[nodiscard]] hi::color parse_color(datum
const& data,
char const* object_name)
385 if (!data.contains(object_name)) {
386 throw parse_error(std::format(
"Missing color '{}'", object_name));
389 auto const color_object = data[object_name];
391 return parse_color_value(color_object);
394 [[nodiscard]] std::vector<hi::color> parse_color_list(datum
const& data,
char const* object_name)
397 if (!data.contains(object_name)) {
398 throw parse_error(std::format(
"Missing color list '{}'", object_name));
401 auto const color_list_object = data[object_name];
402 if (holds_alternative<datum::vector_type>(color_list_object) and not color_list_object.empty() and
403 holds_alternative<datum::vector_type>(color_list_object[0])) {
404 auto r = std::vector<hi::color>{};
406 for (
auto const& color : color_list_object) {
408 r.push_back(parse_color_value(color));
409 }
catch (parse_error
const& e) {
411 std::format(
"Could not parse {}nd entry of color list '{}'\n{}", i + 1, object_name, e.what()));
418 return {parse_color_value(data[object_name])};
419 }
catch (parse_error
const& e) {
420 throw parse_error(std::format(
"Could not parse color '{}'\n{}", object_name, e.what()));
425 [[nodiscard]] hi::text_style parse_text_style_value(datum
const& data)
427 if (not holds_alternative<datum::map_type>(data)) {
428 throw parse_error(std::format(
"Expect a text-style to be an object, got '{}'", data));
431 auto r = hi::text_style{};
435 auto variant = font_variant{};
436 if (data.contains(
"weight")) {
437 variant.set_weight(parse_font_weight(data,
"weight"));
439 variant.set_weight(font_weight::regular);
442 if (data.contains(
"italic")) {
443 variant.set_style(parse_bool(data,
"italic") ? font_style::italic : font_style::normal);
445 variant.set_style(font_style::normal);
448 auto font_id =
find_font(family_id, variant);
450 r.set_font_chain({font_id});
451 r.set_size(unit::points_per_em(gsl::narrow<short>(parse_float(data,
"size"))));
452 r.set_color(parse_color(data,
"color"));
453 r.set_line_spacing(1.0f);
454 r.set_paragraph_spacing(1.5f);
458 [[nodiscard]]
font_weight parse_font_weight(datum
const& data,
char const* object_name)
460 if (!data.contains(object_name)) {
461 throw parse_error(std::format(
"Missing '{}'", object_name));
464 auto const object = data[object_name];
465 if (
auto i = get_if<long long>(
object)) {
467 }
else if (
auto s = get_if<std::string>(
object)) {
468 return font_weight_from_string(*s);
470 throw parse_error(std::format(
"Unable to parse font weight, got {}.",
object.type_name()));
474 [[nodiscard]] hi::text_style parse_text_style(datum
const& data,
char const* object_name)
477 if (!data.contains(object_name)) {
478 throw parse_error(std::format(
"Missing text-style '{}'", object_name));
481 auto const textStyleObject = data[object_name];
483 return parse_text_style_value(textStyleObject);
484 }
catch (parse_error
const& e) {
485 throw parse_error(std::format(
"Could not parse text-style '{}'\n{}", object_name, e.what()));
489 void parse(datum
const& data)
491 hi_assert(holds_alternative<datum::map_type>(data));
493 name = parse_string(data,
"name");
495 auto const mode_name = to_lower(parse_string(data,
"mode"));
496 if (mode_name ==
"light") {
497 mode = theme_mode::light;
498 }
else if (mode_name ==
"dark") {
499 mode = theme_mode::dark;
501 throw parse_error(std::format(
"Attribute 'mode' must be \"light\" or \"dark\", got \"{}\".", mode_name));
504 named_color<
"blue"> = parse_color(data,
"blue");
505 named_color<
"green"> = parse_color(data,
"green");
506 named_color<
"indigo"> = parse_color(data,
"indigo");
507 named_color<
"orange"> = parse_color(data,
"orange");
508 named_color<
"pink"> = parse_color(data,
"pink");
509 named_color<
"purple"> = parse_color(data,
"purple");
510 named_color<
"red"> = parse_color(data,
"red");
511 named_color<
"teal"> = parse_color(data,
"teal");
512 named_color<
"yellow"> = parse_color(data,
"yellow");
514 named_color<
"gray0"> = parse_color(data,
"gray0");
515 named_color<
"gray1"> = parse_color(data,
"gray1");
516 named_color<
"gray2"> = parse_color(data,
"gray2");
517 named_color<
"gray3"> = parse_color(data,
"gray3");
518 named_color<
"gray4"> = parse_color(data,
"gray4");
519 named_color<
"gray5"> = parse_color(data,
"gray5");
520 named_color<
"gray6"> = parse_color(data,
"gray6");
521 named_color<
"gray7"> = parse_color(data,
"gray7");
522 named_color<
"gray8"> = parse_color(data,
"gray8");
523 named_color<
"gray9"> = parse_color(data,
"gray9");
524 named_color<
"gray10"> = parse_color(data,
"gray10");
526 _accent_colors = parse_color_list(data,
"accent-color");
527 _border_colors = parse_color_list(data,
"border-color");
528 _fill_colors = parse_color_list(data,
"fill-color");
529 _text_select_colors = parse_color_list(data,
"text-select-color");
530 _primary_cursor_colors = parse_color_list(data,
"primary-cursor-color");
531 _secondary_cursor_colors = parse_color_list(data,
"secondary-cursor-color");
533 _text_style_set.clear();
534 _text_style_set.push_back({}, parse_text_style(data,
"label-style"));
535 _text_style_set.push_back({phrasing_mask::warning}, parse_text_style(data,
"warning-label-style"));
536 _text_style_set.push_back({phrasing_mask::error}, parse_text_style(data,
"error-label-style"));
537 _text_style_set.push_back({phrasing_mask::example}, parse_text_style(data,
"help-label-style"));
538 _text_style_set.push_back({phrasing_mask::placeholder}, parse_text_style(data,
"placeholder-label-style"));
540 _margin = narrow_cast<float>(parse_int(data,
"margin"));
541 _border_width = narrow_cast<float>(parse_int(data,
"border-width"));
542 _rounding_radius = narrow_cast<float>(parse_int(data,
"rounding-radius"));
543 _size = narrow_cast<float>(parse_int(data,
"size"));
544 _large_size = narrow_cast<float>(parse_int(data,
"large-size"));
545 _icon_size = narrow_cast<float>(parse_int(data,
"icon-size"));
546 _large_icon_size = narrow_cast<float>(parse_int(data,
"large-icon-size"));
547 _label_icon_size = narrow_cast<float>(parse_int(data,
"label-icon-size"));
549 auto const base_font = _text_style_set.front().font_chain()[0];
550 auto const base_size = _text_style_set.front().size();
551 auto const base_cap_height = std::get<unit::points_per_em_s>(base_size) * base_font->metrics.cap_height;
552 _baseline_adjustment = ceil_in(unit::points, base_cap_height);
555 [[nodiscard]]
friend std::string
to_string(theme
const& rhs)
noexcept
557 return std::format(
"{}:{}", rhs.name, rhs.mode);
560 friend std::ostream& operator<<(std::ostream& lhs, theme
const& rhs)