Updated lib_ui sources to TDesktop version 3.0.1
This commit is contained in:
commit
29fdd935ad
18 changed files with 1096 additions and 48 deletions
|
|
@ -188,6 +188,8 @@ PRIVATE
|
||||||
ui/widgets/side_bar_button.h
|
ui/widgets/side_bar_button.h
|
||||||
ui/widgets/shadow.cpp
|
ui/widgets/shadow.cpp
|
||||||
ui/widgets/shadow.h
|
ui/widgets/shadow.h
|
||||||
|
ui/widgets/time_input.cpp
|
||||||
|
ui/widgets/time_input.h
|
||||||
ui/widgets/tooltip.cpp
|
ui/widgets/tooltip.cpp
|
||||||
ui/widgets/tooltip.h
|
ui/widgets/tooltip.h
|
||||||
ui/wrap/fade_wrap.cpp
|
ui/wrap/fade_wrap.cpp
|
||||||
|
|
@ -266,4 +268,6 @@ PUBLIC
|
||||||
target_link_libraries(lib_ui
|
target_link_libraries(lib_ui
|
||||||
PUBLIC
|
PUBLIC
|
||||||
desktop-app::lib_base
|
desktop-app::lib_base
|
||||||
|
PRIVATE
|
||||||
|
desktop-app::external_zlib
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ constexpr auto kSetVersion = uint32(2);
|
||||||
constexpr auto kCacheVersion = uint32(6);
|
constexpr auto kCacheVersion = uint32(6);
|
||||||
constexpr auto kMaxId = uint32(1 << 8);
|
constexpr auto kMaxId = uint32(1 << 8);
|
||||||
|
|
||||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
#ifdef Q_OS_MAC
|
||||||
constexpr auto kScaleForTouchBar = 150;
|
constexpr auto kScaleForTouchBar = 150;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ auto CanClearUniversal = false;
|
||||||
auto WaitingToSwitchBackToId = 0;
|
auto WaitingToSwitchBackToId = 0;
|
||||||
auto Updates = rpl::event_stream<>();
|
auto Updates = rpl::event_stream<>();
|
||||||
|
|
||||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
#ifdef Q_OS_MAC
|
||||||
auto TouchbarSize = -1;
|
auto TouchbarSize = -1;
|
||||||
auto TouchbarInstance = std::unique_ptr<Instance>();
|
auto TouchbarInstance = std::unique_ptr<Instance>();
|
||||||
auto TouchbarEmoji = (Instance*)nullptr;
|
auto TouchbarEmoji = (Instance*)nullptr;
|
||||||
|
|
@ -498,7 +498,7 @@ void Init() {
|
||||||
InstanceNormal = std::make_unique<Instance>(SizeNormal);
|
InstanceNormal = std::make_unique<Instance>(SizeNormal);
|
||||||
InstanceLarge = std::make_unique<Instance>(SizeLarge);
|
InstanceLarge = std::make_unique<Instance>(SizeLarge);
|
||||||
|
|
||||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
#ifdef Q_OS_MAC
|
||||||
if (style::Scale() != kScaleForTouchBar) {
|
if (style::Scale() != kScaleForTouchBar) {
|
||||||
TouchbarSize = int(style::ConvertScale(18 * 4 / 3.,
|
TouchbarSize = int(style::ConvertScale(18 * 4 / 3.,
|
||||||
kScaleForTouchBar * style::DevicePixelRatio()));
|
kScaleForTouchBar * style::DevicePixelRatio()));
|
||||||
|
|
@ -516,7 +516,7 @@ void Clear() {
|
||||||
|
|
||||||
InstanceNormal = nullptr;
|
InstanceNormal = nullptr;
|
||||||
InstanceLarge = nullptr;
|
InstanceLarge = nullptr;
|
||||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
#ifdef Q_OS_MAC
|
||||||
TouchbarInstance = nullptr;
|
TouchbarInstance = nullptr;
|
||||||
TouchbarEmoji = nullptr;
|
TouchbarEmoji = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -620,7 +620,7 @@ int GetSizeLarge() {
|
||||||
return SizeLarge;
|
return SizeLarge;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
#ifdef Q_OS_MAC
|
||||||
int GetSizeTouchbar() {
|
int GetSizeTouchbar() {
|
||||||
return (style::Scale() == kScaleForTouchBar)
|
return (style::Scale() == kScaleForTouchBar)
|
||||||
? GetSizeLarge()
|
? GetSizeLarge()
|
||||||
|
|
@ -763,7 +763,7 @@ const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Draw(QPainter &p, EmojiPtr emoji, int size, int x, int y) {
|
void Draw(QPainter &p, EmojiPtr emoji, int size, int x, int y) {
|
||||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
#ifdef Q_OS_MAC
|
||||||
const auto s = (style::Scale() == kScaleForTouchBar)
|
const auto s = (style::Scale() == kScaleForTouchBar)
|
||||||
? SizeLarge
|
? SizeLarge
|
||||||
: TouchbarSize;
|
: TouchbarSize;
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ void ClearNeedSwitchToId();
|
||||||
|
|
||||||
[[nodiscard]] int GetSizeNormal();
|
[[nodiscard]] int GetSizeNormal();
|
||||||
[[nodiscard]] int GetSizeLarge();
|
[[nodiscard]] int GetSizeLarge();
|
||||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
#ifdef Q_OS_MAC
|
||||||
[[nodiscard]] int GetSizeTouchbar();
|
[[nodiscard]] int GetSizeTouchbar();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,23 @@
|
||||||
#include "ui/style/style_core.h"
|
#include "ui/style/style_core.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "base/flat_map.h"
|
#include "base/flat_map.h"
|
||||||
|
#include "base/debug_log.h"
|
||||||
|
#include "base/bytes.h"
|
||||||
#include "styles/palette.h"
|
#include "styles/palette.h"
|
||||||
#include "styles/style_basic.h"
|
#include "styles/style_basic.h"
|
||||||
|
|
||||||
|
#include "zlib.h"
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
#include <QtCore/QBuffer>
|
||||||
|
#include <QtGui/QImageReader>
|
||||||
|
#include <QtSvg/QSvgRenderer>
|
||||||
|
|
||||||
namespace Images {
|
namespace Images {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
// They should be smaller.
|
||||||
|
constexpr auto kMaxGzipFileSize = 5 * 1024 * 1024;
|
||||||
|
|
||||||
TG_FORCE_INLINE uint64 blurGetColors(const uchar *p) {
|
TG_FORCE_INLINE uint64 blurGetColors(const uchar *p) {
|
||||||
return (uint64)p[0] + ((uint64)p[1] << 16) + ((uint64)p[2] << 32) + ((uint64)p[3] << 48);
|
return (uint64)p[0] + ((uint64)p[1] << 16) + ((uint64)p[2] << 32) + ((uint64)p[3] << 48);
|
||||||
}
|
}
|
||||||
|
|
@ -68,6 +79,210 @@ std::array<QImage, 4> PrepareCornersMask(int radius) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <int kBits> // 4 means 16x16, 3 means 8x8
|
||||||
|
[[nodiscard]] QImage DitherGeneric(const QImage &image) {
|
||||||
|
static_assert(kBits >= 1 && kBits <= 4);
|
||||||
|
|
||||||
|
constexpr auto kSquareSide = (1 << kBits);
|
||||||
|
constexpr auto kShift = kSquareSide / 2;
|
||||||
|
constexpr auto kMask = (kSquareSide - 1);
|
||||||
|
|
||||||
|
const auto width = image.width();
|
||||||
|
const auto height = image.height();
|
||||||
|
const auto area = width * height;
|
||||||
|
const auto shifts = std::make_unique<uchar[]>(area);
|
||||||
|
bytes::set_random(bytes::make_span(shifts.get(), area));
|
||||||
|
|
||||||
|
// shiftx = int(shift & kMask) - kShift;
|
||||||
|
// shifty = int((shift >> 4) & kMask) - kShift;
|
||||||
|
// Clamp shifts close to edges.
|
||||||
|
for (auto y = 0; y != kShift; ++y) {
|
||||||
|
const auto min = kShift - y;
|
||||||
|
const auto shifted = (min << 4);
|
||||||
|
auto shift = shifts.get() + y * width;
|
||||||
|
for (const auto till = shift + width; shift != till; ++shift) {
|
||||||
|
if (((*shift >> 4) & kMask) < min) {
|
||||||
|
*shift = shifted | (*shift & 0x0F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto y = height - (kShift - 1); y != height; ++y) {
|
||||||
|
const auto max = kShift + (height - y - 1);
|
||||||
|
const auto shifted = (max << 4);
|
||||||
|
auto shift = shifts.get() + y * width;
|
||||||
|
for (const auto till = shift + width; shift != till; ++shift) {
|
||||||
|
if (((*shift >> 4) & kMask) > max) {
|
||||||
|
*shift = shifted | (*shift & 0x0F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto shift = shifts.get(), ytill = shift + area
|
||||||
|
; shift != ytill
|
||||||
|
; shift += width - kShift) {
|
||||||
|
for (const auto till = shift + kShift; shift != till; ++shift) {
|
||||||
|
const auto min = (till - shift);
|
||||||
|
if ((*shift & kMask) < min) {
|
||||||
|
*shift = (*shift & 0xF0) | min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto shift = shifts.get(), ytill = shift + area; shift != ytill;) {
|
||||||
|
shift += width - (kShift - 1);
|
||||||
|
for (const auto till = shift + (kShift - 1); shift != till; ++shift) {
|
||||||
|
const auto max = kShift + (till - shift - 1);
|
||||||
|
if ((*shift & kMask) > max) {
|
||||||
|
*shift = (*shift & 0xF0) | max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = image;
|
||||||
|
result.detach();
|
||||||
|
|
||||||
|
const auto src = reinterpret_cast<const uint32*>(image.constBits());
|
||||||
|
const auto dst = reinterpret_cast<uint32*>(result.bits());
|
||||||
|
for (auto index = 0; index != area; ++index) {
|
||||||
|
const auto shift = shifts[index];
|
||||||
|
const auto shiftx = int(shift & kMask) - kShift;
|
||||||
|
const auto shifty = int((shift >> 4) & kMask) - kShift;
|
||||||
|
dst[index] = src[index + (shifty * width) + shiftx];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QImage GenerateSmallComplexGradient(
|
||||||
|
const std::vector<QColor> &colors,
|
||||||
|
int rotation,
|
||||||
|
float progress) {
|
||||||
|
const auto positions = std::vector<std::pair<float, float>>{
|
||||||
|
{ 0.80f, 0.10f },
|
||||||
|
{ 0.60f, 0.20f },
|
||||||
|
{ 0.35f, 0.25f },
|
||||||
|
{ 0.25f, 0.60f },
|
||||||
|
{ 0.20f, 0.90f },
|
||||||
|
{ 0.40f, 0.80f },
|
||||||
|
{ 0.65f, 0.75f },
|
||||||
|
{ 0.75f, 0.40f },
|
||||||
|
};
|
||||||
|
const auto positionsForPhase = [&](int phase) {
|
||||||
|
auto result = std::vector<std::pair<float, float>>(4);
|
||||||
|
for (auto i = 0; i != 4; ++i) {
|
||||||
|
result[i] = positions[(phase + i * 2) % 8];
|
||||||
|
result[i].second = 1.f - result[i].second;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const auto phase = std::clamp(rotation, 0, 315) / 45;
|
||||||
|
const auto previousPhase = (phase + 1) % 8;
|
||||||
|
const auto previous = positionsForPhase(previousPhase);
|
||||||
|
const auto current = positionsForPhase(phase);
|
||||||
|
|
||||||
|
constexpr auto kWidth = 64;
|
||||||
|
constexpr auto kHeight = 64;
|
||||||
|
static const auto pixelCache = [&] {
|
||||||
|
auto result = std::make_unique<float[]>(kWidth * kHeight * 2);
|
||||||
|
const auto invwidth = 1.f / kWidth;
|
||||||
|
const auto invheight = 1.f / kHeight;
|
||||||
|
auto floats = result.get();
|
||||||
|
for (auto y = 0; y != kHeight; ++y) {
|
||||||
|
const auto directPixelY = y * invheight;
|
||||||
|
const auto centerDistanceY = directPixelY - 0.5f;
|
||||||
|
const auto centerDistanceY2 = centerDistanceY * centerDistanceY;
|
||||||
|
for (auto x = 0; x != kWidth; ++x) {
|
||||||
|
const auto directPixelX = x * invwidth;
|
||||||
|
const auto centerDistanceX = directPixelX - 0.5f;
|
||||||
|
const auto centerDistance = sqrtf(
|
||||||
|
centerDistanceX * centerDistanceX + centerDistanceY2);
|
||||||
|
|
||||||
|
const auto swirlFactor = 0.35f * centerDistance;
|
||||||
|
const auto theta = swirlFactor * swirlFactor * 0.8f * 8.0f;
|
||||||
|
const auto sinTheta = sinf(theta);
|
||||||
|
const auto cosTheta = cosf(theta);
|
||||||
|
*floats++ = std::max(
|
||||||
|
0.0f,
|
||||||
|
std::min(
|
||||||
|
1.0f,
|
||||||
|
(0.5f
|
||||||
|
+ centerDistanceX * cosTheta
|
||||||
|
- centerDistanceY * sinTheta)));
|
||||||
|
*floats++ = std::max(
|
||||||
|
0.0f,
|
||||||
|
std::min(
|
||||||
|
1.0f,
|
||||||
|
(0.5f
|
||||||
|
+ centerDistanceX * sinTheta
|
||||||
|
+ centerDistanceY * cosTheta)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}();
|
||||||
|
const auto colorsCount = int(colors.size());
|
||||||
|
auto colorsFloat = std::vector<std::array<float, 3>>(colorsCount);
|
||||||
|
for (auto i = 0; i != colorsCount; ++i) {
|
||||||
|
colorsFloat[i] = {
|
||||||
|
float(colors[i].red()),
|
||||||
|
float(colors[i].green()),
|
||||||
|
float(colors[i].blue()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
auto result = QImage(
|
||||||
|
kWidth,
|
||||||
|
kHeight,
|
||||||
|
QImage::Format_RGB32);
|
||||||
|
Assert(result.bytesPerLine() == kWidth * 4);
|
||||||
|
|
||||||
|
auto cache = pixelCache.get();
|
||||||
|
auto pixels = reinterpret_cast<uint32*>(result.bits());
|
||||||
|
for (auto y = 0; y != kHeight; ++y) {
|
||||||
|
for (auto x = 0; x != kWidth; ++x) {
|
||||||
|
const auto pixelX = *cache++;
|
||||||
|
const auto pixelY = *cache++;
|
||||||
|
|
||||||
|
auto distanceSum = 0.f;
|
||||||
|
auto r = 0.f;
|
||||||
|
auto g = 0.f;
|
||||||
|
auto b = 0.f;
|
||||||
|
for (auto i = 0; i != colorsCount; ++i) {
|
||||||
|
const auto colorX = previous[i].first
|
||||||
|
+ (current[i].first - previous[i].first) * progress;
|
||||||
|
const auto colorY = previous[i].second
|
||||||
|
+ (current[i].second - previous[i].second) * progress;
|
||||||
|
|
||||||
|
const auto dx = pixelX - colorX;
|
||||||
|
const auto dy = pixelY - colorY;
|
||||||
|
const auto distance = std::max(
|
||||||
|
0.0f,
|
||||||
|
0.9f - sqrtf(dx * dx + dy * dy));
|
||||||
|
const auto square = distance * distance;
|
||||||
|
const auto fourth = square * square;
|
||||||
|
distanceSum += fourth;
|
||||||
|
|
||||||
|
r += fourth * colorsFloat[i][0];
|
||||||
|
g += fourth * colorsFloat[i][1];
|
||||||
|
b += fourth * colorsFloat[i][2];
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto red = uint32(r / distanceSum);
|
||||||
|
const auto green = uint32(g / distanceSum);
|
||||||
|
const auto blue = uint32(b / distanceSum);
|
||||||
|
*pixels++ = 0xFF000000U | (red << 16) | (green << 8) | blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QImage GenerateComplexGradient(
|
||||||
|
QSize size,
|
||||||
|
const std::vector<QColor> &colors,
|
||||||
|
int rotation,
|
||||||
|
float progress) {
|
||||||
|
auto exact = GenerateSmallComplexGradient(colors, rotation, progress);
|
||||||
|
return (exact.size() == size)
|
||||||
|
? exact
|
||||||
|
: exact.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
QPixmap PixmapFast(QImage &&image) {
|
QPixmap PixmapFast(QImage &&image) {
|
||||||
|
|
@ -111,6 +326,134 @@ std::array<QImage, 4> PrepareCorners(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray UnpackGzip(const QByteArray &bytes) {
|
||||||
|
z_stream stream;
|
||||||
|
stream.zalloc = nullptr;
|
||||||
|
stream.zfree = nullptr;
|
||||||
|
stream.opaque = nullptr;
|
||||||
|
stream.avail_in = 0;
|
||||||
|
stream.next_in = nullptr;
|
||||||
|
int res = inflateInit2(&stream, 16 + MAX_WBITS);
|
||||||
|
if (res != Z_OK) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
const auto guard = gsl::finally([&] { inflateEnd(&stream); });
|
||||||
|
|
||||||
|
auto result = QByteArray(kMaxGzipFileSize + 1, char(0));
|
||||||
|
stream.avail_in = bytes.size();
|
||||||
|
stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(bytes.data()));
|
||||||
|
stream.avail_out = 0;
|
||||||
|
while (!stream.avail_out) {
|
||||||
|
stream.avail_out = result.size();
|
||||||
|
stream.next_out = reinterpret_cast<Bytef*>(result.data());
|
||||||
|
int res = inflate(&stream, Z_NO_FLUSH);
|
||||||
|
if (res != Z_OK && res != Z_STREAM_END) {
|
||||||
|
return bytes;
|
||||||
|
} else if (!stream.avail_out) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.resize(result.size() - stream.avail_out);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ReadResult ReadGzipSvg(const ReadArgs &args) {
|
||||||
|
const auto bytes = UnpackGzip(args.content);
|
||||||
|
if (bytes.isEmpty()) {
|
||||||
|
LOG(("Svg Error: Couldn't unpack gzip-ed content."));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto renderer = QSvgRenderer(bytes);
|
||||||
|
if (!renderer.isValid()) {
|
||||||
|
LOG(("Svg Error: Invalid data."));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto size = renderer.defaultSize();
|
||||||
|
if (!args.maxSize.isEmpty()
|
||||||
|
&& (size.width() > args.maxSize.width()
|
||||||
|
|| size.height() > args.maxSize.height())) {
|
||||||
|
size = size.scaled(args.maxSize, Qt::KeepAspectRatio);
|
||||||
|
}
|
||||||
|
if (size.isEmpty()) {
|
||||||
|
LOG(("Svg Error: Bad size %1x%2."
|
||||||
|
).arg(renderer.defaultSize().width()
|
||||||
|
).arg(renderer.defaultSize().height()));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto result = ReadResult();
|
||||||
|
result.image = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
result.image.fill(Qt::transparent);
|
||||||
|
{
|
||||||
|
QPainter p(&result.image);
|
||||||
|
renderer.render(&p, QRect(QPoint(), size));
|
||||||
|
}
|
||||||
|
result.format = "svg";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ReadResult ReadOther(const ReadArgs &args) {
|
||||||
|
auto bytes = args.content;
|
||||||
|
if (bytes.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto buffer = QBuffer(&bytes);
|
||||||
|
auto reader = QImageReader(&buffer);
|
||||||
|
reader.setAutoTransform(true);
|
||||||
|
if (!reader.canRead()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto size = reader.size();
|
||||||
|
if (size.width() * size.height() > kReadMaxArea) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto result = ReadResult();
|
||||||
|
if (!reader.read(&result.image) || result.image.isNull()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
result.animated = reader.supportsAnimation()
|
||||||
|
&& (reader.imageCount() > 1);
|
||||||
|
result.format = reader.format().toLower();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadResult Read(ReadArgs &&args) {
|
||||||
|
if (args.content.isEmpty()) {
|
||||||
|
if (args.path.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto file = QFile(args.path);
|
||||||
|
if (file.size() > kReadBytesLimit
|
||||||
|
|| !file.open(QIODevice::ReadOnly)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
args.content = file.readAll();
|
||||||
|
}
|
||||||
|
auto result = args.gzipSvg ? ReadGzipSvg(args) : ReadOther(args);
|
||||||
|
if (result.image.isNull()) {
|
||||||
|
args = ReadArgs();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (args.returnContent) {
|
||||||
|
result.content = args.content;
|
||||||
|
} else {
|
||||||
|
args.content = QByteArray();
|
||||||
|
}
|
||||||
|
if (!args.maxSize.isEmpty()
|
||||||
|
&& (result.image.width() > args.maxSize.width()
|
||||||
|
|| result.image.height() > args.maxSize.height())) {
|
||||||
|
result.image = result.image.scaled(
|
||||||
|
args.maxSize,
|
||||||
|
Qt::KeepAspectRatio,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
if (args.forceOpaque
|
||||||
|
&& result.format != qstr("jpg")
|
||||||
|
&& result.format != qstr("jpeg")) {
|
||||||
|
result.image = prepareOpaque(std::move(result.image));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QImage prepareBlur(QImage img) {
|
QImage prepareBlur(QImage img) {
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
return img;
|
return img;
|
||||||
|
|
@ -447,6 +790,72 @@ QImage BlurLargeImage(QImage image, int radius) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QImage DitherImage(QImage image) {
|
||||||
|
Expects(image.bytesPerLine() == image.width() * 4);
|
||||||
|
|
||||||
|
const auto width = image.width();
|
||||||
|
const auto height = image.height();
|
||||||
|
const auto min = std::min(width, height);
|
||||||
|
const auto max = std::max(width, height);
|
||||||
|
if (max >= 1024 && min >= 512) {
|
||||||
|
return DitherGeneric<4>(image);
|
||||||
|
} else if (max >= 512 && min >= 256) {
|
||||||
|
return DitherGeneric<3>(image);
|
||||||
|
} else if (max >= 256 && min >= 128) {
|
||||||
|
return DitherGeneric<2>(image);
|
||||||
|
} else if (min >= 32) {
|
||||||
|
return DitherGeneric<1>(image);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QImage GenerateGradient(
|
||||||
|
QSize size,
|
||||||
|
const std::vector<QColor> &colors,
|
||||||
|
int rotation,
|
||||||
|
float progress) {
|
||||||
|
Expects(!colors.empty());
|
||||||
|
Expects(colors.size() <= 4);
|
||||||
|
|
||||||
|
if (size.isEmpty()) {
|
||||||
|
return QImage();
|
||||||
|
} else if (colors.size() > 2) {
|
||||||
|
return GenerateComplexGradient(size, colors, rotation, progress);
|
||||||
|
}
|
||||||
|
auto result = QImage(size, QImage::Format_RGB32);
|
||||||
|
if (colors.size() == 1) {
|
||||||
|
result.fill(colors.front());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto p = QPainter(&result);
|
||||||
|
const auto width = size.width();
|
||||||
|
const auto height = size.height();
|
||||||
|
const auto [start, finalStop] = [&]() -> std::pair<QPoint, QPoint> {
|
||||||
|
const auto type = std::clamp(rotation, 0, 315) / 45;
|
||||||
|
switch (type) {
|
||||||
|
case 0: return { { 0, 0 }, { 0, height } };
|
||||||
|
case 1: return { { width, 0 }, { 0, height } };
|
||||||
|
case 2: return { { width, 0 }, { 0, 0 } };
|
||||||
|
case 3: return { { width, height }, { 0, 0 } };
|
||||||
|
case 4: return { { 0, height }, { 0, 0 } };
|
||||||
|
case 5: return { { 0, height }, { width, 0 } };
|
||||||
|
case 6: return { { 0, 0 }, { width, 0 } };
|
||||||
|
case 7: return { { 0, 0 }, { width, height } };
|
||||||
|
}
|
||||||
|
Unexpected("Rotation value in GenerateDitheredGradient.");
|
||||||
|
}();
|
||||||
|
auto gradient = QLinearGradient(start, finalStop);
|
||||||
|
gradient.setStops(QGradientStops{
|
||||||
|
{ 0.0, colors[0] },
|
||||||
|
{ 1.0, colors[1] }
|
||||||
|
});
|
||||||
|
p.fillRect(QRect(QPoint(), size), QBrush(std::move(gradient)));
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void prepareCircle(QImage &img) {
|
void prepareCircle(QImage &img) {
|
||||||
Assert(!img.isNull());
|
Assert(!img.isNull());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,13 @@ namespace Images {
|
||||||
|
|
||||||
[[nodiscard]] QPixmap PixmapFast(QImage &&image);
|
[[nodiscard]] QPixmap PixmapFast(QImage &&image);
|
||||||
[[nodiscard]] QImage BlurLargeImage(QImage image, int radius);
|
[[nodiscard]] QImage BlurLargeImage(QImage image, int radius);
|
||||||
|
[[nodiscard]] QImage DitherImage(QImage image);
|
||||||
|
|
||||||
|
[[nodiscard]] QImage GenerateGradient(
|
||||||
|
QSize size,
|
||||||
|
const std::vector<QColor> &colors, // colors.size() <= 4.
|
||||||
|
int rotation = 0,
|
||||||
|
float progress = 1.f);
|
||||||
|
|
||||||
[[nodiscard]] const std::array<QImage, 4> &CornersMask(
|
[[nodiscard]] const std::array<QImage, 4> &CornersMask(
|
||||||
ImageRoundRadius radius);
|
ImageRoundRadius radius);
|
||||||
|
|
@ -39,6 +46,28 @@ namespace Images {
|
||||||
int radius,
|
int radius,
|
||||||
const style::color &color);
|
const style::color &color);
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray UnpackGzip(const QByteArray &bytes);
|
||||||
|
|
||||||
|
// Try to read images up to 64MB.
|
||||||
|
inline constexpr auto kReadBytesLimit = 64 * 1024 * 1024;
|
||||||
|
inline constexpr auto kReadMaxArea = 12'032 * 9'024;
|
||||||
|
|
||||||
|
struct ReadArgs {
|
||||||
|
QString path;
|
||||||
|
QByteArray content;
|
||||||
|
QSize maxSize;
|
||||||
|
bool gzipSvg = false;
|
||||||
|
bool forceOpaque = false;
|
||||||
|
bool returnContent = false;
|
||||||
|
};
|
||||||
|
struct ReadResult {
|
||||||
|
QImage image;
|
||||||
|
QByteArray content;
|
||||||
|
QByteArray format;
|
||||||
|
bool animated = false;
|
||||||
|
};
|
||||||
|
[[nodiscard]] ReadResult Read(ReadArgs &&args);
|
||||||
|
|
||||||
QImage prepareBlur(QImage image);
|
QImage prepareBlur(QImage image);
|
||||||
void prepareRound(
|
void prepareRound(
|
||||||
QImage &image,
|
QImage &image,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "ui/platform/win/ui_window_title_win.h"
|
#include "ui/platform/win/ui_window_title_win.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include "base/platform/win/base_windows_safe_library.h"
|
#include "base/platform/win/base_windows_safe_library.h"
|
||||||
|
#include "base/integration.h"
|
||||||
#include "base/debug_log.h"
|
#include "base/debug_log.h"
|
||||||
#include "styles/palette.h"
|
#include "styles/palette.h"
|
||||||
|
|
||||||
|
|
@ -150,15 +151,19 @@ bool WindowHelper::NativeFilter::nativeEventFilter(
|
||||||
const QByteArray &eventType,
|
const QByteArray &eventType,
|
||||||
void *message,
|
void *message,
|
||||||
long *result) {
|
long *result) {
|
||||||
|
auto filtered = false;
|
||||||
const auto msg = static_cast<MSG*>(message);
|
const auto msg = static_cast<MSG*>(message);
|
||||||
const auto i = _windowByHandle.find(msg->hwnd);
|
const auto i = _windowByHandle.find(msg->hwnd);
|
||||||
return (i != end(_windowByHandle))
|
if (i != end(_windowByHandle)) {
|
||||||
? i->second->handleNativeEvent(
|
base::Integration::Instance().enterFromEventLoop([&] {
|
||||||
msg->message,
|
filtered = i->second->handleNativeEvent(
|
||||||
msg->wParam,
|
msg->message,
|
||||||
msg->lParam,
|
msg->wParam,
|
||||||
reinterpret_cast<LRESULT*>(result))
|
msg->lParam,
|
||||||
: false;
|
reinterpret_cast<LRESULT*>(result));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowHelper::WindowHelper(not_null<RpWidget*> window)
|
WindowHelper::WindowHelper(not_null<RpWidget*> window)
|
||||||
|
|
|
||||||
|
|
@ -144,11 +144,7 @@ namespace internal {
|
||||||
|
|
||||||
QImage createCircleMask(int size, QColor bg, QColor fg) {
|
QImage createCircleMask(int size, QColor bg, QColor fg) {
|
||||||
int realSize = size * DevicePixelRatio();
|
int realSize = size * DevicePixelRatio();
|
||||||
#ifndef OS_MAC_OLD
|
|
||||||
auto result = QImage(realSize, realSize, QImage::Format::Format_Grayscale8);
|
auto result = QImage(realSize, realSize, QImage::Format::Format_Grayscale8);
|
||||||
#else // OS_MAC_OLD
|
|
||||||
auto result = QImage(realSize, realSize, QImage::Format::Format_RGB32);
|
|
||||||
#endif // OS_MAC_OLD
|
|
||||||
{
|
{
|
||||||
QPainter p(&result);
|
QPainter p(&result);
|
||||||
PainterHighQualityEnabler hq(p);
|
PainterHighQualityEnabler hq(p);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include "ui/style/style_core.h"
|
#include "ui/style/style_core.h"
|
||||||
#include "base/basic_types.h"
|
#include "base/basic_types.h"
|
||||||
|
#include "styles/palette.h"
|
||||||
|
|
||||||
#include <QtGui/QPainter>
|
#include <QtGui/QPainter>
|
||||||
|
|
||||||
|
|
@ -20,8 +21,8 @@ uint32 colorKey(QColor c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
base::flat_map<const IconMask*, QImage> iconMasks;
|
base::flat_map<const IconMask*, QImage> iconMasks;
|
||||||
QMap<QPair<const IconMask*, uint32>, QPixmap> iconPixmaps;
|
base::flat_map<QPair<const IconMask*, uint32>, QPixmap> iconPixmaps;
|
||||||
OrderedSet<IconData*> iconData;
|
base::flat_set<IconData*> iconData;
|
||||||
|
|
||||||
QImage createIconMask(const IconMask *mask, int scale) {
|
QImage createIconMask(const IconMask *mask, int scale) {
|
||||||
auto maskImage = QImage::fromData(mask->data(), mask->size(), "PNG");
|
auto maskImage = QImage::fromData(mask->data(), mask->size(), "PNG");
|
||||||
|
|
@ -86,6 +87,14 @@ QSize readGeneratedSize(const IconMask *mask, int scale) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
MonoIcon::MonoIcon(const MonoIcon &other, const style::palette &palette)
|
||||||
|
: _mask(other._mask)
|
||||||
|
, _color(
|
||||||
|
palette.colorAtIndex(
|
||||||
|
style::main_palette::indexOfColor(other._color)))
|
||||||
|
, _offset(other._offset) {
|
||||||
|
}
|
||||||
|
|
||||||
MonoIcon::MonoIcon(const IconMask *mask, Color color, QPoint offset)
|
MonoIcon::MonoIcon(const IconMask *mask, Color color, QPoint offset)
|
||||||
: _mask(mask)
|
: _mask(mask)
|
||||||
, _color(std::move(color))
|
, _color(std::move(color))
|
||||||
|
|
@ -268,17 +277,31 @@ void MonoIcon::ensureColorizedImage(QColor color) const {
|
||||||
|
|
||||||
void MonoIcon::createCachedPixmap() const {
|
void MonoIcon::createCachedPixmap() const {
|
||||||
auto key = qMakePair(_mask, colorKey(_color->c));
|
auto key = qMakePair(_mask, colorKey(_color->c));
|
||||||
auto j = iconPixmaps.constFind(key);
|
auto j = iconPixmaps.find(key);
|
||||||
if (j == iconPixmaps.cend()) {
|
if (j == end(iconPixmaps)) {
|
||||||
auto image = colorizeImage(_maskImage, _color);
|
auto image = colorizeImage(_maskImage, _color);
|
||||||
j = iconPixmaps.insert(key, QPixmap::fromImage(std::move(image)));
|
j = iconPixmaps.emplace(
|
||||||
|
key,
|
||||||
|
QPixmap::fromImage(std::move(image))).first;
|
||||||
}
|
}
|
||||||
_pixmap = j.value();
|
_pixmap = j->second;
|
||||||
_size = _pixmap.size() / DevicePixelRatio();
|
_size = _pixmap.size() / DevicePixelRatio();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
void IconData::created() {
|
||||||
iconData.insert(this);
|
iconData.emplace(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData::~IconData() {
|
||||||
|
iconData.remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IconData::fill(QPainter &p, const QRect &rect) const {
|
void IconData::fill(QPainter &p, const QRect &rect) const {
|
||||||
|
|
@ -304,7 +327,8 @@ void IconData::fill(QPainter &p, const QRect &rect, QColor colorOverride) const
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage IconData::instance(QColor colorOverride, int scale) const {
|
QImage IconData::instance(QColor colorOverride, int scale) const {
|
||||||
Assert(_parts.size() == 1);
|
Expects(_parts.size() == 1);
|
||||||
|
|
||||||
auto &part = _parts[0];
|
auto &part = _parts[0];
|
||||||
Assert(part.offset() == QPoint(0, 0));
|
Assert(part.offset() == QPoint(0, 0));
|
||||||
return part.instance(colorOverride, scale);
|
return part.instance(colorOverride, scale);
|
||||||
|
|
@ -330,6 +354,15 @@ int IconData::height() const {
|
||||||
return _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 resetIcons() {
|
void resetIcons() {
|
||||||
iconPixmaps.clear();
|
iconPixmaps.clear();
|
||||||
for (const auto data : iconData) {
|
for (const auto data : iconData) {
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ public:
|
||||||
MonoIcon &operator=(const MonoIcon &other) = delete;
|
MonoIcon &operator=(const MonoIcon &other) = delete;
|
||||||
MonoIcon(MonoIcon &&other) = default;
|
MonoIcon(MonoIcon &&other) = default;
|
||||||
MonoIcon &operator=(MonoIcon &&other) = default;
|
MonoIcon &operator=(MonoIcon &&other) = default;
|
||||||
|
MonoIcon(const MonoIcon &other, const style::palette &palette);
|
||||||
MonoIcon(const IconMask *mask, Color color, QPoint offset);
|
MonoIcon(const IconMask *mask, Color color, QPoint offset);
|
||||||
|
|
||||||
void reset() const;
|
void reset() const;
|
||||||
|
|
@ -82,13 +83,18 @@ private:
|
||||||
|
|
||||||
class IconData {
|
class IconData {
|
||||||
public:
|
public:
|
||||||
|
struct FromIcons {
|
||||||
|
};
|
||||||
template <typename ...MonoIcons>
|
template <typename ...MonoIcons>
|
||||||
IconData(MonoIcons &&...icons) {
|
IconData(FromIcons, MonoIcons &&...icons) {
|
||||||
created();
|
created();
|
||||||
_parts.reserve(sizeof...(MonoIcons));
|
_parts.reserve(sizeof...(MonoIcons));
|
||||||
addIcons(std::forward<MonoIcons>(icons)...);
|
addIcons(std::forward<MonoIcons>(icons)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconData(const IconData &other, const style::palette &palette);
|
||||||
|
~IconData();
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
for (const auto &part : _parts) {
|
for (const auto &part : _parts) {
|
||||||
part.reset();
|
part.reset();
|
||||||
|
|
@ -149,11 +155,17 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ... MonoIcons>
|
template <typename ... MonoIcons>
|
||||||
Icon(MonoIcons&&... icons) : _data(new IconData(std::forward<MonoIcons>(icons)...)), _owner(true) {
|
Icon(MonoIcons&&... icons)
|
||||||
|
: _data(new IconData(
|
||||||
|
IconData::FromIcons{},
|
||||||
|
std::forward<MonoIcons>(icons)...))
|
||||||
|
, _owner(true) {
|
||||||
}
|
}
|
||||||
Icon(const Icon &other) : _data(other._data) {
|
Icon(const Icon &other) : _data(other._data) {
|
||||||
}
|
}
|
||||||
Icon(Icon &&other) : _data(base::take(other._data)), _owner(base::take(other._owner)) {
|
Icon(Icon &&other)
|
||||||
|
: _data(base::take(other._data))
|
||||||
|
, _owner(base::take(other._owner)) {
|
||||||
}
|
}
|
||||||
Icon &operator=(const Icon &other) {
|
Icon &operator=(const Icon &other) {
|
||||||
Expects(!_owner);
|
Expects(!_owner);
|
||||||
|
|
@ -247,6 +259,8 @@ public:
|
||||||
return Proxy(*this, paletteOverride);
|
return Proxy(*this, paletteOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Icon withPalette(const style::palette &palette) const;
|
||||||
|
|
||||||
~Icon() {
|
~Icon() {
|
||||||
if (auto data = base::take(_data)) {
|
if (auto data = base::take(_data)) {
|
||||||
if (_owner) {
|
if (_owner) {
|
||||||
|
|
|
||||||
|
|
@ -134,14 +134,7 @@ bool IsBad(QChar ch) {
|
||||||
|| (ch >= 127 && ch < 160 && ch != 156)
|
|| (ch >= 127 && ch < 160 && ch != 156)
|
||||||
|
|
||||||
// qt harfbuzz crash see https://github.com/telegramdesktop/tdesktop/issues/4551
|
// qt harfbuzz crash see https://github.com/telegramdesktop/tdesktop/issues/4551
|
||||||
|| (Platform::IsMac() && ch == 6158)
|
|| (Platform::IsMac() && ch == 6158);
|
||||||
|
|
||||||
// tmp hack see https://bugreports.qt.io/browse/QTBUG-48910
|
|
||||||
|| (Platform::IsMac10_11OrGreater()
|
|
||||||
&& !Platform::IsMac10_12OrGreater()
|
|
||||||
&& ch >= 0x0B00
|
|
||||||
&& ch <= 0x0B7F
|
|
||||||
&& IsDiac(ch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
@ -1984,12 +1977,7 @@ private:
|
||||||
if (item == -1)
|
if (item == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#ifdef OS_MAC_OLD
|
|
||||||
auto end = _e->findItem(line.from + line.length - 1);
|
|
||||||
#else // OS_MAC_OLD
|
|
||||||
auto end = _e->findItem(line.from + line.length - 1, item);
|
auto end = _e->findItem(line.from + line.length - 1, item);
|
||||||
#endif // OS_MAC_OLD
|
|
||||||
|
|
||||||
auto blockIndex = _lineStartBlock;
|
auto blockIndex = _lineStartBlock;
|
||||||
auto currentBlock = _t->_blocks[blockIndex].get();
|
auto currentBlock = _t->_blocks[blockIndex].get();
|
||||||
auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
||||||
|
|
|
||||||
|
|
@ -88,9 +88,7 @@ QRegularExpression CreateRegExp(const QString &expression) {
|
||||||
auto result = QRegularExpression(
|
auto result = QRegularExpression(
|
||||||
expression,
|
expression,
|
||||||
QRegularExpression::UseUnicodePropertiesOption);
|
QRegularExpression::UseUnicodePropertiesOption);
|
||||||
#ifndef OS_MAC_OLD
|
|
||||||
result.optimize();
|
result.optimize();
|
||||||
#endif // OS_MAC_OLD
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ enum class EntityType : uchar {
|
||||||
Mention,
|
Mention,
|
||||||
MentionName,
|
MentionName,
|
||||||
BotCommand,
|
BotCommand,
|
||||||
|
MediaTimestamp,
|
||||||
|
|
||||||
Bold,
|
Bold,
|
||||||
Semibold,
|
Semibold,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
#include <QtGui/QWindow>
|
#include <QtGui/QWindow>
|
||||||
#include <QtGui/QtEvents>
|
#include <QtGui/QtEvents>
|
||||||
|
#include <QWheelEvent>
|
||||||
#include <private/qhighdpiscaling_p.h>
|
#include <private/qhighdpiscaling_p.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
@ -169,9 +170,7 @@ void SendSynteticMouseEvent(QWidget *widget, QEvent::Type type, Qt::MouseButton
|
||||||
, button
|
, button
|
||||||
, QGuiApplication::mouseButtons() | button
|
, QGuiApplication::mouseButtons() | button
|
||||||
, QGuiApplication::keyboardModifiers()
|
, QGuiApplication::keyboardModifiers()
|
||||||
#ifndef OS_MAC_OLD
|
|
||||||
, Qt::MouseEventSynthesizedByApplication
|
, Qt::MouseEventSynthesizedByApplication
|
||||||
#endif // OS_MAC_OLD
|
|
||||||
);
|
);
|
||||||
ev.setTimestamp(crl::now());
|
ev.setTimestamp(crl::now());
|
||||||
QGuiApplication::sendEvent(windowHandle, &ev);
|
QGuiApplication::sendEvent(windowHandle, &ev);
|
||||||
|
|
@ -220,4 +219,15 @@ void DisableCustomScaling() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int WheelDirection(not_null<QWheelEvent*> e) {
|
||||||
|
// Only a mouse wheel is accepted.
|
||||||
|
constexpr auto step = static_cast<int>(QWheelEvent::DefaultDeltasPerStep);
|
||||||
|
const auto delta = e->angleDelta().y();
|
||||||
|
const auto absDelta = std::abs(delta);
|
||||||
|
if (absDelta != step) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (delta / absDelta);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
class QPixmap;
|
class QPixmap;
|
||||||
class QImage;
|
class QImage;
|
||||||
|
class QWheelEvent;
|
||||||
|
|
||||||
enum class RectPart;
|
enum class RectPart;
|
||||||
using RectParts = base::flags<RectPart>;
|
using RectParts = base::flags<RectPart>;
|
||||||
|
|
@ -193,4 +194,6 @@ QPointer<const Widget> MakeWeak(not_null<const Widget*> object) {
|
||||||
|
|
||||||
void DisableCustomScaling();
|
void DisableCustomScaling();
|
||||||
|
|
||||||
|
int WheelDirection(not_null<QWheelEvent*> e);
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -1309,7 +1309,6 @@ InputField::InputField(
|
||||||
, _lastTextWithTags(value)
|
, _lastTextWithTags(value)
|
||||||
, _placeholderFull(std::move(placeholder)) {
|
, _placeholderFull(std::move(placeholder)) {
|
||||||
_inner->setDocument(CreateChild<InputDocument>(_inner.get(), _st));
|
_inner->setDocument(CreateChild<InputDocument>(_inner.get(), _st));
|
||||||
|
|
||||||
_inner->setAcceptRichText(false);
|
_inner->setAcceptRichText(false);
|
||||||
resize(_st.width, _minHeight);
|
resize(_st.width, _minHeight);
|
||||||
|
|
||||||
|
|
@ -3008,7 +3007,7 @@ void InputField::inputMethodEventInner(QInputMethodEvent *e) {
|
||||||
const auto weak = Ui::MakeWeak(this);
|
const auto weak = Ui::MakeWeak(this);
|
||||||
_inner->QTextEdit::inputMethodEvent(e);
|
_inner->QTextEdit::inputMethodEvent(e);
|
||||||
|
|
||||||
if (weak) {
|
if (weak && _inputMethodCommit.has_value()) {
|
||||||
const auto text = *base::take(_inputMethodCommit);
|
const auto text = *base::take(_inputMethodCommit);
|
||||||
if (!processMarkdownReplaces(text)) {
|
if (!processMarkdownReplaces(text)) {
|
||||||
processInstantReplaces(text);
|
processInstantReplaces(text);
|
||||||
|
|
|
||||||
473
ui/widgets/time_input.cpp
Normal file
473
ui/widgets/time_input.cpp
Normal file
|
|
@ -0,0 +1,473 @@
|
||||||
|
// 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/widgets/time_input.h"
|
||||||
|
|
||||||
|
#include "ui/widgets/input_fields.h"
|
||||||
|
#include "ui/ui_utility.h"
|
||||||
|
|
||||||
|
#include <QtCore/QRegularExpression>
|
||||||
|
#include <QTime>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
QTime ValidateTime(const QString &value) {
|
||||||
|
const auto match = QRegularExpression(
|
||||||
|
"^(\\d{1,2})\\:(\\d\\d)$").match(value);
|
||||||
|
if (!match.hasMatch()) {
|
||||||
|
return QTime();
|
||||||
|
}
|
||||||
|
const auto readInt = [](const QString &value) {
|
||||||
|
auto ref = value.midRef(0);
|
||||||
|
while (!ref.isEmpty() && ref.at(0) == '0') {
|
||||||
|
ref = ref.mid(1);
|
||||||
|
}
|
||||||
|
return ref.toInt();
|
||||||
|
};
|
||||||
|
return QTime(readInt(match.captured(1)), readInt(match.captured(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GetHour(const QString &value) {
|
||||||
|
if (const auto time = ValidateTime(value); time.isValid()) {
|
||||||
|
return QString::number(time.hour());
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GetMinute(const QString &value) {
|
||||||
|
if (const auto time = ValidateTime(value); time.isValid()) {
|
||||||
|
return QString("%1").arg(time.minute(), 2, 10, QChar('0'));
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class TimePart final : public MaskedInputField {
|
||||||
|
public:
|
||||||
|
using MaskedInputField::MaskedInputField;
|
||||||
|
|
||||||
|
void setMaxValue(int value);
|
||||||
|
void setWheelStep(int value);
|
||||||
|
|
||||||
|
rpl::producer<> erasePrevious() const;
|
||||||
|
rpl::producer<QChar> putNext() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
|
void wheelEvent(QWheelEvent *e) override;
|
||||||
|
|
||||||
|
void correctValue(
|
||||||
|
const QString &was,
|
||||||
|
int wasCursor,
|
||||||
|
QString &now,
|
||||||
|
int &nowCursor) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _maxValue = 0;
|
||||||
|
int _maxDigits = 0;
|
||||||
|
int _wheelStep = 0;
|
||||||
|
rpl::event_stream<> _erasePrevious;
|
||||||
|
rpl::event_stream<QChar> _putNext;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
int Number(not_null<TimePart*> field) {
|
||||||
|
const auto text = field->getLastText();
|
||||||
|
auto ref = text.midRef(0);
|
||||||
|
while (!ref.isEmpty() && ref.at(0) == '0') {
|
||||||
|
ref = ref.mid(1);
|
||||||
|
}
|
||||||
|
return ref.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::setMaxValue(int value) {
|
||||||
|
_maxValue = value;
|
||||||
|
_maxDigits = 0;
|
||||||
|
while (value > 0) {
|
||||||
|
++_maxDigits;
|
||||||
|
value /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::setWheelStep(int value) {
|
||||||
|
_wheelStep = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> TimePart::erasePrevious() const {
|
||||||
|
return _erasePrevious.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QChar> TimePart::putNext() const {
|
||||||
|
return _putNext.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::keyPressEvent(QKeyEvent *e) {
|
||||||
|
const auto isBackspace = (e->key() == Qt::Key_Backspace);
|
||||||
|
const auto isBeginning = (cursorPosition() == 0);
|
||||||
|
if (isBackspace && isBeginning && !hasSelectedText()) {
|
||||||
|
_erasePrevious.fire({});
|
||||||
|
} else {
|
||||||
|
MaskedInputField::keyPressEvent(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::wheelEvent(QWheelEvent *e) {
|
||||||
|
const auto direction = WheelDirection(e);
|
||||||
|
auto time = Number(this) + (direction * _wheelStep);
|
||||||
|
const auto max = _maxValue + 1;
|
||||||
|
if (time < 0) {
|
||||||
|
time += max;
|
||||||
|
} else if (time >= max) {
|
||||||
|
time -= max;
|
||||||
|
}
|
||||||
|
setText(QString::number(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::correctValue(
|
||||||
|
const QString &was,
|
||||||
|
int wasCursor,
|
||||||
|
QString &now,
|
||||||
|
int &nowCursor) {
|
||||||
|
auto newText = QString();
|
||||||
|
auto newCursor = -1;
|
||||||
|
const auto oldCursor = nowCursor;
|
||||||
|
const auto oldLength = now.size();
|
||||||
|
auto accumulated = 0;
|
||||||
|
auto limit = 0;
|
||||||
|
for (; limit != oldLength; ++limit) {
|
||||||
|
if (now[limit].isDigit()) {
|
||||||
|
accumulated *= 10;
|
||||||
|
accumulated += (now[limit].unicode() - '0');
|
||||||
|
if (accumulated > _maxValue || limit == _maxDigits) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto i = 0; i != limit;) {
|
||||||
|
if (now[i].isDigit()) {
|
||||||
|
newText += now[i];
|
||||||
|
}
|
||||||
|
if (++i == oldCursor) {
|
||||||
|
newCursor = newText.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newCursor < 0) {
|
||||||
|
newCursor = newText.size();
|
||||||
|
}
|
||||||
|
if (newText != now) {
|
||||||
|
now = newText;
|
||||||
|
setText(now);
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
}
|
||||||
|
if (newCursor != nowCursor) {
|
||||||
|
nowCursor = newCursor;
|
||||||
|
setCursorPosition(nowCursor);
|
||||||
|
}
|
||||||
|
if (accumulated > _maxValue
|
||||||
|
|| (limit == _maxDigits && oldLength > _maxDigits)) {
|
||||||
|
if (oldCursor > limit) {
|
||||||
|
_putNext.fire('0' + (accumulated % 10));
|
||||||
|
} else {
|
||||||
|
_putNext.fire(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeInput::TimeInput(
|
||||||
|
QWidget *parent,
|
||||||
|
const QString &value,
|
||||||
|
const style::InputField &stField,
|
||||||
|
const style::InputField &stDateField,
|
||||||
|
const style::FlatLabel &stSeparator,
|
||||||
|
const style::margins &stSeparatorPadding)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _stField(stField)
|
||||||
|
, _stDateField(stDateField)
|
||||||
|
, _stSeparator(stSeparator)
|
||||||
|
, _stSeparatorPadding(stSeparatorPadding)
|
||||||
|
, _hour(
|
||||||
|
this,
|
||||||
|
_stField,
|
||||||
|
rpl::never<QString>(),
|
||||||
|
GetHour(value))
|
||||||
|
, _separator1(
|
||||||
|
this,
|
||||||
|
object_ptr<FlatLabel>(
|
||||||
|
this,
|
||||||
|
QString(":"),
|
||||||
|
_stSeparator),
|
||||||
|
_stSeparatorPadding)
|
||||||
|
, _minute(
|
||||||
|
this,
|
||||||
|
_stField,
|
||||||
|
rpl::never<QString>(),
|
||||||
|
GetMinute(value))
|
||||||
|
, _value(valueCurrent()) {
|
||||||
|
const auto focused = [=](const object_ptr<TimePart> &field) {
|
||||||
|
return [this, pointer = MakeWeak(field.data())]{
|
||||||
|
_borderAnimationStart = pointer->borderAnimationStart()
|
||||||
|
+ pointer->x()
|
||||||
|
- _hour->x();
|
||||||
|
setFocused(true);
|
||||||
|
_focuses.fire({});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const auto blurred = [=] {
|
||||||
|
setFocused(false);
|
||||||
|
};
|
||||||
|
const auto changed = [=] {
|
||||||
|
_value = valueCurrent();
|
||||||
|
};
|
||||||
|
connect(_hour, &MaskedInputField::focused, focused(_hour));
|
||||||
|
connect(_minute, &MaskedInputField::focused, focused(_minute));
|
||||||
|
connect(_hour, &MaskedInputField::blurred, blurred);
|
||||||
|
connect(_minute, &MaskedInputField::blurred, blurred);
|
||||||
|
connect(_hour, &MaskedInputField::changed, changed);
|
||||||
|
connect(_minute, &MaskedInputField::changed, changed);
|
||||||
|
_hour->setMaxValue(23);
|
||||||
|
_hour->setWheelStep(1);
|
||||||
|
_hour->putNext() | rpl::start_with_next([=](QChar ch) {
|
||||||
|
putNext(_minute, ch);
|
||||||
|
}, lifetime());
|
||||||
|
_minute->setMaxValue(59);
|
||||||
|
_minute->setWheelStep(10);
|
||||||
|
_minute->erasePrevious() | rpl::start_with_next([=] {
|
||||||
|
erasePrevious(_hour);
|
||||||
|
}, lifetime());
|
||||||
|
_separator1->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
setMouseTracking(true);
|
||||||
|
|
||||||
|
_value.changes(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
setErrorShown(false);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
const auto submitHour = [=] {
|
||||||
|
if (hour()) {
|
||||||
|
_minute->setFocus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto submitMinute = [=] {
|
||||||
|
if (minute()) {
|
||||||
|
if (hour()) {
|
||||||
|
_submitRequests.fire({});
|
||||||
|
} else {
|
||||||
|
_hour->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connect(
|
||||||
|
_hour,
|
||||||
|
&MaskedInputField::submitted,
|
||||||
|
submitHour);
|
||||||
|
connect(
|
||||||
|
_minute,
|
||||||
|
&MaskedInputField::submitted,
|
||||||
|
submitMinute);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::putNext(const object_ptr<TimePart> &field, QChar ch) {
|
||||||
|
field->setCursorPosition(0);
|
||||||
|
if (ch.unicode()) {
|
||||||
|
field->setText(ch + field->getLastText());
|
||||||
|
field->setCursorPosition(1);
|
||||||
|
}
|
||||||
|
field->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::erasePrevious(const object_ptr<TimePart> &field) {
|
||||||
|
const auto text = field->getLastText();
|
||||||
|
if (!text.isEmpty()) {
|
||||||
|
field->setCursorPosition(text.size() - 1);
|
||||||
|
field->setText(text.mid(0, text.size() - 1));
|
||||||
|
}
|
||||||
|
field->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeInput::setFocusFast() {
|
||||||
|
if (hour()) {
|
||||||
|
_minute->setFocusFast();
|
||||||
|
} else {
|
||||||
|
_hour->setFocusFast();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeInput::hour() const {
|
||||||
|
return Number(_hour);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeInput::minute() const {
|
||||||
|
return Number(_minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TimeInput::valueCurrent() const {
|
||||||
|
const auto result = QString("%1:%2"
|
||||||
|
).arg(hour()
|
||||||
|
).arg(minute(), 2, 10, QChar('0'));
|
||||||
|
return ValidateTime(result).isValid() ? result : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> TimeInput::value() const {
|
||||||
|
return _value.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> TimeInput::submitRequests() const {
|
||||||
|
return _submitRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> TimeInput::focuses() const {
|
||||||
|
return _focuses.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::paintEvent(QPaintEvent *e) {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
const auto &_st = _stDateField;
|
||||||
|
const auto height = _st.heightMin;
|
||||||
|
if (_st.border) {
|
||||||
|
p.fillRect(0, height - _st.border, width(), _st.border, _st.borderFg);
|
||||||
|
}
|
||||||
|
auto errorDegree = _a_error.value(_error ? 1. : 0.);
|
||||||
|
auto borderShownDegree = _a_borderShown.value(1.);
|
||||||
|
auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
|
||||||
|
if (_st.borderActive && (borderOpacity > 0.)) {
|
||||||
|
auto borderStart = std::clamp(_borderAnimationStart, 0, width());
|
||||||
|
auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
|
||||||
|
auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree);
|
||||||
|
if (borderTo > borderFrom) {
|
||||||
|
auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree);
|
||||||
|
p.setOpacity(borderOpacity);
|
||||||
|
p.fillRect(borderFrom, height - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg);
|
||||||
|
p.setOpacity(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Widget>
|
||||||
|
bool TimeInput::insideSeparator(QPoint position, const Widget &widget) const {
|
||||||
|
const auto x = position.x();
|
||||||
|
const auto y = position.y();
|
||||||
|
return (x >= widget->x() && x < widget->x() + widget->width())
|
||||||
|
&& (y >= _hour->y() && y < _hour->y() + _hour->height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
const auto cursor = insideSeparator(e->pos(), _separator1)
|
||||||
|
? style::cur_text
|
||||||
|
: style::cur_default;
|
||||||
|
if (_cursor != cursor) {
|
||||||
|
_cursor = cursor;
|
||||||
|
setCursor(_cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::mousePressEvent(QMouseEvent *e) {
|
||||||
|
const auto x = e->pos().x();
|
||||||
|
const auto focus1 = [&] {
|
||||||
|
if (_hour->getLastText().size() > 1) {
|
||||||
|
_minute->setFocus();
|
||||||
|
} else {
|
||||||
|
_hour->setFocus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (insideSeparator(e->pos(), _separator1)) {
|
||||||
|
focus1();
|
||||||
|
_borderAnimationStart = x - _hour->x();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeInput::resizeGetHeight(int width) {
|
||||||
|
const auto &_st = _stField;
|
||||||
|
const auto &font = _st.placeholderFont;
|
||||||
|
const auto addToWidth = _stSeparatorPadding.left();
|
||||||
|
const auto hourWidth = _st.textMargins.left()
|
||||||
|
+ _st.placeholderMargins.left()
|
||||||
|
+ font->width(QString("23"))
|
||||||
|
+ _st.placeholderMargins.right()
|
||||||
|
+ _st.textMargins.right()
|
||||||
|
+ addToWidth;
|
||||||
|
const auto minuteWidth = _st.textMargins.left()
|
||||||
|
+ _st.placeholderMargins.left()
|
||||||
|
+ font->width(QString("59"))
|
||||||
|
+ _st.placeholderMargins.right()
|
||||||
|
+ _st.textMargins.right()
|
||||||
|
+ addToWidth;
|
||||||
|
const auto full = hourWidth
|
||||||
|
- addToWidth
|
||||||
|
+ _separator1->width()
|
||||||
|
+ minuteWidth
|
||||||
|
- addToWidth;
|
||||||
|
auto left = (width - full) / 2;
|
||||||
|
auto top = 0;
|
||||||
|
_hour->setGeometry(left, top, hourWidth, _hour->height());
|
||||||
|
left += hourWidth - addToWidth;
|
||||||
|
_separator1->resizeToNaturalWidth(width);
|
||||||
|
_separator1->move(left, top);
|
||||||
|
left += _separator1->width();
|
||||||
|
_minute->setGeometry(left, top, minuteWidth, _minute->height());
|
||||||
|
return _stDateField.heightMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::showError() {
|
||||||
|
setErrorShown(true);
|
||||||
|
if (!_focused) {
|
||||||
|
setInnerFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::setInnerFocus() {
|
||||||
|
if (hour()) {
|
||||||
|
_minute->setFocus();
|
||||||
|
} else {
|
||||||
|
_hour->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::setErrorShown(bool error) {
|
||||||
|
if (_error != error) {
|
||||||
|
_error = error;
|
||||||
|
_a_error.start(
|
||||||
|
[=] { update(); },
|
||||||
|
_error ? 0. : 1.,
|
||||||
|
_error ? 1. : 0.,
|
||||||
|
_stDateField.duration);
|
||||||
|
startBorderAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::setFocused(bool focused) {
|
||||||
|
if (_focused != focused) {
|
||||||
|
_focused = focused;
|
||||||
|
_a_focused.start(
|
||||||
|
[=] { update(); },
|
||||||
|
_focused ? 0. : 1.,
|
||||||
|
_focused ? 1. : 0.,
|
||||||
|
_stDateField.duration);
|
||||||
|
startBorderAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::startBorderAnimation() {
|
||||||
|
auto borderVisible = (_error || _focused);
|
||||||
|
if (_borderVisible != borderVisible) {
|
||||||
|
_borderVisible = borderVisible;
|
||||||
|
const auto duration = _stDateField.duration;
|
||||||
|
if (_borderVisible) {
|
||||||
|
if (_a_borderOpacity.animating()) {
|
||||||
|
_a_borderOpacity.start([=] { update(); }, 0., 1., duration);
|
||||||
|
} else {
|
||||||
|
_a_borderShown.start([=] { update(); }, 0., 1., duration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_a_borderOpacity.start([=] { update(); }, 1., 0., duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
80
ui/widgets/time_input.h
Normal file
80
ui/widgets/time_input.h
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// 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/rp_widget.h"
|
||||||
|
|
||||||
|
#include "ui/effects/animations.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class TimePart;
|
||||||
|
|
||||||
|
class TimeInput final : public RpWidget {
|
||||||
|
public:
|
||||||
|
TimeInput(
|
||||||
|
QWidget *parent,
|
||||||
|
const QString &value,
|
||||||
|
const style::InputField &stField,
|
||||||
|
const style::InputField &stDateField,
|
||||||
|
const style::FlatLabel &stSeparator,
|
||||||
|
const style::margins &stSeparatorPadding);
|
||||||
|
|
||||||
|
bool setFocusFast();
|
||||||
|
rpl::producer<QString> value() const;
|
||||||
|
rpl::producer<> submitRequests() const;
|
||||||
|
rpl::producer<> focuses() const;
|
||||||
|
QString valueCurrent() const;
|
||||||
|
void showError();
|
||||||
|
|
||||||
|
int resizeGetHeight(int width) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setInnerFocus();
|
||||||
|
void putNext(const object_ptr<TimePart> &field, QChar ch);
|
||||||
|
void erasePrevious(const object_ptr<TimePart> &field);
|
||||||
|
void setErrorShown(bool error);
|
||||||
|
void setFocused(bool focused);
|
||||||
|
void startBorderAnimation();
|
||||||
|
template <typename Widget>
|
||||||
|
bool insideSeparator(QPoint position, const Widget &widget) const;
|
||||||
|
|
||||||
|
int hour() const;
|
||||||
|
int minute() const;
|
||||||
|
|
||||||
|
const style::InputField &_stField;
|
||||||
|
const style::InputField &_stDateField;
|
||||||
|
const style::FlatLabel &_stSeparator;
|
||||||
|
const style::margins &_stSeparatorPadding;
|
||||||
|
|
||||||
|
object_ptr<TimePart> _hour;
|
||||||
|
object_ptr<PaddingWrap<FlatLabel>> _separator1;
|
||||||
|
object_ptr<TimePart> _minute;
|
||||||
|
rpl::variable<QString> _value;
|
||||||
|
rpl::event_stream<> _submitRequests;
|
||||||
|
rpl::event_stream<> _focuses;
|
||||||
|
|
||||||
|
style::cursor _cursor = style::cur_default;
|
||||||
|
Animations::Simple _a_borderShown;
|
||||||
|
int _borderAnimationStart = 0;
|
||||||
|
Animations::Simple _a_borderOpacity;
|
||||||
|
bool _borderVisible = false;
|
||||||
|
|
||||||
|
Animations::Simple _a_error;
|
||||||
|
bool _error = false;
|
||||||
|
Animations::Simple _a_focused;
|
||||||
|
bool _focused = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
|
|
@ -1188,6 +1188,12 @@ historySendActionUploadStrokeNumerator: 16px;
|
||||||
historySendActionUploadSizeNumerator: 32px;
|
historySendActionUploadSizeNumerator: 32px;
|
||||||
historySendActionUploadDenominator: 8.;
|
historySendActionUploadDenominator: 8.;
|
||||||
|
|
||||||
|
historySendActionChooseStickerDuration: 2000;
|
||||||
|
historySendActionChooseStickerPosition: point(1px, -10px);
|
||||||
|
historySendActionChooseStickerEyeWidth: 7px;
|
||||||
|
historySendActionChooseStickerEyeHeight: 11px;
|
||||||
|
historySendActionChooseStickerEyeStep: 2px;
|
||||||
|
|
||||||
MediaPlayerButton {
|
MediaPlayerButton {
|
||||||
playPosition: point;
|
playPosition: point;
|
||||||
playOuter: size;
|
playOuter: size;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue