init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,234 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_core.h"
#include "ui/effects/animation_value.h"
#include "ui/painter.h"
#include "styles/style_basic.h"
#include "styles/palette.h"
#include <QtGui/QPainter>
#include <rpl/event_stream.h>
#include <rpl/variable.h>
namespace style {
namespace internal {
namespace {
constexpr auto kMinContrastAlpha = 64;
constexpr auto kMinContrastDistance = 64 * 64 * 4;
constexpr auto kContrastDeltaL = 64;
auto PaletteChanges = rpl::event_stream<>();
auto PaletteVersion = 0;
auto ShortAnimationRunning = rpl::variable<bool>(false);
auto RunningShortAnimations = 0;
[[nodiscard]] std::vector<internal::ModuleBase*> &StyleModules() {
static auto result = std::vector<internal::ModuleBase*>();
return result;
}
void StartModules(int scale) {
for (const auto module : StyleModules()) {
module->start(scale);
}
}
} // namespace
void registerModule(ModuleBase *module) {
StyleModules().push_back(module);
}
void StartShortAnimation() {
if (++RunningShortAnimations == 1) {
ShortAnimationRunning = true;
}
}
void StopShortAnimation() {
if (--RunningShortAnimations == 0) {
ShortAnimationRunning = false;
}
}
} // namespace internal
void StartManager(int scale) {
internal::RegisterFontFamily("Open Sans");
internal::StartModules(scale);
}
void StopManager() {
internal::DestroyFonts();
internal::DestroyIcons();
}
rpl::producer<> PaletteChanged() {
return internal::PaletteChanges.events();
}
int PaletteVersion() {
return internal::PaletteVersion;
}
void NotifyPaletteChanged() {
++internal::PaletteVersion;
internal::PaletteChanges.fire({});
}
rpl::producer<bool> ShortAnimationPlaying() {
return internal::ShortAnimationRunning.value();
}
void colorizeImage(
const QImage &src,
const QColor &color,
not_null<QImage*> outResult,
QRect srcRect,
QPoint dstPoint,
bool useAlpha) {
// In background_box ColorizePattern we use the fact that
// colorizeImage takes only first byte of the mask, so it
// could be used for wallpaper patterns, which have values
// in ranges (0, 0, 0, 0) to (0, 0, 0, 255) (only 'alpha').
if (srcRect.isNull()) {
srcRect = src.rect();
} else {
Assert(src.rect().contains(srcRect));
}
auto width = srcRect.width();
auto height = srcRect.height();
Assert(outResult->rect().contains(QRect(dstPoint, srcRect.size())));
outResult->detach();
auto pattern = anim::shifted(color);
constexpr auto resultIntsPerPixel = 1;
auto resultIntsPerLine = (outResult->bytesPerLine() >> 2);
auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
auto resultInts = reinterpret_cast<uint32*>(outResult->bits())
+ (dstPoint.y() * resultIntsPerLine)
+ (dstPoint.x() * resultIntsPerPixel);
Assert(resultIntsAdded >= 0);
Assert(outResult->depth()
== static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3));
Assert(outResult->bytesPerLine() == (resultIntsPerLine << 2));
auto maskBytesPerPixel = (src.depth() >> 3);
auto maskBytesPerLine = src.bytesPerLine();
auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
auto maskBytes = src.constBits()
+ (srcRect.y() * maskBytesPerLine)
+ (srcRect.x() * maskBytesPerPixel)
+ (useAlpha ? 3 : 0);
Assert(maskBytesAdded >= 0);
Assert(src.depth() == (maskBytesPerPixel << 3));
for (int y = 0; y != height; ++y) {
for (int x = 0; x != width; ++x) {
auto maskOpacity = static_cast<anim::ShiftedMultiplier>(*maskBytes) + 1;
*resultInts = anim::unshifted(pattern * maskOpacity);
maskBytes += maskBytesPerPixel;
resultInts += resultIntsPerPixel;
}
maskBytes += maskBytesAdded;
resultInts += resultIntsAdded;
}
outResult->setDevicePixelRatio(src.devicePixelRatio());
}
QImage TransparentPlaceholder() {
const auto size = st::transparentPlaceholderSize * DevicePixelRatio();
auto result = QImage(
2 * size, 2 * size,
QImage::Format_ARGB32_Premultiplied);
result.fill(st::mediaviewTransparentBg->c);
{
QPainter p(&result);
p.fillRect(0, size, size, size, st::mediaviewTransparentFg);
p.fillRect(size, 0, size, size, st::mediaviewTransparentFg);
}
result.setDevicePixelRatio(DevicePixelRatio());
return result;
}
namespace internal {
QImage createCircleMask(int size, QColor bg, QColor fg) {
int realSize = size * DevicePixelRatio();
auto result = QImage(realSize, realSize, QImage::Format::Format_Grayscale8);
{
QPainter p(&result);
PainterHighQualityEnabler hq(p);
p.fillRect(0, 0, realSize, realSize, bg);
p.setPen(Qt::NoPen);
p.setBrush(fg);
p.drawEllipse(0, 0, realSize, realSize);
}
result.setDevicePixelRatio(DevicePixelRatio());
return result;
}
[[nodiscard]] bool GoodForContrast(const QColor &c1, const QColor &c2) {
auto r1 = 0;
auto g1 = 0;
auto b1 = 0;
auto r2 = 0;
auto g2 = 0;
auto b2 = 0;
c1.getRgb(&r1, &g1, &b1);
c2.getRgb(&r2, &g2, &b2);
const auto rMean = (r1 + r2) / 2;
const auto r = r1 - r2;
const auto g = g1 - g2;
const auto b = b1 - b2;
const auto distance = (((512 + rMean) * r * r) >> 8)
+ (4 * g * g)
+ (((767 - rMean) * b * b) >> 8);
return (distance > kMinContrastDistance);
}
QColor EnsureContrast(const QColor &over, const QColor &under) {
auto overH = 0;
auto overS = 0;
auto overL = 0;
auto overA = 0;
auto underH = 0;
auto underS = 0;
auto underL = 0;
over.getHsl(&overH, &overS, &overL, &overA);
under.getHsl(&underH, &underS, &underL);
const auto good = GoodForContrast(over, under);
if (overA >= kMinContrastAlpha && good) {
return over;
}
const auto newA = std::max(overA, kMinContrastAlpha);
const auto newL = (overL > underL && overL + kContrastDeltaL <= 255)
? (overL + kContrastDeltaL)
: (overL < underL && overL - kContrastDeltaL >= 0)
? (overL - kContrastDeltaL)
: (underL > 128)
? (underL - kContrastDeltaL)
: (underL + kContrastDeltaL);
return QColor::fromHsl(overH, overS, newL, newA).toRgb();
}
void EnsureContrast(ColorData &over, const ColorData &under) {
const auto good = EnsureContrast(over.c, under.c);
if (over.c != good) {
over.c = good;
over.p = QPen(good);
over.b = QBrush(good);
}
}
} // namespace internal
} // namespace style

View File

@@ -0,0 +1,94 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/style/style_core_scale.h"
#include "ui/style/style_core_types.h"
#include "ui/style/style_core_direction.h"
#include <rpl/producer.h>
namespace style {
namespace internal {
// Objects of derived classes are created in global scope.
// They call [un]registerModule() in [de|con]structor.
class ModuleBase {
public:
virtual void start(int scale) = 0;
virtual ~ModuleBase() = default;
};
void registerModule(ModuleBase *module);
[[nodiscard]] QColor EnsureContrast(const QColor &over, const QColor &under);
void EnsureContrast(ColorData &over, const ColorData &under);
void StartShortAnimation();
void StopShortAnimation();
} // namespace internal
void StartManager(int scale);
void StopManager();
[[nodiscard]] rpl::producer<> PaletteChanged();
[[nodiscard]] int PaletteVersion();
void NotifyPaletteChanged();
[[nodiscard]] rpl::producer<bool> ShortAnimationPlaying();
// *outResult must be r.width() x r.height(), ARGB32_Premultiplied.
// QRect(0, 0, src.width(), src.height()) must contain r.
void colorizeImage(
const QImage &src,
const QColor &color,
not_null<QImage*> outResult,
QRect srcRect = QRect(),
QPoint dstPoint = QPoint(0, 0),
bool useAlpha = false);
[[nodiscard]] inline QImage colorizeImage(
const QImage &src,
const QColor &color,
QRect srcRect = QRect()) {
if (srcRect.isNull()) {
srcRect = src.rect();
}
auto result = QImage(
srcRect.size(),
QImage::Format_ARGB32_Premultiplied);
colorizeImage(src, color, &result, srcRect);
return result;
}
[[nodiscard]] inline QImage colorizeImage(
const QImage &src,
const color &color,
QRect srcRect = QRect()) {
return colorizeImage(src, color->c, srcRect);
}
[[nodiscard]] QImage TransparentPlaceholder();
namespace internal {
QImage createCircleMask(int size, QColor bg, QColor fg);
} // namespace internal
inline QImage createCircleMask(int size) {
return internal::createCircleMask(size, QColor(0, 0, 0), QColor(255, 255, 255));
}
inline QImage createInvertedCircleMask(int size) {
return internal::createCircleMask(size, QColor(255, 255, 255), QColor(0, 0, 0));
}
} // namespace style

View File

@@ -0,0 +1,39 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_core_color.h"
#include "ui/style/style_core_palette.h"
namespace style {
namespace internal {
Color::Proxy Color::operator[](const style::palette &paletteOverride) const {
const auto index = main_palette::indexOfColor(*this);
return { (index >= 0) ? paletteOverride.colorAtIndex(index) : (*this) };
}
ColorData::ColorData(uchar r, uchar g, uchar b, uchar a)
: c(int(r), int(g), int(b), int(a))
, p(c)
, b(c) {
}
void ColorData::set(uchar r, uchar g, uchar b, uchar a) {
this->c = QColor(int(r), int(g), int(b), int(a));
this->p = QPen(c);
this->b = QBrush(c);
}
void ComplexColor::subscribeToPaletteChanges() {
style::PaletteChanged(
) | rpl::on_next([=] {
refresh();
}, _lifetime);
}
} // namespace internal
} // namespace style

View File

@@ -0,0 +1,189 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <QtGui/QColor>
#include <QtGui/QPen>
#include <QtGui/QBrush>
#include <rpl/lifetime.h>
namespace style {
class palette_data;
class palette;
namespace internal {
class Color;
class OwnedColor;
class ColorData {
public:
QColor c;
QPen p;
QBrush b;
[[nodiscard]] QColor transparent() const {
return QColor(c.red(), c.green(), c.blue(), 0);
}
private:
ColorData(uchar r, uchar g, uchar b, uchar a);
ColorData(const ColorData &other) = default;
ColorData &operator=(const ColorData &other) = default;
void set(uchar r, uchar g, uchar b, uchar a);
friend class Color;
friend class OwnedColor;
friend class style::palette;
friend class style::palette_data;
};
class Color {
public:
Color(Qt::Initialization = Qt::Uninitialized) {
}
Color(const Color &other) = default;
Color &operator=(const Color &other) = default;
void set(uchar r, uchar g, uchar b, uchar a) const {
_data->set(r, g, b, a);
}
[[nodiscard]] operator const QBrush &() const {
return _data->b;
}
[[nodiscard]] operator const QPen &() const {
return _data->p;
}
[[nodiscard]] ColorData *operator->() const {
return _data;
}
[[nodiscard]] ColorData *get() const {
return _data;
}
[[nodiscard]] explicit operator bool() const {
return !!_data;
}
class Proxy;
[[nodiscard]] Proxy operator[](
const style::palette &paletteOverride) const;
private:
friend class OwnedColor;
friend class style::palette;
friend class style::palette_data;
Color(ColorData *data) : _data(data) {
}
ColorData *_data = nullptr;
};
class OwnedColor final {
public:
explicit OwnedColor(const QColor &color)
: _data(color.red(), color.green(), color.blue(), color.alpha())
, _color(&_data) {
}
OwnedColor(const OwnedColor &other)
: _data(other._data)
, _color(&_data) {
}
OwnedColor &operator=(const OwnedColor &other) {
_data = other._data;
return *this;
}
void update(const QColor &color) {
_data.set(color.red(), color.green(), color.blue(), color.alpha());
}
[[nodiscard]] const Color &color() const {
return _color;
}
private:
ColorData _data;
Color _color;
};
class ComplexColor final {
public:
explicit ComplexColor(Fn<QColor()> generator)
: _owned(generator())
, _generator(std::move(generator)) {
subscribeToPaletteChanges();
}
ComplexColor(const ComplexColor &other)
: _owned(other._owned)
, _generator(other._generator) {
subscribeToPaletteChanges();
}
ComplexColor &operator=(const ComplexColor &other) {
_owned = other._owned;
_generator = other._generator;
return *this;
}
[[nodiscard]] const Color &color() const {
return _owned.color();
}
void refresh() {
_owned.update(_generator());
}
private:
void subscribeToPaletteChanges();
OwnedColor _owned;
Fn<QColor()> _generator;
rpl::lifetime _lifetime;
};
class Color::Proxy {
public:
Proxy(Color color) : _color(color) {
}
Proxy(const Proxy &other) = default;
[[nodiscard]] operator const QBrush &() const { return _color; }
[[nodiscard]] operator const QPen &() const { return _color; }
[[nodiscard]] ColorData *operator->() const { return _color.get(); }
[[nodiscard]] ColorData *get() const { return _color.get(); }
[[nodiscard]] explicit operator bool() const { return !!_color; }
[[nodiscard]] Color clone() const { return _color; }
private:
Color _color;
};
inline bool operator==(Color a, Color b) {
return a->c == b->c;
}
inline bool operator!=(Color a, Color b) {
return a->c != b->c;
}
} // namespace internal
} // namespace style

View File

@@ -0,0 +1,56 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_core_direction.h"
#include "ui/text/text_entity.h"
namespace style {
namespace {
bool RightToLeftValue = false;
} // namespace
bool RightToLeft() {
return RightToLeftValue;
}
void SetRightToLeft(bool rtl) {
RightToLeftValue = rtl;
}
} // namespace style
namespace st {
QString wrap_rtl(const QString &text) {
const auto wrapper = QChar(rtl() ? 0x200F : 0x200E);
auto result = QString();
result.reserve(text.size() + 3);
result
.append(wrapper) // Don't override phrase direction by first symbol.
.append(QChar(0x2068)) // Isolate tag content.
.append(text)
.append(QChar(0x2069)); // End of isolation.
return result;
}
TextWithEntities wrap_rtl(const TextWithEntities &text) {
const auto wrapper = QChar(rtl() ? 0x200F : 0x200E);
auto result = TextWithEntities();
result.reserve(text.text.size() + 3, text.entities.size());
result
.append(wrapper) // Don't override phrase direction by first symbol.
.append(QChar(0x2068)) // Isolate tag content.
.append(text)
.append(QChar(0x2069)); // End of isolation.
return result;
}
} // namespace st

View File

@@ -0,0 +1,76 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/style/style_core_types.h"
#include <QtCore/QPoint>
#include <QtCore/QRect>
namespace style {
[[nodiscard]] bool RightToLeft();
void SetRightToLeft(bool rtl);
[[nodiscard]] inline Qt::LayoutDirection LayoutDirection() {
return RightToLeft() ? Qt::RightToLeft : Qt::LeftToRight;
}
inline QRect centerrect(const QRect &inRect, const QRect &rect) {
return QRect(
inRect.x() + (inRect.width() - rect.width()) / 2,
inRect.y() + (inRect.height() - rect.height()) / 2,
rect.width(),
rect.height());
}
inline QRect centerrect(const QRect &inRect, const style::icon &icon) {
return centerrect(inRect, QRect(0, 0, icon.width(), icon.height()));
}
inline QPoint rtlpoint(int x, int y, int outerw) {
return QPoint(RightToLeft() ? (outerw - x) : x, y);
}
inline QPoint rtlpoint(const QPoint &p, int outerw) {
return RightToLeft() ? QPoint(outerw - p.x(), p.y()) : p;
}
inline QPointF rtlpoint(const QPointF &p, int outerw) {
return RightToLeft() ? QPointF(outerw - p.x(), p.y()) : p;
}
inline QRect rtlrect(int x, int y, int w, int h, int outerw) {
return QRect(RightToLeft() ? (outerw - x - w) : x, y, w, h);
}
inline QRect rtlrect(const QRect &r, int outerw) {
return RightToLeft()
? QRect(outerw - r.x() - r.width(), r.y(), r.width(), r.height())
: r;
}
inline QRectF rtlrect(const QRectF &r, int outerw) {
return RightToLeft()
? QRectF(outerw - r.x() - r.width(), r.y(), r.width(), r.height())
: r;
}
} // namespace style
struct TextWithEntities;
namespace st {
[[nodiscard]] inline bool rtl() {
return style::RightToLeft();
}
[[nodiscard]] QString wrap_rtl(const QString &text);
[[nodiscard]] TextWithEntities wrap_rtl(const TextWithEntities &text);
} // namespace st

View File

@@ -0,0 +1,518 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_core_font.h"
#include "base/algorithm.h"
#include "base/debug_log.h"
#include "base/base_file_utilities.h"
#include "ui/integration.h"
#include <QtCore/QMap>
#include <QtCore/QVector>
#include <QtCore/QDir>
#include <QtGui/QFontInfo>
#include <QtGui/QFontDatabase>
#if __has_include(<glib.h>)
#include <glib.h>
#endif // __has_include(<glib.h>)
void style_InitFontsResource() {
#ifdef Q_OS_MAC // Use resources from the .app bundle on macOS.
base::RegisterBundledResources(u"lib_ui.rcc"_q);
#else // Q_OS_MAC
#ifndef LIB_UI_USE_PACKAGED_FONTS
Q_INIT_RESOURCE(fonts);
#endif // !LIB_UI_USE_PACKAGED_FONTS
#ifdef Q_OS_WIN
Q_INIT_RESOURCE(win);
#endif // Q_OS_WIN
#endif // Q_OS_MAC
}
namespace style {
namespace {
QString Custom;
} // namespace
const QString &SystemFontTag() {
static const auto result = u"(system)"_q + QChar(0);
return result;
}
void SetCustomFont(const QString &font) {
Custom = font;
}
namespace internal {
struct ResolvedFont {
ResolvedFont(FontResolveResult result, FontVariants *modified);
FontResolveResult result;
FontData data;
};
ResolvedFont::ResolvedFont(FontResolveResult result, FontVariants *modified)
: result(std::move(result))
, data(this->result, modified) {
}
namespace {
bool Started = false;
base::flat_map<QString, int> FontFamilyIndices;
std::vector<QString> FontFamilies;
base::flat_map<uint32, std::unique_ptr<ResolvedFont>> FontsByKey;
base::flat_map<uint64, uint32> QtFontsKeys;
[[nodiscard]] uint32 FontKey(int size, FontFlags flags, int family) {
return (uint32(family) << 18)
| (uint32(size) << 6)
| uint32(flags.value());
}
[[nodiscard]] uint64 QtFontKey(const QFont &font) {
static auto Families = base::flat_map<QString, int>();
const auto family = font.family();
auto i = Families.find(family);
if (i == end(Families)) {
i = Families.emplace(family, Families.size()).first;
}
return (uint64(i->second) << 24)
| (uint64(font.weight()) << 16)
| (uint64(font.bold() ? 1 : 0) << 15)
| (uint64(font.italic() ? 1 : 0) << 14)
| (uint64(font.underline() ? 1 : 0) << 13)
| (uint64(font.strikeOut() ? 1 : 0) << 12)
| (uint64(font.pixelSize()));
}
bool LoadCustomFont(const QString &filePath) {
auto regularId = QFontDatabase::addApplicationFont(filePath);
if (regularId < 0) {
LOG(("Font Error: could not add '%1'.").arg(filePath));
return false;
}
for (auto &family : QFontDatabase::applicationFontFamilies(regularId)) {
LOG(("Font: from '%1' loaded '%2'").arg(filePath, family));
}
return true;
}
[[nodiscard]] QString SystemMonospaceFont() {
const auto type = QFontDatabase::FixedFont;
return QFontDatabase::systemFont(type).family();
}
[[nodiscard]] QString ManualMonospaceFont() {
const auto kTryFirst = std::initializer_list<QString>{
u"Cascadia Mono"_q,
u"Consolas"_q,
u"Liberation Mono"_q,
u"Menlo"_q,
u"Courier"_q,
};
for (const auto &family : kTryFirst) {
const auto resolved = QFontInfo(QFont(family)).family();
if (resolved.trimmed().startsWith(family, Qt::CaseInsensitive)) {
return family;
}
}
return QString();
}
[[nodiscard]] QString MonospaceFont() {
static const auto family = [&]() -> QString {
const auto manual = ManualMonospaceFont();
const auto system = SystemMonospaceFont();
#ifdef Q_OS_WIN
// Prefer our monospace font.
const auto useSystem = manual.isEmpty();
#else // Q_OS_WIN
// Prefer system monospace font.
const auto metrics = QFontMetrics(QFont(system));
const auto useSystem = manual.isEmpty()
|| (metrics.horizontalAdvance(QChar('i')) == metrics.horizontalAdvance(QChar('W')));
#endif // Q_OS_WIN
return useSystem ? system : manual;
}();
return family;
}
struct Metrics {
int pixelSize = 0;
float64 ascent = 0.;
float64 height = 0.;
};
[[nodiscard]] Metrics ComputeMetrics(QFont font, bool adjust) {
constexpr auto kMaxSizeShift = 8;
const auto startSize = font.pixelSize();
const auto metrics = QFontMetricsF(font);
const auto simple = [&] {
return Metrics{
.pixelSize = startSize,
.ascent = metrics.ascent(),
.height = metrics.height(),
};
};
const auto family = font.family();
const auto basic = u"Open Sans"_q;
if (family == basic || !adjust) {
return simple();
}
auto copy = font;
copy.setFamily(basic);
const auto basicMetrics = QFontMetricsF(copy);
static const auto Full = u"bdfghijklpqtyBDFGHIJKLPQTY1234567890[]{}()"_q;
// I tried to choose height in such way that
// - normal fonts won't be too large,
// - some exotic fonts, like Symbol (Greek), won't be too small,
// - some other exotic fonts, like Segoe Script, won't be too large.
const auto Height = [](const QFontMetricsF &metrics) {
//static const auto Test = u"acemnorsuvwxz"_q;
//return metrics.tightBoundingRect(Test).height();
//static const auto Test = u"acemnorsuvwxz"_q;
//auto result = metrics.boundingRect(Test[0]).height();
//for (const auto &ch : Test | ranges::views::drop(1)) {
// const auto single = metrics.boundingRect(ch).height();
// if (result > single) {
// result = single;
// }
//}
//return result;
//static const auto Test = u"acemnorsuvwxz"_q;
//auto result = 0.;
//for (const auto &ch : Test) {
// result -= metrics.boundingRect(ch).y();
//}
//return result / Test.size();
static const char16_t Test[] = u"acemnorsuvwxz";
constexpr auto kCount = int(std::size(Test)) - 1;
auto heights = std::array<float64, kCount>{};
for (auto i = 0; i != kCount; ++i) {
heights[i] = -metrics.boundingRect(QChar(Test[i])).y();
}
ranges::sort(heights);
//return heights[kCount / 2];
// Average the middle third.
const auto from = kCount / 3;
const auto till = kCount - from;
auto result = 0.;
for (auto i = from; i != till; ++i) {
result += heights[i];
}
return result / (till - from);
};
const auto desired = Height(basicMetrics);
const auto desiredFull = basicMetrics.tightBoundingRect(Full);
if (desired < 1. || desiredFull.height() < desired) {
return simple();
}
const auto adjusted = [&](int size, const QFontMetricsF &metrics) {
const auto full = metrics.tightBoundingRect(Full);
const auto desiredTightHeight = desiredFull.height();
const auto heightAdd = basicMetrics.height() - desiredTightHeight;
const auto tightHeight = full.height();
return Metrics{
.pixelSize = size,
.ascent = basicMetrics.ascent(),
.height = tightHeight + heightAdd,
};
};
auto current = Height(metrics);
if (current < 1.) {
return simple();
} else if (std::abs(current - desired) < 0.2) {
return adjusted(startSize, metrics);
}
const auto adjustedByFont = [&](const QFont &font) {
return adjusted(font.pixelSize(), QFontMetricsF(font));
};
const auto max = std::min(kMaxSizeShift, startSize - 1);
if (current < desired) {
for (auto i = 0; i != max; ++i) {
const auto shift = i + 1;
font.setPixelSize(startSize + shift);
const auto metrics = QFontMetricsF(font);
const auto now = Height(metrics);
if (now > desired) {
const auto better = (now - desired) < (desired - current);
if (better) {
return adjusted(startSize + shift, metrics);
}
font.setPixelSize(startSize + shift - 1);
return adjustedByFont(font);
}
current = now;
}
font.setPixelSize(startSize + max);
return adjustedByFont(font);
} else {
for (auto i = 0; i != max; ++i) {
const auto shift = i + 1;
font.setPixelSize(startSize - shift);
const auto metrics = QFontMetricsF(font);
const auto now = Height(metrics);
if (now < desired) {
const auto better = (desired - now) < (current - desired);
if (better) {
return adjusted(startSize - shift, metrics);
}
font.setPixelSize(startSize - shift + 1);
return adjustedByFont(font);
}
current = now;
}
font.setPixelSize(startSize - max);
return adjustedByFont(font);
}
}
[[nodiscard]] FontResolveResult ResolveFont(
const QString &family,
FontFlags flags,
int size) {
auto font = QFont(QFont().family());
const auto monospace = (flags & FontFlag::Monospace) != 0;
const auto system = !monospace && (family == SystemFontTag());
const auto overriden = !monospace && !system && !family.isEmpty();
if (monospace) {
font.setFamily(MonospaceFont());
} else if (system) {
} else if (overriden) {
font.setFamily(family);
} else {
font.setFamily("Open Sans"_q);
}
font.setPixelSize(size);
const auto adjust = (overriden || system);
const auto metrics = ComputeMetrics(font, adjust);
font.setPixelSize(metrics.pixelSize);
font.setWeight((flags & (FontFlag::Bold | FontFlag::Semibold))
? QFont::DemiBold
: QFont::Normal);
if (font.bold()) {
const auto style = QFontInfo(font).styleName();
if (!style.isEmpty() && !style.startsWith(
"Semibold",
Qt::CaseInsensitive)) {
font.setBold(true);
}
}
font.setItalic(flags & FontFlag::Italic);
font.setUnderline(flags & FontFlag::Underline);
font.setStrikeOut(flags & FontFlag::StrikeOut);
const auto index = (family == Custom) ? 0 : RegisterFontFamily(family);
return {
.font = font,
.ascent = metrics.ascent,
.height = metrics.height,
.iascent = int(base::SafeRound(metrics.ascent)),
.iheight = int(base::SafeRound(metrics.height)),
.requestedFamily = index,
.requestedSize = size,
.requestedFlags = flags,
};
}
} // namespace
void StartFonts() {
if (Started) {
return;
}
Started = true;
style_InitFontsResource();
const auto name = u"Open Sans"_q;
for (const auto &file : QDir(u":/gui/fonts/"_q).entryInfoList()) {
LoadCustomFont(file.canonicalFilePath());
}
if (!QFontInfo(name).family().trimmed().startsWith(
name,
Qt::CaseInsensitive)) {
const auto text = u"Unable to load '"_q
+ name
+ u"', expect font metric issues."_q;
LOG(("Font Error: %1").arg(text));
#if __has_include(<glib.h>)
g_warning("%s", text.toUtf8().constData());
#endif // __has_include(<glib.h>)
}
QFont::insertSubstitution(name, u"Vazirmatn UI NL"_q);
#ifdef Q_OS_MAC
const auto list = QStringList{
u"STIXGeneral"_q,
u".SF NS Text"_q,
u"Helvetica Neue"_q,
u"Lucida Grande"_q,
};
QFont::insertSubstitutions(name, list);
#endif // Q_OS_MAC
}
void DestroyFonts() {
base::take(FontsByKey);
}
int RegisterFontFamily(const QString &family) {
auto i = FontFamilyIndices.find(family);
if (i == end(FontFamilyIndices)) {
i = FontFamilyIndices.emplace(family, FontFamilies.size()).first;
FontFamilies.push_back(family);
}
return i->second;
}
FontData::FontData(const FontResolveResult &result, FontVariants *modified)
: f(result.font)
, _m(f)
, _size(result.requestedSize)
, _family(result.requestedFamily)
, _flags(result.requestedFlags) {
if (modified) {
memcpy(&_modified, modified, sizeof(_modified));
}
_modified[int(_flags)] = Font(this);
height = int(base::SafeRound(result.height));
ascent = int(base::SafeRound(result.ascent));
descent = height - ascent;
spacew = width(QLatin1Char(' '));
elidew = width(u"..."_q);
}
Font FontData::bold(bool set) const {
return otherFlagsFont(FontFlag::Bold, set);
}
Font FontData::italic(bool set) const {
return otherFlagsFont(FontFlag::Italic, set);
}
Font FontData::underline(bool set) const {
return otherFlagsFont(FontFlag::Underline, set);
}
Font FontData::strikeout(bool set) const {
return otherFlagsFont(FontFlag::StrikeOut, set);
}
Font FontData::semibold(bool set) const {
return otherFlagsFont(FontFlag::Semibold, set);
}
Font FontData::monospace(bool set) const {
return otherFlagsFont(FontFlag::Monospace, set);
}
int FontData::size() const {
return _size;
}
FontFlags FontData::flags() const {
return _flags;
}
int FontData::family() const {
return _family;
}
Font FontData::otherFlagsFont(FontFlag flag, bool set) const {
const auto newFlags = set ? (_flags | flag) : (_flags & ~flag);
if (!_modified[newFlags]) {
_modified[newFlags] = Font(_size, newFlags, _family, &_modified);
}
return _modified[newFlags];
}
Font::Font(int size, FontFlags flags, const QString &family) {
init(size, flags, RegisterFontFamily(family), 0);
}
Font::Font(int size, FontFlags flags, int family) {
init(size, flags, family, 0);
}
Font::Font(int size, FontFlags flags, int family, FontVariants *modified) {
init(size, flags, family, modified);
}
void Font::init(
int size,
FontFlags flags,
int family,
FontVariants *modified) {
const auto key = FontKey(size, flags, family);
auto i = FontsByKey.find(key);
if (i == end(FontsByKey)) {
i = FontsByKey.emplace(
key,
std::make_unique<ResolvedFont>(
ResolveFont(
family ? FontFamilies[family] : Custom,
flags,
size),
modified)).first;
QtFontsKeys.emplace(QtFontKey(i->second->data.f), key);
}
_data = &i->second->data;
}
OwnedFont::OwnedFont(const QString &custom, FontFlags flags, int size)
: _data(ResolveFont(custom, flags, size), nullptr) {
_font._data = &_data;
}
} // namespace internal
const FontResolveResult *FindAdjustResult(const QFont &font) {
const auto key = internal::QtFontKey(font);
const auto i = internal::QtFontsKeys.find(key);
return (i != end(internal::QtFontsKeys))
? &internal::FontsByKey[i->second]->result
: nullptr;
}
} // namespace style

View File

@@ -0,0 +1,188 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/basic_types.h"
#include "base/flags.h"
#include <QtGui/QFont>
#include <QtGui/QFontMetrics>
#include <cmath>
namespace style {
[[nodiscard]] const QString &SystemFontTag();
void SetCustomFont(const QString &font);
enum class FontFlag : uchar {
Bold = 0x01,
Italic = 0x02,
Underline = 0x04,
StrikeOut = 0x08,
Semibold = 0x10,
Monospace = 0x20,
};
inline constexpr bool is_flag_type(FontFlag) { return true; }
using FontFlags = base::flags<FontFlag>;
struct FontResolveResult {
QFont font;
float64 ascent = 0.;
float64 height = 0.;
int iascent = 0;
int iheight = 0;
int requestedFamily = 0;
int requestedSize = 0;
FontFlags requestedFlags;
};
[[nodiscard]] const FontResolveResult *FindAdjustResult(const QFont &font);
namespace internal {
void StartFonts();
void DestroyFonts();
int RegisterFontFamily(const QString &family);
inline constexpr auto kFontVariants = 0x40;
class Font;
using FontVariants = std::array<Font, kFontVariants>;
class FontData;
class Font final {
public:
Font(Qt::Initialization = Qt::Uninitialized) {
}
Font(int size, FontFlags flags, const QString &family);
Font(int size, FontFlags flags, int family);
[[nodiscard]] FontData *operator->() const {
return _data;
}
[[nodiscard]] FontData *get() const {
return _data;
}
[[nodiscard]] operator bool() const {
return _data != nullptr;
}
[[nodiscard]] operator const QFont &() const;
private:
friend class FontData;
friend class OwnedFont;
FontData *_data = nullptr;
void init(int size, FontFlags flags, int family, FontVariants *modified);
friend void StartManager();
explicit Font(FontData *data) : _data(data) {
}
Font(int size, FontFlags flags, int family, FontVariants *modified);
};
class FontData {
public:
[[nodiscard]] int width(const QString &text) const {
return int(std::ceil(_m.horizontalAdvance(text)));
}
[[nodiscard]] int width(const QString &text, int from, int to) const {
return width(text.mid(from, to));
}
[[nodiscard]] int width(QChar ch) const {
return int(std::ceil(_m.horizontalAdvance(ch)));
}
[[nodiscard]] QString elided(
const QString &str,
int width,
Qt::TextElideMode mode = Qt::ElideRight) const {
return _m.elidedText(str, mode, width);
}
[[nodiscard]] Font bold(bool set = true) const;
[[nodiscard]] Font italic(bool set = true) const;
[[nodiscard]] Font underline(bool set = true) const;
[[nodiscard]] Font strikeout(bool set = true) const;
[[nodiscard]] Font semibold(bool set = true) const;
[[nodiscard]] Font monospace(bool set = true) const;
[[nodiscard]] int size() const;
[[nodiscard]] FontFlags flags() const;
[[nodiscard]] int family() const;
QFont f;
int height = 0;
int ascent = 0;
int descent = 0;
int spacew = 0;
int elidew = 0;
private:
friend class OwnedFont;
friend struct ResolvedFont;
mutable FontVariants _modified;
[[nodiscard]] Font otherFlagsFont(FontFlag flag, bool set) const;
FontData(const FontResolveResult &data, FontVariants *modified);
QFontMetricsF _m;
int _size = 0;
int _family = 0;
FontFlags _flags = 0;
};
inline bool operator==(const Font &a, const Font &b) {
return a.get() == b.get();
}
inline bool operator!=(const Font &a, const Font &b) {
return a.get() != b.get();
}
inline Font::operator const QFont &() const {
Expects(_data != nullptr);
return _data->f;
}
class OwnedFont final {
public:
OwnedFont(const QString &custom, FontFlags flags, int size);
OwnedFont(const OwnedFont &other)
: _data(other._data) {
_font._data = &_data;
}
OwnedFont &operator=(const OwnedFont &other) {
_data = other._data;
return *this;
}
[[nodiscard]] const Font &font() const {
return _font;
}
[[nodiscard]] FontData *operator->() const {
return _font.get();
}
[[nodiscard]] FontData *get() const {
return _font.get();
}
private:
FontData _data;
Font _font;
};
} // namespace internal
} // namespace style

View File

@@ -0,0 +1,517 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_core_icon.h"
#include "ui/style/style_core_palette.h"
#include "ui/style/style_core.h"
#include "ui/painter.h"
#include "base/basic_types.h"
#include <QtCore/QMutex>
#include <QtGui/QPainter>
#include <QtSvg/QSvgRenderer>
namespace style {
namespace internal {
namespace {
[[nodiscard]] uint32 ColorKey(QColor c) {
return (uint32(c.red()) << 24)
| (uint32(c.green()) << 16)
| (uint32(c.blue()) << 8)
| uint32(c.alpha());
}
base::flat_map<const IconMask*, QImage> IconMasks;
QMutex IconMasksMutex;
base::flat_map<QPair<const IconMask*, uint32>, QPixmap> iconPixmaps;
base::flat_set<IconData*> iconData;
[[nodiscard]] QImage CreateIconMask(
not_null<const IconMask*> mask,
int scale,
bool ignoreDpr = false) {
const auto ratio = ignoreDpr ? 1 : DevicePixelRatio();
const auto realscale = scale * ratio;
auto data = QByteArray::fromRawData(
reinterpret_cast<const char*>(mask->data()),
mask->size());
if (data.startsWith("SVG:")) {
auto size = QSize();
data = QByteArray::fromRawData(
data.constData() + 4,
data.size() - 4);
if (data.startsWith("SIZE:")) {
data = QByteArray::fromRawData(
data.constData() + 5,
data.size() - 5);
QDataStream stream(data);
stream.setVersion(QDataStream::Qt_5_1);
qint32 width = 0, height = 0;
stream >> width >> height;
Assert(stream.status() == QDataStream::Ok);
size = QSize(width, height);
data = QByteArray::fromRawData(
data.constData() + 8,
data.size() - 8);
}
auto svg = QSvgRenderer(data);
Assert(svg.isValid());
if (size.isEmpty()) {
size = svg.defaultSize();
}
const auto width = ConvertScale(size.width(), scale);
const auto height = ConvertScale(size.height(), scale);
auto maskImage = QImage(
QSize(width, height) * ratio,
QImage::Format_ARGB32_Premultiplied);
maskImage.fill(Qt::transparent);
maskImage.setDevicePixelRatio(ratio);
auto p = QPainter(&maskImage);
auto hq = PainterHighQualityEnabler(p);
svg.render(&p, QRectF(0, 0, width, height));
return maskImage;
}
auto maskImage = QImage::fromData(mask->data(), mask->size(), "PNG");
maskImage.setDevicePixelRatio(ratio);
Assert(!maskImage.isNull());
// images are laid out like this:
// 100x 200x
// 300x
const auto width = maskImage.width() / 3;
const auto height = maskImage.height() / 5;
const auto one = QRect(0, 0, width, height);
const auto two = QRect(width, 0, width * 2, height * 2);
const auto three = QRect(0, height * 2, width * 3, height * 3);
if (realscale == 100) {
return maskImage.copy(one);
} else if (realscale == 200) {
return maskImage.copy(two);
} else if (realscale == 300) {
return maskImage.copy(three);
}
return maskImage.copy(
(realscale > 200) ? three : two
).scaled(
ConvertScale(width, scale) * ratio,
ConvertScale(height, scale) * ratio,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
[[nodiscard]] QImage ResolveIconMask(not_null<const IconMask*> mask) {
QMutexLocker lock(&IconMasksMutex);
if (const auto i = IconMasks.find(mask); i != end(IconMasks)) {
return i->second;
}
return IconMasks.emplace(
mask,
CreateIconMask(mask, Scale())
).first->second;
}
[[nodiscard]] QSize readGeneratedSize(
const IconMask *mask,
int scale,
bool ignoreDpr = false) {
auto data = mask->data();
auto size = mask->size();
auto generateTag = qstr("GENERATE:");
if (size > generateTag.size()
&& !memcmp(data, generateTag.data(), generateTag.size())) {
size -= generateTag.size();
data += generateTag.size();
auto sizeTag = qstr("SIZE:");
if (size > sizeTag.size()
&& !memcmp(data, sizeTag.data(), sizeTag.size())) {
size -= sizeTag.size();
data += sizeTag.size();
auto baForStream = QByteArray::fromRawData(
reinterpret_cast<const char*>(data),
size);
QDataStream stream(baForStream);
stream.setVersion(QDataStream::Qt_5_1);
qint32 width = 0, height = 0;
stream >> width >> height;
Assert(stream.status() == QDataStream::Ok);
return QSize(
ConvertScale(width, scale),
ConvertScale(height, scale));
} else {
Unexpected("Bad data in generated icon!");
}
}
return QSize();
}
} // namespace
MonoIcon::MonoIcon(const MonoIcon &other, const style::palette &palette)
: _mask(other._mask)
, _color(
palette.colorAtIndex(
style::main_palette::indexOfColor(other._color)))
, _padding(other._padding) {
}
MonoIcon::MonoIcon(const IconMask *mask, Color color, QMargins padding)
: _mask(mask)
, _color(std::move(color))
, _padding(padding) {
}
void MonoIcon::reset() const {
_pixmap = QPixmap();
_size = QSize();
}
int MonoIcon::width() const {
ensureLoaded();
return _size.width();
}
int MonoIcon::height() const {
ensureLoaded();
return _size.height();
}
QSize MonoIcon::size() const {
ensureLoaded();
return _size;
}
QSize MonoIcon::inner() const {
ensureLoaded();
return _size.shrunkBy(_padding);
}
void MonoIcon::paint(QPainter &p, const QPoint &pos, int outerw) const {
const auto partPosX = RightToLeft()
? (outerw - pos.x() - width() + _padding.right())
: (pos.x() + _padding.left());
const auto partPosY = pos.y() + _padding.top();
ensureLoaded();
if (_pixmap.isNull()) {
p.fillRect(QRect(QPoint(partPosX, partPosY), inner()), _color);
} else {
p.drawPixmap(partPosX, partPosY, _pixmap);
}
}
void MonoIcon::fill(QPainter &p, const QRect &rect) const {
Expects(_padding.isNull());
ensureLoaded();
if (_pixmap.isNull()) {
p.fillRect(rect, _color);
} else {
p.drawPixmap(rect, _pixmap);
}
}
void MonoIcon::paint(
QPainter &p,
const QPoint &pos,
int outerw,
QColor colorOverride) const {
const auto partPosX = RightToLeft()
? (outerw - pos.x() - width() + _padding.right())
: (pos.x() + _padding.left());
const auto partPosY = pos.y() + _padding.top();
ensureLoaded();
if (_pixmap.isNull()) {
p.fillRect(
QRect(QPoint(partPosX, partPosY), inner()),
colorOverride);
} else {
ensureColorizedImage(colorOverride);
p.drawImage(partPosX, partPosY, _colorizedImage);
}
}
void MonoIcon::fill(
QPainter &p,
const QRect &rect,
QColor colorOverride) const {
Expects(_padding.isNull());
ensureLoaded();
if (_pixmap.isNull()) {
p.fillRect(rect, colorOverride);
} else {
ensureColorizedImage(colorOverride);
p.drawImage(rect, _colorizedImage);
}
}
void MonoIcon::paint(
QPainter &p,
const QPoint &pos,
int outerw,
const style::palette &paletteOverride) const {
auto size = readGeneratedSize(_mask, Scale());
auto maskImage = QImage();
if (size.isEmpty()) {
maskImage = CreateIconMask(_mask, Scale());
size = maskImage.size() / DevicePixelRatio();
}
const auto partPosX = RightToLeft()
? (outerw - pos.x() - width() + _padding.right())
: (pos.x() + _padding.left());
const auto partPosY = pos.y() + _padding.top();
if (!maskImage.isNull()) {
auto colorized = QImage(
maskImage.size(),
QImage::Format_ARGB32_Premultiplied);
colorizeImage(maskImage, _color[paletteOverride]->c, &colorized);
p.drawImage(partPosX, partPosY, colorized);
} else {
p.fillRect(
QRect(QPoint(partPosX, partPosY), inner()),
_color[paletteOverride]);
}
}
void MonoIcon::fill(
QPainter &p,
const QRect &rect,
const style::palette &paletteOverride) const {
Expects(_padding.isNull());
auto size = readGeneratedSize(_mask, Scale());
auto maskImage = QImage();
if (size.isEmpty()) {
maskImage = CreateIconMask(_mask, Scale());
size = maskImage.size() / DevicePixelRatio();
}
if (!maskImage.isNull()) {
auto colorized = QImage(
maskImage.size(),
QImage::Format_ARGB32_Premultiplied);
colorizeImage(maskImage, _color[paletteOverride]->c, &colorized);
p.drawImage(rect, colorized);
} else {
p.fillRect(rect, _color[paletteOverride]);
}
}
QImage MonoIcon::instance(
QColor colorOverride,
int scale,
bool ignoreDpr) const {
Expects(_padding.isNull() || scale == kScaleAuto);
if (scale == kScaleAuto) {
ensureLoaded();
const auto ratio = DevicePixelRatio();
auto result = QImage(
size() * ratio,
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
if (_pixmap.isNull()) {
if (_padding.isNull()) {
result.fill(colorOverride);
} else {
result.fill(Qt::transparent);
auto p = QPainter(&result);
p.fillRect(
QRect(QPoint(), size()).marginsRemoved(_padding),
colorOverride);
}
} else {
if (!_padding.isNull()) {
result.fill(Qt::transparent);
}
colorizeImage(
_maskImage,
colorOverride,
&result,
QRect(),
QPoint(_padding.left(), _padding.top()) * ratio);
}
return result;
}
const auto ratio = ignoreDpr ? 1 : DevicePixelRatio();
auto size = readGeneratedSize(_mask, scale, ignoreDpr);
if (!size.isEmpty()) {
auto result = QImage(
size * ratio,
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
result.fill(colorOverride);
return result;
}
auto mask = CreateIconMask(_mask, scale, ignoreDpr);
auto result = QImage(mask.size(), QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
colorizeImage(mask, colorOverride, &result);
return result;
}
void MonoIcon::ensureLoaded() const {
if (_size.isValid()) {
return;
} else if (!_maskImage.isNull()) {
createCachedPixmap();
return;
}
_size = readGeneratedSize(_mask, Scale());
if (!_size.isEmpty()) {
_size = _size.grownBy(_padding);
} else {
_maskImage = ResolveIconMask(_mask);
createCachedPixmap();
}
}
void MonoIcon::ensureColorizedImage(QColor color) const {
if (_colorizedImage.isNull()) {
_colorizedImage = QImage(
_maskImage.size(),
QImage::Format_ARGB32_Premultiplied);
}
colorizeImage(_maskImage, color, &_colorizedImage);
}
void MonoIcon::createCachedPixmap() const {
auto key = qMakePair(_mask, ColorKey(_color->c));
auto j = iconPixmaps.find(key);
if (j == end(iconPixmaps)) {
auto image = colorizeImage(_maskImage, _color);
j = iconPixmaps.emplace(
key,
QPixmap::fromImage(std::move(image))).first;
}
_pixmap = j->second;
_size = (_pixmap.size() / DevicePixelRatio()).grownBy(_padding);
}
IconData::IconData(const IconData &other, const style::palette &palette) {
created();
_parts.reserve(other._parts.size());
for (const auto &part : other._parts) {
_parts.push_back(MonoIcon(part, palette));
}
}
void IconData::created() {
iconData.emplace(this);
}
IconData::~IconData() {
iconData.remove(this);
}
void IconData::fill(QPainter &p, const QRect &rect) const {
if (_parts.empty()) return;
auto partSize = _parts[0].size();
for (const auto &part : _parts) {
Assert(part.size() == partSize);
part.fill(p, rect);
}
}
void IconData::fill(QPainter &p, const QRect &rect, QColor colorOverride) const {
if (_parts.empty()) return;
auto partSize = _parts[0].size();
for (const auto &part : _parts) {
Assert(part.size() == partSize);
part.fill(p, rect, colorOverride);
}
}
QImage IconData::instance(
QColor colorOverride,
int scale,
bool ignoreDpr) const {
Expects(_parts.size() == 1);
return _parts[0].instance(colorOverride, scale, ignoreDpr);
}
int IconData::width() const {
if (_width < 0) {
_width = 0;
for (const auto &part : _parts) {
accumulate_max(_width, part.width());
}
}
return _width;
}
int IconData::height() const {
if (_height < 0) {
_height = 0;
for (const auto &part : _parts) {
accumulate_max(_height, part.height());
}
}
return _height;
}
Icon Icon::withPalette(const style::palette &palette) const {
Expects(_data != nullptr);
auto result = Icon(Qt::Uninitialized);
result._data = new IconData(*_data, palette);
result._owner = true;
return result;
}
void Icon::paintInCenter(QPainter &p, const QRectF &outer) const {
const auto dx = outer.x() + (outer.width() - width()) / 2.;
const auto dy = outer.y() + (outer.height() - height()) / 2.;
p.translate(dx, dy);
_data->paint(p, QPoint(), outer.x() * 2. + outer.width());
p.translate(-dx, -dy);
}
void Icon::paintInCenter(
QPainter &p,
const QRectF &outer,
QColor override) const {
const auto dx = outer.x() + (outer.width() - width()) / 2;
const auto dy = outer.y() + (outer.height() - height()) / 2;
p.translate(dx, dy);
_data->paint(p, QPoint(), outer.x() * 2 + outer.width(), override);
p.translate(-dx, -dy);
}
void ResetIcons() {
iconPixmaps.clear();
for (const auto data : iconData) {
data->reset();
}
}
void DestroyIcons() {
iconData.clear();
iconPixmaps.clear();
QMutexLocker lock(&IconMasksMutex);
IconMasks.clear();
}
} // namespace internal
} // namespace style

View File

@@ -0,0 +1,374 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/style/style_core_color.h"
#include "ui/style/style_core_scale.h"
#include "base/algorithm.h"
#include "base/assertion.h"
#include <vector>
namespace style {
namespace internal {
class IconMask {
public:
template <int N>
IconMask(const uchar (&data)[N]) : _data(data), _size(N) {
static_assert(N > 0, "invalid image data");
}
const uchar *data() const {
return _data;
}
int size() const {
return _size;
}
private:
const uchar *_data;
const int _size;
};
class MonoIcon {
public:
MonoIcon() = default;
MonoIcon(const MonoIcon &other) = delete;
MonoIcon &operator=(const MonoIcon &other) = delete;
MonoIcon(MonoIcon &&other) = default;
MonoIcon &operator=(MonoIcon &&other) = default;
MonoIcon(const MonoIcon &other, const style::palette &palette);
MonoIcon(const IconMask *mask, Color color, QMargins padding);
void reset() const;
[[nodiscard]] int width() const;
[[nodiscard]] int height() const;
[[nodiscard]] QSize size() const;
void paint(QPainter &p, const QPoint &pos, int outerw) const;
void fill(QPainter &p, const QRect &rect) const;
void paint(
QPainter &p,
const QPoint &pos,
int outerw,
QColor colorOverride) const;
void fill(QPainter &p, const QRect &rect, QColor colorOverride) const;
void paint(
QPainter &p,
const QPoint &pos,
int outerw,
const style::palette &paletteOverride) const;
void fill(
QPainter &p,
const QRect &rect,
const style::palette &paletteOverride) const;
[[nodiscard]] QImage instance(
QColor colorOverride,
int scale,
bool ignoreDpr) const;
~MonoIcon() {
}
private:
void ensureLoaded() const;
void createCachedPixmap() const;
void ensureColorizedImage(QColor color) const;
[[nodiscard]] QSize inner() const;
const IconMask *_mask = nullptr;
Color _color;
QMargins _padding = { 0, 0, 0, 0 };
mutable QImage _maskImage, _colorizedImage;
mutable QPixmap _pixmap; // for pixmaps
mutable QSize _size; // for rects
};
class IconData {
public:
template <typename ...MonoIcons>
IconData(std::in_place_t, MonoIcons &&...icons) {
created();
_parts.reserve(sizeof...(MonoIcons));
addIcons(std::forward<MonoIcons>(icons)...);
}
IconData(const IconData &other, const style::palette &palette);
~IconData();
void reset() {
for (const auto &part : _parts) {
part.reset();
}
}
bool empty() const {
return _parts.empty();
}
void paint(QPainter &p, const QPoint &pos, int outerw) const {
for (const auto &part : _parts) {
part.paint(p, pos, outerw);
}
}
void fill(QPainter &p, const QRect &rect) const;
void paint(
QPainter &p,
const QPoint &pos,
int outerw,
QColor colorOverride) const {
for (const auto &part : _parts) {
part.paint(p, pos, outerw, colorOverride);
}
}
void fill(QPainter &p, const QRect &rect, QColor colorOverride) const;
void paint(
QPainter &p,
const QPoint &pos,
int outerw,
const style::palette &paletteOverride) const {
for (const auto &part : _parts) {
part.paint(p, pos, outerw, paletteOverride);
}
}
void fill(
QPainter &p,
const QRect &rect,
const style::palette &paletteOverride) const;
[[nodiscard]] QImage instance(
QColor colorOverride,
int scale,
bool ignoreDpr) const;
int width() const;
int height() const;
private:
void created();
template <typename ... MonoIcons>
void addIcons() {
}
template <typename ... MonoIcons>
void addIcons(MonoIcon &&icon, MonoIcons&&... icons) {
_parts.push_back(std::move(icon));
addIcons(std::forward<MonoIcons>(icons)...);
}
std::vector<MonoIcon> _parts;
mutable int _width = -1;
mutable int _height = -1;
};
class Icon {
public:
Icon(Qt::Initialization = Qt::Uninitialized) {
}
template <typename ... MonoIcons>
Icon(std::in_place_t, MonoIcons&&... icons)
: _data(new IconData(std::in_place, std::forward<MonoIcons>(icons)...))
, _owner(true) {
}
Icon(const Icon &other) : _data(other._data) {
}
Icon(Icon &&other)
: _data(base::take(other._data))
, _owner(base::take(other._owner)) {
}
Icon &operator=(const Icon &other) {
Expects(!_owner);
_data = other._data;
_owner = false;
return *this;
}
Icon &operator=(Icon &&other) {
Expects(!_owner);
_data = base::take(other._data);
_owner = base::take(other._owner);
return *this;
}
bool empty() const {
return _data->empty();
}
int width() const {
return _data->width();
}
int height() const {
return _data->height();
}
QSize size() const {
return QSize(width(), height());
}
void paint(QPainter &p, const QPoint &pos, int outerw) const {
return _data->paint(p, pos, outerw);
}
void paint(QPainter &p, int x, int y, int outerw) const {
return _data->paint(p, QPoint(x, y), outerw);
}
void paintInCenter(QPainter &p, const QRect &outer) const {
return _data->paint(
p,
QPoint(
outer.x() + (outer.width() - width()) / 2,
outer.y() + (outer.height() - height()) / 2),
outer.x() * 2 + outer.width());
}
void paintInCenter(QPainter &p, const QRectF &outer) const;
void fill(QPainter &p, const QRect &rect) const {
return _data->fill(p, rect);
}
void paint(
QPainter &p,
const QPoint &pos,
int outerw,
QColor colorOverride) const {
return _data->paint(p, pos, outerw, colorOverride);
}
void paint(
QPainter &p,
int x,
int y,
int outerw,
QColor colorOverride) const {
return _data->paint(p, QPoint(x, y), outerw, colorOverride);
}
void paintInCenter(
QPainter &p,
const QRect &outer,
QColor colorOverride) const {
return _data->paint(
p,
QPoint(
outer.x() + (outer.width() - width()) / 2,
outer.y() + (outer.height() - height()) / 2),
outer.x() * 2 + outer.width(),
colorOverride);
}
void paintInCenter(
QPainter &p,
const QRectF &outer,
QColor override) const;
void fill(QPainter &p, const QRect &rect, QColor colorOverride) const {
return _data->fill(p, rect, colorOverride);
}
[[nodiscard]] QImage instance(
QColor colorOverride,
int scale = kScaleAuto,
bool ignoreDpr = false) const {
return _data->instance(colorOverride, scale, ignoreDpr);
}
class Proxy {
public:
Proxy(const Icon &icon, const style::palette &palette)
: _icon(icon)
, _palette(palette) {
}
Proxy(const Proxy &other) = default;
bool empty() const { return _icon.empty(); }
int width() const { return _icon.width(); }
int height() const { return _icon.height(); }
QSize size() const { return _icon.size(); }
void paint(QPainter &p, const QPoint &pos, int outerw) const {
return _icon.paintWithPalette(p, pos, outerw, _palette);
}
void paint(QPainter &p, int x, int y, int outerw) const {
return _icon.paintWithPalette(p, x, y, outerw, _palette);
}
void paintInCenter(QPainter &p, const QRect &outer) const {
return _icon.paintInCenterWithPalette(p, outer, _palette);
}
void fill(QPainter &p, const QRect &rect) const {
return _icon.fillWithPalette(p, rect, _palette);
}
private:
const Icon &_icon;
const style::palette &_palette;
};
Proxy operator[](const style::palette &paletteOverride) const {
return Proxy(*this, paletteOverride);
}
Icon withPalette(const style::palette &palette) const;
~Icon() {
if (auto data = base::take(_data)) {
if (_owner) {
delete data;
}
}
}
private:
friend class Proxy;
void paintWithPalette(
QPainter &p,
const QPoint &pos,
int outerw,
const style::palette &paletteOverride) const {
return _data->paint(p, pos, outerw, paletteOverride);
}
void paintWithPalette(
QPainter &p,
int x,
int y,
int outerw,
const style::palette &paletteOverride) const {
return _data->paint(p, QPoint(x, y), outerw, paletteOverride);
}
void paintInCenterWithPalette(
QPainter &p,
const QRect &outer,
const style::palette &paletteOverride) const {
return _data->paint(
p,
QPoint(
outer.x() + (outer.width() - width()) / 2,
outer.y() + (outer.height() - height()) / 2),
outer.x() * 2 + outer.width(),
paletteOverride);
}
void fillWithPalette(
QPainter &p,
const QRect &rect,
const style::palette &paletteOverride) const {
return _data->fill(p, rect, paletteOverride);
}
IconData *_data = nullptr;
bool _owner = false;
};
void ResetIcons();
void DestroyIcons();
} // namespace internal
} // namespace style

View File

@@ -0,0 +1,263 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_core_palette.h"
#include "ui/style/style_palette_colorizer.h"
namespace style {
struct palette::FinalizeHelper {
not_null<const colorizer*> with;
base::flat_set<int> ignoreKeys;
base::flat_map<
int,
std::pair<colorizer::Color, colorizer::Color>> keepContrast;
};
palette::palette() = default;
palette::~palette() {
clear();
}
int palette::indexOfColor(style::color c) const {
auto start = data(0);
if (c._data >= start && c._data < start + kCount) {
return static_cast<int>(c._data - start);
}
return -1;
}
color palette::colorAtIndex(int index) const {
Expects(index >= 0 && index < kCount);
Expects(_ready);
return _colors[index];
}
void palette::finalize(const colorizer &with) {
if (_ready) return;
_ready = true;
_finalizeHelper = PrepareFinalizeHelper(with);
palette_data::finalize(*this);
_finalizeHelper = nullptr;
}
void palette::finalize() {
finalize(colorizer());
}
palette &palette::operator=(const palette &other) {
auto wasReady = _ready;
for (int i = 0; i != kCount; ++i) {
if (other._status[i] != Status::Initial) {
if (_status[i] == Status::Initial) {
new (data(i)) internal::ColorData(*other.data(i));
} else {
*data(i) = *other.data(i);
}
_status[i] = Status::Loaded;
} else if (_status[i] != Status::Initial) {
data(i)->~ColorData();
_status[i] = Status::Initial;
_ready = false;
}
}
if (wasReady && !_ready) {
finalize();
}
return *this;
}
QByteArray palette::save() const {
if (!_ready) {
const_cast<palette*>(this)->finalize();
}
auto result = QByteArray(kCount * 4, Qt::Uninitialized);
for (auto i = 0, index = 0; i != kCount; ++i) {
result[index++] = static_cast<uchar>(data(i)->c.red());
result[index++] = static_cast<uchar>(data(i)->c.green());
result[index++] = static_cast<uchar>(data(i)->c.blue());
result[index++] = static_cast<uchar>(data(i)->c.alpha());
}
return result;
}
bool palette::load(const QByteArray &cache) {
if (cache.size() != kCount * 4) {
return false;
}
auto p = reinterpret_cast<const uchar*>(cache.constData());
for (auto i = 0; i != kCount; ++i) {
setData(i, { p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3] });
}
return true;
}
palette::SetResult palette::setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {
auto nameIndex = internal::GetPaletteIndex(name);
if (nameIndex < 0) return SetResult::KeyNotFound;
auto duplicate = (_status[nameIndex] != Status::Initial);
setData(nameIndex, { r, g, b, a });
return duplicate ? SetResult::Duplicate : SetResult::Ok;
}
palette::SetResult palette::setColor(QLatin1String name, const QColor &color) {
auto r = 0;
auto g = 0;
auto b = 0;
auto a = 0;
color.getRgb(&r, &g, &b, &a);
return setColor(name, uchar(r), uchar(g), uchar(b), uchar(a));
}
palette::SetResult palette::setColor(QLatin1String name, QLatin1String from) {
const auto nameIndex = internal::GetPaletteIndex(name);
if (nameIndex < 0) {
return SetResult::KeyNotFound;
}
const auto duplicate = (_status[nameIndex] != Status::Initial);
const auto fromIndex = internal::GetPaletteIndex(from);
if (fromIndex < 0 || _status[fromIndex] != Status::Loaded) {
return SetResult::ValueNotFound;
}
setData(nameIndex, *data(fromIndex));
return duplicate ? SetResult::Duplicate : SetResult::Ok;
}
void palette::reset(const colorizer &with) {
clear();
finalize(with);
}
void palette::reset() {
clear();
finalize();
}
void palette::clear() {
for (auto i = 0; i != kCount; ++i) {
if (_status[i] != Status::Initial) {
data(i)->~ColorData();
_status[i] = Status::Initial;
_ready = false;
}
}
}
void palette::compute(int index, int fallbackIndex, TempColorData value) {
if (_status[index] == Status::Initial) {
if (fallbackIndex >= 0 && _status[fallbackIndex] == Status::Loaded) {
_status[index] = Status::Loaded;
new (data(index)) internal::ColorData(*data(fallbackIndex));
} else {
if (!_finalizeHelper
|| _finalizeHelper->ignoreKeys.contains(index)) {
_status[index] = Status::Created;
} else {
const auto &with = *_finalizeHelper->with;
const auto i = _finalizeHelper->keepContrast.find(index);
if (i == end(_finalizeHelper->keepContrast)) {
colorize(value.r, value.g, value.b, with);
} else {
colorize(i->second, value.r, value.g, value.b, with);
}
_status[index] = Status::Loaded;
}
new (data(index)) internal::ColorData(value.r, value.g, value.b, value.a);
}
}
}
void palette::setData(int index, const internal::ColorData &value) {
if (_status[index] == Status::Initial) {
new (data(index)) internal::ColorData(value);
} else {
*data(index) = value;
}
_status[index] = Status::Loaded;
}
auto palette::PrepareFinalizeHelper(const colorizer &with)
-> std::unique_ptr<FinalizeHelper> {
if (!with) {
return nullptr;
}
auto result = std::make_unique<FinalizeHelper>(FinalizeHelper{
.with = &with,
});
result->ignoreKeys.reserve(with.ignoreKeys.size() + 1);
result->ignoreKeys.emplace(0);
for (const auto &key : with.ignoreKeys) {
if (const auto index = internal::GetPaletteIndex(key); index > 0) {
result->ignoreKeys.emplace(index);
}
}
for (const auto &[key, contrast] : with.keepContrast) {
if (const auto index = internal::GetPaletteIndex(key); index > 0) {
result->keepContrast.emplace(index, contrast);
}
}
return result;
}
namespace main_palette {
namespace {
palette &GetMutable() {
return const_cast<palette&>(*get());
}
} // namespace
QByteArray save() {
return GetMutable().save();
}
bool load(const QByteArray &cache) {
if (GetMutable().load(cache)) {
style::internal::ResetIcons();
return true;
}
return false;
}
palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {
return GetMutable().setColor(name, r, g, b, a);
}
palette::SetResult setColor(QLatin1String name, QLatin1String from) {
return GetMutable().setColor(name, from);
}
void apply(const palette &other) {
GetMutable() = other;
style::internal::ResetIcons();
}
void reset() {
GetMutable().reset();
style::internal::ResetIcons();
}
void reset(const colorizer &with) {
GetMutable().reset(with);
style::internal::ResetIcons();
}
int indexOfColor(color c) {
return GetMutable().indexOfColor(c);
}
} // namespace main_palette
} // namespace style

View File

@@ -0,0 +1,75 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/style/style_core.h"
#include "styles/palette.h"
namespace style {
struct colorizer;
class palette : public palette_data {
public:
palette();
palette(const palette &other) = delete;
palette &operator=(const palette &other);
~palette();
QByteArray save() const;
bool load(const QByteArray &cache);
enum class SetResult {
Ok,
KeyNotFound,
ValueNotFound,
Duplicate,
};
SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);
SetResult setColor(QLatin1String name, const QColor &color);
SetResult setColor(QLatin1String name, QLatin1String from);
void reset(const colorizer &with);
void reset();
// Created not inited, should be finalized before usage.
void finalize(const colorizer &with);
void finalize();
int indexOfColor(color c) const;
color colorAtIndex(int index) const;
private:
struct FinalizeHelper;
struct TempColorData { uchar r, g, b, a; };
friend class palette_data;
[[nodiscard]] static auto PrepareFinalizeHelper(const colorizer &with)
-> std::unique_ptr<FinalizeHelper>;
void clear();
void compute(int index, int fallbackIndex, TempColorData value);
void setData(int index, const internal::ColorData &value);
std::unique_ptr<FinalizeHelper> _finalizeHelper;
bool _ready = false;
};
namespace main_palette {
not_null<const palette*> get();
QByteArray save();
bool load(const QByteArray &cache);
palette::SetResult setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);
palette::SetResult setColor(QLatin1String name, QLatin1String from);
void apply(const palette &other);
void reset();
void reset(const colorizer &with);
int indexOfColor(color c);
} // namespace main_palette
} // namespace style

View File

@@ -0,0 +1,49 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_core_scale.h"
#include "base/assertion.h"
namespace style {
namespace {
int DevicePixelRatioValue = 1;
int ScaleValue = kScaleDefault;
} // namespace
int DevicePixelRatio() {
return DevicePixelRatioValue;
}
void SetDevicePixelRatio(int ratio) {
DevicePixelRatioValue = std::clamp(ratio, 1, kScaleMax / kScaleMin);
}
int Scale() {
return ScaleValue;
}
void SetScale(int scale) {
Expects(scale != 0);
ScaleValue = scale;
}
int MaxScaleForRatio(int ratio) {
Expects(ratio > 0);
return std::max(kScaleMax / ratio, kScaleAlwaysAllowMax);
}
int CheckScale(int scale) {
return (scale == kScaleAuto)
? kScaleAuto
: std::clamp(scale, kScaleMin, MaxScaleForRatio(DevicePixelRatio()));
}
} // namespace style

View File

@@ -0,0 +1,71 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/algorithm.h"
#include "base/assertion.h"
#include <QtCore/QSize>
#include <algorithm>
#include <cmath>
namespace style {
inline constexpr auto kScaleAuto = 0;
inline constexpr auto kScaleMin = 50;
inline constexpr auto kScaleDefault = 100;
inline constexpr auto kScaleMax = 300;
inline constexpr auto kScaleAlwaysAllowMax = 200;
[[nodiscard]] int DevicePixelRatio();
void SetDevicePixelRatio(int ratio);
[[nodiscard]] int Scale();
void SetScale(int scale);
[[nodiscard]] int MaxScaleForRatio(int ratio);
[[nodiscard]] int CheckScale(int scale);
template <typename T>
[[nodiscard]] inline T ConvertScale(T value, int scale) {
if (value < 0.) {
Assert(!(T(-value) < 0.)); // T = int, value = INT_MIN.
return -ConvertScale(-value, scale);
}
const auto result = T(base::SafeRound(
(double(value) * scale / 100.) - 0.01));
return (!std::is_integral_v<T> || !value || result) ? result : 1;
}
template <typename T>
[[nodiscard]] inline T ConvertScale(T value) {
return ConvertScale(value, Scale());
}
template <typename T>
[[nodiscard]] inline T ConvertScaleExact(T value, int scale) {
return (value < 0.)
? (-ConvertScale(-value, scale))
: T(double(value) * scale / 100.);
}
template <typename T>
[[nodiscard]] inline T ConvertScaleExact(T value) {
return ConvertScaleExact(value, Scale());
}
[[nodiscard]] inline QSize ConvertScale(QSize size) {
return QSize(ConvertScale(size.width()), ConvertScale(size.height()));
}
[[nodiscard]] inline float64 ConvertFloatScale(float64 value) {
constexpr auto kPrecision = 1000000.;
return ConvertScale(value * kPrecision) / kPrecision;
}
} // namespace style

View File

@@ -0,0 +1,10 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_core_types.h"
namespace style {
} // namespace style

View File

@@ -0,0 +1,57 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include <QtCore/QString>
#include <QtCore/QPoint>
#include <QtCore/QRect>
#include <QtGui/QColor>
#include <QtGui/QCursor>
#include <QtGui/QFont>
#include "ui/style/style_core_font.h"
#include "ui/style/style_core_color.h"
#include "ui/style/style_core_icon.h"
namespace style {
using string = QString;
using rect = QRect;
using point = QPoint;
using size = QSize;
using cursor = Qt::CursorShape;
using align = Qt::Alignment;
using margins = QMargins;
using font = internal::Font;
using owned_font = internal::OwnedFont;
using color = internal::Color;
using owned_color = internal::OwnedColor;
using complex_color = internal::ComplexColor;
using icon = internal::Icon;
static constexpr cursor cur_default = Qt::ArrowCursor;
static constexpr cursor cur_pointer = Qt::PointingHandCursor;
static constexpr cursor cur_text = Qt::IBeamCursor;
static constexpr cursor cur_cross = Qt::CrossCursor;
static constexpr cursor cur_sizever = Qt::SizeVerCursor;
static constexpr cursor cur_sizehor = Qt::SizeHorCursor;
static constexpr cursor cur_sizebdiag = Qt::SizeBDiagCursor;
static constexpr cursor cur_sizefdiag = Qt::SizeFDiagCursor;
static constexpr cursor cur_sizeall = Qt::SizeAllCursor;
static constexpr align al_topleft = (Qt::AlignTop | Qt::AlignLeft);
static constexpr align al_top = (Qt::AlignTop | Qt::AlignHCenter);
static constexpr align al_topright = (Qt::AlignTop | Qt::AlignRight);
static constexpr align al_right = (Qt::AlignVCenter | Qt::AlignRight);
static constexpr align al_bottomright = (Qt::AlignBottom | Qt::AlignRight);
static constexpr align al_bottom = (Qt::AlignBottom | Qt::AlignHCenter);
static constexpr align al_bottomleft = (Qt::AlignBottom | Qt::AlignLeft);
static constexpr align al_left = (Qt::AlignVCenter | Qt::AlignLeft);
static constexpr align al_center = (Qt::AlignVCenter | Qt::AlignHCenter);
static constexpr align al_justify = Qt::AlignJustify;
} // namespace style

View File

@@ -0,0 +1,208 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/style/style_palette_colorizer.h"
namespace style {
namespace {
constexpr auto kEnoughLightnessForContrast = 64;
void FillColorizeResult(uchar &r, uchar &g, uchar &b, const QColor &color) {
auto nowR = 0;
auto nowG = 0;
auto nowB = 0;
color.getRgb(&nowR, &nowG, &nowB);
r = uchar(nowR);
g = uchar(nowG);
b = uchar(nowB);
}
[[nodiscard]] std::optional<colorizer::Color> colorize(
const colorizer::Color &color,
const colorizer &with) {
const auto changeColor = std::abs(color.hue - with.was.hue)
< with.hueThreshold;
if (!changeColor) {
return std::nullopt;
}
const auto nowHue = color.hue + (with.now.hue - with.was.hue);
const auto nowSaturation = ((color.saturation > with.was.saturation)
&& (with.now.saturation > with.was.saturation))
? (((with.now.saturation * (255 - with.was.saturation))
+ ((color.saturation - with.was.saturation)
* (255 - with.now.saturation)))
/ (255 - with.was.saturation))
: ((color.saturation != with.was.saturation)
&& (with.was.saturation != 0))
? ((color.saturation * with.now.saturation)
/ with.was.saturation)
: with.now.saturation;
const auto nowValue = (color.value > with.was.value)
? (((with.now.value * (255 - with.was.value))
+ ((color.value - with.was.value)
* (255 - with.now.value)))
/ (255 - with.was.value))
: (color.value < with.was.value)
? ((color.value * with.now.value)
/ with.was.value)
: with.now.value;
return colorizer::Color{
((nowHue + 360) % 360),
nowSaturation,
nowValue
};
}
} // namespace
QColor ColorFromHex(std::string_view hex) {
Expects(hex.size() == 6);
const auto component = [](char a, char b) {
const auto convert = [](char ch) {
Expects((ch >= '0' && ch <= '9')
|| (ch >= 'A' && ch <= 'F')
|| (ch >= 'a' && ch <= 'f'));
return (ch >= '0' && ch <= '9')
? int(ch - '0')
: int(ch - ((ch >= 'A' && ch <= 'F') ? 'A' : 'a') + 10);
};
return convert(a) * 16 + convert(b);
};
return QColor(
component(hex[0], hex[1]),
component(hex[2], hex[3]),
component(hex[4], hex[5]));
};
void colorize(uchar &r, uchar &g, uchar &b, const colorizer &with) {
const auto changed = colorize(QColor(int(r), int(g), int(b)), with);
if (changed) {
FillColorizeResult(r, g, b, *changed);
}
}
void colorize(
QLatin1String name,
uchar &r,
uchar &g,
uchar &b,
const colorizer &with) {
if (with.ignoreKeys.contains(name)) {
return;
}
const auto i = with.keepContrast.find(name);
if (i == end(with.keepContrast)) {
colorize(r, g, b, with);
} else {
colorize(i->second, r, g, b, with);
}
}
void colorize(
const std::pair<colorizer::Color, colorizer::Color> &contrast,
uchar &r,
uchar &g,
uchar &b,
const colorizer &with) {
const auto check = contrast.first;
const auto rgb = QColor(int(r), int(g), int(b));
const auto changed = colorize(rgb, with);
const auto checked = colorize(check, with).value_or(check);
const auto lightness = [](QColor hsv) {
return hsv.value() - (hsv.value() * hsv.saturation()) / 511;
};
const auto changedLightness = lightness(changed.value_or(rgb).toHsv());
const auto checkedLightness = lightness(
QColor::fromHsv(checked.hue, checked.saturation, checked.value));
const auto delta = std::abs(changedLightness - checkedLightness);
if (delta >= kEnoughLightnessForContrast) {
if (changed) {
FillColorizeResult(r, g, b, *changed);
}
return;
}
const auto replace = contrast.second;
const auto result = colorize(replace, with).value_or(replace);
FillColorizeResult(
r,
g,
b,
QColor::fromHsv(result.hue, result.saturation, result.value));
}
void colorize(uint32 &pixel, const colorizer &with) {
const auto chars = reinterpret_cast<uchar*>(&pixel);
colorize(chars[2], chars[1], chars[0], with);
}
void colorize(QImage &image, const colorizer &with) {
image = std::move(image).convertToFormat(QImage::Format_ARGB32);
const auto bytes = image.bits();
const auto bytesPerLine = image.bytesPerLine();
for (auto line = 0; line != image.height(); ++line) {
const auto ints = reinterpret_cast<uint32*>(
bytes + line * bytesPerLine);
const auto end = ints + image.width();
for (auto p = ints; p != end; ++p) {
colorize(*p, with);
}
}
}
std::optional<QColor> colorize(const QColor &color, const colorizer &with) {
auto hue = 0;
auto saturation = 0;
auto lightness = 0;
auto alpha = 0;
color.getHsv(&hue, &saturation, &lightness, &alpha);
const auto result = colorize(
colorizer::Color{ hue, saturation, lightness },
with);
if (!result) {
return std::nullopt;
}
const auto &hsv = *result;
return QColor::fromHsv(hsv.hue, hsv.saturation, hsv.value, alpha);
}
QByteArray colorize(
QLatin1String hexColor,
const style::colorizer &with) {
Expects(hexColor.size() == 7 || hexColor.size() == 9);
auto color = ColorFromHex(std::string_view(hexColor.data() + 1, 6));
const auto changed = colorize(color, with).value_or(color).toRgb();
auto result = QByteArray();
result.reserve(hexColor.size());
result.append(hexColor.data()[0]);
const auto addHex = [&](int code) {
if (code >= 0 && code < 10) {
result.append('0' + code);
} else if (code >= 10 && code < 16) {
result.append('a' + (code - 10));
}
};
const auto addValue = [&](int code) {
addHex(code / 16);
addHex(code % 16);
};
addValue(changed.red());
addValue(changed.green());
addValue(changed.blue());
if (hexColor.size() == 9) {
result.append(hexColor.data()[7]);
result.append(hexColor.data()[8]);
}
return result;
}
} // namespace style

View File

@@ -0,0 +1,59 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
namespace style {
struct colorizer {
struct Color {
int hue = 0;
int saturation = 0;
int value = 0;
};
int hueThreshold = 0;
int lightnessMin = 0;
int lightnessMax = 255;
Color was;
Color now;
base::flat_set<QLatin1String> ignoreKeys;
base::flat_map<QLatin1String, std::pair<Color, Color>> keepContrast;
explicit operator bool() const {
return (hueThreshold > 0);
}
};
[[nodiscard]] QColor ColorFromHex(std::string_view hex);
void colorize(
uchar &r,
uchar &g,
uchar &b,
const colorizer &with);
void colorize(
QLatin1String name,
uchar &r,
uchar &g,
uchar &b,
const colorizer &with);
void colorize(
const std::pair<colorizer::Color, colorizer::Color> &contrast,
uchar &r,
uchar &g,
uchar &b,
const colorizer &with);
void colorize(QImage &image, const colorizer &with);
[[nodiscard]] std::optional<QColor> colorize(
const QColor &color,
const colorizer &with);
[[nodiscard]] QByteArray colorize(
QLatin1String hexColor,
const colorizer &with);
} // namespace style