diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6646223..1f9a6cf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -139,8 +139,6 @@ PRIVATE
ui/style/style_core.h
ui/style/style_core_color.cpp
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.h
ui/style/style_core_font.cpp
@@ -263,6 +261,7 @@ PRIVATE
ui/abstract_button.h
ui/animated_icon.cpp
ui/animated_icon.h
+ ui/arc_angles.h
ui/basic_click_handlers.cpp
ui/basic_click_handlers.h
ui/cached_special_layer_shadow_corners.cpp
diff --git a/fonts/DAOpenSansBold.ttf b/fonts/DAOpenSansBold.ttf
deleted file mode 100644
index bdc55af..0000000
Binary files a/fonts/DAOpenSansBold.ttf and /dev/null differ
diff --git a/fonts/DAOpenSansBoldItalic.ttf b/fonts/DAOpenSansBoldItalic.ttf
deleted file mode 100644
index 47ebb8b..0000000
Binary files a/fonts/DAOpenSansBoldItalic.ttf and /dev/null differ
diff --git a/fonts/DAOpenSansSemiboldAsBold.ttf b/fonts/DAOpenSansSemiboldAsBold.ttf
deleted file mode 100644
index ef2eadd..0000000
Binary files a/fonts/DAOpenSansSemiboldAsBold.ttf and /dev/null differ
diff --git a/fonts/DAOpenSansSemiboldItalicAsBold.ttf b/fonts/DAOpenSansSemiboldItalicAsBold.ttf
deleted file mode 100644
index 1507d53..0000000
Binary files a/fonts/DAOpenSansSemiboldItalicAsBold.ttf and /dev/null differ
diff --git a/fonts/DAVazirBold.ttf b/fonts/DAVazirBold.ttf
deleted file mode 100644
index 28e6a07..0000000
Binary files a/fonts/DAVazirBold.ttf and /dev/null differ
diff --git a/fonts/DAVazirMedium.ttf b/fonts/DAVazirMedium.ttf
deleted file mode 100644
index 4e303f9..0000000
Binary files a/fonts/DAVazirMedium.ttf and /dev/null differ
diff --git a/fonts/DAVazirMediumAsBold.ttf b/fonts/DAVazirMediumAsBold.ttf
deleted file mode 100644
index f69068c..0000000
Binary files a/fonts/DAVazirMediumAsBold.ttf and /dev/null differ
diff --git a/fonts/DAVazirRegular.ttf b/fonts/DAVazirRegular.ttf
deleted file mode 100644
index 15985f8..0000000
Binary files a/fonts/DAVazirRegular.ttf and /dev/null differ
diff --git a/fonts/DAOpenSansRegularItalic.ttf b/fonts/OpenSans-Italic.ttf
similarity index 97%
rename from fonts/DAOpenSansRegularItalic.ttf
rename to fonts/OpenSans-Italic.ttf
index 6c6e046..88c01b6 100644
Binary files a/fonts/DAOpenSansRegularItalic.ttf and b/fonts/OpenSans-Italic.ttf differ
diff --git a/fonts/DAOpenSansRegular.ttf b/fonts/OpenSans-Regular.ttf
similarity index 97%
rename from fonts/DAOpenSansRegular.ttf
rename to fonts/OpenSans-Regular.ttf
index 705a2e5..abd6746 100644
Binary files a/fonts/DAOpenSansRegular.ttf and b/fonts/OpenSans-Regular.ttf differ
diff --git a/fonts/DAOpenSansSemibold.ttf b/fonts/OpenSans-SemiBold.ttf
similarity index 98%
rename from fonts/DAOpenSansSemibold.ttf
rename to fonts/OpenSans-SemiBold.ttf
index 71d61b7..6bfb1f8 100644
Binary files a/fonts/DAOpenSansSemibold.ttf and b/fonts/OpenSans-SemiBold.ttf differ
diff --git a/fonts/DAOpenSansSemiboldItalic.ttf b/fonts/OpenSans-SemiBoldItalic.ttf
similarity index 98%
rename from fonts/DAOpenSansSemiboldItalic.ttf
rename to fonts/OpenSans-SemiBoldItalic.ttf
index 7e45d7a..3ba9213 100644
Binary files a/fonts/DAOpenSansSemiboldItalic.ttf and b/fonts/OpenSans-SemiBoldItalic.ttf differ
diff --git a/fonts/Vazirmatn-UI-NL-Regular.ttf b/fonts/Vazirmatn-UI-NL-Regular.ttf
new file mode 100644
index 0000000..9deb47e
Binary files /dev/null and b/fonts/Vazirmatn-UI-NL-Regular.ttf differ
diff --git a/fonts/Vazirmatn-UI-NL-SemiBold.ttf b/fonts/Vazirmatn-UI-NL-SemiBold.ttf
new file mode 100644
index 0000000..3255b93
Binary files /dev/null and b/fonts/Vazirmatn-UI-NL-SemiBold.ttf differ
diff --git a/fonts/fonts.qrc b/fonts/fonts.qrc
index 22a4a29..19baebb 100644
--- a/fonts/fonts.qrc
+++ b/fonts/fonts.qrc
@@ -1,13 +1,10 @@
- DAOpenSansRegular.ttf
- DAOpenSansRegularItalic.ttf
- DAOpenSansSemiboldAsBold.ttf
- DAOpenSansSemiboldItalicAsBold.ttf
- DAOpenSansSemibold.ttf
- DAOpenSansSemiboldItalic.ttf
- DAVazirRegular.ttf
- DAVazirMediumAsBold.ttf
- DAVazirMedium.ttf
+ OpenSans-Regular.ttf
+ OpenSans-Italic.ttf
+ OpenSans-SemiBold.ttf
+ OpenSans-SemiBoldItalic.ttf
+ Vazirmatn-UI-NL-Regular.ttf
+ Vazirmatn-UI-NL-SemiBold.ttf
diff --git a/ui/arc_angles.h b/ui/arc_angles.h
new file mode 100644
index 0000000..39c1ce2
--- /dev/null
+++ b/ui/arc_angles.h
@@ -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
diff --git a/ui/basic.style b/ui/basic.style
index 8d55bac..de0312c 100644
--- a/ui/basic.style
+++ b/ui/basic.style
@@ -118,6 +118,8 @@ defaultVerticalListSkip: 6px;
shakeShift: 4px;
shakeDuration: 300;
+universalDuration: 120;
+
// floating badge colors
roundedFg: radialFg;
roundedBg: radialBg; // closest to #00000066
diff --git a/ui/colors.palette b/ui/colors.palette
index 71081e0..699842c 100644
--- a/ui/colors.palette
+++ b/ui/colors.palette
@@ -670,6 +670,10 @@ statisticsChartLineIndigo: #7f79f3; // represents indigo color on statistical ch
statisticsChartLinePurple: #9f79e8; // represents purple 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
ktgTopBarBg: topBarBg; // Kotatogram: top bar background
ktgTopBarNameFg: dialogsNameFg; // Kotatogram: top bar name text
diff --git a/ui/effects/animations.cpp b/ui/effects/animations.cpp
index b0c69d9..3fb737a 100644
--- a/ui/effects/animations.cpp
+++ b/ui/effects/animations.cpp
@@ -6,8 +6,9 @@
//
#include "ui/effects/animations.h"
-#include "ui/ui_utility.h"
#include "base/invoke_queued.h"
+#include "ui/ui_utility.h"
+#include "styles/style_basic.h"
#include
@@ -22,7 +23,7 @@ namespace Ui {
namespace Animations {
namespace {
-constexpr auto kAnimationTick = crl::time(1000) / 120;
+constexpr auto kAnimationTick = crl::time(1000) / st::universalDuration;
constexpr auto kIgnoreUpdatesTimeout = crl::time(4);
Manager *ManagerInstance = nullptr;
diff --git a/ui/effects/cross_animation.cpp b/ui/effects/cross_animation.cpp
index 18a9cd4..e847ab3 100644
--- a/ui/effects/cross_animation.cpp
+++ b/ui/effects/cross_animation.cpp
@@ -7,6 +7,7 @@
#include "ui/effects/cross_animation.h"
#include "ui/effects/animation_value.h"
+#include "ui/arc_angles.h"
#include "ui/painter.h"
#include
@@ -17,7 +18,6 @@ namespace {
constexpr auto kPointCount = 12;
constexpr auto kStaticLoadingValue = float64(-666);
-constexpr auto kFullArcLength = 360 * 16;
//
@@ -148,12 +148,12 @@ void CrossAnimation::paint(
auto pathDeleteSize = kPointCount;
const auto staticLoading = (loading == kStaticLoadingValue);
- auto loadingArcLength = staticLoading ? kFullArcLength : 0;
+ auto loadingArcLength = staticLoading ? arc::kFullLength : 0;
if (loading > 0.) {
transformLoadingCross(loading, pathDelete, pathDeleteSize);
auto loadingArc = (loading >= 0.5) ? (loading - 1.) : loading;
- loadingArcLength = qRound(-loadingArc * 2 * kFullArcLength);
+ loadingArcLength = qRound(-loadingArc * 2 * arc::kFullLength);
}
if (!staticLoading) {
@@ -184,9 +184,9 @@ void CrossAnimation::paint(
if (staticLoading) {
anim::DrawStaticLoading(p, roundPart, stroke, color);
} else {
- auto loadingArcStart = kFullArcLength / 8;
+ auto loadingArcStart = arc::kQuarterLength / 2;
if (shown < 1.) {
- loadingArcStart -= qRound(-(shown - 1.) * kFullArcLength / 4.);
+ loadingArcStart -= qRound(-(shown - 1.) * arc::kQuarterLength);
}
if (loadingArcLength < 0) {
loadingArcStart += loadingArcLength;
diff --git a/ui/effects/radial_animation.cpp b/ui/effects/radial_animation.cpp
index 2ae1d53..8de21b6 100644
--- a/ui/effects/radial_animation.cpp
+++ b/ui/effects/radial_animation.cpp
@@ -6,16 +6,14 @@
//
#include "ui/effects/radial_animation.h"
+#include "ui/arc_angles.h"
#include "ui/painter.h"
#include "styles/style_widgets.h"
namespace Ui {
namespace {
-constexpr auto kFullArcLength = 360 * 16;
-constexpr auto kQuarterArcLength = (kFullArcLength / 4);
-constexpr auto kMinArcLength = (kFullArcLength / 360);
-constexpr auto kAlmostFullArcLength = (kFullArcLength - kMinArcLength);
+constexpr auto kFullArcLength = arc::kFullLength;
} // namespace
@@ -23,14 +21,14 @@ const int RadialState::kFull = kFullArcLength;
void RadialAnimation::start(float64 prg) {
_firstStart = _lastStart = _lastTime = crl::now();
- const auto iprg = qRound(qMax(prg, 0.0001) * kAlmostFullArcLength);
- const auto iprgstrict = qRound(prg * kAlmostFullArcLength);
+ const auto iprg = qRound(qMax(prg, 0.0001) * arc::kAlmostFullLength);
+ const auto iprgstrict = qRound(prg * arc::kAlmostFullLength);
_arcEnd = anim::value(iprgstrict, iprg);
_animation.start();
}
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()))
|| (_finished != finished);
if (_finished != finished) {
@@ -101,13 +99,13 @@ void RadialAnimation::draw(
}
RadialState RadialAnimation::computeState() const {
- auto length = kMinArcLength + qRound(_arcEnd.current());
- auto from = kQuarterArcLength
+ auto length = arc::kMinLength + qRound(_arcEnd.current());
+ auto from = arc::kQuarterLength
- length
- (anim::Disabled() ? 0 : qRound(_arcStart.current()));
if (style::RightToLeft()) {
- from = kQuarterArcLength - (from - kQuarterArcLength) - length;
- if (from < 0) from += kFullArcLength;
+ from = arc::kQuarterLength - (from - arc::kQuarterLength) - length;
+ if (from < 0) from += arc::kFullLength;
}
return { _opacity, from, length };
}
diff --git a/ui/gl/gl_detection.cpp b/ui/gl/gl_detection.cpp
index 150eb8b..a6707af 100644
--- a/ui/gl/gl_detection.cpp
+++ b/ui/gl/gl_detection.cpp
@@ -33,6 +33,7 @@ namespace Ui::GL {
namespace {
bool ForceDisabled/* = false*/;
+bool LastCheckCrashed/* = false*/;
#ifdef Q_OS_WIN
ANGLE ResolvedANGLE/* = ANGLE::Auto*/;
@@ -59,9 +60,14 @@ void CrashCheckStart() {
const char kOptionAllowLinuxNvidiaOpenGL[] = "allow-linux-nvidia-opengl";
Capabilities CheckCapabilities(QWidget *widget, bool avoidWidgetCreation) {
- if (ForceDisabled) {
- LOG_ONCE(("OpenGL: Force-disabled."));
- return {};
+ if (!Platform::IsMac()) {
+ if (ForceDisabled) {
+ LOG_ONCE(("OpenGL: Force-disabled."));
+ return {};
+ } else if (LastCheckCrashed) {
+ LOG_ONCE(("OpenGL: Last-crashed."));
+ return {};
+ }
}
[[maybe_unused]] static const auto BugListInited = [] {
@@ -243,8 +249,17 @@ Backend ChooseBackendDefault(Capabilities capabilities) {
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() {
- return QFile::exists(Integration::Instance().openglCheckFilePath());
+ DetectLastCheckCrash();
+ return LastCheckCrashed;
}
void CrashCheckFinish() {
@@ -252,7 +267,9 @@ void CrashCheckFinish() {
}
void ForceDisable(bool disable) {
- ForceDisabled = disable;
+ if (!Platform::IsMac()) {
+ ForceDisabled = disable;
+ }
}
#ifdef Q_OS_WIN
diff --git a/ui/gl/gl_detection.h b/ui/gl/gl_detection.h
index 97e6606..5b23fa7 100644
--- a/ui/gl/gl_detection.h
+++ b/ui/gl/gl_detection.h
@@ -31,6 +31,7 @@ struct Capabilities {
void ForceDisable(bool disable);
+void DetectLastCheckCrash();
[[nodiscard]] bool LastCrashCheckFailed();
void CrashCheckFinish();
diff --git a/ui/integration.cpp b/ui/integration.cpp
index f249c18..5306967 100644
--- a/ui/integration.cpp
+++ b/ui/integration.cpp
@@ -6,7 +6,6 @@
//
#include "ui/integration.h"
-#include "ui/style/style_core_custom_font.h"
#include "ui/gl/gl_detection.h"
#include "ui/text/text_entity.h"
#include "ui/text/text_block.h"
@@ -45,10 +44,6 @@ void Integration::textActionsUpdated() {
void Integration::activationFromTopPanel() {
}
-style::CustomFontSettings Integration::fontSettings() {
- return {};
-}
-
bool Integration::screenIsLocked() {
return false;
}
diff --git a/ui/integration.h b/ui/integration.h
index 7c39b0d..4b3a9b4 100644
--- a/ui/integration.h
+++ b/ui/integration.h
@@ -23,10 +23,6 @@ class ClickHandler;
struct ClickContext;
struct EntityLinkData;
-namespace style {
-struct CustomFontSettings;
-} // namespace style
-
namespace Ui {
namespace Emoji {
class One;
@@ -53,8 +49,6 @@ public:
virtual void textActionsUpdated();
virtual void activationFromTopPanel();
- virtual style::CustomFontSettings fontSettings();
-
[[nodiscard]] virtual bool screenIsLocked();
[[nodiscard]] virtual std::shared_ptr createLinkHandler(
diff --git a/ui/layers/box_content.cpp b/ui/layers/box_content.cpp
index 9ed0e96..5575885 100644
--- a/ui/layers/box_content.cpp
+++ b/ui/layers/box_content.cpp
@@ -263,8 +263,21 @@ RectParts BoxContent::customCornersFilling() {
}
void BoxContent::scrollToY(int top, int bottom) {
+ scrollTo({ top, bottom });
+}
+
+void BoxContent::scrollTo(ScrollToRequest request, anim::type animated) {
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);
+ }
}
}
diff --git a/ui/layers/box_content.h b/ui/layers/box_content.h
index 5526b41..237d38c 100644
--- a/ui/layers/box_content.h
+++ b/ui/layers/box_content.h
@@ -13,6 +13,7 @@
#include "ui/widgets/labels.h"
#include "ui/layers/layer_widget.h"
#include "ui/layers/show.h"
+#include "ui/effects/animations.h"
#include "ui/effects/animation_value.h"
#include "ui/text/text_entity.h"
#include "ui/rp_widget.h"
@@ -59,6 +60,7 @@ class ScrollArea;
class FlatLabel;
class FadeShadow;
class BoxContent;
+struct ScrollToRequest;
class BoxContentDelegate {
public:
@@ -213,6 +215,9 @@ public:
void scrollByDraggingDelta(int delta);
void scrollToY(int top, int bottom = -1);
+ void scrollTo(
+ ScrollToRequest request,
+ anim::type animated = anim::type::instant);
void sendScrollViewportEvent(not_null event);
[[nodiscard]] rpl::producer<> scrolls() const;
[[nodiscard]] int scrollTop() const;
@@ -318,6 +323,7 @@ private:
object_ptr _bottomShadow = { nullptr };
Ui::DraggingScrollManager _draggingScroll;
+ Ui::Animations::Simple _scrollAnimation;
rpl::event_stream<> _boxClosingStream;
diff --git a/ui/painter.h b/ui/painter.h
index dcc3f89..f9e630a 100644
--- a/ui/painter.h
+++ b/ui/painter.h
@@ -22,14 +22,16 @@ public:
void drawTextLeft(int x, int y, int outerw, const QString &text, int textWidth = -1) {
QFontMetrics m(fontMetrics());
- auto ascent = (_ascent == 0 ? m.ascent() : _ascent);
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);
}
void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1) {
QFontMetrics m(fontMetrics());
- auto ascent = (_ascent == 0 ? m.ascent() : _ascent);
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);
}
void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix, const QRect &from) {
@@ -84,10 +86,6 @@ public:
[[nodiscard]] bool inactive() const {
return _inactive;
}
- void setFont(const style::font &font) {
- _ascent = font->ascent;
- QPainter::setFont(font->f);
- }
void setTextSpoilerMess(not_null mess) {
_spoilerMess = mess;
}
@@ -102,7 +100,6 @@ private:
const style::TextPalette *_textPalette = nullptr;
Ui::Text::SpoilerMess *_spoilerMess = nullptr;
bool _inactive = false;
- int _ascent = 0;
};
diff --git a/ui/platform/mac/ui_window_title_mac.mm b/ui/platform/mac/ui_window_title_mac.mm
index 4b1100b..ddfe882 100644
--- a/ui/platform/mac/ui_window_title_mac.mm
+++ b/ui/platform/mac/ui_window_title_mac.mm
@@ -107,13 +107,13 @@ void TitleWidget::init(int height) {
const auto apple = (family == u".AppleSystemUIFont"_q);
setFromFont(style::font(
apple ? 13 : (height * 15) / 24,
- apple ? style::internal::FontBold : 0,
+ apple ? style::FontFlag::Bold : style::FontFlag(),
family));
break;
}
}
if (!_textStyle) {
- setFromFont(style::font(13, style::internal::FontSemibold, 0));
+ setFromFont(style::font(13, style::FontFlag::Semibold, 0));
}
}
diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp
index 926f51f..8ae5966 100644
--- a/ui/platform/ui_platform_window.cpp
+++ b/ui/platform/ui_platform_window.cpp
@@ -424,11 +424,36 @@ void DefaultWindowHelper::updateRoundingOverlay() {
rect.bottomRight() - QPoint(radiusWithFix, radiusWithFix),
radiusSize
)) || !rect.contains(clip);
- }) | rpl::start_with_next([=] {
+ }) | rpl::start_with_next([=](QRect clip) {
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);
_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);
Shadow::paint(p, rect, window()->width(), Shadow(), _sides, _corners);
}, _roundingOverlay->lifetime());
diff --git a/ui/platform/win/ui_window_win.cpp b/ui/platform/win/ui_window_win.cpp
index 4ac4d77..cedf461 100644
--- a/ui/platform/win/ui_window_win.cpp
+++ b/ui/platform/win/ui_window_win.cpp
@@ -576,6 +576,7 @@ bool WindowHelper::handleNativeEvent(
}
updateWindowFrameColors(active);
window()->update();
+ _title->update();
} return false;
case WM_NCPAINT: {
diff --git a/ui/rp_widget.cpp b/ui/rp_widget.cpp
index 41b24bc..0b57704 100644
--- a/ui/rp_widget.cpp
+++ b/ui/rp_widget.cpp
@@ -15,9 +15,6 @@
#include
#include
-// 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 {
public:
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
diff --git a/ui/style/style_core.cpp b/ui/style/style_core.cpp
index 8015bf3..e589944 100644
--- a/ui/style/style_core.cpp
+++ b/ui/style/style_core.cpp
@@ -29,12 +29,12 @@ auto PaletteVersion = 0;
auto ShortAnimationRunning = rpl::variable(false);
auto RunningShortAnimations = 0;
-std::vector &StyleModules() {
+[[nodiscard]] std::vector &StyleModules() {
static auto result = std::vector();
return result;
}
-void startModules(int scale) {
+void StartModules(int scale) {
for (const auto module : StyleModules()) {
module->start(scale);
}
@@ -60,14 +60,14 @@ void StopShortAnimation() {
} // namespace internal
-void startManager(int scale) {
- internal::registerFontFamily("Open Sans");
- internal::startModules(scale);
+void StartManager(int scale) {
+ internal::RegisterFontFamily("Open Sans");
+ internal::StartModules(scale);
}
-void stopManager() {
- internal::destroyFonts();
- internal::destroyIcons();
+void StopManager() {
+ internal::DestroyFonts();
+ internal::DestroyIcons();
}
rpl::producer<> PaletteChanged() {
diff --git a/ui/style/style_core.h b/ui/style/style_core.h
index 0d517f9..aed0de3 100644
--- a/ui/style/style_core.h
+++ b/ui/style/style_core.h
@@ -35,8 +35,8 @@ void StopShortAnimation();
} // namespace internal
-void startManager(int scale);
-void stopManager();
+void StartManager(int scale);
+void StopManager();
[[nodiscard]] rpl::producer<> PaletteChanged();
[[nodiscard]] int PaletteVersion();
diff --git a/ui/style/style_core_color.h b/ui/style/style_core_color.h
index 40a4def..1d7a592 100644
--- a/ui/style/style_core_color.h
+++ b/ui/style/style_core_color.h
@@ -57,27 +57,28 @@ public:
_data->set(r, g, b, a);
}
- operator const QBrush &() const {
+ [[nodiscard]] operator const QBrush &() const {
return _data->b;
}
- operator const QPen &() const {
+ [[nodiscard]] operator const QPen &() const {
return _data->p;
}
- ColorData *operator->() const {
+ [[nodiscard]] ColorData *operator->() const {
return _data;
}
- ColorData *v() const {
+ [[nodiscard]] ColorData *get() const {
return _data;
}
- explicit operator bool() const {
+ [[nodiscard]] explicit operator bool() const {
return !!_data;
}
class Proxy;
- Proxy operator[](const style::palette &paletteOverride) const;
+ [[nodiscard]] Proxy operator[](
+ const style::palette &paletteOverride) const;
private:
friend class OwnedColor;
@@ -164,12 +165,12 @@ public:
}
Proxy(const Proxy &other) = default;
- operator const QBrush &() const { return _color; }
- operator const QPen &() const { return _color; }
- ColorData *operator->() const { return _color.v(); }
- ColorData *v() const { return _color.v(); }
- explicit operator bool() const { return _color ? true : false; }
- Color clone() const { return _color; }
+ [[nodiscard]] operator const QBrush &() const { return _color; }
+ [[nodiscard]] operator const QPen &() const { return _color; }
+ [[nodiscard]] ColorData *operator->() const { return _color.get(); }
+ [[nodiscard]] ColorData *get() const { return _color.get(); }
+ [[nodiscard]] explicit operator bool() const { return !!_color; }
+ [[nodiscard]] Color clone() const { return _color; }
private:
Color _color;
diff --git a/ui/style/style_core_custom_font.cpp b/ui/style/style_core_custom_font.cpp
deleted file mode 100644
index 6c279a1..0000000
--- a/ui/style/style_core_custom_font.cpp
+++ /dev/null
@@ -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
-#include
-
-namespace style {
-namespace {
-
-using namespace internal;
-
-auto RegularFont = CustomFont();
-auto BoldFont = CustomFont();
-
-} // namespace
-
-void SetCustomFonts(const CustomFont ®ular, 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
diff --git a/ui/style/style_core_custom_font.h b/ui/style/style_core_custom_font.h
deleted file mode 100644
index 289168b..0000000
--- a/ui/style/style_core_custom_font.h
+++ /dev/null
@@ -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 ®ular, const CustomFont &bold);
-
-[[nodiscard]] QFont ResolveFont(const QString &familyOverride, uint32 flags, int size);
-
-} // namespace style
diff --git a/ui/style/style_core_font.cpp b/ui/style/style_core_font.cpp
index 3e58c5f..db336ea 100644
--- a/ui/style/style_core_font.cpp
+++ b/ui/style/style_core_font.cpp
@@ -8,19 +8,15 @@
#include "base/algorithm.h"
#include "base/debug_log.h"
+#include "base/variant.h"
#include "base/base_file_utilities.h"
-#include "ui/style/style_core_custom_font.h"
#include "ui/integration.h"
+#include "ui/style/style_core_scale.h"
#include
#include
#include
#include
-#include
-
-#if __has_include()
-#include
-#endif
void style_InitFontsResource() {
#ifdef Q_OS_MAC // Use resources from the .app bundle on macOS.
@@ -40,80 +36,118 @@ void style_InitFontsResource() {
}
namespace style {
-namespace internal {
namespace {
-QMap fontFamilyMap;
-QVector fontFamilies;
-QMap fontsMap;
+QString Custom;
+CustomFontSettings CustomSettings;
-uint32 fontKey(int size, uint32 flags, int family) {
- return (((uint32(family) << 12) | uint32(size)) << 6) | flags;
+} // namespace
+
+const QString &SystemFontTag() {
+ static const auto result = u"(system)"_q + QChar(0);
+ return result;
}
-bool ValidateFont(const QString &familyName, int flags = 0) {
- QFont checkFont(familyName);
- 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;
- }
+void SetCustomFont(const QString &font) {
+ Custom = font;
+}
- auto metrics = QFontMetrics(checkFont);
- if (!metrics.height()) {
- LOG(("Font Error: got a zero height in '%1'.").arg(familyName));
- return false;
- }
+void SetCustomFontSettings(const CustomFontSettings &settings) {
+ CustomSettings = settings;
+}
- 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 FontFamilyIndices;
+std::vector FontFamilies;
+base::flat_map> FontsByKey;
+base::flat_map 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();
+
+ 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
-bool LoadCustomFont(const QString &filePath, const QString &familyName, int flags = 0) {
+bool LoadCustomFont(const QString &filePath) {
auto regularId = QFontDatabase::addApplicationFont(filePath);
if (regularId < 0) {
LOG(("Font Error: could not add '%1'.").arg(filePath));
return false;
}
- const auto found = [&] {
- for (auto &family : QFontDatabase::applicationFontFamilies(regularId)) {
- 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;
+ for (auto &family : QFontDatabase::applicationFontFamilies(regularId)) {
+ LOG(("Font: from '%1' loaded '%2'").arg(filePath, family));
}
- return ValidateFont(familyName, flags);
+ return true;
}
#endif // !LIB_UI_USE_PACKAGED_FONTS
-[[nodiscard]] QString SystemMonospaceFont() {
- const auto type = QFontDatabase::FixedFont;
- return QFontDatabase::systemFont(type).family();
-}
-
bool TryFont(const QString &attempt) {
const auto resolved = QFontInfo(QFont(attempt)).family();
return !resolved.trimmed().compare(attempt, Qt::CaseInsensitive);
}
+[[nodiscard]] QString SystemMonospaceFont() {
+ const auto type = QFontDatabase::FixedFont;
+ return QFontDatabase::systemFont(type).family();
+}
+
[[nodiscard]] QString ManualMonospaceFont() {
const auto kTryFirst = std::initializer_list{
- "Cascadia Mono",
- "Consolas",
- "Liberation Mono",
- "Menlo",
- "Courier"
+ u"Cascadia Mono"_q,
+ u"Consolas"_q,
+ u"Liberation Mono"_q,
+ u"Menlo"_q,
+ u"Courier"_q,
};
for (const auto &family : kTryFirst) {
if (TryFont(family)) {
@@ -123,188 +157,10 @@ bool TryFont(const QString &attempt) {
return QString();
}
-QFontMetrics GetFontMetrics(int size) {
-#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() // !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() {
+[[nodiscard]] QString MonospaceFont() {
static const auto family = [&]() -> QString {
- const auto fontSettings = Ui::Integration::Instance().fontSettings();
-
- if (TryFont(fontSettings.monospaceFont)) {
- return fontSettings.monospaceFont;
+ if (TryFont(CustomSettings.monospaceFont)) {
+ return CustomSettings.monospaceFont;
}
const auto manual = ManualMonospaceFont();
@@ -319,86 +175,306 @@ QString MonospaceFont() {
const auto useSystem = manual.isEmpty()
|| (metrics.horizontalAdvance(QChar('i')) == metrics.horizontalAdvance(QChar('W')));
#endif // Q_OS_WIN
- return (useSystem || fontSettings.useSystemFont) ? system : manual;
+ return useSystem ? system : manual;
}();
return family;
}
-void destroyFonts() {
- for (auto fontData : std::as_const(fontsMap)) {
- delete fontData;
+struct Metrics {
+ int pixelSize = 0;
+ float64 ascent = 0.;
+ float64 height = 0.;
+};
+[[nodiscard]] Metrics ComputeMetrics(QFont font, bool adjust) {
+ constexpr auto kMaxSizeShift = 8;
+
+ const auto startSize = font.pixelSize();
+ const auto metrics = QFontMetricsF(font);
+ const auto simple = [&] {
+ return Metrics{
+ .pixelSize = startSize,
+ .ascent = metrics.ascent(),
+ .height = metrics.height(),
+ };
+ };
+
+ const auto family = font.family();
+ const auto basic = u"Open Sans"_q;
+ if (family == basic || !adjust) {
+ return simple();
}
- fontsMap.clear();
-}
-int registerFontFamily(const QString &family) {
- auto result = fontFamilyMap.value(family, -1);
- if (result < 0) {
- result = fontFamilies.size();
- fontFamilyMap.insert(family, result);
- fontFamilies.push_back(family);
+ auto copy = font;
+ copy.setFamily(basic);
+ const auto basicMetrics = QFontMetricsF(copy);
+
+ static const auto Full = u"bdfghijklpqtyBDFGHIJKLPQTY1234567890[]{}()"_q;
+
+ // I tried to choose height in such way that
+ // - normal fonts won't be too large,
+ // - some exotic fonts, like Symbol (Greek), won't be too small,
+ // - some other exotic fonts, like Segoe Script, won't be too large.
+ const auto Height = [](const QFontMetricsF &metrics) {
+ //static const auto Test = u"acemnorsuvwxz"_q;
+ //return metrics.tightBoundingRect(Test).height();
+
+ //static const auto Test = u"acemnorsuvwxz"_q;
+ //auto result = metrics.boundingRect(Test[0]).height();
+ //for (const auto &ch : Test | ranges::views::drop(1)) {
+ // const auto single = metrics.boundingRect(ch).height();
+ // if (result > single) {
+ // result = single;
+ // }
+ //}
+ //return result;
+
+ //static const auto Test = u"acemnorsuvwxz"_q;
+ //auto result = 0.;
+ //for (const auto &ch : Test) {
+ // result -= metrics.boundingRect(ch).y();
+ //}
+ //return result / Test.size();
+
+ static const char16_t Test[] = u"acemnorsuvwxz";
+ constexpr auto kCount = int(std::size(Test)) - 1;
+
+ auto heights = std::array{};
+ 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)
-: f(ResolveFont(family ? fontFamilies[family] : QString(), flags, size))
-, _m(f)
-, _size(size)
-, _flags(flags)
-, _family(family) {
- if (other) {
- memcpy(_modified, other, sizeof(_modified));
+ const auto adjusted = [&](int size, const QFontMetricsF &metrics) {
+ const auto full = metrics.tightBoundingRect(Full);
+ const auto desiredTightHeight = desiredFull.height();
+ const auto heightAdd = basicMetrics.height() - desiredTightHeight;
+ const auto tightHeight = full.height();
+ return Metrics{
+ .pixelSize = size,
+ .ascent = basicMetrics.ascent(),
+ .height = tightHeight + heightAdd,
+ };
+ };
+
+ auto current = Height(metrics);
+ if (current < 1.) {
+ return simple();
+ } else if (std::abs(current - desired) < 0.2) {
+ return adjusted(startSize, metrics);
}
- _modified[_flags] = Font(this);
- const auto fontSettings = Ui::Integration::Instance().fontSettings();
- if (fontSettings.useOriginalMetrics && !(_flags & FontMonospace)) {
- const auto mOrig = GetFontMetrics(size);
-
- height = int(base::SafeRound(mOrig.height()));
- ascent = int(base::SafeRound(mOrig.ascent()));
- descent = int(base::SafeRound(mOrig.descent()));
+ const auto adjustedByFont = [&](const QFont &font) {
+ return adjusted(font.pixelSize(), QFontMetricsF(font));
+ };
+ const auto max = std::min(kMaxSizeShift, startSize - 1);
+ if (current < desired) {
+ for (auto i = 0; i != max; ++i) {
+ const auto shift = i + 1;
+ font.setPixelSize(startSize + shift);
+ const auto metrics = QFontMetricsF(font);
+ const auto now = Height(metrics);
+ if (now > desired) {
+ const auto better = (now - desired) < (desired - current);
+ if (better) {
+ return adjusted(startSize + shift, metrics);
+ }
+ font.setPixelSize(startSize + shift - 1);
+ return adjustedByFont(font);
+ }
+ current = now;
+ }
+ font.setPixelSize(startSize + max);
+ return adjustedByFont(font);
} else {
- height = int(base::SafeRound(_m.height()));
- ascent = int(base::SafeRound(_m.ascent()));
- descent = int(base::SafeRound(_m.descent()));
+ for (auto i = 0; i != max; ++i) {
+ const auto shift = i + 1;
+ font.setPixelSize(startSize - shift);
+ const auto metrics = QFontMetricsF(font);
+ const auto now = Height(metrics);
+ if (now < desired) {
+ const auto better = (desired - now) < (current - desired);
+ if (better) {
+ return adjusted(startSize - shift, metrics);
+ }
+ font.setPixelSize(startSize - shift + 1);
+ return adjustedByFont(font);
+ }
+ current = now;
+ }
+ font.setPixelSize(startSize - max);
+ return adjustedByFont(font);
+ }
+}
+
+[[nodiscard]] FontResolveResult ResolveFont(
+ const QString &family,
+ FontFlags flags,
+ int size) {
+ auto font = QFont(QFont().family());
+
+ const auto monospace = (flags & FontFlag::Monospace) != 0;
+ const auto system = !monospace && (family == SystemFontTag());
+ const auto overriden = !monospace && !system && !family.isEmpty();
+ if (monospace) {
+ font.setFamily(MonospaceFont());
+ } else if (system) {
+ } else if (overriden) {
+ font.setFamily(family);
+ } else {
+ font.setFamily("Open Sans"_q);
+#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(' '));
- elidew = width("...");
+ elidew = width(u"..."_q);
}
Font FontData::bold(bool set) const {
- return otherFlagsFont(FontBold, set);
+ return otherFlagsFont(FontFlag::Bold, set);
}
Font FontData::italic(bool set) const {
- return otherFlagsFont(FontItalic, set);
+ return otherFlagsFont(FontFlag::Italic, set);
}
Font FontData::underline(bool set) const {
- return otherFlagsFont(FontUnderline, set);
+ return otherFlagsFont(FontFlag::Underline, set);
}
Font FontData::strikeout(bool set) const {
- return otherFlagsFont(FontStrikeOut, set);
+ return otherFlagsFont(FontFlag::StrikeOut, set);
}
Font FontData::semibold(bool set) const {
- return otherFlagsFont(FontSemibold, set);
+ return otherFlagsFont(FontFlag::Semibold, set);
}
Font FontData::monospace(bool set) const {
- return otherFlagsFont(FontMonospace, set);
+ return otherFlagsFont(FontFlag::Monospace, set);
}
int FontData::size() const {
return _size;
}
-uint32 FontData::flags() const {
+FontFlags FontData::flags() const {
return _flags;
}
@@ -406,45 +482,60 @@ int FontData::family() const {
return _family;
}
-Font FontData::otherFlagsFont(uint32 flag, bool set) const {
- int32 newFlags = set ? (_flags | flag) : (_flags & ~flag);
- if (!_modified[newFlags].v()) {
- _modified[newFlags] = Font(_size, newFlags, _family, _modified);
+Font FontData::otherFlagsFont(FontFlag flag, bool set) const {
+ const auto newFlags = set ? (_flags | flag) : (_flags & ~flag);
+ if (!_modified[newFlags]) {
+ _modified[newFlags] = Font(_size, newFlags, _family, &_modified);
}
return _modified[newFlags];
}
-Font::Font(int size, uint32 flags, const QString &family) {
- if (fontFamilyMap.isEmpty()) {
- 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, FontFlags flags, const QString &family) {
+ init(size, flags, RegisterFontFamily(family), 0);
}
-Font::Font(int size, uint32 flags, int family) {
+Font::Font(int size, FontFlags flags, int family) {
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);
}
-void Font::init(int size, uint32 flags, int family, Font *modified) {
- uint32 key = fontKey(size, flags, family);
- auto i = fontsMap.constFind(key);
- if (i == fontsMap.cend()) {
- i = fontsMap.insert(key, new FontData(size, flags, family, modified));
+void Font::init(
+ int size,
+ FontFlags flags,
+ int family,
+ FontVariants *modified) {
+ const auto key = FontKey(size, flags, family);
+ auto i = FontsByKey.find(key);
+ if (i == end(FontsByKey)) {
+ i = FontsByKey.emplace(
+ key,
+ std::make_unique(
+ 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
+
+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
diff --git a/ui/style/style_core_font.h b/ui/style/style_core_font.h
index f26c125..ad43c02 100644
--- a/ui/style/style_core_font.h
+++ b/ui/style/style_core_font.h
@@ -7,6 +7,7 @@
#pragma once
#include "base/basic_types.h"
+#include "base/flags.h"
#include
#include
@@ -14,59 +15,86 @@
#include
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;
+
+struct FontResolveResult {
+ QFont font;
+ float64 ascent = 0.;
+ float64 height = 0.;
+ int iascent = 0;
+ int iheight = 0;
+ int requestedFamily = 0;
+ int requestedSize = 0;
+ FontFlags requestedFlags;
+};
+[[nodiscard]] const FontResolveResult *FindAdjustResult(const QFont &font);
+
namespace internal {
void StartFonts();
-[[nodiscard]] QString GetPossibleEmptyOverride(int32 flags = 0);
-[[nodiscard]] QString GetFontOverride(int32 flags = 0);
-[[nodiscard]] QString MonospaceFont();
-void destroyFonts();
-int registerFontFamily(const QString &family);
+void DestroyFonts();
+int RegisterFontFamily(const QString &family);
+
+inline constexpr auto kFontVariants = 0x40;
+
+class Font;
+using FontVariants = std::array;
class FontData;
-class Font {
+class Font final {
public:
Font(Qt::Initialization = Qt::Uninitialized) {
}
- Font(int size, uint32 flags, const QString &family);
- Font(int size, uint32 flags, int family);
+ Font(int size, FontFlags flags, const QString &family);
+ Font(int size, FontFlags flags, int family);
- FontData *operator->() const {
- return ptr;
+ [[nodiscard]] FontData *operator->() const {
+ return _data;
}
- FontData *v() const {
- return ptr;
+ [[nodiscard]] FontData *get() const {
+ return _data;
}
- operator bool() const {
- return !!ptr;
+ [[nodiscard]] operator bool() const {
+ return _data != nullptr;
}
- operator const QFont &() const;
+ [[nodiscard]] operator const QFont &() const;
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 OwnedFont;
-};
+ FontData *_data = nullptr;
-enum FontFlags {
- FontBold = 0x01,
- FontItalic = 0x02,
- FontUnderline = 0x04,
- FontStrikeOut = 0x08,
- FontSemibold = 0x10,
- FontMonospace = 0x20,
+ void init(int size, FontFlags flags, int family, FontVariants *modified);
+ friend void StartManager();
+
+ explicit Font(FontData *data) : _data(data) {
+ }
+ Font(int size, FontFlags flags, int family, FontVariants *modified);
- FontDifferentFlags = 0x40,
};
class FontData {
@@ -94,37 +122,74 @@ public:
[[nodiscard]] Font semibold(bool set = true) const;
[[nodiscard]] Font monospace(bool set = true) const;
- int size() const;
- uint32 flags() const;
- int family() const;
+ [[nodiscard]] int size() const;
+ [[nodiscard]] FontFlags flags() const;
+ [[nodiscard]] int family() const;
QFont f;
- int32 height, ascent, descent, spacew, elidew;
+ int height = 0;
+ int ascent = 0;
+ int descent = 0;
+ int spacew = 0;
+ int elidew = 0;
private:
- mutable Font _modified[FontDifferentFlags];
+ friend class OwnedFont;
+ friend struct ResolvedFont;
- Font otherFlagsFont(uint32 flag, bool set) const;
- FontData(int size, uint32 flags, int family, Font *other);
+ mutable FontVariants _modified;
+
+ [[nodiscard]] Font otherFlagsFont(FontFlag flag, bool set) const;
+ FontData(const FontResolveResult &data, FontVariants *modified);
- friend class Font;
QFontMetricsF _m;
- int _size;
- uint32 _flags;
- int _family;
+ int _size = 0;
+ int _family = 0;
+ FontFlags _flags = 0;
};
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) {
- return a.v() != b.v();
+ return a.get() != b.get();
}
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 style
diff --git a/ui/style/style_core_icon.cpp b/ui/style/style_core_icon.cpp
index 3ddc9ee..3f00b90 100644
--- a/ui/style/style_core_icon.cpp
+++ b/ui/style/style_core_icon.cpp
@@ -393,14 +393,14 @@ Icon Icon::withPalette(const style::palette &palette) const {
return result;
}
-void resetIcons() {
+void ResetIcons() {
iconPixmaps.clear();
for (const auto data : iconData) {
data->reset();
}
}
-void destroyIcons() {
+void DestroyIcons() {
iconData.clear();
iconPixmaps.clear();
diff --git a/ui/style/style_core_icon.h b/ui/style/style_core_icon.h
index 9afd9ee..398f176 100644
--- a/ui/style/style_core_icon.h
+++ b/ui/style/style_core_icon.h
@@ -295,8 +295,8 @@ private:
};
-void resetIcons();
-void destroyIcons();
+void ResetIcons();
+void DestroyIcons();
} // namespace internal
} // namespace style
diff --git a/ui/style/style_core_palette.cpp b/ui/style/style_core_palette.cpp
index 687c764..bad3e65 100644
--- a/ui/style/style_core_palette.cpp
+++ b/ui/style/style_core_palette.cpp
@@ -226,7 +226,7 @@ QByteArray save() {
bool load(const QByteArray &cache) {
if (GetMutable().load(cache)) {
- style::internal::resetIcons();
+ style::internal::ResetIcons();
return true;
}
return false;
@@ -242,17 +242,17 @@ palette::SetResult setColor(QLatin1String name, QLatin1String from) {
void apply(const palette &other) {
GetMutable() = other;
- style::internal::resetIcons();
+ style::internal::ResetIcons();
}
void reset() {
GetMutable().reset();
- style::internal::resetIcons();
+ style::internal::ResetIcons();
}
void reset(const colorizer &with) {
GetMutable().reset(with);
- style::internal::resetIcons();
+ style::internal::ResetIcons();
}
int indexOfColor(color c) {
diff --git a/ui/style/style_core_types.h b/ui/style/style_core_types.h
index c30adae..4dd34c0 100644
--- a/ui/style/style_core_types.h
+++ b/ui/style/style_core_types.h
@@ -27,6 +27,7 @@ using cursor = Qt::CursorShape;
using align = Qt::Alignment;
using margins = QMargins;
using font = internal::Font;
+using owned_font = internal::OwnedFont;
using color = internal::Color;
using owned_color = internal::OwnedColor;
using complex_color = internal::ComplexColor;
diff --git a/ui/text/text.cpp b/ui/text/text.cpp
index 367134c..c3d20dc 100644
--- a/ui/text/text.cpp
+++ b/ui/text/text.cpp
@@ -984,6 +984,8 @@ void String::enumerateLines(
return withElided(true);
}
lineHeight = 0;
+ last_rBearing = 0;// b->f_rbearing(); (0 for newline)
+ last_rPadding = 0;// b->f_rpadding(); (0 for newline)
initNextParagraph(i + 1, index);
longWordLine = true;
@@ -1810,8 +1812,7 @@ bool IsSpace(QChar ch) {
|| (ch == QChar::LineSeparator)
|| (ch == QChar::ObjectReplacementCharacter)
|| (ch == QChar::CarriageReturn)
- || (ch == QChar::Tabulation)
- || (ch == QChar(8203)/*Zero width space.*/);
+ || (ch == QChar::Tabulation);
}
bool IsDiacritic(QChar ch) { // diacritic and variation selectors
@@ -1821,6 +1822,9 @@ bool IsDiacritic(QChar ch) { // diacritic and variation selectors
}
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
// QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad");
// \xcc[\xb3\xbf\x8a] // 819, 831, 778
@@ -1836,7 +1840,9 @@ bool IsReplacedBySpace(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
diff --git a/ui/text/text_block.cpp b/ui/text/text_block.cpp
index afe8fb7..9403e8b 100644
--- a/ui/text/text_block.cpp
+++ b/ui/text/text_block.cpp
@@ -373,28 +373,31 @@ bool BlockParser::isSpaceBreak(
style::font WithFlags(
const style::font &font,
TextBlockFlags flags,
- uint32 fontFlags) {
+ style::FontFlags fontFlags) {
using namespace style::internal;
+ using Flag = style::FontFlag;
if (!flags && !fontFlags) {
return font;
- } else if (IsMono(flags) || (fontFlags & FontMonospace)) {
+ } else if (IsMono(flags) || (fontFlags & Flag::Monospace)) {
return font->monospace();
}
auto result = font;
- if ((flags & TextBlockFlag::Bold) || (fontFlags & FontBold)) {
+ if ((flags & TextBlockFlag::Bold) || (fontFlags & Flag::Bold)) {
result = result->bold();
} else if ((flags & TextBlockFlag::Semibold)
- || (fontFlags & FontSemibold)) {
+ || (fontFlags & Flag::Semibold)) {
result = result->semibold();
}
- if ((flags & TextBlockFlag::Italic) || (fontFlags & FontItalic)) {
+ if ((flags & TextBlockFlag::Italic) || (fontFlags & Flag::Italic)) {
result = result->italic();
}
- if ((flags & TextBlockFlag::Underline) || (fontFlags & FontUnderline)) {
+ if ((flags & TextBlockFlag::Underline)
+ || (fontFlags & Flag::Underline)) {
result = result->underline();
}
- if ((flags & TextBlockFlag::StrikeOut) || (fontFlags & FontStrikeOut)) {
+ if ((flags & TextBlockFlag::StrikeOut)
+ || (fontFlags & Flag::StrikeOut)) {
result = result->strikeout();
}
if (flags & TextBlockFlag::Tilde) { // Tilde fix in OpenSans.
diff --git a/ui/text/text_block.h b/ui/text/text_block.h
index aa51882..0eee5e7 100644
--- a/ui/text/text_block.h
+++ b/ui/text/text_block.h
@@ -47,7 +47,7 @@ using TextBlockFlags = base::flags;
[[nodiscard]] style::font WithFlags(
const style::font &font,
TextBlockFlags flags,
- uint32 fontFlags = 0);
+ style::FontFlags fontFlags = 0);
[[nodiscard]] Qt::LayoutDirection UnpackParagraphDirection(
bool ltr,
diff --git a/ui/text/text_parser.cpp b/ui/text/text_parser.cpp
index 96a045d..34876de 100644
--- a/ui/text/text_parser.cpp
+++ b/ui/text/text_parser.cpp
@@ -78,7 +78,17 @@ constexpr auto kMaxDiacAfterSymbol = 2;
const auto &font = st.font;
return (font->size() * style::DevicePixelRatio() == 13)
&& (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
@@ -567,7 +577,7 @@ void Parser::parseCurrentChar() {
createBlock(-_emojiLookback);
}
_t->_text.push_back(_ch);
- _allowDiacritic = true;
+ _allowDiacritic = IsDiacriticAllowedAfter(_ch);
}
if (!isDiacritic) {
_diacritics = 0;
diff --git a/ui/text/text_renderer.cpp b/ui/text/text_renderer.cpp
index 58e72bd..397c22a 100644
--- a/ui/text/text_renderer.cpp
+++ b/ui/text/text_renderer.cpp
@@ -156,6 +156,12 @@ bool Distinct(FixedRange a, FixedRange b) {
Renderer::Renderer(const Ui::Text::String &t)
: _t(&t)
, _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() {
diff --git a/ui/ui_utility.h b/ui/ui_utility.h
index 5e2dd81..5601942 100644
--- a/ui/ui_utility.h
+++ b/ui/ui_utility.h
@@ -56,16 +56,23 @@ inline base::unique_qptr CreateObject(Args &&...args) {
template
inline Value *CreateChild(
- Parent *parent,
+ Parent parent,
Args &&...args) {
- Expects(parent != nullptr);
+ if constexpr (std::is_pointer_v) {
+ Expects(parent != nullptr);
- if constexpr (std::is_base_of_v) {
- return new Value(parent, std::forward(args)...);
+ if constexpr (std::is_base_of_v) {
+ return new Value(parent, std::forward(args)...);
+ } else {
+ return CreateChild>(
+ parent,
+ std::forward(args)...)->value();
+ }
+ } else if constexpr (requires(const Parent & t) { t.get(); }) {
+ return new Value(parent.get(), std::forward(args)...);
} else {
- return CreateChild>(
- parent,
- std::forward(args)...)->value();
+ static_assert(requires(const Parent &t) { t.data(); });
+ return new Value(parent.data(), std::forward(args)...);
}
}
diff --git a/ui/widgets/buttons.cpp b/ui/widgets/buttons.cpp
index 389f957..bc1e439 100644
--- a/ui/widgets/buttons.cpp
+++ b/ui/widgets/buttons.cpp
@@ -25,9 +25,7 @@ LinkButton::LinkButton(
, _st(st)
, _text(text)
, _textWidth(st.font->width(_text)) {
- resize(
- naturalWidth(),
- _st.padding.top() + _st.font->height + _st.padding.bottom());
+ resizeToText();
setCursor(style::cur_pointer);
}
@@ -59,10 +57,16 @@ void LinkButton::paintEvent(QPaintEvent *e) {
void LinkButton::setText(const QString &text) {
_text = text;
_textWidth = _st.font->width(_text);
- resize(naturalWidth(), _st.font->height);
+ resizeToText();
update();
}
+void LinkButton::resizeToText() {
+ resize(
+ naturalWidth(),
+ _st.padding.top() + _st.font->height + _st.padding.bottom());
+}
+
void LinkButton::setColorOverride(std::optional textFg) {
_textFgOverride = textFg;
update();
diff --git a/ui/widgets/buttons.h b/ui/widgets/buttons.h
index d39f65e..9f57206 100644
--- a/ui/widgets/buttons.h
+++ b/ui/widgets/buttons.h
@@ -37,6 +37,8 @@ protected:
void onStateChanged(State was, StateChangeSource source) override;
private:
+ void resizeToText();
+
const style::LinkButton &_st;
QString _text;
int _textWidth = 0;
diff --git a/ui/widgets/elastic_scroll.cpp b/ui/widgets/elastic_scroll.cpp
index ec5c2a0..e9838c3 100644
--- a/ui/widgets/elastic_scroll.cpp
+++ b/ui/widgets/elastic_scroll.cpp
@@ -381,6 +381,14 @@ ElasticScroll::ElasticScroll(
}, _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) {
if (_touchDisabled != handle) {
return;
@@ -1071,13 +1079,15 @@ void ElasticScroll::keyPressEvent(QKeyEvent *e) {
}
void ElasticScroll::enterEventHook(QEnterEvent *e) {
- if (!_disabled) {
+ if (_bar && !_disabled) {
_bar->toggle(true);
}
}
void ElasticScroll::leaveEventHook(QEvent *e) {
- _bar->toggle(false);
+ if (_bar) {
+ _bar->toggle(false);
+ }
}
void ElasticScroll::scrollTo(ScrollToRequest request) {
diff --git a/ui/widgets/elastic_scroll.h b/ui/widgets/elastic_scroll.h
index 8404c98..e905341 100644
--- a/ui/widgets/elastic_scroll.h
+++ b/ui/widgets/elastic_scroll.h
@@ -113,6 +113,7 @@ public:
QWidget *parent,
const style::ScrollArea &st = st::defaultScrollArea,
Qt::Orientation orientation = Qt::Vertical);
+ ~ElasticScroll();
void setHandleTouch(bool handle);
bool viewportEvent(QEvent *e);
diff --git a/ui/widgets/fields/input_field.cpp b/ui/widgets/fields/input_field.cpp
index eba736f..95b6d4e 100644
--- a/ui/widgets/fields/input_field.cpp
+++ b/ui/widgets/fields/input_field.cpp
@@ -6,12 +6,12 @@
//
#include "ui/widgets/fields/input_field.h"
-#include "ui/widgets/popup_menu.h"
#include "ui/text/text.h"
+#include "ui/widgets/labels.h"
+#include "ui/widgets/popup_menu.h"
#include "ui/emoji_config.h"
#include "ui/ui_utility.h"
#include "ui/painter.h"
-#include "ui/style/style_core_custom_font.h"
#include "base/invoke_queued.h"
#include "base/random.h"
#include "base/platform/base_platform_info.h"
@@ -138,7 +138,7 @@ bool IsNewline(QChar ch) {
}
[[nodiscard]] bool IsCustomEmojiLink(QStringView link) {
- return link.startsWith(Ui::InputField::kCustomEmojiTagStart);
+ return link.startsWith(InputField::kCustomEmojiTagStart);
}
[[nodiscard]] QString MakeUniqueCustomEmojiLink(QStringView link) {
@@ -157,7 +157,7 @@ bool IsNewline(QChar ch) {
}
[[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);
return base::StringViewMid(
link,
@@ -702,16 +702,16 @@ QString AccumulateText(Iterator begin, Iterator end) {
return result;
}
-QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, const QFont &font) {
+QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, int lineHeight) {
const auto factor = style::DevicePixelRatio();
const auto size = Emoji::GetSizeNormal();
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();
result.setWidth(width / factor);
result.setHeight(height / factor);
result.setName(emoji->toUrl());
- result.setVerticalAlignment(QTextCharFormat::AlignBottom);
+ result.setVerticalAlignment(QTextCharFormat::AlignTop);
return result;
}
@@ -751,6 +751,7 @@ QTextCharFormat PrepareTagFormat(
result.setProperty(
kCustomEmojiId,
CustomEmojiIdFromLink(replaceWith));
+ result.setVerticalAlignment(QTextCharFormat::AlignTop);
} else if (IsValidMarkdownLink(tag)) {
color = st::defaultTextPalette.linkFg;
} else if (tag == kTagBold) {
@@ -923,6 +924,7 @@ struct FormattingAction {
RemoveTag,
RemoveNewline,
ClearInstantReplace,
+ FixLineHeight,
};
Type type = Type::Invalid;
@@ -1026,7 +1028,12 @@ private:
void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji) {
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);
cursor.insertText(kObjectReplacement, format);
}
@@ -1043,7 +1050,7 @@ void InsertCustomEmojiAtCursor(
format.setProperty(kCustomEmojiText, text);
format.setProperty(kCustomEmojiLink, unique);
format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
- format.setVerticalAlignment(QTextCharFormat::AlignBottom);
+ format.setVerticalAlignment(QTextCharFormat::AlignTop);
format.setFont(field->st().font);
format.setForeground(field->st().textFg);
format.setBackground(QBrush());
@@ -1122,8 +1129,12 @@ const InstantReplaces &InstantReplaces::Custom() {
return result;
}
-CustomEmojiObject::CustomEmojiObject(Factory factory, Fn paused)
-: _factory(std::move(factory))
+CustomEmojiObject::CustomEmojiObject(
+ const style::font &font,
+ Factory factory,
+ Fn paused)
+: _font(font)
+, _factory(std::move(factory))
, _paused(std::move(paused))
, _now(crl::now()) {
}
@@ -1143,10 +1154,9 @@ QSizeF CustomEmojiObject::intrinsicSize(
const QTextFormat &format) {
const auto size = st::emojiSize * 1.;
const auto width = size + st::emojiPadding * 2.;
- const auto font = format.toCharFormat().font();
- const auto height = std::min(QFontMetrics(font).height() * 1., size);
+ const auto height = std::max(_font->height * 1., size);
if (!_skip) {
- const auto emoji = Ui::Text::AdjustCustomEmojiSize(st::emojiSize);
+ const auto emoji = Text::AdjustCustomEmojiSize(st::emojiSize);
_skip = (st::emojiSize - emoji) / 2;
}
return { width, height };
@@ -1236,6 +1246,30 @@ InputField::InputField(
_inner->setAcceptRichText(false);
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.
&& !_st.borderRadius) {
setAttribute(Qt::WA_OpaquePaintEvent);
@@ -1256,11 +1290,21 @@ InputField::InputField(
) | rpl::start_with_next([=] {
updatePalette();
}, lifetime());
+ {
+ auto cursor = _inner->textCursor();
- _defaultCharFormat = _inner->textCursor().charFormat();
- updatePalette();
- _inner->textCursor().setCharFormat(_defaultCharFormat);
+ _defaultCharFormat = cursor.charFormat();
+ updatePalette();
+ cursor.setCharFormat(_defaultCharFormat);
+ _defaultBlockFormat = cursor.blockFormat();
+ _defaultBlockFormat.setLineHeight(
+ _st.font->height,
+ QTextBlockFormat::FixedHeight);
+ cursor.setBlockFormat(_defaultBlockFormat);
+
+ _inner->setTextCursor(cursor);
+ }
_inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@@ -1268,14 +1312,6 @@ InputField::InputField(
_inner->viewport()->setAutoFillBackground(false);
_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);
setAttribute(Qt::WA_AcceptTouchEvents);
@@ -1310,6 +1346,7 @@ InputField::InputField(
auto cursor = textCursor();
if (!cursor.hasSelection() && !cursor.position()) {
cursor.setCharFormat(_defaultCharFormat);
+ cursor.setBlockFormat(_defaultBlockFormat);
setTextCursor(cursor);
}
}, lifetime());
@@ -1329,8 +1366,8 @@ InputField::InputField(
setCursor(style::cur_text);
heightAutoupdated();
- if (!_lastTextWithTags.text.isEmpty()) {
- setTextWithTags(_lastTextWithTags, HistoryAction::Clear);
+ if (!value.text.isEmpty()) {
+ setTextWithTags(value, HistoryAction::Clear);
}
startBorderAnimation();
@@ -1476,7 +1513,7 @@ void InputField::setTagMimeProcessor(Fn processor) {
void InputField::setCustomEmojiFactory(
CustomEmojiFactory factory,
Fn paused) {
- _customEmojiObject = std::make_unique([=](
+ _customEmojiObject = std::make_unique(_st.font, [=](
QStringView data) {
return factory(data, [=] { customEmojiRepaint(); });
}, std::move(paused));
@@ -1781,6 +1818,13 @@ void InputField::paintEvent(QPaintEvent *e) {
const auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
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()) {
auto placeholderShiftDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
p.save();
@@ -1788,7 +1832,7 @@ void InputField::paintEvent(QPaintEvent *e) {
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);
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
@@ -1815,29 +1859,16 @@ void InputField::paintEvent(QPaintEvent *e) {
p.setFont(_st.placeholderFont);
p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree));
-
if (_st.placeholderAlign == style::al_topleft && _placeholderAfterSymbols > 0) {
const auto skipWidth = placeholderSkipWidth();
p.drawText(
- _st.textMargins.left() + _st.placeholderMargins.left() + skipWidth,
- _st.textMargins.top() + _st.placeholderMargins.top() + _st.placeholderFont->ascent,
+ margins.left() + skipWidth,
+ margins.top() + _st.placeholderFont->ascent,
_placeholder);
} else {
- auto r = rect().marginsRemoved(_st.textMargins + _st.placeholderMargins);
+ auto r = rect().marginsRemoved(margins);
r.moveLeft(r.left() + placeholderLeft);
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);
}
@@ -1880,7 +1911,7 @@ void InputField::focusInEvent(QFocusEvent *e) {
? mapFromGlobal(QCursor::pos()).x()
: (width() / 2);
InvokeQueued(this, [=] {
- if (hasFocus()) {
+ if (QWidget::hasFocus()) {
focusInner();
}
});
@@ -2139,7 +2170,7 @@ bool InputField::isRedoAvailable() const {
void InputField::processFormatting(int insertPosition, int insertEnd) {
// Tilde formatting.
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 tildeFixedFont = _st.font->semibold()->f;
@@ -2170,6 +2201,10 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
if (tillBlock.isValid()) tillBlock = tillBlock.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) {
auto fragment = fragmentIt.fragment();
Assert(fragment.isValid());
@@ -2351,6 +2386,11 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
PrepareFormattingOptimization(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.intervalEnd, QTextCursor::KeepAnchor);
if (action.type == ActionType::InsertEmoji
@@ -2420,7 +2460,6 @@ void InputField::documentContentsChanged(
if (_correcting) {
return;
}
-
// In case of input method events Qt emits
// document content change signals for a whole
// text block where the even took place.
@@ -2466,7 +2505,7 @@ void InputField::documentContentsChanged(
QTextCursor(document).endEditBlock();
handleContentsChanged();
const auto added = charsAdded - _emojiSurrogateAmount;
- _documentContentsChanges.fire({position, charsRemoved, added});
+ _documentContentsChanges.fire({ position, charsRemoved, added });
_emojiSurrogateAmount = 0;
});
@@ -2680,6 +2719,7 @@ void InputField::setTextWithTags(
_realCharsAdded = textWithTags.text.size();
const auto document = _inner->document();
auto cursor = QTextCursor(document);
+ cursor.setBlockFormat(_defaultBlockFormat);
if (historyAction == HistoryAction::Clear) {
document->setUndoRedoEnabled(false);
cursor.beginEditBlock();
@@ -2927,21 +2967,32 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
#endif // Q_OS_MAC
} else {
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 allowedModifiers = (enter && ctrl)
? (~Qt::ControlModifier)
: (enter && shift)
? (~Qt::ShiftModifier)
- // Qt bug workaround https://bugreports.qt.io/browse/QTBUG-49771
- : (backspace && Platform::IsX11())
- ? (Qt::ControlModifier)
: oldModifiers;
const auto changeModifiers = (oldModifiers & ~allowedModifiers) != 0;
if (changeModifiers) {
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) {
e->setModifiers(oldModifiers);
}
@@ -2954,7 +3005,10 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
} 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);
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();
}
if (check) {
@@ -3185,7 +3239,7 @@ void InputField::inputMethodEventInner(QInputMethodEvent *e) {
}
_inputMethodCommit = e->commitString();
- const auto weak = Ui::MakeWeak(this);
+ const auto weak = MakeWeak(this);
_inner->QTextEdit::inputMethodEvent(e);
if (weak && _inputMethodCommit.has_value()) {
@@ -3356,7 +3410,7 @@ void InputField::commitInstantReplacement(
result.setProperty(kCustomEmojiText, with);
result.setProperty(kCustomEmojiLink, unique);
result.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
- result.setVerticalAlignment(QTextCharFormat::AlignBottom);
+ result.setVerticalAlignment(QTextCharFormat::AlignTop);
return result;
}
const auto use = Integration::Instance().defaultEmojiVariant(
@@ -3619,8 +3673,13 @@ void InputField::commitMarkdownLinkEdit(
_insertedTags.clear();
_reverseMarkdownReplacement = false;
+ _correcting = true;
+ cursor.joinPreviousEditBlock();
cursor.setCharFormat(_defaultCharFormat);
+ cursor.setBlockFormat(_defaultBlockFormat);
+ cursor.endEditBlock();
_inner->setTextCursor(cursor);
+ _correcting = false;
}
void InputField::toggleSelectionMarkdown(const QString &tag) {
@@ -3950,14 +4009,18 @@ void InputField::insertFromMimeDataInner(const QMimeData *source) {
void InputField::resizeEvent(QResizeEvent *e) {
refreshPlaceholder(_placeholderFull.current());
_inner->setGeometry(rect().marginsRemoved(
- _st.textMargins + _additionalMargins));
+ _st.textMargins + _additionalMargins + _customFontMargins));
_borderAnimationStart = width() / 2;
RpWidget::resizeEvent(e);
checkContentHeight();
}
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.) {
auto placeholderFont = _st.placeholderFont->f;
placeholderFont.setStyleStrategy(QFont::PreferMatch);
@@ -3965,7 +4028,9 @@ void InputField::refreshPlaceholder(const QString &text) {
_placeholder = metrics.elidedText(text, Qt::ElideRight, availableWidth);
_placeholderPath = QPainterPath();
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 {
_placeholder = _st.placeholderFont->elided(text, availableWidth);
@@ -4054,4 +4119,52 @@ int FieldCharacterCount(not_null field) {
return field->lastTextSizeWithoutSurrogatePairsCount();
}
+void AddLengthLimitLabel(not_null field, int limit) {
+ struct State {
+ rpl::variable length;
+ };
+ const auto state = field->lifetime().make_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(
+ 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());
+ }, 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
diff --git a/ui/widgets/fields/input_field.h b/ui/widgets/fields/input_field.h
index 8f15f44..957509c 100644
--- a/ui/widgets/fields/input_field.h
+++ b/ui/widgets/fields/input_field.h
@@ -80,7 +80,10 @@ class CustomEmojiObject : public QObject, public QTextObjectInterface {
public:
using Factory = Fn(QStringView)>;
- CustomEmojiObject(Factory factory, Fn paused);
+ CustomEmojiObject(
+ const style::font &font,
+ Factory factory,
+ Fn paused);
~CustomEmojiObject();
void *qt_metacast(const char *iid) override;
@@ -100,6 +103,7 @@ public:
void clear();
private:
+ const style::font _font;
Factory _factory;
Fn _paused;
base::flat_map> _emoji;
@@ -499,6 +503,8 @@ private:
std::optional _inputMethodCommit;
QMargins _additionalMargins;
+ QMargins _customFontMargins;
+ int _placeholderCustomFontSkip = 0;
bool _forcePlaceholderHidden = false;
bool _reverseMarkdownReplacement = false;
@@ -561,6 +567,7 @@ private:
base::unique_qptr _contextMenu;
QTextCharFormat _defaultCharFormat;
+ QTextBlockFormat _defaultBlockFormat;
rpl::variable _scrollTop;
@@ -583,4 +590,6 @@ void PrepareFormattingOptimization(not_null document);
[[nodiscard]] int FieldCharacterCount(not_null field);
+void AddLengthLimitLabel(not_null field, int limit);
+
} // namespace Ui
diff --git a/ui/widgets/fields/masked_input_field.cpp b/ui/widgets/fields/masked_input_field.cpp
index 2a9112d..dde671a 100644
--- a/ui/widgets/fields/masked_input_field.cpp
+++ b/ui/widgets/fields/masked_input_field.cpp
@@ -390,7 +390,9 @@ void MaskedInputField::refreshPlaceholder(const QString &text) {
_placeholder = metrics.elidedText(text, Qt::ElideRight, availableWidth);
_placeholderPath = QPainterPath();
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 {
_placeholder = _st.placeholderFont->elided(text, availableWidth);
diff --git a/ui/widgets/fields/time_part_input.cpp b/ui/widgets/fields/time_part_input.cpp
index 5ca80a8..a2e7b40 100644
--- a/ui/widgets/fields/time_part_input.cpp
+++ b/ui/widgets/fields/time_part_input.cpp
@@ -42,18 +42,32 @@ rpl::producer<> TimePart::erasePrevious() const {
return _erasePrevious.events();
}
+rpl::producer<> TimePart::jumpToPrevious() const {
+ return _jumpToPrevious.events();
+}
+
rpl::producer TimePart::putNext() const {
return _putNext.events();
}
void TimePart::keyPressEvent(QKeyEvent *e) {
- const auto isBackspace = (e->key() == Qt::Key_Backspace);
- const auto isBeginning = (cursorPosition() == 0);
- if (isBackspace && isBeginning && !hasSelectedText()) {
- _erasePrevious.fire({});
- } else {
- MaskedInputField::keyPressEvent(e);
+ const auto position = cursorPosition();
+ const auto selection = hasSelectedText();
+ if (!selection && !position) {
+ if (e->key() == Qt::Key_Backspace) {
+ _erasePrevious.fire({});
+ 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) {
@@ -78,10 +92,10 @@ void TimePart::correctValue(
int wasCursor,
QString &now,
int &nowCursor) {
- auto newText = QString();
- auto newCursor = -1;
const auto oldCursor = nowCursor;
const auto oldLength = now.size();
+ auto newCursor = (oldCursor > 0) ? -1 : 0;
+ auto newText = QString();
auto accumulated = 0;
auto limit = 0;
for (; limit != oldLength; ++limit) {
diff --git a/ui/widgets/fields/time_part_input.h b/ui/widgets/fields/time_part_input.h
index 306a471..0ca4dcf 100644
--- a/ui/widgets/fields/time_part_input.h
+++ b/ui/widgets/fields/time_part_input.h
@@ -18,6 +18,7 @@ public:
void setWheelStep(int value);
[[nodiscard]] rpl::producer<> erasePrevious() const;
+ [[nodiscard]] rpl::producer<> jumpToPrevious() const;
[[nodiscard]] rpl::producer putNext() const;
[[nodiscard]] std::optional number();
@@ -37,6 +38,7 @@ private:
int _maxDigits = 0;
int _wheelStep = 0;
rpl::event_stream<> _erasePrevious;
+ rpl::event_stream<> _jumpToPrevious;
rpl::event_stream _putNext;
};
diff --git a/ui/widgets/labels.cpp b/ui/widgets/labels.cpp
index 8b75997..a1aaeb2 100644
--- a/ui/widgets/labels.cpp
+++ b/ui/widgets/labels.cpp
@@ -687,7 +687,7 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason)
const auto request = ContextMenuRequest{
.menu = _contextMenu.get(),
.link = ClickHandler::getActive(),
- .hasSelection = hasSelection,
+ .selection = _selectable ? _selection : TextSelection(),
.uponSelection = uponSelection,
.fullSelection = _selectable && _text.isFullSelection(_selection),
};
@@ -716,7 +716,7 @@ void FlatLabel::fillContextMenu(ContextMenuRequest request) {
Integration::Instance().phraseContextCopySelected(),
[=] { copySelectedText(); });
} else if (_selectable
- && !request.hasSelection
+ && request.selection.empty()
&& !_contextCopyText.isEmpty()) {
request.menu->addAction(
_contextCopyText,
diff --git a/ui/widgets/labels.h b/ui/widgets/labels.h
index cb66f5c..d43e9ac 100644
--- a/ui/widgets/labels.h
+++ b/ui/widgets/labels.h
@@ -117,6 +117,10 @@ public:
const style::FlatLabel &st = st::defaultFlatLabel,
const style::PopupMenu &stMenu = st::defaultPopupMenu);
+ [[nodiscard]] const style::FlatLabel &st() const {
+ return _st;
+ }
+
void setOpacity(float64 o);
void setTextColorOverride(std::optional color);
@@ -154,7 +158,7 @@ public:
struct ContextMenuRequest {
not_null menu;
ClickHandlerPtr link;
- bool hasSelection = false;
+ TextSelection selection;
bool uponSelection = false;
bool fullSelection = false;
};
diff --git a/ui/widgets/menu/menu_add_action_callback.h b/ui/widgets/menu/menu_add_action_callback.h
index e1cde37..a0e833a 100644
--- a/ui/widgets/menu/menu_add_action_callback.h
+++ b/ui/widgets/menu/menu_add_action_callback.h
@@ -8,6 +8,10 @@
#include "ui/style/style_core.h"
+namespace style {
+struct MenuSeparator;
+} // namespace style
+
namespace Ui {
class PopupMenu;
} // namespace Ui
@@ -20,6 +24,7 @@ public:
QString text;
Fn handler;
const style::icon *icon;
+ const style::MenuSeparator *separatorSt = nullptr;
Fn)> fillSubmenu;
int addTopShift = 0;
bool isSeparator = false;
diff --git a/ui/widgets/menu/menu_add_action_callback_factory.cpp b/ui/widgets/menu/menu_add_action_callback_factory.cpp
index 5429d61..e120843 100644
--- a/ui/widgets/menu/menu_add_action_callback_factory.cpp
+++ b/ui/widgets/menu/menu_add_action_callback_factory.cpp
@@ -27,8 +27,8 @@ MenuCallback CreateAddActionCallback(not_null menu) {
action->setMenu(Ui::CreateChild(menu->menu().get()));
a.fillSubmenu(menu->ensureSubmenu(action, menu->st()));
return action;
- } else if (a.isSeparator) {
- return menu->addSeparator();
+ } else if (a.separatorSt || a.isSeparator) {
+ return menu->addSeparator(a.separatorSt);
} else if (a.isAttention) {
return menu->addAction(base::make_unique_q(
menu,
diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp
index 3a79351..62531a7 100644
--- a/ui/widgets/popup_menu.cpp
+++ b/ui/widgets/popup_menu.cpp
@@ -900,12 +900,6 @@ void PopupMenu::deleteOnHide(bool del) {
}
void PopupMenu::popup(const QPoint &p) {
- if (_clearLastSeparator) {
- _menu->clearLastSeparator();
- for (const auto &[action, submenu] : _submenus) {
- submenu->menu()->clearLastSeparator();
- }
- }
if (prepareGeometryFor(p)) {
popupPrepared();
return;
@@ -957,10 +951,15 @@ bool PopupMenu::prepareGeometryFor(const QPoint &p) {
}
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 screen = usingScreenGeometry
- ? QGuiApplication::screenAt(p)
- : nullptr;
+ const auto screen = QGuiApplication::screenAt(p);
if ((usingScreenGeometry && !screen)
|| (!parent
&& ::Platform::IsMac()
@@ -1002,7 +1001,7 @@ bool PopupMenu::prepareGeometryFor(const QPoint &p, PopupMenu *parent) {
_additionalMenuPadding.left() - _st.shadow.extend.left(),
0),
_padding.top() - _topShift);
- auto r = screen ? screen->availableGeometry() : QRect();
+ auto r = usingScreenGeometry ? screen->availableGeometry() : QRect();
const auto parentWidth = _parent ? _parent->inner().width() : 0;
if (style::RightToLeft()) {
const auto badLeft = !r.isNull() && w.x() - width() < r.x() - _margins.left();
diff --git a/ui/widgets/scroll_area.cpp b/ui/widgets/scroll_area.cpp
index 81fc8bf..c8acf08 100644
--- a/ui/widgets/scroll_area.cpp
+++ b/ui/widgets/scroll_area.cpp
@@ -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
-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);
- Assert(_st != nullptr);
- Assert(_st->shColor.v() != nullptr);
}
void ScrollShadow::paintEvent(QPaintEvent *e) {
@@ -706,32 +709,48 @@ void ScrollArea::scrollToWidget(not_null widget) {
}
}
-void ScrollArea::scrollToY(int toTop, int toBottom) {
+int ScrollArea::computeScrollTo(int toTop, int toBottom) {
if (const auto inner = widget()) {
SendPendingMoveResizeEvents(inner);
}
SendPendingMoveResizeEvents(this);
- int toMin = 0, toMax = scrollTopMax();
+ const auto toMin = 0;
+ const auto toMax = scrollTopMax();
if (toTop < toMin) {
toTop = toMin;
} else if (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 (toBottom < toTop) toBottom = toTop;
- if (toBottom <= curBottom) return;
+ if (toBottom < toTop) {
+ toBottom = toTop;
+ }
+ if (toBottom <= curBottom) {
+ return curTop;
+ }
scToTop = toBottom - curHeight;
- if (scToTop > toTop) scToTop = toTop;
- if (scToTop == curTop) return;
+ if (scToTop > toTop) {
+ scToTop = toTop;
+ }
+ if (scToTop == curTop) {
+ return curTop;
+ }
} else {
scToTop = toTop;
}
- verticalScrollBar()->setValue(scToTop);
+ return scToTop;
+}
+
+void ScrollArea::scrollToY(int toTop, int toBottom) {
+ verticalScrollBar()->setValue(computeScrollTo(toTop, toBottom));
}
void ScrollArea::doSetOwnedWidget(object_ptr w) {
diff --git a/ui/widgets/scroll_area.h b/ui/widgets/scroll_area.h
index b1d03a2..25f7360 100644
--- a/ui/widgets/scroll_area.h
+++ b/ui/widgets/scroll_area.h
@@ -166,6 +166,7 @@ public:
void scrollTo(ScrollToRequest request);
void scrollToWidget(not_null widget);
+ [[nodiscard]] int computeScrollTo(int toTop, int toBottom);
void scrollToY(int toTop, int toBottom = -1);
void disableScroll(bool dis);
diff --git a/ui/widgets/separate_panel.cpp b/ui/widgets/separate_panel.cpp
index 1ee1437..01bf57a 100644
--- a/ui/widgets/separate_panel.cpp
+++ b/ui/widgets/separate_panel.cpp
@@ -676,6 +676,12 @@ void SeparatePanel::initGeometry(QSize size) {
}();
move(rect.topLeft());
setFixedSize(rect.size());
+ createWinId();
+ if (_useTransparency) {
+ Platform::SetWindowMargins(this, _padding);
+ } else {
+ Platform::UnsetWindowMargins(this);
+ }
updateControlsGeometry();
}
diff --git a/ui/widgets/time_input.cpp b/ui/widgets/time_input.cpp
index 66041e6..b21f54e 100644
--- a/ui/widgets/time_input.cpp
+++ b/ui/widgets/time_input.cpp
@@ -110,6 +110,10 @@ TimeInput::TimeInput(
_minute->erasePrevious() | rpl::start_with_next([=] {
erasePrevious(_hour);
}, lifetime());
+ _minute->jumpToPrevious() | rpl::start_with_next([=] {
+ _hour->setCursorPosition(_hour->getLastText().size());
+ _hour->setFocus();
+ }, lifetime());
_separator1->setAttribute(Qt::WA_TransparentForMouseEvents);
setMouseTracking(true);
diff --git a/ui/widgets/tooltip.cpp b/ui/widgets/tooltip.cpp
index 73af93a..44dcd93 100644
--- a/ui/widgets/tooltip.cpp
+++ b/ui/widgets/tooltip.cpp
@@ -77,7 +77,7 @@ Tooltip::~Tooltip() {
void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *st) {
const auto usingScreenGeometry = !::Platform::IsWayland();
- const auto screen = usingScreenGeometry ? QGuiApplication::screenAt(m) : nullptr;
+ const auto screen = QGuiApplication::screenAt(m);
if (usingScreenGeometry && !screen) {
Hide();
return;
@@ -118,10 +118,13 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *
p.setX(m.x() - (s.width() / 2));
}
- // adjust tooltip position
if (screen) {
createWinId();
windowHandle()->setScreen(screen);
+ }
+
+ // adjust tooltip position
+ if (usingScreenGeometry) {
const auto r = screen->availableGeometry();
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()));
diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style
index 55c6a4c..8dbd1fe 100644
--- a/ui/widgets/widgets.style
+++ b/ui/widgets/widgets.style
@@ -744,7 +744,7 @@ defaultCheck: Check {
diameter: 22px;
thickness: 2px;
icon: defaultCheckboxIcon;
- duration: 120;
+ duration: universalDuration;
rippleAreaPadding: 8px;
}
defaultRadio: Radio {
@@ -755,7 +755,7 @@ defaultRadio: Radio {
thickness: 2px;
outerSkip: 10px; // * 0.1
skip: 60px; // * 0.1
- duration: 120;
+ duration: universalDuration;
rippleAreaPadding: 8px;
}
defaultToggle: Toggle {
@@ -763,7 +763,7 @@ defaultToggle: Toggle {
toggledFg: windowBgActive;
untoggledBg: windowBg;
untoggledFg: checkboxFg;
- duration: 120;
+ duration: universalDuration;
border: 2px;
shift: 1px;
diameter: 16px;
@@ -910,6 +910,9 @@ defaultInputField: InputField {
heightMin: 55px;
heightMax: 148px;
}
+defaultInputFieldLimit: FlatLabel(defaultFlatLabel) {
+ textFg: windowSubTextFg;
+}
defaultIconButton: IconButton {
iconPosition: point(-1px, -1px);
@@ -1016,6 +1019,7 @@ SettingsSlider {
labelStyle: TextStyle;
labelFg: color;
labelFgActive: color;
+ strictSkip: pixels;
duration: int;
rippleBottomSkip: pixels;
rippleBg: color;