mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
* move message_log_widget to game * move files * update headers * fix cmakelists * oracle fixes * split implementation out to cpp * fix recursive import * fix main file * format
This commit is contained in:
parent
f484c98152
commit
17dcaf9afa
337 changed files with 728 additions and 721 deletions
392
cockatrice/src/interface/pixel_map_generator.cpp
Normal file
392
cockatrice/src/interface/pixel_map_generator.cpp
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
#include "pixel_map_generator.h"
|
||||
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDomDocument>
|
||||
#include <QFile>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QSvgRenderer>
|
||||
|
||||
#define DEFAULT_COLOR_UNREGISTERED "#32c8ec";
|
||||
#define DEFAULT_COLOR_REGISTERED "#5ed900";
|
||||
#define DEFAULT_COLOR_MODERATOR_LEFT "#ffffff";
|
||||
#define DEFAULT_COLOR_MODERATOR_RIGHT "#000000";
|
||||
#define DEFAULT_COLOR_ADMIN "#ff2701";
|
||||
|
||||
/**
|
||||
* Loads in an svg from file and scales it without affecting image quality.
|
||||
*
|
||||
* @param svgPath The path to the svg file, with file extension.
|
||||
* @param size The desired size of the pixmap.
|
||||
* @param expandOnly If true, then keep the size of the initial pixmap to at least the svg size.
|
||||
*
|
||||
* @return The svg loaded into a Pixmap with the given size, or an empty Pixmap if the loading failed.
|
||||
*/
|
||||
static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnly = false)
|
||||
{
|
||||
QSvgRenderer svgRenderer(svgPath);
|
||||
|
||||
if (!svgRenderer.isValid()) {
|
||||
qCWarning(PixelMapGeneratorLog) << "Failed to load" << svgPath;
|
||||
return {};
|
||||
}
|
||||
|
||||
// If expandOnly, make sure the pixmap is at least as large as the svg, so that we don't lose any detail.
|
||||
// QIcon.pixmap(size) will automatically scale down the image, but it won't scale it up.
|
||||
QSize pixmapSize = expandOnly ? svgRenderer.defaultSize().expandedTo(size) : size;
|
||||
QPixmap pix(pixmapSize);
|
||||
pix.fill(Qt::transparent);
|
||||
|
||||
QPainter pixPainter(&pix);
|
||||
svgRenderer.render(&pixPainter);
|
||||
|
||||
// Converting the pixmap to a QIcon and back is the easiest way to scale down a svg without affecting image quality
|
||||
if (expandOnly) {
|
||||
return QIcon(pix).pixmap(size);
|
||||
}
|
||||
|
||||
return pix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load path image from non-SVG formats, otherwise fall back to SVG.
|
||||
* This is to allow custom themes to support non-SVG format type overrides, since SVG requires custom loading.
|
||||
* @param path The path to the file, with no file extension. File formats will be automatically detected.
|
||||
* @param size The desired size of the pixmap.
|
||||
* @param expandOnly If true, then keep the size of the initial pixmap to at least the size (Only relevant if SVG).
|
||||
*
|
||||
* @return The loaded image into a Pixmap with the given size, or an empty Pixmap if the loading failed.
|
||||
*/
|
||||
static QPixmap tryLoadImage(const QString &path, const QSize &size, bool expandOnly = false)
|
||||
{
|
||||
const auto formats = {"png", "jpg"};
|
||||
|
||||
QPixmap returnPixmap;
|
||||
for (const auto &format : formats) {
|
||||
if (returnPixmap.load(path, format)) {
|
||||
return returnPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
}
|
||||
|
||||
return loadSvg(path + ".svg", size, expandOnly);
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> PhasePixmapGenerator::pmCache;
|
||||
|
||||
QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name)
|
||||
{
|
||||
QString key = name + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap = tryLoadImage("theme:phases/" + name, QSize(height, height));
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> CounterPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool highlight)
|
||||
{
|
||||
// The colorless counter is named "x" by the server but the file is named "general.svg"
|
||||
if (name == "x") {
|
||||
name = "general";
|
||||
}
|
||||
|
||||
if (highlight)
|
||||
name.append("_highlight");
|
||||
QString key = name + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height));
|
||||
|
||||
// fall back to colorless counter if the name can't be found
|
||||
if (pixmap.isNull()) {
|
||||
name = "general";
|
||||
if (highlight)
|
||||
name.append("_highlight");
|
||||
pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height));
|
||||
}
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QPixmap PingPixmapGenerator::generatePixmap(int size, int value, int max)
|
||||
{
|
||||
int key = size * 1000000 + max * 1000 + value;
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap(size, size);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
QColor color;
|
||||
if ((max == -1) || (value == -1))
|
||||
color = Qt::black;
|
||||
else
|
||||
color.setHsv(120 * (1.0 - ((double)value / max)), 255, 255);
|
||||
|
||||
QRadialGradient g(QPointF((double)pixmap.width() / 2, (double)pixmap.height() / 2),
|
||||
qMin(pixmap.width(), pixmap.height()) / 2.0);
|
||||
g.setColorAt(0, color);
|
||||
g.setColorAt(1, Qt::transparent);
|
||||
painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QBrush(g));
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<int, QPixmap> PingPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countryCode)
|
||||
{
|
||||
if (countryCode.size() != 2)
|
||||
return QPixmap();
|
||||
QString key = countryCode + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
int width = height * 2;
|
||||
QPixmap pixmap = tryLoadImage("theme:countries/" + countryCode.toLower(), QSize(width, height), true);
|
||||
|
||||
QPainter painter(&pixmap);
|
||||
painter.setPen(Qt::black);
|
||||
|
||||
// width/height offset was determined through trial-and-error
|
||||
#ifdef Q_OS_MACOS
|
||||
painter.drawRect(0, 0, pixmap.width() / 2, pixmap.height() / 2);
|
||||
#else
|
||||
painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
|
||||
#endif
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> CountryPixmapGenerator::pmCache;
|
||||
|
||||
/**
|
||||
* Updates tags in the svg
|
||||
*
|
||||
* @param elem The svg
|
||||
* @param tagName tag with attribute to update
|
||||
* @param attrName attribute to be updated
|
||||
* @param idName id that the tag has to match
|
||||
* @param attrValue the value to update the attribute to
|
||||
*/
|
||||
static void setAttrRecur(QDomElement &elem,
|
||||
const QString &tagName,
|
||||
const QString &attrName,
|
||||
const QString &idName,
|
||||
const QString &attrValue);
|
||||
|
||||
void setAttrRecur(QDomElement &elem,
|
||||
const QString &tagName,
|
||||
const QString &attrName,
|
||||
const QString &idName,
|
||||
const QString &attrValue)
|
||||
{
|
||||
if (elem.tagName().compare(tagName) == 0) {
|
||||
if (elem.attribute("id").compare(idName) == 0) {
|
||||
elem.setAttribute(attrName, attrValue);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < elem.childNodes().count(); i++) {
|
||||
if (!elem.childNodes().at(i).isElement()) {
|
||||
continue;
|
||||
}
|
||||
auto docElem = elem.childNodes().at(i).toElement();
|
||||
setAttrRecur(docElem, tagName, attrName, idName, attrValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the usericon svg and fills in its colors.
|
||||
* The image is kept as a QIcon to preserve the image quality.
|
||||
*
|
||||
* Call icon.pixmap(w, h) in order to convert this icon into a pixmap with the given dimensions.
|
||||
* Avoid scaling the pixmap in other ways, as that destroys image quality.
|
||||
*
|
||||
* @param minSize If the dimensions of the source svg is smaller than this, then it will be scaled up to this size
|
||||
*/
|
||||
static QIcon loadAndColorSvg(const QString &iconPath,
|
||||
const QString &colorLeft,
|
||||
const std::optional<QString> &colorRight,
|
||||
const int minSize)
|
||||
{
|
||||
QFile file(iconPath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCWarning(PixelMapGeneratorLog) << "Unable to open" << iconPath;
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto &baData = file.readAll();
|
||||
QDomDocument doc;
|
||||
doc.setContent(baData);
|
||||
|
||||
auto docElem = doc.documentElement();
|
||||
|
||||
setAttrRecur(docElem, "path", "fill", "left", colorLeft);
|
||||
if (colorRight.has_value()) {
|
||||
setAttrRecur(docElem, "path", "fill", "right", colorRight.value());
|
||||
}
|
||||
|
||||
QSvgRenderer svgRenderer(doc.toByteArray());
|
||||
|
||||
QPixmap pix(svgRenderer.defaultSize().expandedTo(QSize(minSize, minSize)));
|
||||
pix.fill(Qt::transparent);
|
||||
|
||||
QPainter pixPainter(&pix);
|
||||
svgRenderer.render(&pixPainter);
|
||||
|
||||
return QIcon(pix);
|
||||
}
|
||||
|
||||
QPixmap UserLevelPixmapGenerator::generatePixmap(int height,
|
||||
UserLevelFlags userLevel,
|
||||
ServerInfo_User::PawnColorsOverride pawnColorsOverride,
|
||||
bool isBuddy,
|
||||
const QString &privLevel)
|
||||
{
|
||||
return generateIcon(height, userLevel, pawnColorsOverride, isBuddy, privLevel).pixmap(height, height);
|
||||
}
|
||||
|
||||
QIcon UserLevelPixmapGenerator::generateIcon(int minHeight,
|
||||
UserLevelFlags userLevel,
|
||||
ServerInfo_User::PawnColorsOverride pawnColorsOverride,
|
||||
bool isBuddy,
|
||||
const QString &privLevel)
|
||||
{
|
||||
std::optional<QString> colorLeft = std::nullopt;
|
||||
if (pawnColorsOverride.has_left_side()) {
|
||||
colorLeft = QString::fromStdString(pawnColorsOverride.left_side());
|
||||
}
|
||||
|
||||
std::optional<QString> colorRight = std::nullopt;
|
||||
if (pawnColorsOverride.has_right_side()) {
|
||||
colorRight = QString::fromStdString(pawnColorsOverride.right_side());
|
||||
}
|
||||
|
||||
QString key = QString::number(minHeight * 10000) + ":" + static_cast<short>(userLevel) + ":" +
|
||||
static_cast<short>(isBuddy) + ":" + privLevel.toLower() + ":" + colorLeft.value_or("") + ":" +
|
||||
colorRight.value_or("");
|
||||
|
||||
if (iconCache.contains(key)) {
|
||||
return iconCache.value(key);
|
||||
}
|
||||
|
||||
QIcon icon = colorLeft.has_value()
|
||||
? generateIconWithColorOverride(minHeight, isBuddy, userLevel, privLevel, colorLeft, colorRight)
|
||||
: generateIconDefault(minHeight, userLevel, isBuddy, privLevel);
|
||||
|
||||
iconCache.insert(key, icon);
|
||||
return icon;
|
||||
}
|
||||
|
||||
static QString getIconType(const bool isBuddy, const UserLevelFlags &userLevelFlags, const QString &privLevel)
|
||||
{
|
||||
if (isBuddy) {
|
||||
return "star";
|
||||
}
|
||||
|
||||
if (userLevelFlags.testFlag(ServerInfo_User_UserLevelFlag_IsJudge)) {
|
||||
return "pawn_judge";
|
||||
}
|
||||
|
||||
if (!privLevel.isEmpty() && privLevel.toLower() != "none") {
|
||||
return QString("pawn_%1").arg(privLevel.toLower());
|
||||
}
|
||||
|
||||
return "pawn";
|
||||
}
|
||||
|
||||
QIcon UserLevelPixmapGenerator::generateIconDefault(int height,
|
||||
UserLevelFlags userLevel,
|
||||
bool isBuddy,
|
||||
const QString &privLevel)
|
||||
{
|
||||
const auto &iconType = getIconType(isBuddy, userLevel, privLevel);
|
||||
|
||||
QString arity = "single";
|
||||
QString colorLeft;
|
||||
std::optional<QString> colorRight = std::nullopt;
|
||||
|
||||
if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
|
||||
colorLeft = DEFAULT_COLOR_ADMIN;
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
|
||||
colorLeft = DEFAULT_COLOR_MODERATOR_LEFT;
|
||||
colorRight = DEFAULT_COLOR_MODERATOR_RIGHT;
|
||||
arity = "double";
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) {
|
||||
colorLeft = DEFAULT_COLOR_REGISTERED;
|
||||
} else {
|
||||
colorLeft = DEFAULT_COLOR_UNREGISTERED;
|
||||
}
|
||||
|
||||
const auto &iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity);
|
||||
return loadAndColorSvg(iconPath, colorLeft, colorRight, height);
|
||||
}
|
||||
|
||||
QIcon UserLevelPixmapGenerator::generateIconWithColorOverride(int height,
|
||||
bool isBuddy,
|
||||
const UserLevelFlags &userLevelFlags,
|
||||
const QString &privLevel,
|
||||
const std::optional<QString> &colorLeft,
|
||||
const std::optional<QString> &colorRight)
|
||||
{
|
||||
const auto &iconType = getIconType(isBuddy, userLevelFlags, privLevel);
|
||||
const QString &arity = colorRight.has_value() ? "double" : "single";
|
||||
const auto &iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity);
|
||||
return loadAndColorSvg(iconPath, colorLeft.value(), colorRight, height);
|
||||
}
|
||||
|
||||
QMap<QString, QIcon> UserLevelPixmapGenerator::iconCache;
|
||||
|
||||
QPixmap LockPixmapGenerator::generatePixmap(int height)
|
||||
{
|
||||
|
||||
int key = height;
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap = tryLoadImage("theme:icons/lock", QSize(height, height), true);
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<int, QPixmap> LockPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
|
||||
{
|
||||
QString key = QString::number(expanded) + ":" + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed";
|
||||
QPixmap pixmap = tryLoadImage("theme:icons/" + name, QSize(height, height), true);
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> DropdownIconPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap loadColorAdjustedPixmap(const QString &name)
|
||||
{
|
||||
if (qApp->palette().windowText().color().lightness() > 200) {
|
||||
QImage img(name);
|
||||
img.invertPixels();
|
||||
QPixmap result;
|
||||
result.convertFromImage(img);
|
||||
return result;
|
||||
} else {
|
||||
return QPixmap(name);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue