diff --git a/src/monettheme.h b/src/monettheme.h new file mode 100644 index 0000000..60a2a74 --- /dev/null +++ b/src/monettheme.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include "fcitx-utils/color.h" + +namespace fcitx::m3ui { + +// The colours of the candidate window that follow the desktop, taken from a +// libmonet scheme. Only colours are decided here, the radii and the spacing stay +// with the theme file. +struct MonetColors { + Color surface; // the panel + Color outline; // its border + Color highlightFill; // the pill behind the current candidate + Color normal; // candidate text and the typed pinyin + Color highlight; // the part of the typed pinyin being converted + Color highlightBackground; + Color highlightCandidate; // text on the pill + Color candidateLabel; // the number in front of a candidate + Color highlightCandidateLabel; + Color candidateComment; + Color highlightCandidateComment; +}; + +inline Color toColor(monet::Rgb c, int alpha = 255) { + return Color(c.r, c.g, c.b, static_cast(alpha)); +} + +inline MonetColors mapScheme(const monet::Scheme &s) { + MonetColors c; + c.surface = toColor(s.surfaceContainer, 245); + c.outline = toColor(s.outlineVariant); + c.highlightFill = toColor(s.primaryContainer); + c.normal = toColor(s.onSurface); + c.highlight = toColor(s.primary); + c.highlightBackground = toColor(s.primaryContainer); + c.highlightCandidate = toColor(s.onPrimaryContainer); + c.candidateLabel = toColor(s.onSurfaceVariant); + c.highlightCandidateLabel = toColor(s.onPrimaryContainer); + c.candidateComment = toColor(s.onSurfaceVariant); + c.highlightCandidateComment = toColor(s.onPrimaryContainer); + return c; +} + +} // namespace fcitx::m3ui diff --git a/tests/monettheme_test.cpp b/tests/monettheme_test.cpp new file mode 100644 index 0000000..4af4443 --- /dev/null +++ b/tests/monettheme_test.cpp @@ -0,0 +1,62 @@ +// The candidate window colours taken from a libmonet scheme must stay readable +// for any seed, in both modes. +// g++ -std=c++20 -Isrc -I/include tests/monettheme_test.cpp +// (add the flags from: pkg-config --cflags --libs Fcitx5Utils) +#include +#include + +#include "monettheme.h" + +using namespace fcitx::m3ui; + +static int failures = 0; +#define CHECK_MSG(cond, ...) \ + do { \ + if (!(cond)) { \ + std::printf("FAIL line %d: %s ", __LINE__, #cond); \ + std::printf(__VA_ARGS__); \ + std::printf("\n"); \ + failures++; \ + } \ + } while (0) + +static monet::Rgb rgb(const fcitx::Color &c) { + return {static_cast(std::lround(c.redF() * 255)), static_cast(std::lround(c.greenF() * 255)), + static_cast(std::lround(c.blueF() * 255))}; +} + +int main() { + unsigned state = 4242; + auto next = [&] { state = state * 1664525u + 1013904223u; return static_cast(state >> 24); }; + std::vector seeds = {{61, 174, 233}, {255, 176, 201}, {0, 0, 0}, {255, 255, 255}, {128, 128, 128}, + {255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 0}, {0x67, 0x50, 0xA4}}; + for (int i = 0; i < 300; i++) seeds.push_back({next(), next(), next()}); + for (auto seed : seeds) { + for (auto mode : {monet::Mode::Light, monet::Mode::Dark}) { + const MonetColors c = mapScheme(monet::generate(seed, mode)); + const char *m = mode == monet::Mode::Dark ? "dark" : "light"; + const auto surface = rgb(c.surface), fill = rgb(c.highlightFill); + auto pair = [&](const char *name, monet::Rgb bg, const fcitx::Color &fg, double min) { + CHECK_MSG(monet::contrast(bg, rgb(fg)) >= min, "%s %s, seed %s: %.2f < %.1f", m, name, monet::hex(seed).c_str(), + monet::contrast(bg, rgb(fg)), min); + }; + pair("candidate text", surface, c.normal, 7.0); + pair("candidate number", surface, c.candidateLabel, 4.5); + pair("candidate comment", surface, c.candidateComment, 4.5); + pair("typed pinyin being converted", surface, c.highlight, 4.5); + pair("text on the current candidate", fill, c.highlightCandidate, 7.0); + pair("number on the current candidate", fill, c.highlightCandidateLabel, 7.0); + pair("comment on the current candidate", fill, c.highlightCandidateComment, 7.0); + // the panel border can be seen against the panel + CHECK_MSG(monet::contrast(surface, rgb(c.outline)) >= 1.1, "%s border of seed %s", m, monet::hex(seed).c_str()); + // the pill can be told apart from the panel, by tone since a container is only a few tones off the surface + CHECK_MSG(std::fabs(monet::toneOf(fill) - monet::toneOf(surface)) >= 3.0, "%s pill of seed %s: tones %.1f and %.1f", m, + monet::hex(seed).c_str(), monet::toneOf(fill), monet::toneOf(surface)); + CHECK_MSG(c.surface.alpha() == 245 && c.normal.alpha() == 255 && c.highlightFill.alpha() == 255, "%s alpha of seed %s", m, monet::hex(seed).c_str()); + // dark panel in dark mode, light panel in light mode + CHECK_MSG((monet::toneOf(surface) < 50) == (mode == monet::Mode::Dark), "%s panel tone of seed %s", m, monet::hex(seed).c_str()); + } + } + std::printf(failures ? "%d check(s) failed\n" : "monettheme: all checks passed\n", failures); + return failures ? 1 : 0; +}