34 theme_mode mode = theme_mode::light;
36 theme() noexcept = default;
39 theme& operator=(
theme const&) noexcept = default;
47 hi_log_info(
"Parsing theme at {}", path.string());
48 auto const data = parse_JSON(path);
51 throw io_error(std::format(
"{}: Could not load theme.\n{}", path.string(), e.
what()));
57 template<
typename T = hi::margins>
58 [[nodiscard]]
constexpr T
margin() const noexcept
60 if constexpr (std::is_same_v<T, hi::margins>) {
62 }
else if constexpr (std::is_same_v<T, float>) {
65 hi_static_not_implemented();
78 template<
typename T = hi::corner_radii>
81 if constexpr (std::is_same_v<T, hi::corner_radii>) {
82 return T{_rounding_radius};
83 }
else if constexpr (std::is_same_v<T, float>) {
84 return _rounding_radius;
86 hi_static_not_implemented();
92 [[nodiscard]]
constexpr float size() const noexcept
99 [[nodiscard]]
constexpr float large_size() const noexcept
106 [[nodiscard]]
constexpr float icon_size() const noexcept
115 return _large_icon_size;
122 return _label_icon_size;
129 return _baseline_adjustment;
146 hi_assert(new_dpi != 0.0f);
147 hi_assert(dpi != 0.0f);
148 hi_assert(scale != 0.0f);
150 auto delta_scale = new_dpi / dpi;
152 r.scale = delta_scale * scale;
155 r._margin =
std::round(delta_scale * _margin);
156 r._border_width =
std::round(delta_scale * _border_width);
157 r._rounding_radius =
std::round(delta_scale * _rounding_radius);
159 r._large_size =
std::round(delta_scale * _large_size);
160 r._icon_size =
std::round(delta_scale * _icon_size);
161 r._large_icon_size =
std::round(delta_scale * _large_icon_size);
162 r._label_icon_size =
std::round(delta_scale * _label_icon_size);
164 r._baseline_adjustment =
std::round(delta_scale * _baseline_adjustment);
171 auto const& shades = _colors[std::to_underlying(original_color)];
172 hi_assert(not shades.empty());
174 nesting_level =
std::max(ssize_t{0}, nesting_level);
175 return shades[nesting_level % ssize(shades)];
178 [[nodiscard]]
hi::color color(
hi::color original_color, ssize_t nesting_level = 0) const noexcept
180 if (original_color.is_semantic()) {
181 return color(
static_cast<semantic_color>(original_color), nesting_level);
183 return original_color;
187 [[nodiscard]] hi::text_style text_style(semantic_text_style theme_color)
const noexcept
189 return _text_styles[std::to_underlying(theme_color)];
192 [[nodiscard]] hi::text_style text_style(hi::text_style original_style)
const noexcept
194 if (original_style.is_semantic()) {
195 return text_style(
static_cast<semantic_text_style
>(original_style));
197 return original_style;
204 float _margin = 5.0f;
208 float _border_width = 1.0f;
212 float _rounding_radius = 4.0f;
220 float _large_size = 19.0f;
224 float _icon_size = 8.0f;
228 float _large_icon_size = 23.0f;
232 float _label_icon_size = 15.0f;
236 float _baseline_adjustment = 9.0f;
239 std::array<hi::text_style, semantic_text_style_metadata.size()> _text_styles;
241 [[nodiscard]]
float parse_float(datum
const& data,
char const *object_name)
243 if (!data.contains(object_name)) {
244 throw parse_error(std::format(
"Missing '{}'", object_name));
247 auto const object = data[object_name];
248 if (
auto f = get_if<double>(
object)) {
249 return static_cast<float>(*f);
250 }
else if (
auto ll = get_if<long long>(
object)) {
251 return static_cast<float>(*ll);
254 std::format(
"'{}' attribute must be a floating point number, got {}.", object_name,
object.type_name()));
258 [[nodiscard]]
long long parse_long_long(datum
const& data,
char const *object_name)
260 if (!data.contains(object_name)) {
261 throw parse_error(std::format(
"Missing '{}'", object_name));
264 auto const object = data[object_name];
265 if (
auto f = get_if<long long>(
object)) {
266 return static_cast<long long>(*f);
268 throw parse_error(std::format(
"'{}' attribute must be a integer, got {}.", object_name,
object.type_name()));
272 [[nodiscard]]
int parse_int(datum
const& data,
char const *object_name)
274 auto const value = parse_long_long(data, object_name);
276 throw parse_error(std::format(
"'{}' attribute is out of range, got {}.", object_name, value));
278 return narrow_cast<int>(value);
281 [[nodiscard]]
bool parse_bool(datum
const& data,
char const *object_name)
283 if (!data.contains(object_name)) {
284 throw parse_error(std::format(
"Missing '{}'", object_name));
287 auto const object = data[object_name];
288 if (!holds_alternative<bool>(
object)) {
289 throw parse_error(std::format(
"'{}' attribute must be a boolean, got {}.", object_name,
object.type_name()));
292 return to_bool(
object);
295 [[nodiscard]]
std::string parse_string(datum
const& data,
char const *object_name)
298 if (!data.contains(object_name)) {
299 throw parse_error(std::format(
"Missing '{}'", object_name));
301 auto const object = data[object_name];
302 if (!holds_alternative<std::string>(
object)) {
303 throw parse_error(std::format(
"'{}' attribute must be a string, got {}.", object_name,
object.type_name()));
308 [[nodiscard]]
hi::color parse_color_value(datum
const& data)
310 if (holds_alternative<datum::vector_type>(data)) {
311 if (data.size() != 3 && data.size() != 4) {
312 throw parse_error(std::format(
"Expect 3 or 4 values for a color, got {}.", data));
314 auto const r = data[0];
315 auto const g = data[1];
316 auto const b = data[2];
317 auto const a = data.size() == 4 ? data[3] : (holds_alternative<long long>(r) ? datum{255} : datum{1.0});
319 if (holds_alternative<long long>(r) and holds_alternative<long long>(g) and holds_alternative<long long>(b) and
320 holds_alternative<long long>(a)) {
321 auto const r_ = get<long long>(r);
322 auto const g_ = get<long long>(g);
323 auto const b_ = get<long long>(b);
324 auto const a_ = get<long long>(a);
326 hi_check(r_ >= 0 and r_ <= 255,
"integer red-color value not within 0 and 255");
327 hi_check(g_ >= 0 and g_ <= 255,
"integer green-color value not within 0 and 255");
328 hi_check(b_ >= 0 and b_ <= 255,
"integer blue-color value not within 0 and 255");
329 hi_check(a_ >= 0 and a_ <= 255,
"integer alpha-color value not within 0 and 255");
332 static_cast<uint8_t
>(r_),
static_cast<uint8_t
>(g_),
static_cast<uint8_t
>(b_),
static_cast<uint8_t
>(a_));
335 holds_alternative<double>(r) and holds_alternative<double>(g) and holds_alternative<double>(b) and
336 holds_alternative<double>(a)) {
337 auto const r_ =
static_cast<float>(get<double>(r));
338 auto const g_ =
static_cast<float>(get<double>(g));
339 auto const b_ =
static_cast<float>(get<double>(b));
340 auto const a_ =
static_cast<float>(get<double>(a));
345 throw parse_error(std::format(
"Expect all integers or all floating point numbers in a color, got {}.", data));
348 }
else if (
auto const *color_name = get_if<std::string>(data)) {
349 auto const color_name_ = to_lower(*color_name);
350 if (color_name_.starts_with(
"#")) {
354 throw parse_error(std::format(
"Unable to parse color, got {}.", data));
357 throw parse_error(std::format(
"Unable to parse color, got {}.", data));
361 [[nodiscard]]
hi::color parse_color(datum
const& data,
char const *object_name)
363 if (!data.contains(object_name)) {
364 throw parse_error(std::format(
"Missing color '{}'", object_name));
367 auto const color_object = data[object_name];
370 return parse_color_value(color_object);
371 }
catch (parse_error
const&) {
372 if (
auto s = get_if<std::string>(color_object)) {
383 if (!data.contains(object_name)) {
384 throw parse_error(std::format(
"Missing color list '{}'", object_name));
387 auto const color_list_object = data[object_name];
388 if (holds_alternative<datum::vector_type>(color_list_object) and not color_list_object.empty() and
389 holds_alternative<datum::vector_type>(color_list_object[0])) {
392 for (
auto const& color : color_list_object) {
394 r.push_back(parse_color_value(color));
395 }
catch (parse_error
const& e) {
397 std::format(
"Could not parse {}nd entry of color list '{}'\n{}", i + 1, object_name, e.what()));
404 return {parse_color_value(data[object_name])};
405 }
catch (parse_error
const& e) {
406 throw parse_error(std::format(
"Could not parse color '{}'\n{}", object_name, e.what()));
411 [[nodiscard]] hi::text_style parse_text_style_value(datum
const& data)
413 if (!holds_alternative<datum::map_type>(data)) {
414 throw parse_error(std::format(
"Expect a text-style to be an object, got '{}'", data));
418 auto const font_size = parse_float(data,
"size");
420 auto variant = font_variant{};
421 if (data.contains(
"weight")) {
422 variant.set_weight(parse_font_weight(data,
"weight"));
424 variant.set_weight(font_weight::regular);
427 if (data.contains(
"italic")) {
428 variant.set_style(parse_bool(data,
"italic") ? font_style::italic :
font_style::normal);
430 variant.set_style(font_style::normal);
434 auto const color = this->color(parse_color(data,
"color"), 0);
438 phrasing_mask::all, iso_639{}, iso_15924{}, family_id, variant, font_size, color,
text_decoration{});
439 return hi::text_style(sub_styles);
442 [[nodiscard]]
font_weight parse_font_weight(datum
const& data,
char const *object_name)
444 if (!data.contains(object_name)) {
445 throw parse_error(std::format(
"Missing '{}'", object_name));
448 auto const object = data[object_name];
449 if (
auto i = get_if<long long>(
object)) {
451 }
else if (
auto s = get_if<std::string>(
object)) {
452 return font_weight_from_string(*s);
454 throw parse_error(std::format(
"Unable to parse font weight, got {}.",
object.type_name()));
458 [[nodiscard]] hi::text_style parse_text_style(datum
const& data,
char const *object_name)
461 if (!data.contains(object_name)) {
462 throw parse_error(std::format(
"Missing text-style '{}'", object_name));
465 auto const textStyleObject = data[object_name];
467 return parse_text_style_value(textStyleObject);
468 }
catch (parse_error
const& e) {
469 throw parse_error(std::format(
"Could not parse text-style '{}'\n{}", object_name, e.what()));
473 void parse(datum
const& data)
475 hi_assert(holds_alternative<datum::map_type>(data));
477 name = parse_string(data,
"name");
479 auto const mode_name = to_lower(parse_string(data,
"mode"));
480 if (mode_name ==
"light") {
481 mode = theme_mode::light;
482 }
else if (mode_name ==
"dark") {
483 mode = theme_mode::dark;
485 throw parse_error(std::format(
"Attribute 'mode' must be \"light\" or \"dark\", got \"{}\".", mode_name));
488 std::get<std::to_underlying(semantic_color::blue)>(_colors) = parse_color_list(data,
"blue");
489 std::get<std::to_underlying(semantic_color::green)>(_colors) = parse_color_list(data,
"green");
490 std::get<std::to_underlying(semantic_color::indigo)>(_colors) = parse_color_list(data,
"indigo");
491 std::get<std::to_underlying(semantic_color::orange)>(_colors) = parse_color_list(data,
"orange");
492 std::get<std::to_underlying(semantic_color::pink)>(_colors) = parse_color_list(data,
"pink");
493 std::get<std::to_underlying(semantic_color::purple)>(_colors) = parse_color_list(data,
"purple");
494 std::get<std::to_underlying(semantic_color::red)>(_colors) = parse_color_list(data,
"red");
495 std::get<std::to_underlying(semantic_color::teal)>(_colors) = parse_color_list(data,
"teal");
496 std::get<std::to_underlying(semantic_color::yellow)>(_colors) = parse_color_list(data,
"yellow");
498 std::get<std::to_underlying(semantic_color::gray)>(_colors) = parse_color_list(data,
"gray");
499 std::get<std::to_underlying(semantic_color::gray2)>(_colors) = parse_color_list(data,
"gray2");
500 std::get<std::to_underlying(semantic_color::gray3)>(_colors) = parse_color_list(data,
"gray3");
501 std::get<std::to_underlying(semantic_color::gray4)>(_colors) = parse_color_list(data,
"gray4");
502 std::get<std::to_underlying(semantic_color::gray5)>(_colors) = parse_color_list(data,
"gray5");
503 std::get<std::to_underlying(semantic_color::gray6)>(_colors) = parse_color_list(data,
"gray6");
505 std::get<std::to_underlying(semantic_color::foreground)>(_colors) = parse_color_list(data,
"foreground-color");
506 std::get<std::to_underlying(semantic_color::border)>(_colors) = parse_color_list(data,
"border-color");
507 std::get<std::to_underlying(semantic_color::fill)>(_colors) = parse_color_list(data,
"fill-color");
508 std::get<std::to_underlying(semantic_color::accent)>(_colors) = parse_color_list(data,
"accent-color");
509 std::get<std::to_underlying(semantic_color::text_select)>(_colors) = parse_color_list(data,
"text-select-color");
510 std::get<std::to_underlying(semantic_color::primary_cursor)>(_colors) = parse_color_list(data,
"primary-cursor-color");
511 std::get<std::to_underlying(semantic_color::secondary_cursor)>(_colors) =
512 parse_color_list(data,
"secondary-cursor-color");
514 std::get<std::to_underlying(semantic_text_style::label)>(_text_styles) = parse_text_style(data,
"label-style");
515 std::get<std::to_underlying(semantic_text_style::small_label)>(_text_styles) =
516 parse_text_style(data,
"small-label-style");
517 std::get<std::to_underlying(semantic_text_style::warning)>(_text_styles) = parse_text_style(data,
"warning-label-style");
518 std::get<std::to_underlying(semantic_text_style::error)>(_text_styles) = parse_text_style(data,
"error-label-style");
519 std::get<std::to_underlying(semantic_text_style::help)>(_text_styles) = parse_text_style(data,
"help-label-style");
520 std::get<std::to_underlying(semantic_text_style::placeholder)>(_text_styles) =
521 parse_text_style(data,
"placeholder-label-style");
522 std::get<std::to_underlying(semantic_text_style::link)>(_text_styles) = parse_text_style(data,
"link-label-style");
524 _margin = narrow_cast<float>(parse_int(data,
"margin"));
525 _border_width = narrow_cast<float>(parse_int(data,
"border-width"));
526 _rounding_radius = narrow_cast<float>(parse_int(data,
"rounding-radius"));
527 _size = narrow_cast<float>(parse_int(data,
"size"));
528 _large_size = narrow_cast<float>(parse_int(data,
"large-size"));
529 _icon_size = narrow_cast<float>(parse_int(data,
"icon-size"));
530 _large_icon_size = narrow_cast<float>(parse_int(data,
"large-icon-size"));
531 _label_icon_size = narrow_cast<float>(parse_int(data,
"label-icon-size"));
533 _baseline_adjustment =
std::ceil(std::get<std::to_underlying(semantic_text_style::label)>(_text_styles)->cap_height());
538 return std::format(
"{}:{}", rhs.name, rhs.mode);