1312 lines
46 KiB
C++
1312 lines
46 KiB
C++
/*
|
|
* SPDX-FileCopyrightText: 2016-2017 CSSlayer <wengxt@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*
|
|
*/
|
|
|
|
#include "theme.h"
|
|
#include <fcntl.h>
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <filesystem>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <cairo.h>
|
|
#include <format>
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
#include <gio/gio.h>
|
|
#include <gio/gunixinputstream.h>
|
|
#include <glib.h>
|
|
#include <glibconfig.h>
|
|
#include <librsvg/rsvg.h>
|
|
#include <pango/pango-font.h>
|
|
#include <pango/pango-fontmap.h>
|
|
#include <pango/pango-layout.h>
|
|
#include <pango/pango-types.h>
|
|
#include <pango/pangocairo.h>
|
|
#include "fcitx-config/iniparser.h"
|
|
#include "fcitx-config/rawconfig.h"
|
|
#include "fcitx-utils/color.h"
|
|
#include "fcitx-utils/fs.h"
|
|
#include "fcitx-utils/log.h"
|
|
#include "fcitx-utils/macros.h"
|
|
#include "fcitx-utils/misc.h"
|
|
#include "fcitx-utils/misc_p.h"
|
|
#include "fcitx-utils/rect.h"
|
|
#include "fcitx-utils/standardpaths.h"
|
|
#include "fcitx-utils/stringutils.h"
|
|
#include "fcitx-utils/utf8.h"
|
|
#include "fcitx/icontheme.h"
|
|
#include "fcitx/misc_p.h"
|
|
#include "classicui.h"
|
|
#include "colorhelper.h"
|
|
#include "common.h"
|
|
|
|
namespace fcitx::m3ui {
|
|
|
|
namespace {
|
|
|
|
inline uint32_t charWidth(uint32_t c) {
|
|
if (g_unichar_iszerowidth(c)) {
|
|
return 0;
|
|
}
|
|
return g_unichar_iswide(c) ? 2 : 1;
|
|
}
|
|
|
|
// This is heuristic, but we guarantee that we don't do crazy things with label.
|
|
std::pair<std::string, size_t> extractTextForLabel(const std::string &label) {
|
|
std::string extracted;
|
|
|
|
// We have non white space here because xkb shortDescription have things
|
|
// like fr-tg, mon-a1.
|
|
auto texts = stringutils::split(label, FCITX_WHITESPACE "-_/|");
|
|
if (texts.empty()) {
|
|
return {"", 0};
|
|
}
|
|
|
|
size_t currentWidth = 0;
|
|
for (uint32_t chr : utf8::MakeUTF8CharRange(texts[0])) {
|
|
const auto width = charWidth(chr);
|
|
if (currentWidth + width <= 3) {
|
|
extracted.append(utf8::UCS4ToUTF8(chr));
|
|
currentWidth += width;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {extracted, currentWidth};
|
|
}
|
|
|
|
cairo_status_t readFromFd(void *closure, unsigned char *data,
|
|
unsigned int length) {
|
|
int fd = *static_cast<int *>(closure);
|
|
|
|
while (length) {
|
|
auto sz = fs::safeRead(fd, data, length);
|
|
if (sz <= 0) {
|
|
return CAIRO_STATUS_READ_ERROR;
|
|
}
|
|
length -= sz;
|
|
data += sz;
|
|
}
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
ThemeImage::Pixmap pixBufToPixmap(GdkPixbuf *image) {
|
|
cairo_format_t format;
|
|
cairo_surface_t *surface;
|
|
|
|
if (gdk_pixbuf_get_n_channels(image) == 3) {
|
|
format = CAIRO_FORMAT_RGB24;
|
|
} else {
|
|
format = CAIRO_FORMAT_ARGB32;
|
|
}
|
|
|
|
surface = cairo_image_surface_create(format, gdk_pixbuf_get_width(image),
|
|
gdk_pixbuf_get_height(image));
|
|
|
|
gint width;
|
|
gint height;
|
|
guchar *gdk_pixels;
|
|
guchar *cairo_pixels;
|
|
int gdk_rowstride;
|
|
int cairo_stride;
|
|
int n_channels;
|
|
int j;
|
|
|
|
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
|
|
cairo_surface_destroy(surface);
|
|
return nullptr;
|
|
}
|
|
|
|
cairo_surface_flush(surface);
|
|
|
|
width = gdk_pixbuf_get_width(image);
|
|
height = gdk_pixbuf_get_height(image);
|
|
gdk_pixels = gdk_pixbuf_get_pixels(image);
|
|
gdk_rowstride = gdk_pixbuf_get_rowstride(image);
|
|
n_channels = gdk_pixbuf_get_n_channels(image);
|
|
cairo_stride = cairo_image_surface_get_stride(surface);
|
|
cairo_pixels = cairo_image_surface_get_data(surface);
|
|
|
|
for (j = height; j; j--) {
|
|
guchar *p = gdk_pixels;
|
|
guchar *q = cairo_pixels;
|
|
|
|
if (n_channels == 3) {
|
|
guchar *end = p + (static_cast<ptrdiff_t>(3) * width);
|
|
|
|
while (p < end) {
|
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
q[0] = p[2];
|
|
q[1] = p[1];
|
|
q[2] = p[0];
|
|
q[3] = 0xFF;
|
|
#else
|
|
q[0] = 0xFF;
|
|
q[1] = p[0];
|
|
q[2] = p[1];
|
|
q[3] = p[2];
|
|
#endif
|
|
p += 3;
|
|
q += 4;
|
|
}
|
|
} else {
|
|
guchar *end = p + (static_cast<ptrdiff_t>(4) * width);
|
|
guint t1;
|
|
guint t2;
|
|
guint t3;
|
|
|
|
#define MULT(d, c, a, t) \
|
|
G_STMT_START { \
|
|
(t) = ((c) * (a)) + 0x80; \
|
|
(d) = (((t) >> 8) + (t)) >> 8; \
|
|
} \
|
|
G_STMT_END
|
|
|
|
while (p < end) {
|
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
MULT(q[0], p[2], p[3], t1);
|
|
MULT(q[1], p[1], p[3], t2);
|
|
MULT(q[2], p[0], p[3], t3);
|
|
q[3] = p[3];
|
|
#else
|
|
q[0] = p[3];
|
|
MULT(q[1], p[0], p[3], t1);
|
|
MULT(q[2], p[1], p[3], t2);
|
|
MULT(q[3], p[2], p[3], t3);
|
|
#endif
|
|
|
|
p += 4;
|
|
q += 4;
|
|
}
|
|
|
|
#undef MULT
|
|
}
|
|
|
|
gdk_pixels += gdk_rowstride;
|
|
cairo_pixels += cairo_stride;
|
|
}
|
|
|
|
cairo_surface_mark_dirty(surface);
|
|
return ThemeImage::Pixmap{surface};
|
|
}
|
|
|
|
ThemeImage::Pixmap loadPixmap(UnixFD &file, const std::filesystem::path &path) {
|
|
if (file.fd() < 0) {
|
|
return nullptr;
|
|
}
|
|
if (path.extension() == ".png") {
|
|
int fd = file.fd();
|
|
auto *surface =
|
|
cairo_image_surface_create_from_png_stream(readFromFd, &fd);
|
|
if (!surface) {
|
|
return nullptr;
|
|
}
|
|
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
|
|
g_clear_pointer(&surface, cairo_surface_destroy);
|
|
return nullptr;
|
|
}
|
|
return ThemeImage::Pixmap{surface};
|
|
}
|
|
|
|
GObjectUniquePtr<GInputStream> stream(
|
|
g_unix_input_stream_new(file.fd(), false));
|
|
if (!stream) {
|
|
return nullptr;
|
|
}
|
|
GObjectUniquePtr<GdkPixbuf> image(
|
|
gdk_pixbuf_new_from_stream(stream.get(), nullptr, nullptr));
|
|
g_input_stream_close(stream.get(), nullptr, nullptr);
|
|
if (!image) {
|
|
return nullptr;
|
|
}
|
|
|
|
return pixBufToPixmap(image.get());
|
|
}
|
|
|
|
std::optional<std::pair<int, int>> svgSize(RsvgHandle *svg) {
|
|
double w = 0;
|
|
double h = 0;
|
|
if (rsvg_handle_get_intrinsic_size_in_pixels(svg, &w, &h)) {
|
|
int width = std::max(1, static_cast<int>(std::ceil(w)));
|
|
int height = std::max(1, static_cast<int>(std::ceil(h)));
|
|
return std::make_pair(width, height);
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
ThemeImage::CairoPattern renderSvg(RsvgHandle *svg, int width, int height) {
|
|
const cairo_rectangle_t extents{0, 0, static_cast<double>(width),
|
|
static_cast<double>(height)};
|
|
ThemeImage::Pixmap surface(
|
|
cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, &extents));
|
|
if (cairo_surface_status(surface.get()) != CAIRO_STATUS_SUCCESS) {
|
|
return nullptr;
|
|
}
|
|
UniqueCPtr<cairo_t, cairo_destroy> context(cairo_create(surface.get()));
|
|
if (cairo_status(context.get()) != CAIRO_STATUS_SUCCESS) {
|
|
return nullptr;
|
|
}
|
|
const RsvgRectangle viewport{0, 0, static_cast<double>(width),
|
|
static_cast<double>(height)};
|
|
if (!rsvg_handle_render_document(svg, context.get(), &viewport, nullptr) ||
|
|
cairo_status(context.get()) != CAIRO_STATUS_SUCCESS) {
|
|
return nullptr;
|
|
}
|
|
ThemeImage::CairoPattern pattern(
|
|
cairo_pattern_create_for_surface(surface.get()));
|
|
if (cairo_pattern_status(pattern.get()) != CAIRO_STATUS_SUCCESS) {
|
|
return nullptr;
|
|
}
|
|
return pattern;
|
|
}
|
|
|
|
std::optional<ThemeImage::Svg> loadSvg(UnixFD &file) {
|
|
if (!file.isValid()) {
|
|
return std::nullopt;
|
|
}
|
|
GObjectUniquePtr<GInputStream> stream(
|
|
g_unix_input_stream_new(file.fd(), false));
|
|
if (!stream) {
|
|
return std::nullopt;
|
|
}
|
|
GObjectUniquePtr<RsvgHandle> handle(rsvg_handle_new_from_stream_sync(
|
|
stream.get(), nullptr, RSVG_HANDLE_FLAGS_NONE, nullptr, nullptr));
|
|
g_input_stream_close(stream.get(), nullptr, nullptr);
|
|
if (!handle) {
|
|
return std::nullopt;
|
|
}
|
|
auto size = svgSize(handle.get());
|
|
if (!size) {
|
|
return std::nullopt;
|
|
}
|
|
ThemeImage::Svg svg;
|
|
svg.width = size->first;
|
|
svg.height = size->second;
|
|
svg.pattern = renderSvg(handle.get(), svg.width, svg.height);
|
|
if (!svg.pattern) {
|
|
return std::nullopt;
|
|
}
|
|
return svg;
|
|
}
|
|
|
|
bool isSvgPath(const std::filesystem::path &path) {
|
|
return path.extension() == ".svg" || path.extension() == ".svgz";
|
|
}
|
|
|
|
const std::vector<std::string> &gdkPixbufSupportedFormats() {
|
|
const static std::vector<std::string> formats = []() {
|
|
std::unordered_set<std::string> exts;
|
|
std::vector<std::string> result;
|
|
// PNG is supported by cairo.
|
|
UniqueCPtr<GSList, g_slist_free> list(gdk_pixbuf_get_formats());
|
|
for (GSList *item = list.get(); item; item = g_slist_next(item)) {
|
|
gchar **extension = gdk_pixbuf_format_get_extensions(
|
|
static_cast<GdkPixbufFormat *>(item->data));
|
|
for (auto *iter = extension; iter && *iter; iter++) {
|
|
exts.insert(std::string(*iter));
|
|
}
|
|
g_strfreev(extension);
|
|
}
|
|
|
|
// Only put the common types and we make a preferred order.
|
|
for (std::string ext : {"svg", "svgz", "png", "bmp", "xpm"}) {
|
|
// png is supported by cairo.
|
|
if (ext == "png" || exts.contains(ext)) {
|
|
result.push_back("." + ext);
|
|
}
|
|
}
|
|
CLASSICUI_DEBUG() << "Supported image extensions: " << result;
|
|
return result;
|
|
}();
|
|
|
|
return formats;
|
|
}
|
|
|
|
constexpr double RoundEpsilon = 1e-3;
|
|
|
|
double pixelCeil(double f) { return std::ceil(f - RoundEpsilon); }
|
|
double pixelFloor(double f) { return std::floor(f + RoundEpsilon); }
|
|
|
|
template <typename CreatePattern>
|
|
std::optional<ThemeImage::NineTiles>
|
|
createImage(int width, int height, int marginLeft, int marginTop,
|
|
int marginRight, int marginBottom, bool retainSourceOffset,
|
|
CreatePattern createPattern) {
|
|
ThemeImage::NineTiles result;
|
|
result.width = width;
|
|
result.height = height;
|
|
const int sourceX[] = {0, marginLeft, width - marginRight, width};
|
|
const int sourceY[] = {0, marginTop, height - marginBottom, height};
|
|
for (int y = 0; y < 3; y++) {
|
|
for (int x = 0; x < 3; x++) {
|
|
int tileWidth = sourceX[x + 1] - sourceX[x];
|
|
int tileHeight = sourceY[y + 1] - sourceY[y];
|
|
if (tileWidth <= 0 || tileHeight <= 0) {
|
|
continue;
|
|
}
|
|
auto &resultTile = result.tiles[(y * 3) + x];
|
|
resultTile.width = tileWidth;
|
|
resultTile.height = tileHeight;
|
|
if (retainSourceOffset) {
|
|
resultTile.sourceX = sourceX[x];
|
|
resultTile.sourceY = sourceY[y];
|
|
}
|
|
resultTile.pattern =
|
|
createPattern(sourceX[x], sourceY[y], tileWidth, tileHeight);
|
|
if (cairo_pattern_status(resultTile.pattern.get()) !=
|
|
CAIRO_STATUS_SUCCESS) {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::optional<ThemeImage::NineTiles>
|
|
createImage(cairo_pattern_t *source, int width, int height, int marginLeft = 0,
|
|
int marginTop = 0, int marginRight = 0, int marginBottom = 0) {
|
|
if (cairo_pattern_status(source) != CAIRO_STATUS_SUCCESS) {
|
|
return std::nullopt;
|
|
}
|
|
return createImage(width, height, marginLeft, marginTop, marginRight,
|
|
marginBottom, true, [source](int, int, int, int) {
|
|
return ThemeImage::CairoPattern(
|
|
cairo_pattern_reference(source));
|
|
});
|
|
}
|
|
|
|
std::optional<ThemeImage::NineTiles>
|
|
createImage(ThemeImage::Pixmap pixmap, int marginLeft = 0, int marginTop = 0,
|
|
int marginRight = 0, int marginBottom = 0, int scale = 1) {
|
|
if (!pixmap) {
|
|
return std::nullopt;
|
|
}
|
|
int sourceWidth = cairo_image_surface_get_width(pixmap.get());
|
|
int sourceHeight = cairo_image_surface_get_height(pixmap.get());
|
|
if (sourceWidth % scale || sourceHeight % scale) {
|
|
return std::nullopt;
|
|
}
|
|
int width = sourceWidth / scale;
|
|
int height = sourceHeight / scale;
|
|
return createImage(
|
|
width, height, marginLeft, marginTop, marginRight, marginBottom, false,
|
|
[surface = pixmap.get(), scale](int x, int y, int tileWidth,
|
|
int tileHeight) {
|
|
ThemeImage::Pixmap tile(cairo_surface_create_for_rectangle(
|
|
surface, x * scale, y * scale, tileWidth * scale,
|
|
tileHeight * scale));
|
|
ThemeImage::CairoPattern pattern(
|
|
cairo_pattern_create_for_surface(tile.get()));
|
|
cairo_matrix_t matrix;
|
|
cairo_matrix_init_scale(&matrix, scale, scale);
|
|
cairo_pattern_set_matrix(pattern.get(), &matrix);
|
|
cairo_pattern_set_extend(pattern.get(), CAIRO_EXTEND_PAD);
|
|
return pattern;
|
|
});
|
|
}
|
|
|
|
std::filesystem::path scaledImagePath(const std::filesystem::path &path,
|
|
int scale) {
|
|
return path.parent_path() / std::format("{}@{}x{}", path.stem().string(),
|
|
scale, path.extension().string());
|
|
}
|
|
|
|
template <typename Image, typename Load>
|
|
void loadScaledImages(std::map<int, Image> &images,
|
|
const std::filesystem::path &path, int supportedScale,
|
|
int width, int height, Load load) {
|
|
for (int scale = 2; scale <= supportedScale; scale++) {
|
|
auto scaledPath = scaledImagePath(path, scale);
|
|
auto file = StandardPaths::openPath(scaledPath);
|
|
if (auto image = load(scale, file, scaledPath)) {
|
|
if (image->width == width && image->height == height) {
|
|
images.emplace(scale, std::move(*image));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Image>
|
|
const Image *findScaledImage(const std::map<int, Image> &images, int scale) {
|
|
if (scale == 1) {
|
|
return nullptr;
|
|
}
|
|
auto iter = images.lower_bound(scale);
|
|
if (iter != images.end()) {
|
|
return &iter->second;
|
|
}
|
|
return images.empty() ? nullptr : &images.rbegin()->second;
|
|
}
|
|
|
|
void paintTile(cairo_t *c, double x, double y, double width, double height,
|
|
double alpha, const ThemeImage &image, int marginLeft,
|
|
int marginTop, int marginRight, int marginBottom) {
|
|
|
|
int resizeHeight = image.height() - marginTop - marginBottom;
|
|
int resizeWidth = image.width() - marginLeft - marginRight;
|
|
|
|
if (resizeHeight <= 0) {
|
|
resizeHeight = 1;
|
|
}
|
|
|
|
if (resizeWidth <= 0) {
|
|
resizeWidth = 1;
|
|
}
|
|
|
|
if (height < 0) {
|
|
height = resizeHeight;
|
|
}
|
|
|
|
if (width < 0) {
|
|
width = resizeWidth;
|
|
}
|
|
const auto targetResizeWidth = width - marginLeft - marginRight;
|
|
const auto targetResizeHeight = height - marginTop - marginBottom;
|
|
|
|
double gridX[] = {0.0, static_cast<double>(marginLeft),
|
|
static_cast<double>(width - marginRight), width};
|
|
double gridY[] = {0.0, static_cast<double>(marginTop),
|
|
static_cast<double>(height - marginBottom), height};
|
|
for (double &gx : gridX) {
|
|
gx += x;
|
|
}
|
|
for (double &gy : gridY) {
|
|
gy += y;
|
|
}
|
|
|
|
auto *surface = cairo_get_target(c);
|
|
double xScale;
|
|
double yScale;
|
|
cairo_surface_get_device_scale(surface, &xScale, &yScale);
|
|
for (int i = 0; i < 4; i++) {
|
|
if (i % 2 == 0) {
|
|
gridX[i] = pixelFloor(gridX[i] * xScale) / xScale;
|
|
gridY[i] = pixelFloor(gridY[i] * yScale) / yScale;
|
|
} else {
|
|
gridX[i] = pixelCeil(gridX[i] * xScale) / xScale;
|
|
gridY[i] = pixelCeil(gridY[i] * yScale) / yScale;
|
|
}
|
|
}
|
|
|
|
auto part = [&](int ix, int iy) {
|
|
double dx = gridX[ix];
|
|
double dy = gridY[iy];
|
|
double dw = gridX[ix + 1] - gridX[ix];
|
|
double dh = gridY[iy + 1] - gridY[iy];
|
|
if (dw > 0 && dh > 0) {
|
|
image.paintRegion(
|
|
c, static_cast<ThemeImage::TilePosition>((iy * 3) + ix), dx, dy,
|
|
dw, dh, alpha);
|
|
}
|
|
};
|
|
/*
|
|
* 7 8 9
|
|
* 4 5 6
|
|
* 1 2 3
|
|
*/
|
|
|
|
if (marginLeft && marginBottom) {
|
|
/* part 1 */
|
|
part(0, 2);
|
|
}
|
|
|
|
if (marginRight && marginBottom) {
|
|
/* part 3 */
|
|
part(2, 2);
|
|
}
|
|
|
|
if (marginLeft && marginTop) {
|
|
/* part 7 */
|
|
part(0, 0);
|
|
}
|
|
|
|
if (marginRight && marginTop) {
|
|
/* part 9 */
|
|
part(2, 0);
|
|
}
|
|
|
|
/* part 2 & 8 */
|
|
if (marginTop && targetResizeWidth > 0) {
|
|
part(1, 0);
|
|
}
|
|
|
|
if (marginBottom && targetResizeWidth > 0) {
|
|
part(1, 2);
|
|
}
|
|
|
|
/* part 4 & 6 */
|
|
if (marginLeft && targetResizeHeight > 0) {
|
|
part(0, 1);
|
|
}
|
|
|
|
if (marginRight && targetResizeHeight > 0) {
|
|
part(2, 1);
|
|
}
|
|
|
|
/* part 5 */
|
|
if (targetResizeHeight > 0 && targetResizeWidth > 0) {
|
|
part(1, 1);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ThemeImage::ThemeImage(const IconTheme &iconTheme, const std::string &icon,
|
|
const std::string &label, uint32_t size,
|
|
const M3UI *classicui)
|
|
: size_(size) {
|
|
bool preferTextIcon =
|
|
!label.empty() &&
|
|
((icon == "input-keyboard" &&
|
|
hasTwoKeyboardInCurrentGroup(classicui->instance())) ||
|
|
*classicui->config().preferTextIcon);
|
|
if (!preferTextIcon && !icon.empty()) {
|
|
std::filesystem::path iconPath =
|
|
iconTheme.findIconPath(icon, size, 1, gdkPixbufSupportedFormats());
|
|
auto fd = StandardPaths::openPath(iconPath);
|
|
if (isSvgPath(iconPath)) {
|
|
if (auto svg = loadSvg(fd)) {
|
|
if (auto image = createImage(svg->pattern.get(), svg->width,
|
|
svg->height)) {
|
|
image_ = std::move(*image);
|
|
}
|
|
}
|
|
} else {
|
|
Pixmap pixmap = loadPixmap(fd, iconPath);
|
|
if (auto image = createImage(std::move(pixmap))) {
|
|
image_ = std::move(*image);
|
|
}
|
|
}
|
|
}
|
|
if (!valid()) {
|
|
Pixmap textImage(
|
|
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size));
|
|
drawTextIcon(textImage.get(), label, size, classicui->config());
|
|
if (auto image = createImage(std::move(textImage))) {
|
|
image_ = std::move(*image);
|
|
}
|
|
}
|
|
}
|
|
|
|
ThemeImage::ThemeImage(const Theme &theme, const BackgroundImageConfig &cfg,
|
|
const Color &color, const Color &borderColor) {
|
|
if (!cfg.image->empty()) {
|
|
std::filesystem::path imagePath;
|
|
auto imageFile = StandardPaths::global().open(
|
|
StandardPathsType::PkgData,
|
|
std::filesystem::path("themes") / theme.name() / *cfg.image,
|
|
theme.isSystemTheme() ? StandardPathsMode::System
|
|
: StandardPathsMode::Default,
|
|
&imagePath);
|
|
if (isSvgPath(imagePath)) {
|
|
if (auto svg = loadSvg(imageFile)) {
|
|
if (auto image = createImage(
|
|
svg->pattern.get(), svg->width, svg->height,
|
|
*cfg.margin->marginLeft, *cfg.margin->marginTop,
|
|
*cfg.margin->marginRight, *cfg.margin->marginBottom)) {
|
|
image_ = std::move(*image);
|
|
}
|
|
}
|
|
} else {
|
|
Pixmap pixmap = loadPixmap(imageFile, imagePath);
|
|
if (auto image = createImage(
|
|
std::move(pixmap), *cfg.margin->marginLeft,
|
|
*cfg.margin->marginTop, *cfg.margin->marginRight,
|
|
*cfg.margin->marginBottom)) {
|
|
image_ = std::move(*image);
|
|
loadScaledImages(
|
|
scaledImages_, imagePath, *theme.supportedScale,
|
|
image_->width, image_->height,
|
|
[&cfg](int scale, UnixFD &file,
|
|
const std::filesystem::path &path) {
|
|
return createImage(
|
|
loadPixmap(file, path), *cfg.margin->marginLeft,
|
|
*cfg.margin->marginTop, *cfg.margin->marginRight,
|
|
*cfg.margin->marginBottom, scale);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cfg.overlay->empty()) {
|
|
std::filesystem::path imagePath;
|
|
auto imageFile = StandardPaths::global().open(
|
|
StandardPathsType::PkgData,
|
|
std::filesystem::path("themes") / theme.name() / *cfg.overlay,
|
|
theme.isSystemTheme() ? StandardPathsMode::System
|
|
: StandardPathsMode::Default,
|
|
&imagePath);
|
|
if (isSvgPath(imagePath)) {
|
|
if (auto svg = loadSvg(imageFile)) {
|
|
if (auto overlay = createImage(svg->pattern.get(), svg->width,
|
|
svg->height)) {
|
|
overlay_ = std::move(overlay->tiles[4]);
|
|
}
|
|
}
|
|
} else if (auto overlay =
|
|
createImage(loadPixmap(imageFile, imagePath))) {
|
|
overlay_ = std::move(overlay->tiles[4]);
|
|
loadScaledImages(
|
|
scaledOverlays_, imagePath, *theme.supportedScale,
|
|
overlay_->width, overlay_->height,
|
|
[](int scale, UnixFD &file, const std::filesystem::path &path) {
|
|
if (auto image = createImage(loadPixmap(file, path), 0, 0,
|
|
0, 0, scale)) {
|
|
return std::optional<NineTiles::Tile>(
|
|
std::move(image->tiles[4]));
|
|
}
|
|
return std::optional<NineTiles::Tile>();
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!valid()) {
|
|
constexpr auto minimumSize = 20;
|
|
auto width =
|
|
*cfg.margin->marginLeft + *cfg.margin->marginRight +
|
|
std::max(*cfg.margin->marginLeft + *cfg.margin->marginRight,
|
|
minimumSize);
|
|
auto height =
|
|
*cfg.margin->marginTop + *cfg.margin->marginBottom +
|
|
std::max(*cfg.margin->marginTop + *cfg.margin->marginBottom,
|
|
minimumSize);
|
|
|
|
auto borderWidth =
|
|
std::min({*cfg.borderWidth, *cfg.margin->marginLeft,
|
|
*cfg.margin->marginRight, *cfg.margin->marginTop,
|
|
*cfg.margin->marginBottom});
|
|
|
|
CLASSICUI_DEBUG() << "Paint background: height " << height << " width "
|
|
<< width << " border=" << borderColor
|
|
<< " border width=" << *cfg.borderWidth
|
|
<< " color=" << color;
|
|
ThemeImage::Pattern pattern;
|
|
pattern.width = width;
|
|
pattern.height = height;
|
|
pattern.borderWidth = borderWidth;
|
|
pattern.pattern.reset(cairo_pattern_create_mesh());
|
|
const auto setPatch = [&](double x0, double y0, double x1, double y1,
|
|
const Color &color) {
|
|
cairo_mesh_pattern_begin_patch(pattern.pattern.get());
|
|
cairo_mesh_pattern_move_to(pattern.pattern.get(), x0, y0);
|
|
cairo_mesh_pattern_line_to(pattern.pattern.get(), x1, y0);
|
|
cairo_mesh_pattern_line_to(pattern.pattern.get(), x1, y1);
|
|
cairo_mesh_pattern_line_to(pattern.pattern.get(), x0, y1);
|
|
for (int i = 0; i < 4; i++) {
|
|
cairo_mesh_pattern_set_corner_color_rgba(
|
|
pattern.pattern.get(), i, color.redF(), color.greenF(),
|
|
color.blueF(), color.alphaF());
|
|
}
|
|
cairo_mesh_pattern_end_patch(pattern.pattern.get());
|
|
};
|
|
if (borderWidth > 0) {
|
|
const double x[] = {0.0, static_cast<double>(borderWidth),
|
|
static_cast<double>(width - borderWidth),
|
|
static_cast<double>(width)};
|
|
const double y[] = {0.0, static_cast<double>(borderWidth),
|
|
static_cast<double>(height - borderWidth),
|
|
static_cast<double>(height)};
|
|
for (int row = 0; row < 3; row++) {
|
|
for (int column = 0; column < 3; column++) {
|
|
setPatch(x[column], y[row], x[column + 1], y[row + 1],
|
|
(row == 1 && column == 1) ? color : borderColor);
|
|
}
|
|
}
|
|
} else {
|
|
setPatch(0, 0, width, height, color);
|
|
}
|
|
if (auto image = createImage(
|
|
pattern.pattern.get(), pattern.width, pattern.height,
|
|
*cfg.margin->marginLeft, *cfg.margin->marginTop,
|
|
*cfg.margin->marginRight, *cfg.margin->marginBottom)) {
|
|
image_ = std::move(*image);
|
|
isPattern_ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
ThemeImage::ThemeImage(const Theme &theme, const ActionImageConfig &cfg) {
|
|
if (!cfg.image->empty()) {
|
|
std::filesystem::path imagePath;
|
|
auto imageFile = StandardPaths::global().open(
|
|
StandardPathsType::PkgData,
|
|
std::filesystem::path("themes") / theme.name() / *cfg.image,
|
|
theme.isSystemTheme() ? StandardPathsMode::System
|
|
: StandardPathsMode::Default,
|
|
&imagePath);
|
|
if (isSvgPath(imagePath)) {
|
|
if (auto svg = loadSvg(imageFile)) {
|
|
if (auto image = createImage(svg->pattern.get(), svg->width,
|
|
svg->height)) {
|
|
image_ = std::move(*image);
|
|
}
|
|
}
|
|
} else {
|
|
if (auto image = createImage(loadPixmap(imageFile, imagePath))) {
|
|
image_ = std::move(*image);
|
|
loadScaledImages(scaledImages_, imagePath,
|
|
*theme.supportedScale, image_->width,
|
|
image_->height,
|
|
[](int scale, UnixFD &file,
|
|
const std::filesystem::path &path) {
|
|
return createImage(loadPixmap(file, path),
|
|
0, 0, 0, 0, scale);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ThemeImage::drawTextIcon(cairo_surface_t *surface,
|
|
const std::string &rawLabel, uint32_t size,
|
|
const ClassicUIConfig &config) {
|
|
auto [label, textWidth] = extractTextForLabel(rawLabel);
|
|
auto *cr = cairo_create(surface);
|
|
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
|
|
cairoSetSourceColor(cr, Color("#00000000"));
|
|
cairo_paint(cr);
|
|
|
|
int pixelSize = size * 0.7 * (textWidth >= 3 ? (2.0 / textWidth) : 1.0);
|
|
if (pixelSize < 0) {
|
|
pixelSize = 1;
|
|
}
|
|
auto *fontMap = pango_cairo_font_map_get_default();
|
|
GObjectUniquePtr<PangoContext> context(
|
|
pango_font_map_create_context(fontMap));
|
|
GObjectUniquePtr<PangoLayout> layout(pango_layout_new(context.get()));
|
|
pango_layout_set_single_paragraph_mode(layout.get(), true);
|
|
pango_layout_set_text(layout.get(), label.c_str(), label.size());
|
|
PangoRectangle rect;
|
|
PangoFontDescription *desc =
|
|
pango_font_description_from_string(config.trayFont->c_str());
|
|
pango_font_description_set_absolute_size(desc, pixelSize * PANGO_SCALE);
|
|
pango_layout_set_font_description(layout.get(), desc);
|
|
pango_font_description_free(desc);
|
|
pango_layout_get_pixel_extents(layout.get(), &rect, nullptr);
|
|
cairo_translate(cr, ((size - rect.width) * 0.5) - rect.x,
|
|
((size - rect.height) * 0.5) - rect.y);
|
|
if (config.trayBorderColor->alpha()) {
|
|
cairo_save(cr);
|
|
cairoSetSourceColor(cr, *config.trayBorderColor);
|
|
pango_cairo_layout_path(cr, layout.get());
|
|
cairo_set_line_width(cr, std::min(4, (pixelSize + 4) / 8));
|
|
cairo_stroke(cr);
|
|
cairo_restore(cr);
|
|
}
|
|
|
|
cairo_save(cr);
|
|
cairoSetSourceColor(cr, *config.trayTextColor);
|
|
pango_cairo_show_layout(cr, layout.get());
|
|
cairo_restore(cr);
|
|
|
|
cairo_destroy(cr);
|
|
}
|
|
|
|
void ThemeImage::paintRegion(cairo_t *c, TilePosition tile, double destX,
|
|
double destY, double destWidth, double destHeight,
|
|
double alpha, bool overlay) const {
|
|
if (destWidth <= 0 || destHeight <= 0) {
|
|
return;
|
|
}
|
|
if ((overlay && !overlay_) || (!overlay && !image_)) {
|
|
return;
|
|
}
|
|
const auto scale = [&]() {
|
|
double xScale;
|
|
double yScale;
|
|
cairo_surface_get_device_scale(cairo_get_target(c), &xScale, &yScale);
|
|
return static_cast<int>(std::ceil(std::max(xScale, yScale)));
|
|
}();
|
|
const NineTiles::Tile *source = nullptr;
|
|
if (overlay) {
|
|
if (auto scaledOverlay = findScaledImage(scaledOverlays_, scale)) {
|
|
source = scaledOverlay;
|
|
} else {
|
|
source = &*overlay_;
|
|
}
|
|
} else {
|
|
if (auto scaledImage = findScaledImage(scaledImages_, scale)) {
|
|
source = &scaledImage->tiles[static_cast<std::size_t>(tile)];
|
|
} else {
|
|
source = &image_->tiles[static_cast<std::size_t>(tile)];
|
|
}
|
|
}
|
|
if (!source->pattern) {
|
|
return;
|
|
}
|
|
cairo_save(c);
|
|
cairo_rectangle(c, destX, destY, destWidth, destHeight);
|
|
cairo_clip(c);
|
|
cairo_translate(c, destX, destY);
|
|
cairo_scale(c, destWidth / source->width, destHeight / source->height);
|
|
cairo_translate(c, -source->sourceX, -source->sourceY);
|
|
cairo_set_source(c, source->pattern.get());
|
|
cairo_paint_with_alpha(c, alpha);
|
|
cairo_restore(c);
|
|
}
|
|
|
|
Theme::Theme() : iconTheme_(IconTheme::defaultIconThemeName()) {}
|
|
|
|
Theme::~Theme() {}
|
|
|
|
bool Theme::isSystemThemeName(std::string_view themeName) {
|
|
return themeName == "default" || themeName == "default-dark";
|
|
}
|
|
|
|
const ThemeImage &Theme::loadBackground(const BackgroundImageConfig &cfg) {
|
|
if (auto *image = findValue(backgroundImageTable_, &cfg)) {
|
|
return *image;
|
|
}
|
|
|
|
Color color;
|
|
Color borderColor;
|
|
if (&cfg == &*inputPanel->background) {
|
|
color = inputPanelBackground_;
|
|
borderColor = inputPanelBorder_;
|
|
} else if (&cfg == &*inputPanel->highlight) {
|
|
color = inputPanelHighlightCandidateBackground_;
|
|
borderColor = inputPanelHighlightCandidateBorder_;
|
|
} else if (&cfg == &*menu->background) {
|
|
color = menuBackground_;
|
|
borderColor = menuBorder_;
|
|
} else if (&cfg == &*menu->highlight) {
|
|
color = menuSelectedItemBackground_;
|
|
borderColor = menuSelectedItemBorder_;
|
|
} else if (&cfg == &*menu->separator) {
|
|
color = menuSeparator_;
|
|
borderColor = *cfg.borderColor;
|
|
} else {
|
|
color = *cfg.color;
|
|
borderColor = *cfg.borderColor;
|
|
}
|
|
|
|
auto result = backgroundImageTable_.emplace(
|
|
std::piecewise_construct, std::forward_as_tuple(&cfg),
|
|
std::forward_as_tuple(*this, cfg, color, borderColor));
|
|
assert(result.second);
|
|
return result.first->second;
|
|
}
|
|
|
|
const ThemeImage &Theme::loadAction(const ActionImageConfig &cfg) {
|
|
if (auto *image = findValue(actionImageTable_, &cfg)) {
|
|
return *image;
|
|
}
|
|
|
|
auto result = actionImageTable_.emplace(std::piecewise_construct,
|
|
std::forward_as_tuple(&cfg),
|
|
std::forward_as_tuple(*this, cfg));
|
|
assert(result.second);
|
|
return result.first->second;
|
|
}
|
|
|
|
const ThemeImage &Theme::loadImage(const std::string &icon,
|
|
const std::string &label, uint32_t size,
|
|
const M3UI *classicui) {
|
|
auto &map = trayImageTable_;
|
|
auto name = stringutils::concat("icon:", icon, "label:", label);
|
|
if (auto *image = findValue(map, name)) {
|
|
if (image->size() == size) {
|
|
return *image;
|
|
}
|
|
map.erase(name);
|
|
}
|
|
|
|
auto result = map.emplace(
|
|
std::piecewise_construct, std::forward_as_tuple(name),
|
|
std::forward_as_tuple(iconTheme_, icon, label, size, classicui));
|
|
assert(result.second);
|
|
return result.first->second;
|
|
}
|
|
|
|
void Theme::paint(cairo_t *c, const BackgroundImageConfig &cfg, double dx,
|
|
double dy, double width, double height, double alpha) {
|
|
const ThemeImage &image = loadBackground(cfg);
|
|
auto marginTop = *cfg.margin->marginTop;
|
|
auto marginBottom = *cfg.margin->marginBottom;
|
|
auto marginLeft = *cfg.margin->marginLeft;
|
|
auto marginRight = *cfg.margin->marginRight;
|
|
|
|
paintTile(c, dx, dy, width, height, alpha, image, marginLeft, marginTop,
|
|
marginRight, marginBottom);
|
|
|
|
if (!image.hasOverlay()) {
|
|
return;
|
|
}
|
|
|
|
Rect clipRect;
|
|
auto clipWidth = width - *cfg.overlayClipMargin->marginLeft -
|
|
*cfg.overlayClipMargin->marginRight;
|
|
auto clipHeight = height - *cfg.overlayClipMargin->marginTop -
|
|
*cfg.overlayClipMargin->marginBottom;
|
|
if (clipWidth <= 0 || clipHeight <= 0) {
|
|
return;
|
|
}
|
|
clipRect
|
|
.setPosition(*cfg.overlayClipMargin->marginLeft,
|
|
*cfg.overlayClipMargin->marginTop)
|
|
.setSize(clipWidth, clipHeight);
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
switch (*cfg.gravity) {
|
|
case Gravity::TopLeft:
|
|
case Gravity::CenterLeft:
|
|
case Gravity::BottomLeft:
|
|
x = *cfg.overlayOffsetX;
|
|
break;
|
|
case Gravity::TopCenter:
|
|
case Gravity::Center:
|
|
case Gravity::BottomCenter:
|
|
x = ((width - image.overlayWidth()) / 2) + *cfg.overlayOffsetX;
|
|
break;
|
|
case Gravity::TopRight:
|
|
case Gravity::CenterRight:
|
|
case Gravity::BottomRight:
|
|
x = width - image.overlayWidth() - *cfg.overlayOffsetX;
|
|
break;
|
|
}
|
|
switch (*cfg.gravity) {
|
|
case Gravity::TopLeft:
|
|
case Gravity::TopCenter:
|
|
case Gravity::TopRight:
|
|
y = *cfg.overlayOffsetY;
|
|
break;
|
|
case Gravity::CenterLeft:
|
|
case Gravity::Center:
|
|
case Gravity::CenterRight:
|
|
y = ((height - image.overlayHeight()) / 2) + *cfg.overlayOffsetY;
|
|
break;
|
|
case Gravity::BottomLeft:
|
|
case Gravity::BottomCenter:
|
|
case Gravity::BottomRight:
|
|
y = height - image.overlayHeight() - *cfg.overlayOffsetY;
|
|
break;
|
|
}
|
|
Rect rect;
|
|
rect.setPosition(x, y).setSize(image.overlayWidth(), image.overlayHeight());
|
|
Rect finalRect = rect.intersected(clipRect);
|
|
if (finalRect.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
if (*cfg.hideOverlayIfOversize && !clipRect.contains(rect)) {
|
|
return;
|
|
}
|
|
|
|
cairo_save(c);
|
|
cairo_set_operator(c, CAIRO_OPERATOR_OVER);
|
|
cairo_rectangle(c, finalRect.left(), finalRect.top(), finalRect.width(),
|
|
finalRect.height());
|
|
cairo_clip(c);
|
|
image.paintRegion(c, ThemeImage::TilePosition::Center, x, y,
|
|
image.overlayWidth(), image.overlayHeight(), alpha, true);
|
|
cairo_restore(c);
|
|
}
|
|
|
|
void Theme::paint(cairo_t *c, const ActionImageConfig &cfg, double alpha) {
|
|
const ThemeImage &image = loadAction(cfg);
|
|
int height = image.height();
|
|
int width = image.width();
|
|
|
|
image.paintRegion(c, ThemeImage::TilePosition::Center, 0, 0, width, height,
|
|
alpha);
|
|
}
|
|
|
|
void Theme::reset() {
|
|
trayImageTable_.clear();
|
|
backgroundImageTable_.clear();
|
|
actionImageTable_.clear();
|
|
}
|
|
|
|
void Theme::load(std::string_view name) {
|
|
reset();
|
|
isSystemTheme_ = isSystemThemeName(name);
|
|
ThemeConfig config;
|
|
copyHelper(config);
|
|
// Reset the default value to state.
|
|
syncDefaultValueToCurrent();
|
|
if (auto themeConfigFile = StandardPaths::global().open(
|
|
StandardPathsType::PkgData,
|
|
std::filesystem::path("themes") / name / "theme.conf",
|
|
StandardPathsMode::System);
|
|
themeConfigFile.isValid()) {
|
|
RawConfig themeConfig;
|
|
readFromIni(themeConfig, themeConfigFile.fd());
|
|
Configuration::load(themeConfig, true);
|
|
} else {
|
|
// No sys file, reset default value.
|
|
ThemeConfig config;
|
|
copyHelper(config);
|
|
}
|
|
syncDefaultValueToCurrent();
|
|
if (!isSystemTheme_) {
|
|
// For system theme, we don't load user file, as user file is only for
|
|
// custom theme.
|
|
if (auto themeConfigFile = StandardPaths::global().open(
|
|
StandardPathsType::PkgData,
|
|
std::filesystem::path("themes") / name / "theme.conf",
|
|
StandardPathsMode::User);
|
|
themeConfigFile.isValid()) {
|
|
// Has user file, load user file data.
|
|
RawConfig themeConfig;
|
|
readFromIni(themeConfig, themeConfigFile.fd());
|
|
Configuration::load(themeConfig, true);
|
|
}
|
|
}
|
|
name_ = name;
|
|
maskConfig_ = *inputPanel->background;
|
|
maskConfig_.overlay.setValue("");
|
|
maskConfig_.image.setValue(*inputPanel->blurMask);
|
|
menuBlurMaskConfig_ = *menu->background;
|
|
menuBlurMaskConfig_.overlay.setValue("");
|
|
menuBlurMaskConfig_.image.setValue(*menu->blurMask);
|
|
accentColorFields_ = std::unordered_set<ColorField>(
|
|
accentColor.value().begin(), accentColor.value().end());
|
|
}
|
|
|
|
void Theme::load(std::string_view name, const RawConfig &rawConfig) {
|
|
reset();
|
|
isSystemTheme_ = isSystemThemeName(name);
|
|
Configuration::load(rawConfig, true);
|
|
name_ = name;
|
|
}
|
|
|
|
bool Theme::setIconTheme(const std::string &name) {
|
|
if (iconTheme_.internalName() != name) {
|
|
CLASSICUI_DEBUG() << "New Icon theme: " << name;
|
|
iconTheme_ = IconTheme(name);
|
|
trayImageTable_.clear();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
std::vector<Rect> Theme::mask(const BackgroundImageConfig &cfg, int width,
|
|
int height) {
|
|
UniqueCPtr<cairo_surface_t, cairo_surface_destroy> mask(
|
|
cairo_image_surface_create(CAIRO_FORMAT_A1, width, height));
|
|
auto *c = cairo_create(mask.get());
|
|
cairo_set_operator(c, CAIRO_OPERATOR_SOURCE);
|
|
paint(c, cfg, 0, 0, width, height, 1);
|
|
cairo_destroy(c);
|
|
|
|
UniqueCPtr<cairo_region_t, cairo_region_destroy> region(
|
|
cairo_region_create());
|
|
cairo_rectangle_int_t rect;
|
|
#define AddSpan \
|
|
do { \
|
|
rect.x = prev1; \
|
|
rect.y = y; \
|
|
rect.width = x - prev1; \
|
|
rect.height = 1; \
|
|
cairo_region_union_rectangle(region.get(), &rect); \
|
|
} while (0)
|
|
const uint8_t zero = 0;
|
|
const auto *data = cairo_image_surface_get_data(mask.get());
|
|
auto cairo_stride = cairo_image_surface_get_stride(mask.get());
|
|
constexpr bool little = G_BYTE_ORDER == G_LITTLE_ENDIAN;
|
|
int x;
|
|
int y;
|
|
for (y = 0; y < height; ++y) {
|
|
uint8_t all = zero;
|
|
int prev1 = -1;
|
|
for (x = 0; x < width;) {
|
|
uint8_t byte = data[x / 8];
|
|
if (x > width - 8 || byte != all) {
|
|
if constexpr (little) {
|
|
for (int b = 8; b > 0 && x < width; --b) {
|
|
if (!(byte & 0x01) == !all) {
|
|
// More of the same
|
|
} else {
|
|
// A change.
|
|
if (all != zero) {
|
|
AddSpan;
|
|
all = zero;
|
|
} else {
|
|
prev1 = x;
|
|
all = ~zero;
|
|
}
|
|
}
|
|
byte >>= 1;
|
|
++x;
|
|
}
|
|
} else {
|
|
for (int b = 8; b > 0 && x < width; --b) {
|
|
if (!(byte & 0x80) == !all) {
|
|
// More of the same
|
|
} else {
|
|
// A change.
|
|
if (all != zero) {
|
|
AddSpan;
|
|
all = zero;
|
|
} else {
|
|
prev1 = x;
|
|
all = ~zero;
|
|
}
|
|
}
|
|
byte <<= 1;
|
|
++x;
|
|
}
|
|
}
|
|
} else {
|
|
x += 8;
|
|
}
|
|
}
|
|
if (all != zero) {
|
|
AddSpan;
|
|
}
|
|
data += cairo_stride;
|
|
}
|
|
#undef AddSpan
|
|
std::vector<Rect> result;
|
|
for (int i = 0, e = cairo_region_num_rectangles(region.get()); i < e; i++) {
|
|
cairo_region_get_rectangle(region.get(), i, &rect);
|
|
result.push_back(Rect()
|
|
.setPosition(rect.x, rect.y)
|
|
.setSize(rect.width, rect.height));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Theme::applyMonet(const MonetColors &c) {
|
|
// A classic theme draws its panel from images, there is nothing to recolour.
|
|
if (inputPanel->surfaceColor->alpha() == 0) {
|
|
return;
|
|
}
|
|
auto *panel = inputPanel.mutableValue();
|
|
panel->surfaceColor.setValue(c.surface);
|
|
panel->outlineColor.setValue(c.outline);
|
|
panel->highlightFillColor.setValue(c.highlightFill);
|
|
panel->normalColor.setValue(c.normal);
|
|
panel->highlightColor.setValue(c.highlight);
|
|
panel->highlightBackgroundColor.setValue(c.highlightBackground);
|
|
panel->highlightCandidateColor.setValue(c.highlightCandidate);
|
|
panel->candidateLabelColor.setValue(c.candidateLabel);
|
|
panel->highlightCandidateLabelColor.setValue(c.highlightCandidateLabel);
|
|
panel->candidateCommentColor.setValue(c.candidateComment);
|
|
panel->highlightCandidateCommentColor.setValue(c.highlightCandidateComment);
|
|
}
|
|
|
|
void Theme::populateColor(std::optional<Color> accent) {
|
|
inputPanelBackground_ = *inputPanel->background->color;
|
|
inputPanelBorder_ = *inputPanel->background->borderColor;
|
|
inputPanelHighlightCandidateBackground_ = *inputPanel->highlight->color;
|
|
inputPanelHighlightCandidateBorder_ = *inputPanel->highlight->borderColor;
|
|
inputPanelHighlight_ = *inputPanel->highlightBackgroundColor;
|
|
inputPanelText_ = *inputPanel->normalColor;
|
|
inputPanelHighlightText_ = *inputPanel->highlightColor;
|
|
inputPanelHighlightCandidateText_ = *inputPanel->highlightCandidateColor;
|
|
|
|
auto inputPanelCandidateLabelText = *inputPanel->candidateLabelColor;
|
|
auto inputPanelHighlightCandidateLabelText =
|
|
*inputPanel->highlightCandidateLabelColor;
|
|
auto inputPanelCandidateCommentText = *inputPanel->candidateCommentColor;
|
|
auto inputPanelHighlightCandidateCommentText =
|
|
*inputPanel->highlightCandidateCommentColor;
|
|
|
|
menuBackground_ = *menu->background->color;
|
|
menuBorder_ = *menu->background->borderColor;
|
|
menuSelectedItemBackground_ = *menu->highlight->color;
|
|
menuSelectedItemBorder_ = *menu->highlight->borderColor;
|
|
menuSeparator_ = *menu->separator->color;
|
|
menuText_ = *menu->normalColor;
|
|
menuSelectedItemText_ = *menu->highlightTextColor;
|
|
|
|
if (accent) {
|
|
auto [foreground, foregroundDim] = accentForeground(*accent);
|
|
for (auto field : accentColorFields_) {
|
|
switch (field) {
|
|
case ColorField::InputPanel_Background:
|
|
inputPanelBackground_ = *accent;
|
|
inputPanelText_ = foreground;
|
|
if (inputPanelCandidateLabelText) {
|
|
inputPanelCandidateLabelText = foreground;
|
|
}
|
|
if (inputPanelCandidateCommentText) {
|
|
inputPanelCandidateCommentText = foregroundDim;
|
|
}
|
|
break;
|
|
case ColorField::InputPanel_Border:
|
|
inputPanelBorder_ = *accent;
|
|
break;
|
|
case ColorField::InputPanel_HighlightCandidateBackground:
|
|
inputPanelHighlightCandidateBackground_ = *accent;
|
|
inputPanelHighlightCandidateText_ = foreground;
|
|
if (inputPanelHighlightCandidateLabelText) {
|
|
inputPanelHighlightCandidateLabelText = foreground;
|
|
}
|
|
if (inputPanelHighlightCandidateCommentText) {
|
|
inputPanelHighlightCandidateCommentText = foregroundDim;
|
|
}
|
|
break;
|
|
case ColorField::InputPanel_HighlightCandidateBorder:
|
|
inputPanelHighlightCandidateBorder_ = *accent;
|
|
break;
|
|
case ColorField::InputPanel_Highlight:
|
|
inputPanelHighlight_ = *accent;
|
|
inputPanelHighlightText_ = foreground;
|
|
break;
|
|
case ColorField::Menu_Background:
|
|
menuBackground_ = *accent;
|
|
menuText_ = foreground;
|
|
break;
|
|
case ColorField::Menu_Border:
|
|
menuBorder_ = *accent;
|
|
break;
|
|
case ColorField::Menu_SelectedItemBackground:
|
|
menuSelectedItemBackground_ = *accent;
|
|
menuSelectedItemText_ = foreground;
|
|
break;
|
|
case ColorField::Menu_SelectedItemBorder:
|
|
menuSelectedItemBorder_ = *accent;
|
|
break;
|
|
case ColorField::Menu_Separator:
|
|
menuSeparator_ = *accent;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (inputPanelCandidateLabelText) {
|
|
inputPanelCandidateLabelText_ = *inputPanelCandidateLabelText;
|
|
} else {
|
|
inputPanelCandidateLabelText_ = inputPanelText_;
|
|
}
|
|
if (inputPanelHighlightCandidateLabelText) {
|
|
inputPanelHighlightCandidateLabelText_ =
|
|
*inputPanelHighlightCandidateLabelText;
|
|
} else {
|
|
inputPanelHighlightCandidateLabelText_ =
|
|
inputPanelHighlightCandidateText_;
|
|
}
|
|
if (inputPanelCandidateCommentText) {
|
|
inputPanelCandidateCommentText_ = *inputPanelCandidateCommentText;
|
|
} else {
|
|
inputPanelCandidateCommentText_ = inputPanelText_;
|
|
inputPanelCandidateCommentText_.setAlphaF(
|
|
inputPanelCandidateCommentText_.alphaF() * 0.6);
|
|
}
|
|
if (inputPanelHighlightCandidateCommentText) {
|
|
inputPanelHighlightCandidateCommentText_ =
|
|
*inputPanelHighlightCandidateCommentText;
|
|
} else {
|
|
inputPanelHighlightCandidateCommentText_ =
|
|
inputPanelHighlightCandidateText_;
|
|
inputPanelHighlightCandidateCommentText_.setAlphaF(
|
|
inputPanelHighlightCandidateCommentText_.alphaF() * 0.6);
|
|
}
|
|
}
|
|
|
|
} // namespace fcitx::m3ui
|