231 lines
9.1 KiB
C++
231 lines
9.1 KiB
C++
#include "Glyphs.hpp"
|
||
|
||
#include <QAbstractButton>
|
||
#include <QByteArray>
|
||
#include <QDir>
|
||
#include <QImage>
|
||
#include <QLabel>
|
||
#include <QObject>
|
||
#include <QPainter>
|
||
#include <QPen>
|
||
#include <QPixmap>
|
||
#include <QPointF>
|
||
#include <QRectF>
|
||
#include <QSize>
|
||
#include <QString>
|
||
#include <QSvgRenderer>
|
||
|
||
#include "Theme.hpp"
|
||
|
||
namespace geopro::app {
|
||
|
||
namespace {
|
||
|
||
// 每个图标的 SVG 内容(线性风格,24x24 视窗,描边由外层注入颜色)。
|
||
// 路径取自 Lucide 图标集(MIT 许可,业界通用),保证专业、清晰、语义正确。
|
||
QString svgPathFor(Glyph t)
|
||
{
|
||
switch (t) {
|
||
case Glyph::Tree:
|
||
return QStringLiteral(
|
||
"<path d='M21 12h-8'/><path d='M21 6H8'/><path d='M21 18h-8'/>"
|
||
"<path d='M3 6v4c0 1.1.9 2 2 2h3'/><path d='M3 10v6c0 1.1.9 2 2 2h3'/>");
|
||
case Glyph::Dataset:
|
||
return QStringLiteral(
|
||
"<ellipse cx='12' cy='5' rx='9' ry='3'/>"
|
||
"<path d='M3 5v14a9 3 0 0 0 18 0V5'/><path d='M3 12a9 3 0 0 0 18 0'/>");
|
||
case Glyph::Map:
|
||
return QStringLiteral(
|
||
"<path d='m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 "
|
||
"0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z'/>"
|
||
"<path d='m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65'/>"
|
||
"<path d='m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65'/>");
|
||
case Glyph::Detail:
|
||
return QStringLiteral(
|
||
"<path d='M3 3v18h18'/><path d='M18 17V9'/><path d='M13 17V5'/><path d='M8 17v-3'/>");
|
||
case Glyph::Anomaly:
|
||
return QStringLiteral(
|
||
"<path d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 "
|
||
"1.73-3Z'/><path d='M12 9v4'/><path d='M12 17h.01'/>");
|
||
case Glyph::Property:
|
||
return QStringLiteral(
|
||
"<circle cx='12' cy='12' r='10'/><path d='M12 16v-4'/><path d='M12 8h.01'/>");
|
||
case Glyph::Plus:
|
||
return QStringLiteral("<path d='M5 12h14'/><path d='M12 5v14'/>");
|
||
case Glyph::Filter:
|
||
return QStringLiteral(
|
||
"<polygon points='22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3'/>");
|
||
case Glyph::Upload:
|
||
return QStringLiteral(
|
||
"<path d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/>"
|
||
"<path d='M17 8l-5-5-5 5'/><path d='M12 3v12'/>");
|
||
case Glyph::Download:
|
||
return QStringLiteral(
|
||
"<path d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/>"
|
||
"<path d='M7 10l5 5 5-5'/><path d='M12 15V3'/>");
|
||
case Glyph::Collapse:
|
||
return QStringLiteral("<path d='m17 11-5-5-5 5'/><path d='m17 18-5-5-5 5'/>");
|
||
case Glyph::Workspace:
|
||
return QStringLiteral(
|
||
"<rect width='7' height='7' x='3' y='3' rx='1'/>"
|
||
"<rect width='7' height='7' x='14' y='3' rx='1'/>"
|
||
"<rect width='7' height='7' x='14' y='14' rx='1'/>"
|
||
"<rect width='7' height='7' x='3' y='14' rx='1'/>");
|
||
case Glyph::Folder:
|
||
return QStringLiteral(
|
||
"<path d='M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 "
|
||
"0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'/>");
|
||
case Glyph::Help:
|
||
return QStringLiteral(
|
||
"<circle cx='12' cy='12' r='10'/>"
|
||
"<path d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3'/><path d='M12 17h.01'/>");
|
||
case Glyph::Bell:
|
||
return QStringLiteral(
|
||
"<path d='M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9'/>"
|
||
"<path d='M10.3 21a1.94 1.94 0 0 0 3.4 0'/>");
|
||
case Glyph::Gear:
|
||
return QStringLiteral(
|
||
"<path d='M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 "
|
||
"0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 "
|
||
"1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73 "
|
||
".73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 "
|
||
"2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 "
|
||
"2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 "
|
||
".73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 "
|
||
"1-1-1.73V4a2 2 0 0 0-2-2z'/><circle cx='12' cy='12' r='3'/>");
|
||
}
|
||
return QString();
|
||
}
|
||
|
||
} // namespace
|
||
|
||
QIcon makeGlyph(Glyph type, const QColor& color, int px, int padRight)
|
||
{
|
||
const QString svg =
|
||
QStringLiteral("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' "
|
||
"stroke='%1' stroke-width='2' stroke-linecap='round' "
|
||
"stroke-linejoin='round'>%2</svg>")
|
||
.arg(color.name(), svgPathFor(type));
|
||
|
||
QSvgRenderer renderer(svg.toUtf8());
|
||
|
||
// 以 3x 超采样渲染再设 devicePixelRatio,保证在任意缩放/DPI 下都清晰。
|
||
constexpr qreal kSuper = 3.0;
|
||
const int dim = qRound(px * kSuper); // 图标本体边长(方形)
|
||
const int dimW = qRound((px + padRight) * kSuper); // 含右透明内边距的画布宽
|
||
QImage img(dimW, dim, QImage::Format_ARGB32_Premultiplied);
|
||
img.fill(Qt::transparent);
|
||
QPainter p(&img);
|
||
p.setRenderHint(QPainter::Antialiasing, true);
|
||
renderer.render(&p, QRectF(0, 0, dim, dim)); // 图标居左方形渲染,右侧 padRight 留透明
|
||
p.end();
|
||
|
||
QPixmap pm = QPixmap::fromImage(img);
|
||
pm.setDevicePixelRatio(kSuper);
|
||
return QIcon(pm);
|
||
}
|
||
|
||
QString writeChevronIcon(bool open, const QColor& color)
|
||
{
|
||
constexpr int px = 16;
|
||
constexpr int kScale = 3;
|
||
QPixmap pm(px * kScale, px * kScale);
|
||
pm.fill(Qt::transparent);
|
||
|
||
QPainter p(&pm);
|
||
p.setRenderHint(QPainter::Antialiasing, true);
|
||
QPen pen(color, px * kScale * 0.1);
|
||
pen.setCapStyle(Qt::RoundCap);
|
||
pen.setJoinStyle(Qt::RoundJoin);
|
||
p.setPen(pen);
|
||
|
||
const double w = px * kScale;
|
||
const double h = px * kScale;
|
||
if (open) { // ▼ 向下
|
||
p.drawLine(QPointF(w * 0.32, h * 0.42), QPointF(w * 0.5, h * 0.60));
|
||
p.drawLine(QPointF(w * 0.5, h * 0.60), QPointF(w * 0.68, h * 0.42));
|
||
} else { // ▶ 向右
|
||
p.drawLine(QPointF(w * 0.42, h * 0.32), QPointF(w * 0.60, h * 0.5));
|
||
p.drawLine(QPointF(w * 0.60, h * 0.5), QPointF(w * 0.42, h * 0.68));
|
||
}
|
||
p.end();
|
||
|
||
const QString path =
|
||
QDir(QDir::tempPath()).filePath(open ? QStringLiteral("geopro_tree_open.png")
|
||
: QStringLiteral("geopro_tree_closed.png"));
|
||
pm.save(path, "PNG");
|
||
return path;
|
||
}
|
||
|
||
QString writeCheckboxIcon(bool checked, const QColor& border, const QColor& fill, const QColor& check,
|
||
const QString& tag)
|
||
{
|
||
constexpr int px = 16;
|
||
constexpr int kScale = 3;
|
||
QPixmap pm(px * kScale, px * kScale);
|
||
pm.fill(Qt::transparent);
|
||
|
||
QPainter p(&pm);
|
||
p.setRenderHint(QPainter::Antialiasing, true);
|
||
const double s = px * kScale;
|
||
const double inset = s * 0.14;
|
||
const QRectF box(inset, inset, s - 2 * inset, s - 2 * inset);
|
||
const double r = s * 0.14;
|
||
|
||
if (checked) {
|
||
p.setPen(Qt::NoPen);
|
||
p.setBrush(fill);
|
||
p.drawRoundedRect(box, r, r);
|
||
QPen cpen(check, s * 0.12);
|
||
cpen.setCapStyle(Qt::RoundCap);
|
||
cpen.setJoinStyle(Qt::RoundJoin);
|
||
p.setPen(cpen);
|
||
p.drawLine(QPointF(s * 0.30, s * 0.52), QPointF(s * 0.44, s * 0.66));
|
||
p.drawLine(QPointF(s * 0.44, s * 0.66), QPointF(s * 0.70, s * 0.34));
|
||
} else {
|
||
QPen bpen(border, s * 0.085);
|
||
p.setPen(bpen);
|
||
p.setBrush(fill);
|
||
p.drawRoundedRect(box, r, r);
|
||
}
|
||
p.end();
|
||
|
||
const QString path = QDir(QDir::tempPath())
|
||
.filePath(QStringLiteral("geopro_chk_%1_%2.png")
|
||
.arg(tag, checked ? QStringLiteral("on")
|
||
: QStringLiteral("off")));
|
||
pm.save(path, "PNG");
|
||
return path;
|
||
}
|
||
|
||
namespace {
|
||
// 当前主题下的 chrome 图标色:取主题主文本色(暗=浅、亮=深),保证两种模式都清晰。
|
||
QColor themedIconColor()
|
||
{
|
||
return isDarkTheme() ? QColor(0xE6, 0xE8, 0xEB) : QColor(0x1F, 0x2A, 0x3D); // 主文字明/暗
|
||
}
|
||
} // namespace
|
||
|
||
void setThemedGlyph(QLabel* label, Glyph type, int px)
|
||
{
|
||
if (!label) return;
|
||
auto apply = [label, type, px]() {
|
||
label->setPixmap(makeGlyph(type, themedIconColor(), px).pixmap(px, px));
|
||
};
|
||
apply();
|
||
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, label, [apply]() { apply(); });
|
||
}
|
||
|
||
void setThemedGlyph(QAbstractButton* button, Glyph type, int px, int padRight)
|
||
{
|
||
if (!button) return;
|
||
auto apply = [button, type, px, padRight]() {
|
||
button->setIcon(makeGlyph(type, themedIconColor(), px, padRight));
|
||
if (padRight > 0) button->setIconSize(QSize(px + padRight, px)); // 含右内边距,文字被右推
|
||
};
|
||
apply();
|
||
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, button, [apply]() { apply(); });
|
||
}
|
||
|
||
} // namespace geopro::app
|