Updated lib_ui to latest patches-track-wip (TD 5.0.6)

This commit is contained in:
Eric Kotato 2024-08-31 01:35:59 +03:00
commit ac2e96cc7d
70 changed files with 1020 additions and 713 deletions

View file

@ -139,8 +139,6 @@ PRIVATE
ui/style/style_core.h ui/style/style_core.h
ui/style/style_core_color.cpp ui/style/style_core_color.cpp
ui/style/style_core_color.h ui/style/style_core_color.h
ui/style/style_core_custom_font.cpp
ui/style/style_core_custom_font.h
ui/style/style_core_direction.cpp ui/style/style_core_direction.cpp
ui/style/style_core_direction.h ui/style/style_core_direction.h
ui/style/style_core_font.cpp ui/style/style_core_font.cpp
@ -263,6 +261,7 @@ PRIVATE
ui/abstract_button.h ui/abstract_button.h
ui/animated_icon.cpp ui/animated_icon.cpp
ui/animated_icon.h ui/animated_icon.h
ui/arc_angles.h
ui/basic_click_handlers.cpp ui/basic_click_handlers.cpp
ui/basic_click_handlers.h ui/basic_click_handlers.h
ui/cached_special_layer_shadow_corners.cpp ui/cached_special_layer_shadow_corners.cpp

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,13 +1,10 @@
<RCC> <RCC>
<qresource prefix="/gui/fonts"> <qresource prefix="/gui/fonts">
<file>DAOpenSansRegular.ttf</file> <file>OpenSans-Regular.ttf</file>
<file>DAOpenSansRegularItalic.ttf</file> <file>OpenSans-Italic.ttf</file>
<file>DAOpenSansSemiboldAsBold.ttf</file> <file>OpenSans-SemiBold.ttf</file>
<file>DAOpenSansSemiboldItalicAsBold.ttf</file> <file>OpenSans-SemiBoldItalic.ttf</file>
<file>DAOpenSansSemibold.ttf</file> <file>Vazirmatn-UI-NL-Regular.ttf</file>
<file>DAOpenSansSemiboldItalic.ttf</file> <file>Vazirmatn-UI-NL-SemiBold.ttf</file>
<file>DAVazirRegular.ttf</file>
<file>DAVazirMediumAsBold.ttf</file>
<file>DAVazirMedium.ttf</file>
</qresource> </qresource>
</RCC> </RCC>

18
ui/arc_angles.h Normal file
View file

@ -0,0 +1,18 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace arc {
constexpr auto kFullLength = 360 * 16;
constexpr auto kHalfLength = (kFullLength / 2);
constexpr auto kQuarterLength = (kFullLength / 4);
constexpr auto kMinLength = (kFullLength / 360);
constexpr auto kAlmostFullLength = (kFullLength - kMinLength);
} // namespace arc

View file

@ -118,6 +118,8 @@ defaultVerticalListSkip: 6px;
shakeShift: 4px; shakeShift: 4px;
shakeDuration: 300; shakeDuration: 300;
universalDuration: 120;
// floating badge colors // floating badge colors
roundedFg: radialFg; roundedFg: radialFg;
roundedBg: radialBg; // closest to #00000066 roundedBg: radialBg; // closest to #00000066

View file

@ -670,6 +670,10 @@ statisticsChartLineIndigo: #7f79f3; // represents indigo color on statistical ch
statisticsChartLinePurple: #9f79e8; // represents purple color on statistical charts statisticsChartLinePurple: #9f79e8; // represents purple color on statistical charts
statisticsChartLineCyan: #40d0ca; // represents cyan color on statistical charts statisticsChartLineCyan: #40d0ca; // represents cyan color on statistical charts
creditsBg1: #f7d05f; // credits icon gradient 1
creditsBg2: #fcecc0; // credits icon gradient 2
creditsStroke: #da8735; // credits icon stroke
// kotatogram // kotatogram
ktgTopBarBg: topBarBg; // Kotatogram: top bar background ktgTopBarBg: topBarBg; // Kotatogram: top bar background
ktgTopBarNameFg: dialogsNameFg; // Kotatogram: top bar name text ktgTopBarNameFg: dialogsNameFg; // Kotatogram: top bar name text

View file

@ -6,8 +6,9 @@
// //
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/ui_utility.h"
#include "base/invoke_queued.h" #include "base/invoke_queued.h"
#include "ui/ui_utility.h"
#include "styles/style_basic.h"
#include <QtCore/QPointer> #include <QtCore/QPointer>
@ -22,7 +23,7 @@ namespace Ui {
namespace Animations { namespace Animations {
namespace { namespace {
constexpr auto kAnimationTick = crl::time(1000) / 120; constexpr auto kAnimationTick = crl::time(1000) / st::universalDuration;
constexpr auto kIgnoreUpdatesTimeout = crl::time(4); constexpr auto kIgnoreUpdatesTimeout = crl::time(4);
Manager *ManagerInstance = nullptr; Manager *ManagerInstance = nullptr;

View file

@ -7,6 +7,7 @@
#include "ui/effects/cross_animation.h" #include "ui/effects/cross_animation.h"
#include "ui/effects/animation_value.h" #include "ui/effects/animation_value.h"
#include "ui/arc_angles.h"
#include "ui/painter.h" #include "ui/painter.h"
#include <QtCore/QtMath> #include <QtCore/QtMath>
@ -17,7 +18,6 @@ namespace {
constexpr auto kPointCount = 12; constexpr auto kPointCount = 12;
constexpr auto kStaticLoadingValue = float64(-666); constexpr auto kStaticLoadingValue = float64(-666);
constexpr auto kFullArcLength = 360 * 16;
// //
@ -148,12 +148,12 @@ void CrossAnimation::paint(
auto pathDeleteSize = kPointCount; auto pathDeleteSize = kPointCount;
const auto staticLoading = (loading == kStaticLoadingValue); const auto staticLoading = (loading == kStaticLoadingValue);
auto loadingArcLength = staticLoading ? kFullArcLength : 0; auto loadingArcLength = staticLoading ? arc::kFullLength : 0;
if (loading > 0.) { if (loading > 0.) {
transformLoadingCross(loading, pathDelete, pathDeleteSize); transformLoadingCross(loading, pathDelete, pathDeleteSize);
auto loadingArc = (loading >= 0.5) ? (loading - 1.) : loading; auto loadingArc = (loading >= 0.5) ? (loading - 1.) : loading;
loadingArcLength = qRound(-loadingArc * 2 * kFullArcLength); loadingArcLength = qRound(-loadingArc * 2 * arc::kFullLength);
} }
if (!staticLoading) { if (!staticLoading) {
@ -184,9 +184,9 @@ void CrossAnimation::paint(
if (staticLoading) { if (staticLoading) {
anim::DrawStaticLoading(p, roundPart, stroke, color); anim::DrawStaticLoading(p, roundPart, stroke, color);
} else { } else {
auto loadingArcStart = kFullArcLength / 8; auto loadingArcStart = arc::kQuarterLength / 2;
if (shown < 1.) { if (shown < 1.) {
loadingArcStart -= qRound(-(shown - 1.) * kFullArcLength / 4.); loadingArcStart -= qRound(-(shown - 1.) * arc::kQuarterLength);
} }
if (loadingArcLength < 0) { if (loadingArcLength < 0) {
loadingArcStart += loadingArcLength; loadingArcStart += loadingArcLength;

View file

@ -6,16 +6,14 @@
// //
#include "ui/effects/radial_animation.h" #include "ui/effects/radial_animation.h"
#include "ui/arc_angles.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"
namespace Ui { namespace Ui {
namespace { namespace {
constexpr auto kFullArcLength = 360 * 16; constexpr auto kFullArcLength = arc::kFullLength;
constexpr auto kQuarterArcLength = (kFullArcLength / 4);
constexpr auto kMinArcLength = (kFullArcLength / 360);
constexpr auto kAlmostFullArcLength = (kFullArcLength - kMinArcLength);
} // namespace } // namespace
@ -23,14 +21,14 @@ const int RadialState::kFull = kFullArcLength;
void RadialAnimation::start(float64 prg) { void RadialAnimation::start(float64 prg) {
_firstStart = _lastStart = _lastTime = crl::now(); _firstStart = _lastStart = _lastTime = crl::now();
const auto iprg = qRound(qMax(prg, 0.0001) * kAlmostFullArcLength); const auto iprg = qRound(qMax(prg, 0.0001) * arc::kAlmostFullLength);
const auto iprgstrict = qRound(prg * kAlmostFullArcLength); const auto iprgstrict = qRound(prg * arc::kAlmostFullLength);
_arcEnd = anim::value(iprgstrict, iprg); _arcEnd = anim::value(iprgstrict, iprg);
_animation.start(); _animation.start();
} }
bool RadialAnimation::update(float64 prg, bool finished, crl::time ms) { bool RadialAnimation::update(float64 prg, bool finished, crl::time ms) {
const auto iprg = qRound(qMax(prg, 0.0001) * kAlmostFullArcLength); const auto iprg = qRound(qMax(prg, 0.0001) * arc::kAlmostFullLength);
const auto result = (iprg != qRound(_arcEnd.to())) const auto result = (iprg != qRound(_arcEnd.to()))
|| (_finished != finished); || (_finished != finished);
if (_finished != finished) { if (_finished != finished) {
@ -101,13 +99,13 @@ void RadialAnimation::draw(
} }
RadialState RadialAnimation::computeState() const { RadialState RadialAnimation::computeState() const {
auto length = kMinArcLength + qRound(_arcEnd.current()); auto length = arc::kMinLength + qRound(_arcEnd.current());
auto from = kQuarterArcLength auto from = arc::kQuarterLength
- length - length
- (anim::Disabled() ? 0 : qRound(_arcStart.current())); - (anim::Disabled() ? 0 : qRound(_arcStart.current()));
if (style::RightToLeft()) { if (style::RightToLeft()) {
from = kQuarterArcLength - (from - kQuarterArcLength) - length; from = arc::kQuarterLength - (from - arc::kQuarterLength) - length;
if (from < 0) from += kFullArcLength; if (from < 0) from += arc::kFullLength;
} }
return { _opacity, from, length }; return { _opacity, from, length };
} }

View file

@ -33,6 +33,7 @@ namespace Ui::GL {
namespace { namespace {
bool ForceDisabled/* = false*/; bool ForceDisabled/* = false*/;
bool LastCheckCrashed/* = false*/;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
ANGLE ResolvedANGLE/* = ANGLE::Auto*/; ANGLE ResolvedANGLE/* = ANGLE::Auto*/;
@ -59,9 +60,14 @@ void CrashCheckStart() {
const char kOptionAllowLinuxNvidiaOpenGL[] = "allow-linux-nvidia-opengl"; const char kOptionAllowLinuxNvidiaOpenGL[] = "allow-linux-nvidia-opengl";
Capabilities CheckCapabilities(QWidget *widget, bool avoidWidgetCreation) { Capabilities CheckCapabilities(QWidget *widget, bool avoidWidgetCreation) {
if (ForceDisabled) { if (!Platform::IsMac()) {
LOG_ONCE(("OpenGL: Force-disabled.")); if (ForceDisabled) {
return {}; LOG_ONCE(("OpenGL: Force-disabled."));
return {};
} else if (LastCheckCrashed) {
LOG_ONCE(("OpenGL: Last-crashed."));
return {};
}
} }
[[maybe_unused]] static const auto BugListInited = [] { [[maybe_unused]] static const auto BugListInited = [] {
@ -243,8 +249,17 @@ Backend ChooseBackendDefault(Capabilities capabilities) {
return use ? Backend::OpenGL : Backend::Raster; return use ? Backend::OpenGL : Backend::Raster;
} }
void DetectLastCheckCrash() {
[[maybe_unused]] static const auto Once = [] {
LastCheckCrashed = !Platform::IsMac()
&& QFile::exists(Integration::Instance().openglCheckFilePath());
return false;
}();
}
bool LastCrashCheckFailed() { bool LastCrashCheckFailed() {
return QFile::exists(Integration::Instance().openglCheckFilePath()); DetectLastCheckCrash();
return LastCheckCrashed;
} }
void CrashCheckFinish() { void CrashCheckFinish() {
@ -252,7 +267,9 @@ void CrashCheckFinish() {
} }
void ForceDisable(bool disable) { void ForceDisable(bool disable) {
ForceDisabled = disable; if (!Platform::IsMac()) {
ForceDisabled = disable;
}
} }
#ifdef Q_OS_WIN #ifdef Q_OS_WIN

View file

@ -31,6 +31,7 @@ struct Capabilities {
void ForceDisable(bool disable); void ForceDisable(bool disable);
void DetectLastCheckCrash();
[[nodiscard]] bool LastCrashCheckFailed(); [[nodiscard]] bool LastCrashCheckFailed();
void CrashCheckFinish(); void CrashCheckFinish();

View file

@ -6,7 +6,6 @@
// //
#include "ui/integration.h" #include "ui/integration.h"
#include "ui/style/style_core_custom_font.h"
#include "ui/gl/gl_detection.h" #include "ui/gl/gl_detection.h"
#include "ui/text/text_entity.h" #include "ui/text/text_entity.h"
#include "ui/text/text_block.h" #include "ui/text/text_block.h"
@ -45,10 +44,6 @@ void Integration::textActionsUpdated() {
void Integration::activationFromTopPanel() { void Integration::activationFromTopPanel() {
} }
style::CustomFontSettings Integration::fontSettings() {
return {};
}
bool Integration::screenIsLocked() { bool Integration::screenIsLocked() {
return false; return false;
} }

View file

@ -23,10 +23,6 @@ class ClickHandler;
struct ClickContext; struct ClickContext;
struct EntityLinkData; struct EntityLinkData;
namespace style {
struct CustomFontSettings;
} // namespace style
namespace Ui { namespace Ui {
namespace Emoji { namespace Emoji {
class One; class One;
@ -53,8 +49,6 @@ public:
virtual void textActionsUpdated(); virtual void textActionsUpdated();
virtual void activationFromTopPanel(); virtual void activationFromTopPanel();
virtual style::CustomFontSettings fontSettings();
[[nodiscard]] virtual bool screenIsLocked(); [[nodiscard]] virtual bool screenIsLocked();
[[nodiscard]] virtual std::shared_ptr<ClickHandler> createLinkHandler( [[nodiscard]] virtual std::shared_ptr<ClickHandler> createLinkHandler(

View file

@ -263,8 +263,21 @@ RectParts BoxContent::customCornersFilling() {
} }
void BoxContent::scrollToY(int top, int bottom) { void BoxContent::scrollToY(int top, int bottom) {
scrollTo({ top, bottom });
}
void BoxContent::scrollTo(ScrollToRequest request, anim::type animated) {
if (_scroll) { if (_scroll) {
_scroll->scrollToY(top, bottom); const auto v = _scroll->computeScrollTo(request.ymin, request.ymax);
const auto now = _scroll->scrollTop();
if (animated == anim::type::instant || v == now) {
_scrollAnimation.stop();
_scroll->scrollToY(v);
} else {
_scrollAnimation.start([=] {
_scroll->scrollToY(_scrollAnimation.value(v));
}, now, v, st::slideWrapDuration, anim::sineInOut);
}
} }
} }

View file

@ -13,6 +13,7 @@
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/layers/layer_widget.h" #include "ui/layers/layer_widget.h"
#include "ui/layers/show.h" #include "ui/layers/show.h"
#include "ui/effects/animations.h"
#include "ui/effects/animation_value.h" #include "ui/effects/animation_value.h"
#include "ui/text/text_entity.h" #include "ui/text/text_entity.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
@ -59,6 +60,7 @@ class ScrollArea;
class FlatLabel; class FlatLabel;
class FadeShadow; class FadeShadow;
class BoxContent; class BoxContent;
struct ScrollToRequest;
class BoxContentDelegate { class BoxContentDelegate {
public: public:
@ -213,6 +215,9 @@ public:
void scrollByDraggingDelta(int delta); void scrollByDraggingDelta(int delta);
void scrollToY(int top, int bottom = -1); void scrollToY(int top, int bottom = -1);
void scrollTo(
ScrollToRequest request,
anim::type animated = anim::type::instant);
void sendScrollViewportEvent(not_null<QEvent*> event); void sendScrollViewportEvent(not_null<QEvent*> event);
[[nodiscard]] rpl::producer<> scrolls() const; [[nodiscard]] rpl::producer<> scrolls() const;
[[nodiscard]] int scrollTop() const; [[nodiscard]] int scrollTop() const;
@ -318,6 +323,7 @@ private:
object_ptr<FadeShadow> _bottomShadow = { nullptr }; object_ptr<FadeShadow> _bottomShadow = { nullptr };
Ui::DraggingScrollManager _draggingScroll; Ui::DraggingScrollManager _draggingScroll;
Ui::Animations::Simple _scrollAnimation;
rpl::event_stream<> _boxClosingStream; rpl::event_stream<> _boxClosingStream;

View file

@ -22,14 +22,16 @@ public:
void drawTextLeft(int x, int y, int outerw, const QString &text, int textWidth = -1) { void drawTextLeft(int x, int y, int outerw, const QString &text, int textWidth = -1) {
QFontMetrics m(fontMetrics()); QFontMetrics m(fontMetrics());
auto ascent = (_ascent == 0 ? m.ascent() : _ascent);
if (style::RightToLeft() && textWidth < 0) textWidth = m.horizontalAdvance(text); if (style::RightToLeft() && textWidth < 0) textWidth = m.horizontalAdvance(text);
const auto result = style::FindAdjustResult(font());
const auto ascent = result ? result->iascent : m.ascent();
drawText(style::RightToLeft() ? (outerw - x - textWidth) : x, y + ascent, text); drawText(style::RightToLeft() ? (outerw - x - textWidth) : x, y + ascent, text);
} }
void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1) { void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1) {
QFontMetrics m(fontMetrics()); QFontMetrics m(fontMetrics());
auto ascent = (_ascent == 0 ? m.ascent() : _ascent);
if (!style::RightToLeft() && textWidth < 0) textWidth = m.horizontalAdvance(text); if (!style::RightToLeft() && textWidth < 0) textWidth = m.horizontalAdvance(text);
const auto result = style::FindAdjustResult(font());
const auto ascent = result ? result->iascent : m.ascent();
drawText(style::RightToLeft() ? x : (outerw - x - textWidth), y + ascent, text); drawText(style::RightToLeft() ? x : (outerw - x - textWidth), y + ascent, text);
} }
void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix, const QRect &from) { void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix, const QRect &from) {
@ -84,10 +86,6 @@ public:
[[nodiscard]] bool inactive() const { [[nodiscard]] bool inactive() const {
return _inactive; return _inactive;
} }
void setFont(const style::font &font) {
_ascent = font->ascent;
QPainter::setFont(font->f);
}
void setTextSpoilerMess(not_null<Ui::Text::SpoilerMess*> mess) { void setTextSpoilerMess(not_null<Ui::Text::SpoilerMess*> mess) {
_spoilerMess = mess; _spoilerMess = mess;
} }
@ -102,7 +100,6 @@ private:
const style::TextPalette *_textPalette = nullptr; const style::TextPalette *_textPalette = nullptr;
Ui::Text::SpoilerMess *_spoilerMess = nullptr; Ui::Text::SpoilerMess *_spoilerMess = nullptr;
bool _inactive = false; bool _inactive = false;
int _ascent = 0;
}; };

View file

@ -107,13 +107,13 @@ void TitleWidget::init(int height) {
const auto apple = (family == u".AppleSystemUIFont"_q); const auto apple = (family == u".AppleSystemUIFont"_q);
setFromFont(style::font( setFromFont(style::font(
apple ? 13 : (height * 15) / 24, apple ? 13 : (height * 15) / 24,
apple ? style::internal::FontBold : 0, apple ? style::FontFlag::Bold : style::FontFlag(),
family)); family));
break; break;
} }
} }
if (!_textStyle) { if (!_textStyle) {
setFromFont(style::font(13, style::internal::FontSemibold, 0)); setFromFont(style::font(13, style::FontFlag::Semibold, 0));
} }
} }

View file

@ -424,11 +424,36 @@ void DefaultWindowHelper::updateRoundingOverlay() {
rect.bottomRight() - QPoint(radiusWithFix, radiusWithFix), rect.bottomRight() - QPoint(radiusWithFix, radiusWithFix),
radiusSize radiusSize
)) || !rect.contains(clip); )) || !rect.contains(clip);
}) | rpl::start_with_next([=] { }) | rpl::start_with_next([=](QRect clip) {
Painter p(_roundingOverlay); Painter p(_roundingOverlay);
const auto rect = window()->rect().marginsRemoved(resizeArea()); const auto skip = resizeArea();
const auto outer = window()->rect();
const auto rect = outer.marginsRemoved(skip);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
_roundRect.paint(p, rect, RectPart::AllCorners); _roundRect.paint(p, rect, RectPart::AllCorners);
p.setCompositionMode(QPainter::CompositionMode_Source);
const auto outside = std::array{
QRect(0, 0, outer.width(), skip.top()),
QRect(0, skip.top(), skip.left(), outer.height() - skip.top()),
QRect(
outer.width() - skip.right(),
skip.top(),
skip.right(),
outer.height() - skip.top()),
QRect(
skip.left(),
outer.height() - skip.bottom(),
outer.width() - skip.left() - skip.right(),
skip.bottom())
};
for (const auto &part : outside) {
if (const auto fill = clip.intersected(part); !fill.isEmpty()) {
p.fillRect(fill, Qt::transparent);
}
}
p.setCompositionMode(QPainter::CompositionMode_SourceOver); p.setCompositionMode(QPainter::CompositionMode_SourceOver);
Shadow::paint(p, rect, window()->width(), Shadow(), _sides, _corners); Shadow::paint(p, rect, window()->width(), Shadow(), _sides, _corners);
}, _roundingOverlay->lifetime()); }, _roundingOverlay->lifetime());

View file

@ -576,6 +576,7 @@ bool WindowHelper::handleNativeEvent(
} }
updateWindowFrameColors(active); updateWindowFrameColors(active);
window()->update(); window()->update();
_title->update();
} return false; } return false;
case WM_NCPAINT: { case WM_NCPAINT: {

View file

@ -15,9 +15,6 @@
#include <QtGui/QColorSpace> #include <QtGui/QColorSpace>
#include <private/qwidget_p.h> #include <private/qwidget_p.h>
// Patching out this code without patching out all other private API usage
// and the Qt::{Core,Gui,Widgets}Private cmake dependency is asking
// for memory corruption
class TWidgetPrivate : public QWidgetPrivate { class TWidgetPrivate : public QWidgetPrivate {
public: public:
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)

View file

@ -29,12 +29,12 @@ auto PaletteVersion = 0;
auto ShortAnimationRunning = rpl::variable<bool>(false); auto ShortAnimationRunning = rpl::variable<bool>(false);
auto RunningShortAnimations = 0; auto RunningShortAnimations = 0;
std::vector<internal::ModuleBase*> &StyleModules() { [[nodiscard]] std::vector<internal::ModuleBase*> &StyleModules() {
static auto result = std::vector<internal::ModuleBase*>(); static auto result = std::vector<internal::ModuleBase*>();
return result; return result;
} }
void startModules(int scale) { void StartModules(int scale) {
for (const auto module : StyleModules()) { for (const auto module : StyleModules()) {
module->start(scale); module->start(scale);
} }
@ -60,14 +60,14 @@ void StopShortAnimation() {
} // namespace internal } // namespace internal
void startManager(int scale) { void StartManager(int scale) {
internal::registerFontFamily("Open Sans"); internal::RegisterFontFamily("Open Sans");
internal::startModules(scale); internal::StartModules(scale);
} }
void stopManager() { void StopManager() {
internal::destroyFonts(); internal::DestroyFonts();
internal::destroyIcons(); internal::DestroyIcons();
} }
rpl::producer<> PaletteChanged() { rpl::producer<> PaletteChanged() {

View file

@ -35,8 +35,8 @@ void StopShortAnimation();
} // namespace internal } // namespace internal
void startManager(int scale); void StartManager(int scale);
void stopManager(); void StopManager();
[[nodiscard]] rpl::producer<> PaletteChanged(); [[nodiscard]] rpl::producer<> PaletteChanged();
[[nodiscard]] int PaletteVersion(); [[nodiscard]] int PaletteVersion();

View file

@ -57,27 +57,28 @@ public:
_data->set(r, g, b, a); _data->set(r, g, b, a);
} }
operator const QBrush &() const { [[nodiscard]] operator const QBrush &() const {
return _data->b; return _data->b;
} }
operator const QPen &() const { [[nodiscard]] operator const QPen &() const {
return _data->p; return _data->p;
} }
ColorData *operator->() const { [[nodiscard]] ColorData *operator->() const {
return _data; return _data;
} }
ColorData *v() const { [[nodiscard]] ColorData *get() const {
return _data; return _data;
} }
explicit operator bool() const { [[nodiscard]] explicit operator bool() const {
return !!_data; return !!_data;
} }
class Proxy; class Proxy;
Proxy operator[](const style::palette &paletteOverride) const; [[nodiscard]] Proxy operator[](
const style::palette &paletteOverride) const;
private: private:
friend class OwnedColor; friend class OwnedColor;
@ -164,12 +165,12 @@ public:
} }
Proxy(const Proxy &other) = default; Proxy(const Proxy &other) = default;
operator const QBrush &() const { return _color; } [[nodiscard]] operator const QBrush &() const { return _color; }
operator const QPen &() const { return _color; } [[nodiscard]] operator const QPen &() const { return _color; }
ColorData *operator->() const { return _color.v(); } [[nodiscard]] ColorData *operator->() const { return _color.get(); }
ColorData *v() const { return _color.v(); } [[nodiscard]] ColorData *get() const { return _color.get(); }
explicit operator bool() const { return _color ? true : false; } [[nodiscard]] explicit operator bool() const { return !!_color; }
Color clone() const { return _color; } [[nodiscard]] Color clone() const { return _color; }
private: private:
Color _color; Color _color;

View file

@ -1,95 +0,0 @@
// 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_custom_font.h"
#include "ui/style/style_core_font.h"
#include "ui/style/style_core_scale.h"
#include "ui/integration.h"
#include <QGuiApplication>
#include <QFontDatabase>
namespace style {
namespace {
using namespace internal;
auto RegularFont = CustomFont();
auto BoldFont = CustomFont();
} // namespace
void SetCustomFonts(const CustomFont &regular, const CustomFont &bold) {
RegularFont = regular;
BoldFont = bold;
}
QFont ResolveFont(const QString &familyOverride, uint32 flags, int size) {
static auto Database = QFontDatabase();
const auto fontSettings = Ui::Integration::Instance().fontSettings();
const auto overrideIsEmpty = GetPossibleEmptyOverride(flags).isEmpty();
const auto bold = ((flags & FontBold) || (flags & FontSemibold));
const auto italic = (flags & FontItalic);
const auto &custom = bold ? BoldFont : RegularFont;
const auto useCustom = !custom.family.isEmpty();
auto result = QFont(QGuiApplication::font().family());
if (!familyOverride.isEmpty()) {
result.setFamily(familyOverride);
if (bold) {
result.setBold(true);
}
} else if (flags & FontMonospace) {
result.setFamily(MonospaceFont());
} else if (useCustom) {
const auto sizes = Database.smoothSizes(custom.family, custom.style);
const auto good = sizes.isEmpty()
? Database.pointSizes(custom.family, custom.style)
: sizes;
const auto point = good.isEmpty() ? size : good.front();
result = Database.font(custom.family, custom.style, point);
} else {
if (!fontSettings.useSystemFont || !overrideIsEmpty) {
result.setFamily(GetFontOverride(flags));
}
if (bold) {
if (fontSettings.semiboldIsBold) {
result.setBold(true);
#ifdef LIB_UI_USE_PACKAGED_FONTS
} else {
result.setWeight(QFont::DemiBold);
#else // LIB_UI_USE_PACKAGED_FONTS
} else if (fontSettings.useSystemFont) {
result.setWeight(QFont::DemiBold);
} else {
result.setBold(true);
#endif // !LIB_UI_USE_PACKAGED_FONTS
}
if (!fontSettings.semiboldIsBold) {
if (flags & FontItalic) {
result.setStyleName("Semibold Italic");
} else {
result.setStyleName("Semibold");
}
}
}
}
if (italic) {
result.setItalic(true);
}
result.setUnderline(flags & FontUnderline);
result.setStrikeOut(flags & FontStrikeOut);
result.setPixelSize(size + ConvertScale(fontSettings.fontSize));
return result;
}
} // namespace style

View file

@ -1,55 +0,0 @@
// 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 CustomFont {
QString family;
QString style;
};
struct CustomFontSettings {
QString mainFont;
QString semiboldFont;
QString monospaceFont;
int fontSize = 0;
bool semiboldIsBold = false;
bool useSystemFont = false;
bool useOriginalMetrics = false;
};
inline bool operator==(const CustomFont &a, const CustomFont &b) {
return (a.family == b.family) && (a.style == b.style);
}
inline bool operator<(const CustomFont &a, const CustomFont &b) {
return (a.family < b.family)
|| (a.family == b.family && a.style < b.style);
}
inline bool operator!=(const CustomFont &a, const CustomFont &b) {
return !(a == b);
}
inline bool operator>(const CustomFont &a, const CustomFont &b) {
return (b < a);
}
inline bool operator<=(const CustomFont &a, const CustomFont &b) {
return !(b < a);
}
inline bool operator>=(const CustomFont &a, const CustomFont &b) {
return !(a < b);
}
void SetCustomFonts(const CustomFont &regular, const CustomFont &bold);
[[nodiscard]] QFont ResolveFont(const QString &familyOverride, uint32 flags, int size);
} // namespace style

View file

@ -8,19 +8,15 @@
#include "base/algorithm.h" #include "base/algorithm.h"
#include "base/debug_log.h" #include "base/debug_log.h"
#include "base/variant.h"
#include "base/base_file_utilities.h" #include "base/base_file_utilities.h"
#include "ui/style/style_core_custom_font.h"
#include "ui/integration.h" #include "ui/integration.h"
#include "ui/style/style_core_scale.h"
#include <QtCore/QMap> #include <QtCore/QMap>
#include <QtCore/QVector> #include <QtCore/QVector>
#include <QtGui/QFontInfo> #include <QtGui/QFontInfo>
#include <QtGui/QFontDatabase> #include <QtGui/QFontDatabase>
#include <QtWidgets/QApplication>
#if __has_include(<glib.h>)
#include <glib.h>
#endif
void style_InitFontsResource() { void style_InitFontsResource() {
#ifdef Q_OS_MAC // Use resources from the .app bundle on macOS. #ifdef Q_OS_MAC // Use resources from the .app bundle on macOS.
@ -40,80 +36,118 @@ void style_InitFontsResource() {
} }
namespace style { namespace style {
namespace internal {
namespace { namespace {
QMap<QString, int> fontFamilyMap; QString Custom;
QVector<QString> fontFamilies; CustomFontSettings CustomSettings;
QMap<uint32, FontData*> fontsMap;
uint32 fontKey(int size, uint32 flags, int family) { } // namespace
return (((uint32(family) << 12) | uint32(size)) << 6) | flags;
const QString &SystemFontTag() {
static const auto result = u"(system)"_q + QChar(0);
return result;
} }
bool ValidateFont(const QString &familyName, int flags = 0) { void SetCustomFont(const QString &font) {
QFont checkFont(familyName); Custom = font;
checkFont.setBold(flags & style::internal::FontBold); }
checkFont.setItalic(flags & style::internal::FontItalic);
checkFont.setUnderline(flags & style::internal::FontUnderline);
auto realFamily = QFontInfo(checkFont).family();
if (realFamily.trimmed().compare(familyName, Qt::CaseInsensitive)) {
LOG(("Font Error: could not resolve '%1' font, got '%2'.").arg(familyName, realFamily));
return false;
}
auto metrics = QFontMetrics(checkFont); void SetCustomFontSettings(const CustomFontSettings &settings) {
if (!metrics.height()) { CustomSettings = settings;
LOG(("Font Error: got a zero height in '%1'.").arg(familyName)); }
return false;
}
return true; 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 {
#ifndef LIB_UI_USE_PACKAGED_FONTS
const auto FontTypes = std::array{
u"OpenSans-Regular"_q,
u"OpenSans-Italic"_q,
u"OpenSans-SemiBold"_q,
u"OpenSans-SemiBoldItalic"_q,
};
const auto PersianFontTypes = std::array{
u"Vazirmatn-UI-NL-Regular"_q,
u"Vazirmatn-UI-NL-SemiBold"_q,
};
#endif // !LIB_UI_USE_PACKAGED_FONTS
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()));
} }
#ifndef LIB_UI_USE_PACKAGED_FONTS #ifndef LIB_UI_USE_PACKAGED_FONTS
bool LoadCustomFont(const QString &filePath, const QString &familyName, int flags = 0) { bool LoadCustomFont(const QString &filePath) {
auto regularId = QFontDatabase::addApplicationFont(filePath); auto regularId = QFontDatabase::addApplicationFont(filePath);
if (regularId < 0) { if (regularId < 0) {
LOG(("Font Error: could not add '%1'.").arg(filePath)); LOG(("Font Error: could not add '%1'.").arg(filePath));
return false; return false;
} }
const auto found = [&] { for (auto &family : QFontDatabase::applicationFontFamilies(regularId)) {
for (auto &family : QFontDatabase::applicationFontFamilies(regularId)) { LOG(("Font: from '%1' loaded '%2'").arg(filePath, family));
LOG(("Font: from '%1' loaded '%2'").arg(filePath, family));
if (!family.trimmed().compare(familyName, Qt::CaseInsensitive)) {
return true;
}
}
return false;
}();
if (!found) {
LOG(("Font Error: could not locate '%1' font in '%2'.").arg(familyName, filePath));
return false;
} }
return ValidateFont(familyName, flags); return true;
} }
#endif // !LIB_UI_USE_PACKAGED_FONTS #endif // !LIB_UI_USE_PACKAGED_FONTS
[[nodiscard]] QString SystemMonospaceFont() {
const auto type = QFontDatabase::FixedFont;
return QFontDatabase::systemFont(type).family();
}
bool TryFont(const QString &attempt) { bool TryFont(const QString &attempt) {
const auto resolved = QFontInfo(QFont(attempt)).family(); const auto resolved = QFontInfo(QFont(attempt)).family();
return !resolved.trimmed().compare(attempt, Qt::CaseInsensitive); return !resolved.trimmed().compare(attempt, Qt::CaseInsensitive);
} }
[[nodiscard]] QString SystemMonospaceFont() {
const auto type = QFontDatabase::FixedFont;
return QFontDatabase::systemFont(type).family();
}
[[nodiscard]] QString ManualMonospaceFont() { [[nodiscard]] QString ManualMonospaceFont() {
const auto kTryFirst = std::initializer_list<QString>{ const auto kTryFirst = std::initializer_list<QString>{
"Cascadia Mono", u"Cascadia Mono"_q,
"Consolas", u"Consolas"_q,
"Liberation Mono", u"Liberation Mono"_q,
"Menlo", u"Menlo"_q,
"Courier" u"Courier"_q,
}; };
for (const auto &family : kTryFirst) { for (const auto &family : kTryFirst) {
if (TryFont(family)) { if (TryFont(family)) {
@ -123,188 +157,10 @@ bool TryFont(const QString &attempt) {
return QString(); return QString();
} }
QFontMetrics GetFontMetrics(int size) { [[nodiscard]] QString MonospaceFont() {
#ifdef DESKTOP_APP_USE_PACKAGED_FONTS
QFont originalFont("Open Sans");
#else // !DESKTOP_APP_USE_PACKAGED_FONTS
QFont originalFont("DAOpenSansRegular");
#endif // !DESKTOP_APP_USE_PACKAGED_FONTS
originalFont.setPixelSize(size);
return QFontMetrics(originalFont);
}
enum {
FontTypeRegular = 0,
FontTypeRegularItalic,
FontTypeBold,
FontTypeBoldItalic,
FontTypeSemibold,
FontTypeSemiboldItalic,
FontTypesCount,
};
#ifndef LIB_UI_USE_PACKAGED_FONTS
QString FontTypeFiles[FontTypesCount] = {
"DAOpenSansRegular",
"DAOpenSansRegularItalic",
"DAOpenSansSemiboldAsBold",
"DAOpenSansSemiboldItalicAsBold",
"DAOpenSansSemiboldAsBold",
"DAOpenSansSemiboldItalicAsBold",
};
QString FontTypeNames[FontTypesCount] = {
"DAOpenSansRegular",
"DAOpenSansRegularItalic",
"DAOpenSansSemibold",
"DAOpenSansSemiboldItalic",
"DAOpenSansSemibold",
"DAOpenSansSemiboldItalic",
};
QString FontTypePersianFallbackFiles[FontTypesCount] = {
"DAVazirRegular",
"DAVazirRegular",
"DAVazirMediumAsBold",
"DAVazirMediumAsBold",
"DAVazirMediumAsBold",
"DAVazirMediumAsBold",
};
QString FontTypePersianFallback[FontTypesCount] = {
"DAVazirRegular",
"DAVazirRegular",
"DAVazirMedium",
"DAVazirMedium",
"DAVazirMedium",
"DAVazirMedium",
};
int32 FontTypeFlags[FontTypesCount] = {
0,
FontItalic,
FontBold,
FontBold | FontItalic,
FontSemibold,
FontSemibold | FontItalic,
};
#endif // !LIB_UI_USE_PACKAGED_FONTS
bool Started = false;
QString Overrides[FontTypesCount];
} // namespace
void StartFonts() {
if (Started) {
return;
}
Started = true;
style_InitFontsResource();
const auto fontSettings = Ui::Integration::Instance().fontSettings();
#ifndef LIB_UI_USE_PACKAGED_FONTS
if (!fontSettings.useSystemFont) {
[[maybe_unused]] bool areGood[FontTypesCount] = { false };
for (auto i = 0; i != FontTypesCount; ++i) {
const auto file = FontTypeFiles[i];
const auto name = FontTypeNames[i];
const auto flags = FontTypeFlags[i];
areGood[i] = LoadCustomFont(":/gui/fonts/" + file + ".ttf", name, flags);
Overrides[i] = name;
const auto persianFallbackFile = FontTypePersianFallbackFiles[i];
const auto persianFallback = FontTypePersianFallback[i];
LoadCustomFont(":/gui/fonts/" + persianFallbackFile + ".ttf", persianFallback, flags);
#ifdef Q_OS_WIN
// Attempt to workaround a strange font bug with Open Sans Semibold not loading.
// See https://github.com/telegramdesktop/tdesktop/issues/3276 for details.
// Crash happens on "options.maxh / _t->_st->font->height" with "division by zero".
// In that place "_t->_st->font" is "semiboldFont" is "font(13 "Open Sans Semibold").
const auto fallback = "Segoe UI";
if (!areGood[i]) {
if (ValidateFont(fallback, flags)) {
Overrides[i] = fallback;
LOG(("Fonts Info: Using '%1' instead of '%2'.").arg(fallback).arg(name));
}
}
// Disable default fallbacks to Segoe UI, see:
// https://github.com/telegramdesktop/tdesktop/issues/5368
//
//QFont::insertSubstitution(name, fallback);
#endif // Q_OS_WIN
QFont::insertSubstitution(name, persianFallback);
}
#ifdef Q_OS_MAC
auto list = QStringList();
list.append("STIXGeneral");
list.append(".SF NS Text");
list.append("Helvetica Neue");
list.append("Lucida Grande");
for (const auto &name : FontTypeNames) {
QFont::insertSubstitutions(name, list);
}
#endif // Q_OS_MAC
}
#elif __has_include(<glib.h>) // !LIB_UI_USE_PACKAGED_FONTS
g_warning(
"Unable to load patched fonts with Qt workarounds, "
"expect font issues.");
#endif // LIB_UI_USE_PACKAGED_FONTS
if (!fontSettings.mainFont.isEmpty() && ValidateFont(fontSettings.mainFont)) {
Overrides[FontTypeRegular] = fontSettings.mainFont;
Overrides[FontTypeRegularItalic] = fontSettings.mainFont;
}
if (!fontSettings.semiboldFont.isEmpty() && ValidateFont(fontSettings.semiboldFont)) {
Overrides[FontTypeBold] = fontSettings.semiboldFont;
Overrides[FontTypeBoldItalic] = fontSettings.semiboldFont;
Overrides[FontTypeSemibold] = fontSettings.semiboldFont;
Overrides[FontTypeSemiboldItalic] = fontSettings.semiboldFont;
} else if (!fontSettings.mainFont.isEmpty() && ValidateFont(fontSettings.mainFont)) {
Overrides[FontTypeBold] = fontSettings.mainFont;
Overrides[FontTypeBoldItalic] = fontSettings.mainFont;
Overrides[FontTypeSemibold] = fontSettings.mainFont;
Overrides[FontTypeSemiboldItalic] = fontSettings.mainFont;
}
auto appFont = QApplication::font();
appFont.setStyleStrategy(QFont::PreferQuality);
QApplication::setFont(appFont);
}
QString GetPossibleEmptyOverride(int32 flags) {
flags = flags & (FontBold | FontSemibold | FontItalic);
int32 flagsBold = flags & (FontBold | FontItalic);
int32 flagsSemibold = flags & (FontSemibold | FontItalic);
if (flagsSemibold == (FontSemibold | FontItalic)) {
return Overrides[FontTypeSemiboldItalic];
} else if (flagsSemibold == FontSemibold) {
return Overrides[FontTypeSemibold];
} else if (flagsBold == (FontBold | FontItalic)) {
return Overrides[FontTypeBoldItalic];
} else if (flagsBold == FontBold) {
return Overrides[FontTypeBold];
} else if (flags == FontItalic) {
return Overrides[FontTypeRegularItalic];
} else if (flags == 0) {
return Overrides[FontTypeRegular];
}
return QString();
}
QString GetFontOverride(int32 flags) {
const auto result = GetPossibleEmptyOverride(flags);
return result.isEmpty() ? "Open Sans" : result;
}
QString MonospaceFont() {
static const auto family = [&]() -> QString { static const auto family = [&]() -> QString {
const auto fontSettings = Ui::Integration::Instance().fontSettings(); if (TryFont(CustomSettings.monospaceFont)) {
return CustomSettings.monospaceFont;
if (TryFont(fontSettings.monospaceFont)) {
return fontSettings.monospaceFont;
} }
const auto manual = ManualMonospaceFont(); const auto manual = ManualMonospaceFont();
@ -319,86 +175,306 @@ QString MonospaceFont() {
const auto useSystem = manual.isEmpty() const auto useSystem = manual.isEmpty()
|| (metrics.horizontalAdvance(QChar('i')) == metrics.horizontalAdvance(QChar('W'))); || (metrics.horizontalAdvance(QChar('i')) == metrics.horizontalAdvance(QChar('W')));
#endif // Q_OS_WIN #endif // Q_OS_WIN
return (useSystem || fontSettings.useSystemFont) ? system : manual; return useSystem ? system : manual;
}(); }();
return family; return family;
} }
void destroyFonts() { struct Metrics {
for (auto fontData : std::as_const(fontsMap)) { int pixelSize = 0;
delete fontData; 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();
} }
fontsMap.clear();
}
int registerFontFamily(const QString &family) { auto copy = font;
auto result = fontFamilyMap.value(family, -1); copy.setFamily(basic);
if (result < 0) { const auto basicMetrics = QFontMetricsF(copy);
result = fontFamilies.size();
fontFamilyMap.insert(family, result); static const auto Full = u"bdfghijklpqtyBDFGHIJKLPQTY1234567890[]{}()"_q;
fontFamilies.push_back(family);
// 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();
} }
return result;
}
FontData::FontData(int size, uint32 flags, int family, Font *other) const auto adjusted = [&](int size, const QFontMetricsF &metrics) {
: f(ResolveFont(family ? fontFamilies[family] : QString(), flags, size)) const auto full = metrics.tightBoundingRect(Full);
, _m(f) const auto desiredTightHeight = desiredFull.height();
, _size(size) const auto heightAdd = basicMetrics.height() - desiredTightHeight;
, _flags(flags) const auto tightHeight = full.height();
, _family(family) { return Metrics{
if (other) { .pixelSize = size,
memcpy(_modified, other, sizeof(_modified)); .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);
} }
_modified[_flags] = Font(this);
const auto fontSettings = Ui::Integration::Instance().fontSettings(); const auto adjustedByFont = [&](const QFont &font) {
if (fontSettings.useOriginalMetrics && !(_flags & FontMonospace)) { return adjusted(font.pixelSize(), QFontMetricsF(font));
const auto mOrig = GetFontMetrics(size); };
const auto max = std::min(kMaxSizeShift, startSize - 1);
height = int(base::SafeRound(mOrig.height())); if (current < desired) {
ascent = int(base::SafeRound(mOrig.ascent())); for (auto i = 0; i != max; ++i) {
descent = int(base::SafeRound(mOrig.descent())); 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 { } else {
height = int(base::SafeRound(_m.height())); for (auto i = 0; i != max; ++i) {
ascent = int(base::SafeRound(_m.ascent())); const auto shift = i + 1;
descent = int(base::SafeRound(_m.descent())); 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);
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
font.setFeature("ss03", true);
#endif // Qt >= 6.7.0
}
font.setPixelSize(size + ConvertScale(CustomSettings.fontSize));
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 (CustomSettings.semiboldIsBold
|| (!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();
#ifndef LIB_UI_USE_PACKAGED_FONTS
const auto base = u":/gui/fonts/"_q;
const auto name = u"Open Sans"_q;
for (const auto &file : FontTypes) {
LoadCustomFont(base + file + u".ttf"_q);
}
for (const auto &file : PersianFontTypes) {
LoadCustomFont(base + file + u".ttf"_q);
}
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
#endif // !LIB_UI_USE_PACKAGED_FONTS
}
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(' ')); spacew = width(QLatin1Char(' '));
elidew = width("..."); elidew = width(u"..."_q);
} }
Font FontData::bold(bool set) const { Font FontData::bold(bool set) const {
return otherFlagsFont(FontBold, set); return otherFlagsFont(FontFlag::Bold, set);
} }
Font FontData::italic(bool set) const { Font FontData::italic(bool set) const {
return otherFlagsFont(FontItalic, set); return otherFlagsFont(FontFlag::Italic, set);
} }
Font FontData::underline(bool set) const { Font FontData::underline(bool set) const {
return otherFlagsFont(FontUnderline, set); return otherFlagsFont(FontFlag::Underline, set);
} }
Font FontData::strikeout(bool set) const { Font FontData::strikeout(bool set) const {
return otherFlagsFont(FontStrikeOut, set); return otherFlagsFont(FontFlag::StrikeOut, set);
} }
Font FontData::semibold(bool set) const { Font FontData::semibold(bool set) const {
return otherFlagsFont(FontSemibold, set); return otherFlagsFont(FontFlag::Semibold, set);
} }
Font FontData::monospace(bool set) const { Font FontData::monospace(bool set) const {
return otherFlagsFont(FontMonospace, set); return otherFlagsFont(FontFlag::Monospace, set);
} }
int FontData::size() const { int FontData::size() const {
return _size; return _size;
} }
uint32 FontData::flags() const { FontFlags FontData::flags() const {
return _flags; return _flags;
} }
@ -406,45 +482,60 @@ int FontData::family() const {
return _family; return _family;
} }
Font FontData::otherFlagsFont(uint32 flag, bool set) const { Font FontData::otherFlagsFont(FontFlag flag, bool set) const {
int32 newFlags = set ? (_flags | flag) : (_flags & ~flag); const auto newFlags = set ? (_flags | flag) : (_flags & ~flag);
if (!_modified[newFlags].v()) { if (!_modified[newFlags]) {
_modified[newFlags] = Font(_size, newFlags, _family, _modified); _modified[newFlags] = Font(_size, newFlags, _family, &_modified);
} }
return _modified[newFlags]; return _modified[newFlags];
} }
Font::Font(int size, uint32 flags, const QString &family) { Font::Font(int size, FontFlags flags, const QString &family) {
if (fontFamilyMap.isEmpty()) { init(size, flags, RegisterFontFamily(family), 0);
for (uint32 i = 0, s = fontFamilies.size(); i != s; ++i) {
fontFamilyMap.insert(fontFamilies.at(i), i);
}
}
auto i = fontFamilyMap.constFind(family);
if (i == fontFamilyMap.cend()) {
fontFamilies.push_back(family);
i = fontFamilyMap.insert(family, fontFamilies.size() - 1);
}
init(size, flags, i.value(), 0);
} }
Font::Font(int size, uint32 flags, int family) { Font::Font(int size, FontFlags flags, int family) {
init(size, flags, family, 0); init(size, flags, family, 0);
} }
Font::Font(int size, uint32 flags, int family, Font *modified) { Font::Font(int size, FontFlags flags, int family, FontVariants *modified) {
init(size, flags, family, modified); init(size, flags, family, modified);
} }
void Font::init(int size, uint32 flags, int family, Font *modified) { void Font::init(
uint32 key = fontKey(size, flags, family); int size,
auto i = fontsMap.constFind(key); FontFlags flags,
if (i == fontsMap.cend()) { int family,
i = fontsMap.insert(key, new FontData(size, flags, family, modified)); 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);
} }
ptr = i.value(); _data = &i->second->data;
}
OwnedFont::OwnedFont(const QString &custom, FontFlags flags, int size)
: _data(ResolveFont(custom, flags, size), nullptr) {
_font._data = &_data;
} }
} // namespace internal } // 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 } // namespace style

View file

@ -7,6 +7,7 @@
#pragma once #pragma once
#include "base/basic_types.h" #include "base/basic_types.h"
#include "base/flags.h"
#include <QtGui/QFont> #include <QtGui/QFont>
#include <QtGui/QFontMetrics> #include <QtGui/QFontMetrics>
@ -14,59 +15,86 @@
#include <cmath> #include <cmath>
namespace style { namespace style {
struct CustomFontSettings {
QString monospaceFont;
int fontSize = 0;
bool semiboldIsBold = false;
};
[[nodiscard]] const QString &SystemFontTag();
void SetCustomFont(const QString &font);
void SetCustomFontSettings(const CustomFontSettings &settings);
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 { namespace internal {
void StartFonts(); void StartFonts();
[[nodiscard]] QString GetPossibleEmptyOverride(int32 flags = 0);
[[nodiscard]] QString GetFontOverride(int32 flags = 0);
[[nodiscard]] QString MonospaceFont();
void destroyFonts(); void DestroyFonts();
int registerFontFamily(const QString &family); int RegisterFontFamily(const QString &family);
inline constexpr auto kFontVariants = 0x40;
class Font;
using FontVariants = std::array<Font, kFontVariants>;
class FontData; class FontData;
class Font { class Font final {
public: public:
Font(Qt::Initialization = Qt::Uninitialized) { Font(Qt::Initialization = Qt::Uninitialized) {
} }
Font(int size, uint32 flags, const QString &family); Font(int size, FontFlags flags, const QString &family);
Font(int size, uint32 flags, int family); Font(int size, FontFlags flags, int family);
FontData *operator->() const { [[nodiscard]] FontData *operator->() const {
return ptr; return _data;
} }
FontData *v() const { [[nodiscard]] FontData *get() const {
return ptr; return _data;
} }
operator bool() const { [[nodiscard]] operator bool() const {
return !!ptr; return _data != nullptr;
} }
operator const QFont &() const; [[nodiscard]] operator const QFont &() const;
private: private:
FontData *ptr = nullptr;
void init(int size, uint32 flags, int family, Font *modified);
friend void startManager();
Font(FontData *p) : ptr(p) {
}
Font(int size, uint32 flags, int family, Font *modified);
friend class FontData; friend class FontData;
friend class OwnedFont;
}; FontData *_data = nullptr;
enum FontFlags { void init(int size, FontFlags flags, int family, FontVariants *modified);
FontBold = 0x01, friend void StartManager();
FontItalic = 0x02,
FontUnderline = 0x04, explicit Font(FontData *data) : _data(data) {
FontStrikeOut = 0x08, }
FontSemibold = 0x10, Font(int size, FontFlags flags, int family, FontVariants *modified);
FontMonospace = 0x20,
FontDifferentFlags = 0x40,
}; };
class FontData { class FontData {
@ -94,37 +122,74 @@ public:
[[nodiscard]] Font semibold(bool set = true) const; [[nodiscard]] Font semibold(bool set = true) const;
[[nodiscard]] Font monospace(bool set = true) const; [[nodiscard]] Font monospace(bool set = true) const;
int size() const; [[nodiscard]] int size() const;
uint32 flags() const; [[nodiscard]] FontFlags flags() const;
int family() const; [[nodiscard]] int family() const;
QFont f; QFont f;
int32 height, ascent, descent, spacew, elidew; int height = 0;
int ascent = 0;
int descent = 0;
int spacew = 0;
int elidew = 0;
private: private:
mutable Font _modified[FontDifferentFlags]; friend class OwnedFont;
friend struct ResolvedFont;
Font otherFlagsFont(uint32 flag, bool set) const; mutable FontVariants _modified;
FontData(int size, uint32 flags, int family, Font *other);
[[nodiscard]] Font otherFlagsFont(FontFlag flag, bool set) const;
FontData(const FontResolveResult &data, FontVariants *modified);
friend class Font;
QFontMetricsF _m; QFontMetricsF _m;
int _size; int _size = 0;
uint32 _flags; int _family = 0;
int _family; FontFlags _flags = 0;
}; };
inline bool operator==(const Font &a, const Font &b) { inline bool operator==(const Font &a, const Font &b) {
return a.v() == b.v(); return a.get() == b.get();
} }
inline bool operator!=(const Font &a, const Font &b) { inline bool operator!=(const Font &a, const Font &b) {
return a.v() != b.v(); return a.get() != b.get();
} }
inline Font::operator const QFont &() const { inline Font::operator const QFont &() const {
return ptr->f; 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 internal
} // namespace style } // namespace style

View file

@ -393,14 +393,14 @@ Icon Icon::withPalette(const style::palette &palette) const {
return result; return result;
} }
void resetIcons() { void ResetIcons() {
iconPixmaps.clear(); iconPixmaps.clear();
for (const auto data : iconData) { for (const auto data : iconData) {
data->reset(); data->reset();
} }
} }
void destroyIcons() { void DestroyIcons() {
iconData.clear(); iconData.clear();
iconPixmaps.clear(); iconPixmaps.clear();

View file

@ -295,8 +295,8 @@ private:
}; };
void resetIcons(); void ResetIcons();
void destroyIcons(); void DestroyIcons();
} // namespace internal } // namespace internal
} // namespace style } // namespace style

View file

@ -226,7 +226,7 @@ QByteArray save() {
bool load(const QByteArray &cache) { bool load(const QByteArray &cache) {
if (GetMutable().load(cache)) { if (GetMutable().load(cache)) {
style::internal::resetIcons(); style::internal::ResetIcons();
return true; return true;
} }
return false; return false;
@ -242,17 +242,17 @@ palette::SetResult setColor(QLatin1String name, QLatin1String from) {
void apply(const palette &other) { void apply(const palette &other) {
GetMutable() = other; GetMutable() = other;
style::internal::resetIcons(); style::internal::ResetIcons();
} }
void reset() { void reset() {
GetMutable().reset(); GetMutable().reset();
style::internal::resetIcons(); style::internal::ResetIcons();
} }
void reset(const colorizer &with) { void reset(const colorizer &with) {
GetMutable().reset(with); GetMutable().reset(with);
style::internal::resetIcons(); style::internal::ResetIcons();
} }
int indexOfColor(color c) { int indexOfColor(color c) {

View file

@ -27,6 +27,7 @@ using cursor = Qt::CursorShape;
using align = Qt::Alignment; using align = Qt::Alignment;
using margins = QMargins; using margins = QMargins;
using font = internal::Font; using font = internal::Font;
using owned_font = internal::OwnedFont;
using color = internal::Color; using color = internal::Color;
using owned_color = internal::OwnedColor; using owned_color = internal::OwnedColor;
using complex_color = internal::ComplexColor; using complex_color = internal::ComplexColor;

View file

@ -984,6 +984,8 @@ void String::enumerateLines(
return withElided(true); return withElided(true);
} }
lineHeight = 0; lineHeight = 0;
last_rBearing = 0;// b->f_rbearing(); (0 for newline)
last_rPadding = 0;// b->f_rpadding(); (0 for newline)
initNextParagraph(i + 1, index); initNextParagraph(i + 1, index);
longWordLine = true; longWordLine = true;
@ -1810,8 +1812,7 @@ bool IsSpace(QChar ch) {
|| (ch == QChar::LineSeparator) || (ch == QChar::LineSeparator)
|| (ch == QChar::ObjectReplacementCharacter) || (ch == QChar::ObjectReplacementCharacter)
|| (ch == QChar::CarriageReturn) || (ch == QChar::CarriageReturn)
|| (ch == QChar::Tabulation) || (ch == QChar::Tabulation);
|| (ch == QChar(8203)/*Zero width space.*/);
} }
bool IsDiacritic(QChar ch) { // diacritic and variation selectors bool IsDiacritic(QChar ch) { // diacritic and variation selectors
@ -1821,6 +1822,9 @@ bool IsDiacritic(QChar ch) { // diacritic and variation selectors
} }
bool IsReplacedBySpace(QChar ch) { bool IsReplacedBySpace(QChar ch) {
// Those symbols are replaced by space on the Telegram server,
// so we replace them as well, for sent / received consistency.
//
// \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237 // \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237
// QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad"); // QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad");
// \xcc[\xb3\xbf\x8a] // 819, 831, 778 // \xcc[\xb3\xbf\x8a] // 819, 831, 778
@ -1836,7 +1840,9 @@ bool IsReplacedBySpace(QChar ch) {
} }
bool IsTrimmed(QChar ch) { bool IsTrimmed(QChar ch) {
return (IsSpace(ch) || IsBad(ch)); return IsSpace(ch)
|| IsBad(ch)
|| (ch == QChar(8203)); // zero width space
} }
} // namespace Ui::Text } // namespace Ui::Text

View file

@ -373,28 +373,31 @@ bool BlockParser::isSpaceBreak(
style::font WithFlags( style::font WithFlags(
const style::font &font, const style::font &font,
TextBlockFlags flags, TextBlockFlags flags,
uint32 fontFlags) { style::FontFlags fontFlags) {
using namespace style::internal; using namespace style::internal;
using Flag = style::FontFlag;
if (!flags && !fontFlags) { if (!flags && !fontFlags) {
return font; return font;
} else if (IsMono(flags) || (fontFlags & FontMonospace)) { } else if (IsMono(flags) || (fontFlags & Flag::Monospace)) {
return font->monospace(); return font->monospace();
} }
auto result = font; auto result = font;
if ((flags & TextBlockFlag::Bold) || (fontFlags & FontBold)) { if ((flags & TextBlockFlag::Bold) || (fontFlags & Flag::Bold)) {
result = result->bold(); result = result->bold();
} else if ((flags & TextBlockFlag::Semibold) } else if ((flags & TextBlockFlag::Semibold)
|| (fontFlags & FontSemibold)) { || (fontFlags & Flag::Semibold)) {
result = result->semibold(); result = result->semibold();
} }
if ((flags & TextBlockFlag::Italic) || (fontFlags & FontItalic)) { if ((flags & TextBlockFlag::Italic) || (fontFlags & Flag::Italic)) {
result = result->italic(); result = result->italic();
} }
if ((flags & TextBlockFlag::Underline) || (fontFlags & FontUnderline)) { if ((flags & TextBlockFlag::Underline)
|| (fontFlags & Flag::Underline)) {
result = result->underline(); result = result->underline();
} }
if ((flags & TextBlockFlag::StrikeOut) || (fontFlags & FontStrikeOut)) { if ((flags & TextBlockFlag::StrikeOut)
|| (fontFlags & Flag::StrikeOut)) {
result = result->strikeout(); result = result->strikeout();
} }
if (flags & TextBlockFlag::Tilde) { // Tilde fix in OpenSans. if (flags & TextBlockFlag::Tilde) { // Tilde fix in OpenSans.

View file

@ -47,7 +47,7 @@ using TextBlockFlags = base::flags<TextBlockFlag>;
[[nodiscard]] style::font WithFlags( [[nodiscard]] style::font WithFlags(
const style::font &font, const style::font &font,
TextBlockFlags flags, TextBlockFlags flags,
uint32 fontFlags = 0); style::FontFlags fontFlags = 0);
[[nodiscard]] Qt::LayoutDirection UnpackParagraphDirection( [[nodiscard]] Qt::LayoutDirection UnpackParagraphDirection(
bool ltr, bool ltr,

View file

@ -78,7 +78,17 @@ constexpr auto kMaxDiacAfterSymbol = 2;
const auto &font = st.font; const auto &font = st.font;
return (font->size() * style::DevicePixelRatio() == 13) return (font->size() * style::DevicePixelRatio() == 13)
&& (font->flags() == 0) && (font->flags() == 0)
&& (font->f.family() == qstr("DAOpenSansRegular")); && (font->f.family() == qstr("Open Sans"));
}
[[nodiscard]] bool IsDiacriticAllowedAfter(QChar ch) {
const auto code = ch.unicode();
const auto category = ch.category();
return (code > 32)
&& (category != QChar::Other_Control)
&& (category != QChar::Other_Format)
&& (category != QChar::Other_PrivateUse)
&& (category != QChar::Other_NotAssigned);
} }
} // namespace } // namespace
@ -567,7 +577,7 @@ void Parser::parseCurrentChar() {
createBlock(-_emojiLookback); createBlock(-_emojiLookback);
} }
_t->_text.push_back(_ch); _t->_text.push_back(_ch);
_allowDiacritic = true; _allowDiacritic = IsDiacriticAllowedAfter(_ch);
} }
if (!isDiacritic) { if (!isDiacritic) {
_diacritics = 0; _diacritics = 0;

View file

@ -156,6 +156,12 @@ bool Distinct(FixedRange a, FixedRange b) {
Renderer::Renderer(const Ui::Text::String &t) Renderer::Renderer(const Ui::Text::String &t)
: _t(&t) : _t(&t)
, _spoiler(_t->_extended ? _t->_extended->spoiler.get() : nullptr) { , _spoiler(_t->_extended ? _t->_extended->spoiler.get() : nullptr) {
[[maybe_unused]] static const auto Once = [] {
// Running with a Qt version other than the one built upon is
// dangerous due to excessive private API usage in this class
Assert(QLatin1String(qVersion()) == QLatin1String(QT_VERSION_STR));
return true;
}();
} }
Renderer::~Renderer() { Renderer::~Renderer() {

View file

@ -56,16 +56,23 @@ inline base::unique_qptr<Widget> CreateObject(Args &&...args) {
template <typename Value, typename Parent, typename ...Args> template <typename Value, typename Parent, typename ...Args>
inline Value *CreateChild( inline Value *CreateChild(
Parent *parent, Parent parent,
Args &&...args) { Args &&...args) {
Expects(parent != nullptr); if constexpr (std::is_pointer_v<Parent>) {
Expects(parent != nullptr);
if constexpr (std::is_base_of_v<QObject, Value>) { if constexpr (std::is_base_of_v<QObject, Value>) {
return new Value(parent, std::forward<Args>(args)...); return new Value(parent, std::forward<Args>(args)...);
} else {
return CreateChild<details::AttachmentOwner<Value>>(
parent,
std::forward<Args>(args)...)->value();
}
} else if constexpr (requires(const Parent & t) { t.get(); }) {
return new Value(parent.get(), std::forward<Args>(args)...);
} else { } else {
return CreateChild<details::AttachmentOwner<Value>>( static_assert(requires(const Parent &t) { t.data(); });
parent, return new Value(parent.data(), std::forward<Args>(args)...);
std::forward<Args>(args)...)->value();
} }
} }

View file

@ -25,9 +25,7 @@ LinkButton::LinkButton(
, _st(st) , _st(st)
, _text(text) , _text(text)
, _textWidth(st.font->width(_text)) { , _textWidth(st.font->width(_text)) {
resize( resizeToText();
naturalWidth(),
_st.padding.top() + _st.font->height + _st.padding.bottom());
setCursor(style::cur_pointer); setCursor(style::cur_pointer);
} }
@ -59,10 +57,16 @@ void LinkButton::paintEvent(QPaintEvent *e) {
void LinkButton::setText(const QString &text) { void LinkButton::setText(const QString &text) {
_text = text; _text = text;
_textWidth = _st.font->width(_text); _textWidth = _st.font->width(_text);
resize(naturalWidth(), _st.font->height); resizeToText();
update(); update();
} }
void LinkButton::resizeToText() {
resize(
naturalWidth(),
_st.padding.top() + _st.font->height + _st.padding.bottom());
}
void LinkButton::setColorOverride(std::optional<QColor> textFg) { void LinkButton::setColorOverride(std::optional<QColor> textFg) {
_textFgOverride = textFg; _textFgOverride = textFg;
update(); update();

View file

@ -37,6 +37,8 @@ protected:
void onStateChanged(State was, StateChangeSource source) override; void onStateChanged(State was, StateChangeSource source) override;
private: private:
void resizeToText();
const style::LinkButton &_st; const style::LinkButton &_st;
QString _text; QString _text;
int _textWidth = 0; int _textWidth = 0;

View file

@ -381,6 +381,14 @@ ElasticScroll::ElasticScroll(
}, _bar->lifetime()); }, _bar->lifetime());
} }
ElasticScroll::~ElasticScroll() {
// Destroy the _bar cleanly (keeping _bar == nullptr) to avoid a crash:
//
// _bar destructor may send LeaveEvent to ElasticScroll,
// which will try to toggle(false) the _bar in leaveEventHook.
base::take(_bar);
}
void ElasticScroll::setHandleTouch(bool handle) { void ElasticScroll::setHandleTouch(bool handle) {
if (_touchDisabled != handle) { if (_touchDisabled != handle) {
return; return;
@ -1071,13 +1079,15 @@ void ElasticScroll::keyPressEvent(QKeyEvent *e) {
} }
void ElasticScroll::enterEventHook(QEnterEvent *e) { void ElasticScroll::enterEventHook(QEnterEvent *e) {
if (!_disabled) { if (_bar && !_disabled) {
_bar->toggle(true); _bar->toggle(true);
} }
} }
void ElasticScroll::leaveEventHook(QEvent *e) { void ElasticScroll::leaveEventHook(QEvent *e) {
_bar->toggle(false); if (_bar) {
_bar->toggle(false);
}
} }
void ElasticScroll::scrollTo(ScrollToRequest request) { void ElasticScroll::scrollTo(ScrollToRequest request) {

View file

@ -113,6 +113,7 @@ public:
QWidget *parent, QWidget *parent,
const style::ScrollArea &st = st::defaultScrollArea, const style::ScrollArea &st = st::defaultScrollArea,
Qt::Orientation orientation = Qt::Vertical); Qt::Orientation orientation = Qt::Vertical);
~ElasticScroll();
void setHandleTouch(bool handle); void setHandleTouch(bool handle);
bool viewportEvent(QEvent *e); bool viewportEvent(QEvent *e);

View file

@ -6,12 +6,12 @@
// //
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/widgets/popup_menu.h"
#include "ui/text/text.h" #include "ui/text/text.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/emoji_config.h" #include "ui/emoji_config.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/style/style_core_custom_font.h"
#include "base/invoke_queued.h" #include "base/invoke_queued.h"
#include "base/random.h" #include "base/random.h"
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
@ -138,7 +138,7 @@ bool IsNewline(QChar ch) {
} }
[[nodiscard]] bool IsCustomEmojiLink(QStringView link) { [[nodiscard]] bool IsCustomEmojiLink(QStringView link) {
return link.startsWith(Ui::InputField::kCustomEmojiTagStart); return link.startsWith(InputField::kCustomEmojiTagStart);
} }
[[nodiscard]] QString MakeUniqueCustomEmojiLink(QStringView link) { [[nodiscard]] QString MakeUniqueCustomEmojiLink(QStringView link) {
@ -157,7 +157,7 @@ bool IsNewline(QChar ch) {
} }
[[nodiscard]] uint64 CustomEmojiIdFromLink(QStringView link) { [[nodiscard]] uint64 CustomEmojiIdFromLink(QStringView link) {
const auto skip = Ui::InputField::kCustomEmojiTagStart.size(); const auto skip = InputField::kCustomEmojiTagStart.size();
const auto index = link.indexOf('?', skip + 1); const auto index = link.indexOf('?', skip + 1);
return base::StringViewMid( return base::StringViewMid(
link, link,
@ -702,16 +702,16 @@ QString AccumulateText(Iterator begin, Iterator end) {
return result; return result;
} }
QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, const QFont &font) { QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, int lineHeight) {
const auto factor = style::DevicePixelRatio(); const auto factor = style::DevicePixelRatio();
const auto size = Emoji::GetSizeNormal(); const auto size = Emoji::GetSizeNormal();
const auto width = size + st::emojiPadding * factor * 2; const auto width = size + st::emojiPadding * factor * 2;
const auto height = std::max(QFontMetrics(font).height() * factor, size); const auto height = std::max(lineHeight * factor, size);
auto result = QTextImageFormat(); auto result = QTextImageFormat();
result.setWidth(width / factor); result.setWidth(width / factor);
result.setHeight(height / factor); result.setHeight(height / factor);
result.setName(emoji->toUrl()); result.setName(emoji->toUrl());
result.setVerticalAlignment(QTextCharFormat::AlignBottom); result.setVerticalAlignment(QTextCharFormat::AlignTop);
return result; return result;
} }
@ -751,6 +751,7 @@ QTextCharFormat PrepareTagFormat(
result.setProperty( result.setProperty(
kCustomEmojiId, kCustomEmojiId,
CustomEmojiIdFromLink(replaceWith)); CustomEmojiIdFromLink(replaceWith));
result.setVerticalAlignment(QTextCharFormat::AlignTop);
} else if (IsValidMarkdownLink(tag)) { } else if (IsValidMarkdownLink(tag)) {
color = st::defaultTextPalette.linkFg; color = st::defaultTextPalette.linkFg;
} else if (tag == kTagBold) { } else if (tag == kTagBold) {
@ -923,6 +924,7 @@ struct FormattingAction {
RemoveTag, RemoveTag,
RemoveNewline, RemoveNewline,
ClearInstantReplace, ClearInstantReplace,
FixLineHeight,
}; };
Type type = Type::Invalid; Type type = Type::Invalid;
@ -1026,7 +1028,12 @@ private:
void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji) { void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji) {
const auto currentFormat = cursor.charFormat(); const auto currentFormat = cursor.charFormat();
auto format = PrepareEmojiFormat(emoji, currentFormat.font()); const auto blockFormat = cursor.blockFormat();
const auto type = blockFormat.lineHeightType();
const auto height = (type == QTextBlockFormat::FixedHeight)
? blockFormat.lineHeight()
: QFontMetrics(cursor.charFormat().font()).height();
auto format = PrepareEmojiFormat(emoji, height);
ApplyTagFormat(format, currentFormat); ApplyTagFormat(format, currentFormat);
cursor.insertText(kObjectReplacement, format); cursor.insertText(kObjectReplacement, format);
} }
@ -1043,7 +1050,7 @@ void InsertCustomEmojiAtCursor(
format.setProperty(kCustomEmojiText, text); format.setProperty(kCustomEmojiText, text);
format.setProperty(kCustomEmojiLink, unique); format.setProperty(kCustomEmojiLink, unique);
format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link)); format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
format.setVerticalAlignment(QTextCharFormat::AlignBottom); format.setVerticalAlignment(QTextCharFormat::AlignTop);
format.setFont(field->st().font); format.setFont(field->st().font);
format.setForeground(field->st().textFg); format.setForeground(field->st().textFg);
format.setBackground(QBrush()); format.setBackground(QBrush());
@ -1122,8 +1129,12 @@ const InstantReplaces &InstantReplaces::Custom() {
return result; return result;
} }
CustomEmojiObject::CustomEmojiObject(Factory factory, Fn<bool()> paused) CustomEmojiObject::CustomEmojiObject(
: _factory(std::move(factory)) const style::font &font,
Factory factory,
Fn<bool()> paused)
: _font(font)
, _factory(std::move(factory))
, _paused(std::move(paused)) , _paused(std::move(paused))
, _now(crl::now()) { , _now(crl::now()) {
} }
@ -1143,10 +1154,9 @@ QSizeF CustomEmojiObject::intrinsicSize(
const QTextFormat &format) { const QTextFormat &format) {
const auto size = st::emojiSize * 1.; const auto size = st::emojiSize * 1.;
const auto width = size + st::emojiPadding * 2.; const auto width = size + st::emojiPadding * 2.;
const auto font = format.toCharFormat().font(); const auto height = std::max(_font->height * 1., size);
const auto height = std::min(QFontMetrics(font).height() * 1., size);
if (!_skip) { if (!_skip) {
const auto emoji = Ui::Text::AdjustCustomEmojiSize(st::emojiSize); const auto emoji = Text::AdjustCustomEmojiSize(st::emojiSize);
_skip = (st::emojiSize - emoji) / 2; _skip = (st::emojiSize - emoji) / 2;
} }
return { width, height }; return { width, height };
@ -1236,6 +1246,30 @@ InputField::InputField(
_inner->setAcceptRichText(false); _inner->setAcceptRichText(false);
resize(_st.width, _minHeight); resize(_st.width, _minHeight);
{ // In case of default fonts all those should be zero.
const auto metrics = QFontMetricsF(_st.font->f);
const auto realAscent = int(base::SafeRound(metrics.ascent()));
const auto ascentAdd = _st.font->ascent - realAscent;
//const auto realHeight = int(base::SafeRound(metrics.height()));
//const auto heightAdd = _st.font->height - realHeight - ascentAdd;
//_customFontMargins = QMargins(0, ascentAdd, 0, heightAdd);
_customFontMargins = QMargins(0, ascentAdd, 0, -ascentAdd);
// We move _inner down by ascentAdd for the first line to look
// at the same vertical position as in the default font.
//
// But we don't want to get vertical scroll in case the field
// fits pixel-perfect with the default font, so we allow the
// bottom margin to be the same shift, but negative.
if (_mode != Mode::SingleLine) {
const auto metrics = QFontMetricsF(_st.font->f);
const auto leading = qMax(metrics.leading(), qreal(0.0));
const auto adjustment = (metrics.ascent() + leading)
- ((_st.font->height * 4) / 5);
_placeholderCustomFontSkip = int(base::SafeRound(-adjustment));
}
}
if (_st.textBg->c.alphaF() >= 1. if (_st.textBg->c.alphaF() >= 1.
&& !_st.borderRadius) { && !_st.borderRadius) {
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
@ -1256,11 +1290,21 @@ InputField::InputField(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
updatePalette(); updatePalette();
}, lifetime()); }, lifetime());
{
auto cursor = _inner->textCursor();
_defaultCharFormat = _inner->textCursor().charFormat(); _defaultCharFormat = cursor.charFormat();
updatePalette(); updatePalette();
_inner->textCursor().setCharFormat(_defaultCharFormat); cursor.setCharFormat(_defaultCharFormat);
_defaultBlockFormat = cursor.blockFormat();
_defaultBlockFormat.setLineHeight(
_st.font->height,
QTextBlockFormat::FixedHeight);
cursor.setBlockFormat(_defaultBlockFormat);
_inner->setTextCursor(cursor);
}
_inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@ -1268,14 +1312,6 @@ InputField::InputField(
_inner->viewport()->setAutoFillBackground(false); _inner->viewport()->setAutoFillBackground(false);
_inner->setContentsMargins(0, 0, 0, 0); _inner->setContentsMargins(0, 0, 0, 0);
const auto fontSettings = Integration::Instance().fontSettings();
if (fontSettings.useOriginalMetrics) {
const auto metrics = QFontMetricsF(_st.font->f);
const auto heightDelta = (_st.font->height - metrics.height());
const auto topMargin = (_st.font->ascent - metrics.ascent())
+ (heightDelta ? heightDelta / 2 : 0);
_inner->setViewportMargins(0, topMargin, 0, 0);
}
_inner->document()->setDocumentMargin(0); _inner->document()->setDocumentMargin(0);
setAttribute(Qt::WA_AcceptTouchEvents); setAttribute(Qt::WA_AcceptTouchEvents);
@ -1310,6 +1346,7 @@ InputField::InputField(
auto cursor = textCursor(); auto cursor = textCursor();
if (!cursor.hasSelection() && !cursor.position()) { if (!cursor.hasSelection() && !cursor.position()) {
cursor.setCharFormat(_defaultCharFormat); cursor.setCharFormat(_defaultCharFormat);
cursor.setBlockFormat(_defaultBlockFormat);
setTextCursor(cursor); setTextCursor(cursor);
} }
}, lifetime()); }, lifetime());
@ -1329,8 +1366,8 @@ InputField::InputField(
setCursor(style::cur_text); setCursor(style::cur_text);
heightAutoupdated(); heightAutoupdated();
if (!_lastTextWithTags.text.isEmpty()) { if (!value.text.isEmpty()) {
setTextWithTags(_lastTextWithTags, HistoryAction::Clear); setTextWithTags(value, HistoryAction::Clear);
} }
startBorderAnimation(); startBorderAnimation();
@ -1476,7 +1513,7 @@ void InputField::setTagMimeProcessor(Fn<QString(QStringView)> processor) {
void InputField::setCustomEmojiFactory( void InputField::setCustomEmojiFactory(
CustomEmojiFactory factory, CustomEmojiFactory factory,
Fn<bool()> paused) { Fn<bool()> paused) {
_customEmojiObject = std::make_unique<CustomEmojiObject>([=]( _customEmojiObject = std::make_unique<CustomEmojiObject>(_st.font, [=](
QStringView data) { QStringView data) {
return factory(data, [=] { customEmojiRepaint(); }); return factory(data, [=] { customEmojiRepaint(); });
}, std::move(paused)); }, std::move(paused));
@ -1781,6 +1818,13 @@ void InputField::paintEvent(QPaintEvent *e) {
const auto focusedDegree = _a_focused.value(_focused ? 1. : 0.); const auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
paintSurrounding(p, r, errorDegree, focusedDegree); paintSurrounding(p, r, errorDegree, focusedDegree);
const auto skip = int(base::SafeRound(_inner->document()->documentMargin()));
const auto margins = _st.textMargins
+ _st.placeholderMargins
+ QMargins(skip, skip + _placeholderCustomFontSkip, skip, 0)
+ _additionalMargins
+ _customFontMargins;
if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) { if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) {
auto placeholderShiftDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.); auto placeholderShiftDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
p.save(); p.save();
@ -1788,7 +1832,7 @@ void InputField::paintEvent(QPaintEvent *e) {
auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree); auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree);
QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins)); QRect r(rect().marginsRemoved(margins));
r.moveTop(r.top() + placeholderTop); r.moveTop(r.top() + placeholderTop);
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width()); if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
@ -1815,29 +1859,16 @@ void InputField::paintEvent(QPaintEvent *e) {
p.setFont(_st.placeholderFont); p.setFont(_st.placeholderFont);
p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree)); p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree));
if (_st.placeholderAlign == style::al_topleft && _placeholderAfterSymbols > 0) { if (_st.placeholderAlign == style::al_topleft && _placeholderAfterSymbols > 0) {
const auto skipWidth = placeholderSkipWidth(); const auto skipWidth = placeholderSkipWidth();
p.drawText( p.drawText(
_st.textMargins.left() + _st.placeholderMargins.left() + skipWidth, margins.left() + skipWidth,
_st.textMargins.top() + _st.placeholderMargins.top() + _st.placeholderFont->ascent, margins.top() + _st.placeholderFont->ascent,
_placeholder); _placeholder);
} else { } else {
auto r = rect().marginsRemoved(_st.textMargins + _st.placeholderMargins); auto r = rect().marginsRemoved(margins);
r.moveLeft(r.left() + placeholderLeft); r.moveLeft(r.left() + placeholderLeft);
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width()); if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
const auto fontSettings = Integration::Instance().fontSettings();
if (fontSettings.useOriginalMetrics) {
const auto metrics = QFontMetricsF(_st.placeholderFont->f);
if (_mode == Mode::SingleLine) {
const auto heightDelta = (_st.placeholderFont->height - metrics.height());
const auto topMargin = (_st.placeholderFont->ascent - metrics.ascent())
+ (heightDelta ? heightDelta / 2 : 0);
r.moveTop(r.top() + topMargin);
} else {
r.moveTop(r.top() + _st.placeholderFont->ascent - metrics.ascent());
}
}
p.drawText(r, _placeholder, _st.placeholderAlign); p.drawText(r, _placeholder, _st.placeholderAlign);
} }
@ -1880,7 +1911,7 @@ void InputField::focusInEvent(QFocusEvent *e) {
? mapFromGlobal(QCursor::pos()).x() ? mapFromGlobal(QCursor::pos()).x()
: (width() / 2); : (width() / 2);
InvokeQueued(this, [=] { InvokeQueued(this, [=] {
if (hasFocus()) { if (QWidget::hasFocus()) {
focusInner(); focusInner();
} }
}); });
@ -2139,7 +2170,7 @@ bool InputField::isRedoAvailable() const {
void InputField::processFormatting(int insertPosition, int insertEnd) { void InputField::processFormatting(int insertPosition, int insertEnd) {
// Tilde formatting. // Tilde formatting.
const auto tildeFormatting = (_st.font->f.pixelSize() * style::DevicePixelRatio() == 13) const auto tildeFormatting = (_st.font->f.pixelSize() * style::DevicePixelRatio() == 13)
&& (_st.font->f.family() == qstr("DAOpenSansRegular")); && (_st.font->f.family() == qstr("Open Sans"));
auto isTildeFragment = false; auto isTildeFragment = false;
auto tildeFixedFont = _st.font->semibold()->f; auto tildeFixedFont = _st.font->semibold()->f;
@ -2170,6 +2201,10 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
if (tillBlock.isValid()) tillBlock = tillBlock.next(); if (tillBlock.isValid()) tillBlock = tillBlock.next();
for (auto block = fromBlock; block != tillBlock; block = block.next()) { for (auto block = fromBlock; block != tillBlock; block = block.next()) {
if (block.blockFormat().lineHeightType() != QTextBlockFormat::FixedHeight) {
action.type = ActionType::FixLineHeight;
break;
}
for (auto fragmentIt = block.begin(); !fragmentIt.atEnd(); ++fragmentIt) { for (auto fragmentIt = block.begin(); !fragmentIt.atEnd(); ++fragmentIt) {
auto fragment = fragmentIt.fragment(); auto fragment = fragmentIt.fragment();
Assert(fragment.isValid()); Assert(fragment.isValid());
@ -2351,6 +2386,11 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
PrepareFormattingOptimization(document); PrepareFormattingOptimization(document);
auto cursor = QTextCursor(document); auto cursor = QTextCursor(document);
if (action.type == ActionType::FixLineHeight) {
cursor.select(QTextCursor::Document);
cursor.setBlockFormat(_defaultBlockFormat);
continue;
}
cursor.setPosition(action.intervalStart); cursor.setPosition(action.intervalStart);
cursor.setPosition(action.intervalEnd, QTextCursor::KeepAnchor); cursor.setPosition(action.intervalEnd, QTextCursor::KeepAnchor);
if (action.type == ActionType::InsertEmoji if (action.type == ActionType::InsertEmoji
@ -2420,7 +2460,6 @@ void InputField::documentContentsChanged(
if (_correcting) { if (_correcting) {
return; return;
} }
// In case of input method events Qt emits // In case of input method events Qt emits
// document content change signals for a whole // document content change signals for a whole
// text block where the even took place. // text block where the even took place.
@ -2466,7 +2505,7 @@ void InputField::documentContentsChanged(
QTextCursor(document).endEditBlock(); QTextCursor(document).endEditBlock();
handleContentsChanged(); handleContentsChanged();
const auto added = charsAdded - _emojiSurrogateAmount; const auto added = charsAdded - _emojiSurrogateAmount;
_documentContentsChanges.fire({position, charsRemoved, added}); _documentContentsChanges.fire({ position, charsRemoved, added });
_emojiSurrogateAmount = 0; _emojiSurrogateAmount = 0;
}); });
@ -2680,6 +2719,7 @@ void InputField::setTextWithTags(
_realCharsAdded = textWithTags.text.size(); _realCharsAdded = textWithTags.text.size();
const auto document = _inner->document(); const auto document = _inner->document();
auto cursor = QTextCursor(document); auto cursor = QTextCursor(document);
cursor.setBlockFormat(_defaultBlockFormat);
if (historyAction == HistoryAction::Clear) { if (historyAction == HistoryAction::Clear) {
document->setUndoRedoEnabled(false); document->setUndoRedoEnabled(false);
cursor.beginEditBlock(); cursor.beginEditBlock();
@ -2927,21 +2967,32 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
#endif // Q_OS_MAC #endif // Q_OS_MAC
} else { } else {
const auto text = e->text(); const auto text = e->text();
const auto oldPosition = textCursor().position(); const auto old = textCursor();
const auto oldPosition = old.position();
const auto oldSelection = old.hasSelection();
const auto oldModifiers = e->modifiers(); const auto oldModifiers = e->modifiers();
const auto allowedModifiers = (enter && ctrl) const auto allowedModifiers = (enter && ctrl)
? (~Qt::ControlModifier) ? (~Qt::ControlModifier)
: (enter && shift) : (enter && shift)
? (~Qt::ShiftModifier) ? (~Qt::ShiftModifier)
// Qt bug workaround https://bugreports.qt.io/browse/QTBUG-49771
: (backspace && Platform::IsX11())
? (Qt::ControlModifier)
: oldModifiers; : oldModifiers;
const auto changeModifiers = (oldModifiers & ~allowedModifiers) != 0; const auto changeModifiers = (oldModifiers & ~allowedModifiers) != 0;
if (changeModifiers) { if (changeModifiers) {
e->setModifiers(oldModifiers & allowedModifiers); e->setModifiers(oldModifiers & allowedModifiers);
} }
_inner->QTextEdit::keyPressEvent(e); if (e == QKeySequence::InsertParagraphSeparator) {
// qtbase commit dbb9579566f3accd8aa5fe61db9692991117afd3 introduced
// special logic for repeated 'Enter' key presses, which drops the
// block format instead of inserting a newline in case the block format
// is non-trivial. For custom fonts we use non-trivial block formats
// always for the entire QTextEdit, so we revert that logic and simply
// insert a newline as it was before Qt 6.X.Y where this was added.
textCursor().insertBlock();
_inner->ensureCursorVisible();
e->accept();
} else {
_inner->QTextEdit::keyPressEvent(e);
}
if (changeModifiers) { if (changeModifiers) {
e->setModifiers(oldModifiers); e->setModifiers(oldModifiers);
} }
@ -2954,7 +3005,10 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
} else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) { } else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) {
cursor.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); cursor.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
check = true; check = true;
} else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right || e->key() == Qt::Key_Backspace) { } else if (!oldSelection
&& (e->key() == Qt::Key_Left
|| e->key() == Qt::Key_Right
|| e->key() == Qt::Key_Backspace)) {
e->ignore(); e->ignore();
} }
if (check) { if (check) {
@ -3185,7 +3239,7 @@ void InputField::inputMethodEventInner(QInputMethodEvent *e) {
} }
_inputMethodCommit = e->commitString(); _inputMethodCommit = e->commitString();
const auto weak = Ui::MakeWeak(this); const auto weak = MakeWeak(this);
_inner->QTextEdit::inputMethodEvent(e); _inner->QTextEdit::inputMethodEvent(e);
if (weak && _inputMethodCommit.has_value()) { if (weak && _inputMethodCommit.has_value()) {
@ -3356,7 +3410,7 @@ void InputField::commitInstantReplacement(
result.setProperty(kCustomEmojiText, with); result.setProperty(kCustomEmojiText, with);
result.setProperty(kCustomEmojiLink, unique); result.setProperty(kCustomEmojiLink, unique);
result.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link)); result.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
result.setVerticalAlignment(QTextCharFormat::AlignBottom); result.setVerticalAlignment(QTextCharFormat::AlignTop);
return result; return result;
} }
const auto use = Integration::Instance().defaultEmojiVariant( const auto use = Integration::Instance().defaultEmojiVariant(
@ -3619,8 +3673,13 @@ void InputField::commitMarkdownLinkEdit(
_insertedTags.clear(); _insertedTags.clear();
_reverseMarkdownReplacement = false; _reverseMarkdownReplacement = false;
_correcting = true;
cursor.joinPreviousEditBlock();
cursor.setCharFormat(_defaultCharFormat); cursor.setCharFormat(_defaultCharFormat);
cursor.setBlockFormat(_defaultBlockFormat);
cursor.endEditBlock();
_inner->setTextCursor(cursor); _inner->setTextCursor(cursor);
_correcting = false;
} }
void InputField::toggleSelectionMarkdown(const QString &tag) { void InputField::toggleSelectionMarkdown(const QString &tag) {
@ -3950,14 +4009,18 @@ void InputField::insertFromMimeDataInner(const QMimeData *source) {
void InputField::resizeEvent(QResizeEvent *e) { void InputField::resizeEvent(QResizeEvent *e) {
refreshPlaceholder(_placeholderFull.current()); refreshPlaceholder(_placeholderFull.current());
_inner->setGeometry(rect().marginsRemoved( _inner->setGeometry(rect().marginsRemoved(
_st.textMargins + _additionalMargins)); _st.textMargins + _additionalMargins + _customFontMargins));
_borderAnimationStart = width() / 2; _borderAnimationStart = width() / 2;
RpWidget::resizeEvent(e); RpWidget::resizeEvent(e);
checkContentHeight(); checkContentHeight();
} }
void InputField::refreshPlaceholder(const QString &text) { void InputField::refreshPlaceholder(const QString &text) {
const auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right(); const auto margins = _st.textMargins
+ _st.placeholderMargins
+ _additionalMargins
+ _customFontMargins;
const auto availableWidth = rect().marginsRemoved(margins).width();
if (_st.placeholderScale > 0.) { if (_st.placeholderScale > 0.) {
auto placeholderFont = _st.placeholderFont->f; auto placeholderFont = _st.placeholderFont->f;
placeholderFont.setStyleStrategy(QFont::PreferMatch); placeholderFont.setStyleStrategy(QFont::PreferMatch);
@ -3965,7 +4028,9 @@ void InputField::refreshPlaceholder(const QString &text) {
_placeholder = metrics.elidedText(text, Qt::ElideRight, availableWidth); _placeholder = metrics.elidedText(text, Qt::ElideRight, availableWidth);
_placeholderPath = QPainterPath(); _placeholderPath = QPainterPath();
if (!_placeholder.isEmpty()) { if (!_placeholder.isEmpty()) {
_placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder); const auto result = style::FindAdjustResult(placeholderFont);
const auto ascent = result ? result->iascent : metrics.ascent();
_placeholderPath.addText(0, ascent, placeholderFont, _placeholder);
} }
} else { } else {
_placeholder = _st.placeholderFont->elided(text, availableWidth); _placeholder = _st.placeholderFont->elided(text, availableWidth);
@ -4054,4 +4119,52 @@ int FieldCharacterCount(not_null<InputField*> field) {
return field->lastTextSizeWithoutSurrogatePairsCount(); return field->lastTextSizeWithoutSurrogatePairsCount();
} }
void AddLengthLimitLabel(not_null<InputField*> field, int limit) {
struct State {
rpl::variable<int> length;
};
const auto state = field->lifetime().make_state<State>();
state->length = rpl::single(
rpl::empty
) | rpl::then(field->changes()) | rpl::map([=] {
return int(field->getLastText().size());
});
const auto allowExceed = std::max(limit / 2, 9);
field->setMaxLength(limit + allowExceed);
const auto threshold = std::min(limit / 2, 9);
auto warningText = state->length.value() | rpl::map([=](int count) {
const auto left = limit - count;
return (left < threshold) ? QString::number(left) : QString();
});
const auto warning = CreateChild<FlatLabel>(
field.get(),
std::move(warningText),
st::defaultInputFieldLimit);
const auto maxSize = st::defaultInputFieldLimit.style.font->width(
QString::number(-allowExceed));
const auto add = std::max(maxSize - field->st().textMargins.right(), 0);
if (add) {
field->setAdditionalMargins({ 0, 0, add, 0 });
}
state->length.value() | rpl::map(
rpl::mappers::_1 > limit
) | rpl::start_with_next([=](bool exceeded) {
warning->setTextColorOverride(exceeded
? st::attentionButtonFg->c
: std::optional<QColor>());
}, warning->lifetime());
rpl::combine(
field->sizeValue(),
warning->sizeValue()
) | rpl::start_with_next([=] {
// Baseline alignment.
const auto top = field->st().textMargins.top()
+ field->st().font->ascent
- st::defaultInputFieldLimit.style.font->ascent;
warning->moveToRight(0, top);
}, warning->lifetime());
warning->setAttribute(Qt::WA_TransparentForMouseEvents);
}
} // namespace Ui } // namespace Ui

View file

@ -80,7 +80,10 @@ class CustomEmojiObject : public QObject, public QTextObjectInterface {
public: public:
using Factory = Fn<std::unique_ptr<Text::CustomEmoji>(QStringView)>; using Factory = Fn<std::unique_ptr<Text::CustomEmoji>(QStringView)>;
CustomEmojiObject(Factory factory, Fn<bool()> paused); CustomEmojiObject(
const style::font &font,
Factory factory,
Fn<bool()> paused);
~CustomEmojiObject(); ~CustomEmojiObject();
void *qt_metacast(const char *iid) override; void *qt_metacast(const char *iid) override;
@ -100,6 +103,7 @@ public:
void clear(); void clear();
private: private:
const style::font _font;
Factory _factory; Factory _factory;
Fn<bool()> _paused; Fn<bool()> _paused;
base::flat_map<uint64, std::unique_ptr<Text::CustomEmoji>> _emoji; base::flat_map<uint64, std::unique_ptr<Text::CustomEmoji>> _emoji;
@ -499,6 +503,8 @@ private:
std::optional<QString> _inputMethodCommit; std::optional<QString> _inputMethodCommit;
QMargins _additionalMargins; QMargins _additionalMargins;
QMargins _customFontMargins;
int _placeholderCustomFontSkip = 0;
bool _forcePlaceholderHidden = false; bool _forcePlaceholderHidden = false;
bool _reverseMarkdownReplacement = false; bool _reverseMarkdownReplacement = false;
@ -561,6 +567,7 @@ private:
base::unique_qptr<PopupMenu> _contextMenu; base::unique_qptr<PopupMenu> _contextMenu;
QTextCharFormat _defaultCharFormat; QTextCharFormat _defaultCharFormat;
QTextBlockFormat _defaultBlockFormat;
rpl::variable<int> _scrollTop; rpl::variable<int> _scrollTop;
@ -583,4 +590,6 @@ void PrepareFormattingOptimization(not_null<QTextDocument*> document);
[[nodiscard]] int FieldCharacterCount(not_null<InputField*> field); [[nodiscard]] int FieldCharacterCount(not_null<InputField*> field);
void AddLengthLimitLabel(not_null<InputField*> field, int limit);
} // namespace Ui } // namespace Ui

View file

@ -390,7 +390,9 @@ void MaskedInputField::refreshPlaceholder(const QString &text) {
_placeholder = metrics.elidedText(text, Qt::ElideRight, availableWidth); _placeholder = metrics.elidedText(text, Qt::ElideRight, availableWidth);
_placeholderPath = QPainterPath(); _placeholderPath = QPainterPath();
if (!_placeholder.isEmpty()) { if (!_placeholder.isEmpty()) {
_placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, _placeholder); const auto result = style::FindAdjustResult(placeholderFont);
const auto ascent = result ? result->iascent : metrics.ascent();
_placeholderPath.addText(0, ascent, placeholderFont, _placeholder);
} }
} else { } else {
_placeholder = _st.placeholderFont->elided(text, availableWidth); _placeholder = _st.placeholderFont->elided(text, availableWidth);

View file

@ -42,18 +42,32 @@ rpl::producer<> TimePart::erasePrevious() const {
return _erasePrevious.events(); return _erasePrevious.events();
} }
rpl::producer<> TimePart::jumpToPrevious() const {
return _jumpToPrevious.events();
}
rpl::producer<QChar> TimePart::putNext() const { rpl::producer<QChar> TimePart::putNext() const {
return _putNext.events(); return _putNext.events();
} }
void TimePart::keyPressEvent(QKeyEvent *e) { void TimePart::keyPressEvent(QKeyEvent *e) {
const auto isBackspace = (e->key() == Qt::Key_Backspace); const auto position = cursorPosition();
const auto isBeginning = (cursorPosition() == 0); const auto selection = hasSelectedText();
if (isBackspace && isBeginning && !hasSelectedText()) { if (!selection && !position) {
_erasePrevious.fire({}); if (e->key() == Qt::Key_Backspace) {
} else { _erasePrevious.fire({});
MaskedInputField::keyPressEvent(e); return;
} else if (e->key() == Qt::Key_Left) {
_jumpToPrevious.fire({});
return;
}
} else if (!selection && position == getLastText().size()) {
if (e->key() == Qt::Key_Right) {
_putNext.fire(QChar(0));
return;
}
} }
MaskedInputField::keyPressEvent(e);
} }
void TimePart::wheelEvent(QWheelEvent *e) { void TimePart::wheelEvent(QWheelEvent *e) {
@ -78,10 +92,10 @@ void TimePart::correctValue(
int wasCursor, int wasCursor,
QString &now, QString &now,
int &nowCursor) { int &nowCursor) {
auto newText = QString();
auto newCursor = -1;
const auto oldCursor = nowCursor; const auto oldCursor = nowCursor;
const auto oldLength = now.size(); const auto oldLength = now.size();
auto newCursor = (oldCursor > 0) ? -1 : 0;
auto newText = QString();
auto accumulated = 0; auto accumulated = 0;
auto limit = 0; auto limit = 0;
for (; limit != oldLength; ++limit) { for (; limit != oldLength; ++limit) {

View file

@ -18,6 +18,7 @@ public:
void setWheelStep(int value); void setWheelStep(int value);
[[nodiscard]] rpl::producer<> erasePrevious() const; [[nodiscard]] rpl::producer<> erasePrevious() const;
[[nodiscard]] rpl::producer<> jumpToPrevious() const;
[[nodiscard]] rpl::producer<QChar> putNext() const; [[nodiscard]] rpl::producer<QChar> putNext() const;
[[nodiscard]] std::optional<int> number(); [[nodiscard]] std::optional<int> number();
@ -37,6 +38,7 @@ private:
int _maxDigits = 0; int _maxDigits = 0;
int _wheelStep = 0; int _wheelStep = 0;
rpl::event_stream<> _erasePrevious; rpl::event_stream<> _erasePrevious;
rpl::event_stream<> _jumpToPrevious;
rpl::event_stream<QChar> _putNext; rpl::event_stream<QChar> _putNext;
}; };

View file

@ -687,7 +687,7 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason)
const auto request = ContextMenuRequest{ const auto request = ContextMenuRequest{
.menu = _contextMenu.get(), .menu = _contextMenu.get(),
.link = ClickHandler::getActive(), .link = ClickHandler::getActive(),
.hasSelection = hasSelection, .selection = _selectable ? _selection : TextSelection(),
.uponSelection = uponSelection, .uponSelection = uponSelection,
.fullSelection = _selectable && _text.isFullSelection(_selection), .fullSelection = _selectable && _text.isFullSelection(_selection),
}; };
@ -716,7 +716,7 @@ void FlatLabel::fillContextMenu(ContextMenuRequest request) {
Integration::Instance().phraseContextCopySelected(), Integration::Instance().phraseContextCopySelected(),
[=] { copySelectedText(); }); [=] { copySelectedText(); });
} else if (_selectable } else if (_selectable
&& !request.hasSelection && request.selection.empty()
&& !_contextCopyText.isEmpty()) { && !_contextCopyText.isEmpty()) {
request.menu->addAction( request.menu->addAction(
_contextCopyText, _contextCopyText,

View file

@ -117,6 +117,10 @@ public:
const style::FlatLabel &st = st::defaultFlatLabel, const style::FlatLabel &st = st::defaultFlatLabel,
const style::PopupMenu &stMenu = st::defaultPopupMenu); const style::PopupMenu &stMenu = st::defaultPopupMenu);
[[nodiscard]] const style::FlatLabel &st() const {
return _st;
}
void setOpacity(float64 o); void setOpacity(float64 o);
void setTextColorOverride(std::optional<QColor> color); void setTextColorOverride(std::optional<QColor> color);
@ -154,7 +158,7 @@ public:
struct ContextMenuRequest { struct ContextMenuRequest {
not_null<PopupMenu*> menu; not_null<PopupMenu*> menu;
ClickHandlerPtr link; ClickHandlerPtr link;
bool hasSelection = false; TextSelection selection;
bool uponSelection = false; bool uponSelection = false;
bool fullSelection = false; bool fullSelection = false;
}; };

View file

@ -8,6 +8,10 @@
#include "ui/style/style_core.h" #include "ui/style/style_core.h"
namespace style {
struct MenuSeparator;
} // namespace style
namespace Ui { namespace Ui {
class PopupMenu; class PopupMenu;
} // namespace Ui } // namespace Ui
@ -20,6 +24,7 @@ public:
QString text; QString text;
Fn<void()> handler; Fn<void()> handler;
const style::icon *icon; const style::icon *icon;
const style::MenuSeparator *separatorSt = nullptr;
Fn<void(not_null<Ui::PopupMenu*>)> fillSubmenu; Fn<void(not_null<Ui::PopupMenu*>)> fillSubmenu;
int addTopShift = 0; int addTopShift = 0;
bool isSeparator = false; bool isSeparator = false;

View file

@ -27,8 +27,8 @@ MenuCallback CreateAddActionCallback(not_null<Ui::PopupMenu*> menu) {
action->setMenu(Ui::CreateChild<QMenu>(menu->menu().get())); action->setMenu(Ui::CreateChild<QMenu>(menu->menu().get()));
a.fillSubmenu(menu->ensureSubmenu(action, menu->st())); a.fillSubmenu(menu->ensureSubmenu(action, menu->st()));
return action; return action;
} else if (a.isSeparator) { } else if (a.separatorSt || a.isSeparator) {
return menu->addSeparator(); return menu->addSeparator(a.separatorSt);
} else if (a.isAttention) { } else if (a.isAttention) {
return menu->addAction(base::make_unique_q<Ui::Menu::Action>( return menu->addAction(base::make_unique_q<Ui::Menu::Action>(
menu, menu,

View file

@ -900,12 +900,6 @@ void PopupMenu::deleteOnHide(bool del) {
} }
void PopupMenu::popup(const QPoint &p) { void PopupMenu::popup(const QPoint &p) {
if (_clearLastSeparator) {
_menu->clearLastSeparator();
for (const auto &[action, submenu] : _submenus) {
submenu->menu()->clearLastSeparator();
}
}
if (prepareGeometryFor(p)) { if (prepareGeometryFor(p)) {
popupPrepared(); popupPrepared();
return; return;
@ -957,10 +951,15 @@ bool PopupMenu::prepareGeometryFor(const QPoint &p) {
} }
bool PopupMenu::prepareGeometryFor(const QPoint &p, PopupMenu *parent) { bool PopupMenu::prepareGeometryFor(const QPoint &p, PopupMenu *parent) {
if (_clearLastSeparator) {
_menu->clearLastSeparator();
for (const auto &[action, submenu] : _submenus) {
submenu->menu()->clearLastSeparator();
}
}
const auto usingScreenGeometry = !::Platform::IsWayland(); const auto usingScreenGeometry = !::Platform::IsWayland();
const auto screen = usingScreenGeometry const auto screen = QGuiApplication::screenAt(p);
? QGuiApplication::screenAt(p)
: nullptr;
if ((usingScreenGeometry && !screen) if ((usingScreenGeometry && !screen)
|| (!parent || (!parent
&& ::Platform::IsMac() && ::Platform::IsMac()
@ -1002,7 +1001,7 @@ bool PopupMenu::prepareGeometryFor(const QPoint &p, PopupMenu *parent) {
_additionalMenuPadding.left() - _st.shadow.extend.left(), _additionalMenuPadding.left() - _st.shadow.extend.left(),
0), 0),
_padding.top() - _topShift); _padding.top() - _topShift);
auto r = screen ? screen->availableGeometry() : QRect(); auto r = usingScreenGeometry ? screen->availableGeometry() : QRect();
const auto parentWidth = _parent ? _parent->inner().width() : 0; const auto parentWidth = _parent ? _parent->inner().width() : 0;
if (style::RightToLeft()) { if (style::RightToLeft()) {
const auto badLeft = !r.isNull() && w.x() - width() < r.x() - _margins.left(); const auto badLeft = !r.isNull() && w.x() - width() < r.x() - _margins.left();

View file

@ -20,10 +20,13 @@ namespace Ui {
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
ScrollShadow::ScrollShadow(ScrollArea *parent, const style::ScrollArea *st) : QWidget(parent), _st(st) { ScrollShadow::ScrollShadow(ScrollArea *parent, const style::ScrollArea *st)
: QWidget(parent)
, _st(st) {
Expects(_st != nullptr);
Expects(_st->shColor.get() != nullptr);
setVisible(false); setVisible(false);
Assert(_st != nullptr);
Assert(_st->shColor.v() != nullptr);
} }
void ScrollShadow::paintEvent(QPaintEvent *e) { void ScrollShadow::paintEvent(QPaintEvent *e) {
@ -706,32 +709,48 @@ void ScrollArea::scrollToWidget(not_null<QWidget*> widget) {
} }
} }
void ScrollArea::scrollToY(int toTop, int toBottom) { int ScrollArea::computeScrollTo(int toTop, int toBottom) {
if (const auto inner = widget()) { if (const auto inner = widget()) {
SendPendingMoveResizeEvents(inner); SendPendingMoveResizeEvents(inner);
} }
SendPendingMoveResizeEvents(this); SendPendingMoveResizeEvents(this);
int toMin = 0, toMax = scrollTopMax(); const auto toMin = 0;
const auto toMax = scrollTopMax();
if (toTop < toMin) { if (toTop < toMin) {
toTop = toMin; toTop = toMin;
} else if (toTop > toMax) { } else if (toTop > toMax) {
toTop = toMax; toTop = toMax;
} }
bool exact = (toBottom < 0); const auto exact = (toBottom < 0);
int curTop = scrollTop(), curHeight = height(), curBottom = curTop + curHeight, scToTop = toTop; const auto curTop = scrollTop();
const auto curHeight = height();
const auto curBottom = curTop + curHeight;
auto scToTop = toTop;
if (!exact && toTop >= curTop) { if (!exact && toTop >= curTop) {
if (toBottom < toTop) toBottom = toTop; if (toBottom < toTop) {
if (toBottom <= curBottom) return; toBottom = toTop;
}
if (toBottom <= curBottom) {
return curTop;
}
scToTop = toBottom - curHeight; scToTop = toBottom - curHeight;
if (scToTop > toTop) scToTop = toTop; if (scToTop > toTop) {
if (scToTop == curTop) return; scToTop = toTop;
}
if (scToTop == curTop) {
return curTop;
}
} else { } else {
scToTop = toTop; scToTop = toTop;
} }
verticalScrollBar()->setValue(scToTop); return scToTop;
}
void ScrollArea::scrollToY(int toTop, int toBottom) {
verticalScrollBar()->setValue(computeScrollTo(toTop, toBottom));
} }
void ScrollArea::doSetOwnedWidget(object_ptr<QWidget> w) { void ScrollArea::doSetOwnedWidget(object_ptr<QWidget> w) {

View file

@ -166,6 +166,7 @@ public:
void scrollTo(ScrollToRequest request); void scrollTo(ScrollToRequest request);
void scrollToWidget(not_null<QWidget*> widget); void scrollToWidget(not_null<QWidget*> widget);
[[nodiscard]] int computeScrollTo(int toTop, int toBottom);
void scrollToY(int toTop, int toBottom = -1); void scrollToY(int toTop, int toBottom = -1);
void disableScroll(bool dis); void disableScroll(bool dis);

View file

@ -676,6 +676,12 @@ void SeparatePanel::initGeometry(QSize size) {
}(); }();
move(rect.topLeft()); move(rect.topLeft());
setFixedSize(rect.size()); setFixedSize(rect.size());
createWinId();
if (_useTransparency) {
Platform::SetWindowMargins(this, _padding);
} else {
Platform::UnsetWindowMargins(this);
}
updateControlsGeometry(); updateControlsGeometry();
} }

View file

@ -110,6 +110,10 @@ TimeInput::TimeInput(
_minute->erasePrevious() | rpl::start_with_next([=] { _minute->erasePrevious() | rpl::start_with_next([=] {
erasePrevious(_hour); erasePrevious(_hour);
}, lifetime()); }, lifetime());
_minute->jumpToPrevious() | rpl::start_with_next([=] {
_hour->setCursorPosition(_hour->getLastText().size());
_hour->setFocus();
}, lifetime());
_separator1->setAttribute(Qt::WA_TransparentForMouseEvents); _separator1->setAttribute(Qt::WA_TransparentForMouseEvents);
setMouseTracking(true); setMouseTracking(true);

View file

@ -77,7 +77,7 @@ Tooltip::~Tooltip() {
void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *st) { void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *st) {
const auto usingScreenGeometry = !::Platform::IsWayland(); const auto usingScreenGeometry = !::Platform::IsWayland();
const auto screen = usingScreenGeometry ? QGuiApplication::screenAt(m) : nullptr; const auto screen = QGuiApplication::screenAt(m);
if (usingScreenGeometry && !screen) { if (usingScreenGeometry && !screen) {
Hide(); Hide();
return; return;
@ -118,10 +118,13 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *
p.setX(m.x() - (s.width() / 2)); p.setX(m.x() - (s.width() / 2));
} }
// adjust tooltip position
if (screen) { if (screen) {
createWinId(); createWinId();
windowHandle()->setScreen(screen); windowHandle()->setScreen(screen);
}
// adjust tooltip position
if (usingScreenGeometry) {
const auto r = screen->availableGeometry(); const auto r = screen->availableGeometry();
if (r.x() + r.width() - _st->skip < p.x() + s.width() && p.x() + s.width() > m.x()) { if (r.x() + r.width() - _st->skip < p.x() + s.width() && p.x() + s.width() > m.x()) {
p.setX(qMax(r.x() + r.width() - int32(_st->skip) - s.width(), m.x() - s.width())); p.setX(qMax(r.x() + r.width() - int32(_st->skip) - s.width(), m.x() - s.width()));

View file

@ -744,7 +744,7 @@ defaultCheck: Check {
diameter: 22px; diameter: 22px;
thickness: 2px; thickness: 2px;
icon: defaultCheckboxIcon; icon: defaultCheckboxIcon;
duration: 120; duration: universalDuration;
rippleAreaPadding: 8px; rippleAreaPadding: 8px;
} }
defaultRadio: Radio { defaultRadio: Radio {
@ -755,7 +755,7 @@ defaultRadio: Radio {
thickness: 2px; thickness: 2px;
outerSkip: 10px; // * 0.1 outerSkip: 10px; // * 0.1
skip: 60px; // * 0.1 skip: 60px; // * 0.1
duration: 120; duration: universalDuration;
rippleAreaPadding: 8px; rippleAreaPadding: 8px;
} }
defaultToggle: Toggle { defaultToggle: Toggle {
@ -763,7 +763,7 @@ defaultToggle: Toggle {
toggledFg: windowBgActive; toggledFg: windowBgActive;
untoggledBg: windowBg; untoggledBg: windowBg;
untoggledFg: checkboxFg; untoggledFg: checkboxFg;
duration: 120; duration: universalDuration;
border: 2px; border: 2px;
shift: 1px; shift: 1px;
diameter: 16px; diameter: 16px;
@ -910,6 +910,9 @@ defaultInputField: InputField {
heightMin: 55px; heightMin: 55px;
heightMax: 148px; heightMax: 148px;
} }
defaultInputFieldLimit: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
defaultIconButton: IconButton { defaultIconButton: IconButton {
iconPosition: point(-1px, -1px); iconPosition: point(-1px, -1px);
@ -1016,6 +1019,7 @@ SettingsSlider {
labelStyle: TextStyle; labelStyle: TextStyle;
labelFg: color; labelFg: color;
labelFgActive: color; labelFgActive: color;
strictSkip: pixels;
duration: int; duration: int;
rippleBottomSkip: pixels; rippleBottomSkip: pixels;
rippleBg: color; rippleBg: color;