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
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:
234
Telegram/lib_ui/ui/style/style_core.cpp
Normal file
234
Telegram/lib_ui/ui/style/style_core.cpp
Normal 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
|
||||
94
Telegram/lib_ui/ui/style/style_core.h
Normal file
94
Telegram/lib_ui/ui/style/style_core.h
Normal 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
|
||||
39
Telegram/lib_ui/ui/style/style_core_color.cpp
Normal file
39
Telegram/lib_ui/ui/style/style_core_color.cpp
Normal 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
|
||||
189
Telegram/lib_ui/ui/style/style_core_color.h
Normal file
189
Telegram/lib_ui/ui/style/style_core_color.h
Normal 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
|
||||
56
Telegram/lib_ui/ui/style/style_core_direction.cpp
Normal file
56
Telegram/lib_ui/ui/style/style_core_direction.cpp
Normal 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
|
||||
76
Telegram/lib_ui/ui/style/style_core_direction.h
Normal file
76
Telegram/lib_ui/ui/style/style_core_direction.h
Normal 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
|
||||
518
Telegram/lib_ui/ui/style/style_core_font.cpp
Normal file
518
Telegram/lib_ui/ui/style/style_core_font.cpp
Normal 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
|
||||
188
Telegram/lib_ui/ui/style/style_core_font.h
Normal file
188
Telegram/lib_ui/ui/style/style_core_font.h
Normal 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
|
||||
517
Telegram/lib_ui/ui/style/style_core_icon.cpp
Normal file
517
Telegram/lib_ui/ui/style/style_core_icon.cpp
Normal 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
|
||||
374
Telegram/lib_ui/ui/style/style_core_icon.h
Normal file
374
Telegram/lib_ui/ui/style/style_core_icon.h
Normal 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
|
||||
263
Telegram/lib_ui/ui/style/style_core_palette.cpp
Normal file
263
Telegram/lib_ui/ui/style/style_core_palette.cpp
Normal 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
|
||||
75
Telegram/lib_ui/ui/style/style_core_palette.h
Normal file
75
Telegram/lib_ui/ui/style/style_core_palette.h
Normal 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
|
||||
49
Telegram/lib_ui/ui/style/style_core_scale.cpp
Normal file
49
Telegram/lib_ui/ui/style/style_core_scale.cpp
Normal 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
|
||||
71
Telegram/lib_ui/ui/style/style_core_scale.h
Normal file
71
Telegram/lib_ui/ui/style/style_core_scale.h
Normal 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
|
||||
10
Telegram/lib_ui/ui/style/style_core_types.cpp
Normal file
10
Telegram/lib_ui/ui/style/style_core_types.cpp
Normal 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
|
||||
57
Telegram/lib_ui/ui/style/style_core_types.h
Normal file
57
Telegram/lib_ui/ui/style/style_core_types.h
Normal 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
|
||||
208
Telegram/lib_ui/ui/style/style_palette_colorizer.cpp
Normal file
208
Telegram/lib_ui/ui/style/style_palette_colorizer.cpp
Normal 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
|
||||
59
Telegram/lib_ui/ui/style/style_palette_colorizer.h
Normal file
59
Telegram/lib_ui/ui/style/style_palette_colorizer.h
Normal 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
|
||||
Reference in New Issue
Block a user