diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 0d05539e8a..226fa3ad39 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -131,6 +131,8 @@ set(ayugram_files ayu/utils/taptic_engine/platform/taptic_engine_mac.h ayu/ui/ayu_logo.cpp ayu/ui/ayu_logo.h + ayu/ui/ayu_userpic.cpp + ayu/ui/ayu_userpic.h ayu/ui/utils/ayu_profile_values.cpp ayu/ui/utils/ayu_profile_values.h ayu/ui/utils/color_cut_quantizer.cpp diff --git a/Telegram/Resources/art/logo_256_no_margin.png b/Telegram/Resources/art/logo_256_no_margin.png index 00acff3dd5..619217f830 100644 Binary files a/Telegram/Resources/art/logo_256_no_margin.png and b/Telegram/Resources/art/logo_256_no_margin.png differ diff --git a/Telegram/Resources/icons/settings/dino.svg b/Telegram/Resources/icons/settings/dino.svg index 24ebe83cf6..c0ea1db3ee 100644 --- a/Telegram/Resources/icons/settings/dino.svg +++ b/Telegram/Resources/icons/settings/dino.svg @@ -2,7 +2,7 @@ Ava 162 Rexy - + diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 172045afef..a7d54367aa 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -7709,6 +7709,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "ayu_HideNotificationCounters" = "Hide Notification Counters"; "ayu_HideNotificationBadge" = "Hide Notification Badge"; "ayu_HideNotificationBadgeDescription" = "Hides the notification counter on the app icon in the taskbar and tray."; +"ayu_AvatarCorners" = "Avatar Corners"; +"ayu_AvatarCornersCircle" = "Circle"; +"ayu_AvatarCornersSquare" = "Square"; +"ayu_SingleCornerRadius" = "Single Corner Radius"; +"ayu_SingleCornerRadiusDescription" = "Forums will have the same avatar shape as chats."; "ayu_HideAllChats" = "Hide \"All Chats\" Tab"; "ayu_ChannelBottomButton" = "Channel Bottom Button"; "ayu_ChannelBottomButtonHide" = "Hide"; diff --git a/Telegram/SourceFiles/ayu/ayu_infra.cpp b/Telegram/SourceFiles/ayu/ayu_infra.cpp index d3b76814f7..618adb22f8 100644 --- a/Telegram/SourceFiles/ayu/ayu_infra.cpp +++ b/Telegram/SourceFiles/ayu/ayu_infra.cpp @@ -34,6 +34,7 @@ void initUiSettings() { AyuUiSettings::setMonoFont(settings.monoFont()); AyuUiSettings::setWideMultiplier(settings.wideMultiplier()); AyuUiSettings::setMaterialSwitches(settings.materialSwitches()); + AyuUiSettings::setAvatarCorners(settings.avatarCorners()); } void initDatabase() { diff --git a/Telegram/SourceFiles/ayu/ayu_settings.cpp b/Telegram/SourceFiles/ayu/ayu_settings.cpp index 0a3e50e1a0..d7b9b2198a 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.cpp +++ b/Telegram/SourceFiles/ayu/ayu_settings.cpp @@ -17,6 +17,7 @@ #include "main/main_domain.h" #include "main/main_session.h" #include "rpl/combine.h" +#include "ui/ayu_userpic.h" #include "window/window_controller.h" #include @@ -466,6 +467,7 @@ void AyuSettings::validate() { validateRange(_wideMultiplier, 0.5, 4.0, defaults._wideMultiplier); validateRange(_recentStickersCount, 1, 200, defaults._recentStickersCount); + validateRange(_avatarCorners, 0, AyuUserpic::kMaxAvatarCorners, defaults._avatarCorners); const auto embeddedType = _messageShotSettings._embeddedThemeType.current(); auto embeddedTypeValid = (embeddedType == -1) || (embeddedType >= 0 && embeddedType <= 3); // from Window::Theme::EmbeddedType::DayBlue to Window::Theme::EmbeddedType::NightGreen @@ -948,6 +950,20 @@ void AyuSettings::setCrashReporting(bool val) { save(); } +void AyuSettings::setAvatarCorners(int val) { + if (_avatarCorners.current() == val) return; + _avatarCorners = val; + AyuUiSettings::setAvatarCorners(val); + save(); +} + +void AyuSettings::setSingleCornerRadius(bool val) { + if (_singleCornerRadius.current() == val) return; + _singleCornerRadius = val; + repaintApp(); + save(); +} + void to_json(nlohmann::json &j, const AyuSettings &s) { std::map ghostAccounts; for (const auto &[key, value] : s._ghostAccounts) { @@ -1034,6 +1050,8 @@ void to_json(nlohmann::json &j, const AyuSettings &s) { {"adaptiveCoverColor", s._adaptiveCoverColor.current()}, {"improveLinkPreviews", s._improveLinkPreviews.current()}, {"crashReporting", s._crashReporting.current()}, + {"avatarCorners", s._avatarCorners.current()}, + {"singleCornerRadius", s._singleCornerRadius.current()}, {"messageShotSettings", s._messageShotSettings} }; } @@ -1128,6 +1146,8 @@ void from_json(const nlohmann::json &j, AyuSettings &s) { s._adaptiveCoverColor = j.value("adaptiveCoverColor", defaults._adaptiveCoverColor.current()); s._improveLinkPreviews = j.value("improveLinkPreviews", defaults._improveLinkPreviews.current()); s._crashReporting = j.value("crashReporting", defaults._crashReporting.current()); + s._avatarCorners = j.value("avatarCorners", defaults._avatarCorners.current()); + s._singleCornerRadius = j.value("singleCornerRadius", defaults._singleCornerRadius.current()); if (j.contains("messageShotSettings") && j["messageShotSettings"].is_object()) { j["messageShotSettings"].get_to(s._messageShotSettings); diff --git a/Telegram/SourceFiles/ayu/ayu_settings.h b/Telegram/SourceFiles/ayu/ayu_settings.h index 944228f5b4..67e4fb5a3d 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.h +++ b/Telegram/SourceFiles/ayu/ayu_settings.h @@ -302,6 +302,8 @@ public: [[nodiscard]] bool adaptiveCoverColor() const { return _adaptiveCoverColor.current(); } [[nodiscard]] bool improveLinkPreviews() const { return _improveLinkPreviews.current(); } [[nodiscard]] bool crashReporting() const { return _crashReporting.current(); } + [[nodiscard]] int avatarCorners() const { return _avatarCorners.current(); } + [[nodiscard]] bool singleCornerRadius() const { return _singleCornerRadius.current(); } void setSaveDeletedMessages(bool val); void setSaveMessagesHistory(bool val); @@ -379,6 +381,8 @@ public: void setAdaptiveCoverColor(bool val); void setImproveLinkPreviews(bool val); void setCrashReporting(bool val); + void setAvatarCorners(int val); + void setSingleCornerRadius(bool val); [[nodiscard]] rpl::producer useGlobalGhostModeChanges() const { return _useGlobalGhostMode.value(); } [[nodiscard]] rpl::producer saveDeletedMessagesChanges() const { return _saveDeletedMessages.value(); } @@ -457,6 +461,8 @@ public: [[nodiscard]] rpl::producer adaptiveCoverColorChanges() const { return _adaptiveCoverColor.value(); } [[nodiscard]] rpl::producer improveLinkPreviewsChanges() const { return _improveLinkPreviews.value(); } [[nodiscard]] rpl::producer crashReportingChanges() const { return _crashReporting.value(); } + [[nodiscard]] rpl::producer avatarCornersChanges() const { return _avatarCorners.value(); } + [[nodiscard]] rpl::producer singleCornerRadiusChanges() const { return _singleCornerRadius.value(); } friend void to_json(nlohmann::json &j, const AyuSettings &s); friend void from_json(const nlohmann::json &j, AyuSettings &s); @@ -543,6 +549,8 @@ private: rpl::variable _adaptiveCoverColor = true; rpl::variable _improveLinkPreviews = false; rpl::variable _crashReporting = true; + rpl::variable _avatarCorners = 23; + rpl::variable _singleCornerRadius = false; rpl::variable _useGlobalGhostMode = true; std::map> _ghostAccounts; diff --git a/Telegram/SourceFiles/ayu/ui/ayu_userpic.cpp b/Telegram/SourceFiles/ayu/ui/ayu_userpic.cpp new file mode 100644 index 0000000000..f5e385d07e --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/ayu_userpic.cpp @@ -0,0 +1,109 @@ +// This is the source code of AyuGram for Desktop. +// +// We do not and cannot prevent the use of our code, +// but be respectful and credit the original author. +// +// Copyright @Radolyn, 2026 +#include "ayu/ui/ayu_userpic.h" + +#include "ayu/ayu_settings.h" +#include "ayu/ayu_ui_settings.h" +#include "media/streaming/media_streaming_common.h" +#include "ui/image/image_prepare.h" +#include "ui/style/style_core.h" + +#include + +namespace AyuUserpic { + +bool ShouldOverrideShape(Ui::PeerUserpicShape shape) { + using Shape = Ui::PeerUserpicShape; + switch (shape) { + case Shape::Circle: + case Shape::Auto: + return true; + case Shape::Monoforum: + case Shape::Forum: + return AyuSettings::getInstance().singleCornerRadius(); + } + return false; +} + +int ComputeRadius(int pixelSize) { + const auto corners = AyuUiSettings::getAvatarCorners(); + if (corners >= kMaxAvatarCorners) return pixelSize / 2; + if (corners <= 0) return 0; + return int(double(corners) / kMaxAvatarCorners * pixelSize / 2.0); +} + +double ComputeRadiusF(double size) { + const auto corners = AyuUiSettings::getAvatarCorners(); + if (corners >= kMaxAvatarCorners) return size / 2.0; + if (corners <= 0) return 0.0; + return double(corners) / kMaxAvatarCorners * size / 2.0; +} + +bool IsCircle() { + return AyuUiSettings::getAvatarCorners() >= kMaxAvatarCorners; +} + +uint8 PackedState() { + return uint8(AyuUiSettings::getAvatarCorners() & 0x1F) + | (AyuSettings::getInstance().singleCornerRadius() ? 0x20 : 0); +} + +void PaintShape(QPainter &p, int x, int y, int size) { + const auto corners = AyuUiSettings::getAvatarCorners(); + if (corners >= kMaxAvatarCorners) { + p.drawEllipse(x, y, size, size); + } else if (corners <= 0) { + p.drawRect(x, y, size, size); + } else { + const auto r = double(corners) / kMaxAvatarCorners * size / 2.0; + p.drawRoundedRect(x, y, size, size, r, r); + } +} + +void PaintShape(QPainter &p, const QRectF &rect) { + const auto corners = AyuUiSettings::getAvatarCorners(); + if (corners >= kMaxAvatarCorners) { + p.drawEllipse(rect); + } else if (corners <= 0) { + p.drawRect(rect); + } else { + const auto r = double(corners) / kMaxAvatarCorners + * std::min(rect.width(), rect.height()) / 2.0; + p.drawRoundedRect(rect, r, r); + } +} + +QPointF OnlineBadgePosition(int photoSize, double badgeSize) { + const auto corners = AyuUiSettings::getAvatarCorners(); + const auto r = double(corners) / kMaxAvatarCorners * photoSize / 2.0; + const auto edge = photoSize - r * (1.0 - std::cos(M_PI / 4.0)); + const auto pos = std::min(edge - badgeSize / 2.0, photoSize - badgeSize); + return QPointF(pos, pos); +} + +void ApplyFrameRounding( + ::Media::Streaming::FrameRequest &request, + std::array &cornersCache, + QImage &ellipseCache, + QSize size) { + const auto minSide = std::min(size.width(), size.height()); + const auto r = ComputeRadius(minSide); + const auto ratio = style::DevicePixelRatio(); + if (r > 0 && r < minSide / 2) { + if (cornersCache[0].width() != r * ratio) { + cornersCache = Images::CornersMask(r); + } + request.rounding = Images::CornersMaskRef(cornersCache); + } else if (r >= minSide / 2) { + if (ellipseCache.size() != request.outer) { + ellipseCache = Images::EllipseMask(size); + } + request.mask = ellipseCache; + } +} + +} // namespace AyuUserpic diff --git a/Telegram/SourceFiles/ayu/ui/ayu_userpic.h b/Telegram/SourceFiles/ayu/ui/ayu_userpic.h new file mode 100644 index 0000000000..2db137e74c --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/ayu_userpic.h @@ -0,0 +1,35 @@ +// This is the source code of AyuGram for Desktop. +// +// We do not and cannot prevent the use of our code, +// but be respectful and credit the original author. +// +// Copyright @Radolyn, 2026 +#pragma once + +#include "ui/userpic_view.h" + +namespace Media::Streaming { struct FrameRequest; } + +namespace AyuUserpic { + +inline constexpr int kMaxAvatarCorners = 23; + +[[nodiscard]] bool ShouldOverrideShape(Ui::PeerUserpicShape shape); +[[nodiscard]] int ComputeRadius(int pixelSize); +[[nodiscard]] double ComputeRadiusF(double size); +[[nodiscard]] bool IsCircle(); + +[[nodiscard]] uint8 PackedState(); + +void PaintShape(QPainter &p, int x, int y, int size); +void PaintShape(QPainter &p, const QRectF &rect); + +[[nodiscard]] QPointF OnlineBadgePosition(int photoSize, double badgeSize); + +void ApplyFrameRounding( + ::Media::Streaming::FrameRequest &request, + std::array &cornersCache, + QImage &ellipseCache, + QSize size); + +} // namespace AyuUserpic diff --git a/Telegram/SourceFiles/ayu/ui/settings/ayu_builder.cpp b/Telegram/SourceFiles/ayu/ui/settings/ayu_builder.cpp index fd90236667..7ec8245da6 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/ayu_builder.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/ayu_builder.cpp @@ -183,18 +183,20 @@ void AyuSectionBuilder::addSlider(SliderArgs &&args) { _builder.add([&](const Builder::BuildContext &ctx) { v::match(ctx, [&](const Builder::WidgetContext &wctx) { const auto container = wctx.container; - container->add( - object_ptr