Added new class of settings button with description.

This commit is contained in:
23rd
2026-03-24 16:51:44 +03:00
parent 303d10939e
commit 5ccb58b1bb
4 changed files with 407 additions and 0 deletions
+2
View File
@@ -1618,6 +1618,8 @@ PRIVATE
settings/settings_codes.h
settings/settings_common_session.cpp
settings/settings_common_session.h
settings/detailed_settings_button.cpp
settings/detailed_settings_button.h
settings/sections/settings_credits.cpp
settings/sections/settings_credits.h
settings/settings_credits_graphics.cpp
@@ -0,0 +1,299 @@
#include "settings/detailed_settings_button.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_settings.h"
DetailedSettingsButton::DetailedSettingsButton(
QWidget *parent,
rpl::producer<QString> title,
rpl::producer<QString> description,
Settings::IconDescriptor icon,
rpl::producer<bool> toggled,
const style::DetailedSettingsButtonStyle &rowStyle)
: Ui::RippleButton(parent, rowStyle.button.ripple)
, _style(rowStyle)
, _title(0)
, _description(0)
, _iconForeground(icon.icon)
, _iconBackground(icon.background)
, _iconBackgroundBrush(std::move(icon.backgroundBrush)) {
std::move(
title
) | rpl::on_next([=](const QString &text) {
_title.setText(
_style.button.style,
text,
kDefaultTextOptions);
accessibilityNameChanged();
refreshLayout();
}, lifetime());
std::move(
description
) | rpl::on_next([=](const QString &text) {
_description.setText(
_style.description.style,
text,
kDefaultTextOptions);
refreshLayout();
}, lifetime());
_toggle = std::make_unique<Ui::ToggleView>(
_style.button.toggle,
false,
[this] { rtlupdate(toggleRect()); });
addClickHandler([=] {
_toggle->setChecked(!_toggle->checked(), anim::type::normal);
});
std::move(
toggled
) | rpl::on_next([=](bool checked) {
_toggle->setChecked(checked, anim::type::normal);
}, lifetime());
_toggle->checkedChanges() | rpl::on_next([=] {
accessibilityStateChanged({ .checked = true });
}, lifetime());
widthValue() | rpl::on_next([=](int width) {
if (width > 0) {
refreshLayout();
}
}, lifetime());
}
QString DetailedSettingsButton::accessibilityName() {
return _title.toString();
}
Ui::AccessibilityState DetailedSettingsButton::accessibilityState() const {
return { .checkable = true, .checked = _toggle->checked() };
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0)
QAccessible::Role DetailedSettingsButton::accessibilityRole() {
return QAccessible::Role::Switch;
}
#endif
bool DetailedSettingsButton::toggled() const {
return _toggle->checked();
}
rpl::producer<bool> DetailedSettingsButton::toggledChanges() const {
return _toggle->checkedChanges();
}
rpl::producer<not_null<QEvent*>>
DetailedSettingsButton::clickAreaEvents() const {
return events();
}
void DetailedSettingsButton::setToggleLocked(bool locked) {
_toggle->setLocked(locked);
}
void DetailedSettingsButton::onStateChanged(
State was,
StateChangeSource source) {
const auto wasDisabled = !!(was & StateFlag::Disabled);
const auto nowDisabled = isDisabled();
if (!nowDisabled || !isDown()) {
RippleButton::onStateChanged(was, source);
}
_toggle->setStyle(
isOver()
? _style.button.toggleOver
: _style.button.toggle);
if (nowDisabled != wasDisabled) {
setPointerCursor(!nowDisabled);
}
update();
}
void DetailedSettingsButton::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
const auto over = isOver() || isDown();
p.fillRect(
e->rect(),
over ? _style.button.textBgOver : _style.button.textBg);
paintRipple(p, 0, 0);
paintIcon(p);
p.setPen(over
? _style.button.textFgOver
: _style.button.textFg);
_title.drawLeftElided(
p,
_style.button.padding.left(),
titleTop(),
_titleWidth,
width());
p.setPen(_style.description.textFg);
if (_descriptionHeight > 0) {
_description.drawLeft(
p,
_style.button.padding.left(),
_descriptionTop,
_descriptionWidth,
width());
}
const auto toggle = toggleRect();
_toggle->paint(p, toggle.left(), toggle.top(), width());
}
int DetailedSettingsButton::resizeGetHeight(int newWidth) {
_titleWidth = textAvailableWidth(newWidth);
_descriptionWidth
= std::max(1, _titleWidth - _style.descriptionRightSkip);
_descriptionTop = descriptionTopValue();
_descriptionHeight = _description.countHeight(_descriptionWidth);
return _descriptionTop
+ _descriptionHeight
+ _style.descriptionBottomSkip;
}
void DetailedSettingsButton::refreshLayout() {
Ui::PostponeCall(crl::guard(this, [=] {
const auto current = widthNoMargins();
if (current > 0) {
resizeToWidth(current);
}
update();
}));
}
int DetailedSettingsButton::titleTop() const {
return _style.button.padding.top();
}
int DetailedSettingsButton::titleHeight() const {
return _style.button.height;
}
int DetailedSettingsButton::descriptionTopValue() const {
return titleTop() + titleHeight() + _style.descriptionTopSkip;
}
int DetailedSettingsButton::firstDescriptionLineBottom() const {
const auto top = (_descriptionTop > 0)
? _descriptionTop
: descriptionTopValue();
return top + qMax(
_style.description.style.lineHeight,
_style.description.style.font->height);
}
int DetailedSettingsButton::textAvailableWidth(int outerw) const {
const auto toggle = _toggle->getSize();
return std::max(
1,
outerw
- _style.button.padding.left()
- _style.button.padding.right()
- _style.button.toggleSkip
- toggle.width());
}
QRect DetailedSettingsButton::toggleRect() const {
const auto toggle = _toggle->getSize();
const auto anchorBottom = firstDescriptionLineBottom();
return QRect(
width() - _style.button.toggleSkip - toggle.width(),
(anchorBottom - toggle.height()) / 2,
toggle.width(),
toggle.height());
}
QRect DetailedSettingsButton::iconAreaRect() const {
const auto size = iconSize();
return QRect(
_style.button.iconLeft,
titleTop(),
size,
size);
}
QRect DetailedSettingsButton::iconRect() const {
const auto area = iconAreaRect();
const auto padding = _style.iconPadding;
const auto width = std::max(1, area.width() - (2 * padding));
const auto height = std::max(1, area.height() - (2 * padding));
return QRect(
area.x() + padding,
area.y() + padding,
width,
height);
}
QRect DetailedSettingsButton::iconForegroundRect(QRect iconRect) const {
const auto padding = _style.iconForegroundPadding;
const auto width = std::max(1, iconRect.width() - (2 * padding));
const auto height = std::max(1, iconRect.height() - (2 * padding));
return QRect(
iconRect.x() + padding,
iconRect.y() + padding,
width,
height);
}
int DetailedSettingsButton::iconSize() const {
const auto result = firstDescriptionLineBottom() - titleTop();
return std::max(1, result);
}
void DetailedSettingsButton::paintIcon(QPainter &p) const {
if (!_iconForeground && !_iconBackground && !_iconBackgroundBrush) {
return;
}
const auto rect = iconRect();
if (_iconBackground) {
p.setPen(Qt::NoPen);
p.setBrush(*_iconBackground);
{
auto hq = PainterHighQualityEnabler(p);
p.drawRoundedRect(rect, _style.iconRadius, _style.iconRadius);
}
} else if (_iconBackgroundBrush) {
p.setPen(Qt::NoPen);
p.setBrush(*_iconBackgroundBrush);
{
auto hq = PainterHighQualityEnabler(p);
p.drawRoundedRect(rect, _style.iconRadius, _style.iconRadius);
}
}
if (_iconForeground) {
const auto source = _iconForeground->size();
if (!source.isEmpty()) {
const auto foreground = iconForegroundRect(rect);
const auto scale = std::min(
double(foreground.width()) / source.width(),
double(foreground.height()) / source.height());
const auto shiftX = (foreground.width()
- (source.width() * scale)) / 2.;
const auto shiftY = (foreground.height()
- (source.height() * scale)) / 2.;
p.save();
p.translate(foreground.x() + shiftX, foreground.y() + shiftY);
p.scale(scale, scale);
_iconForeground->paint(p, 0, 0, source.width());
p.restore();
}
}
}
not_null<DetailedSettingsButton*> AddDetailedSettingsButton(
not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> title,
rpl::producer<QString> description,
Settings::IconDescriptor icon,
rpl::producer<bool> toggled,
const style::DetailedSettingsButtonStyle &rowStyle) {
return container->add(object_ptr<DetailedSettingsButton>(
container,
std::move(title),
std::move(description),
std::move(icon),
std::move(toggled),
rowStyle));
}
@@ -0,0 +1,76 @@
#pragma once
#include "settings/settings_common.h"
#include "ui/text/text.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
namespace Ui {
class VerticalLayout;
} // namespace Ui
namespace style {
struct DetailedSettingsButtonStyle;
} // namespace style
class DetailedSettingsButton final : public Ui::RippleButton {
public:
DetailedSettingsButton(
QWidget *parent,
rpl::producer<QString> title,
rpl::producer<QString> description,
Settings::IconDescriptor icon,
rpl::producer<bool> toggled,
const style::DetailedSettingsButtonStyle &rowStyle);
QString accessibilityName() override;
Ui::AccessibilityState accessibilityState() const override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0)
QAccessible::Role accessibilityRole() override;
#endif
[[nodiscard]] bool toggled() const;
[[nodiscard]] rpl::producer<bool> toggledChanges() const;
[[nodiscard]] rpl::producer<not_null<QEvent*>> clickAreaEvents() const;
void setToggleLocked(bool locked);
protected:
void onStateChanged(State was, StateChangeSource source) override;
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
private:
void refreshLayout();
[[nodiscard]] int titleTop() const;
[[nodiscard]] int titleHeight() const;
[[nodiscard]] int descriptionTopValue() const;
[[nodiscard]] int firstDescriptionLineBottom() const;
[[nodiscard]] int textAvailableWidth(int outerw) const;
[[nodiscard]] QRect toggleRect() const;
[[nodiscard]] QRect iconAreaRect() const;
[[nodiscard]] QRect iconRect() const;
[[nodiscard]] QRect iconForegroundRect(QRect iconRect) const;
[[nodiscard]] int iconSize() const;
void paintIcon(QPainter &p) const;
const style::DetailedSettingsButtonStyle &_style;
Ui::Text::String _title;
Ui::Text::String _description;
std::unique_ptr<Ui::ToggleView> _toggle;
const style::icon *_iconForeground = nullptr;
const style::color *_iconBackground = nullptr;
const std::optional<QBrush> _iconBackgroundBrush;
int _titleWidth = 0;
int _descriptionWidth = 0;
int _descriptionTop = 0;
int _descriptionHeight = 0;
};
[[nodiscard]] not_null<DetailedSettingsButton*> AddDetailedSettingsButton(
not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> title,
rpl::producer<QString> description,
Settings::IconDescriptor icon,
rpl::producer<bool> toggled,
const style::DetailedSettingsButtonStyle &rowStyle);
@@ -762,3 +762,33 @@ settingsAgeVerifyTitle: FlatLabel(boxTitle) {
settingsAgeVerifyIcon: icon {{ "settings/filled_verify_age-48x48", windowFgActive }};
settingsAgeVerifyIconPadding: margins(16px, 16px, 16px, 16px);
settingsAgeVerifyIconMargin: margins(0px, 24px, 0px, 14px);
DetailedSettingsButtonStyle {
button: SettingsButton;
description: FlatLabel;
iconRadius: pixels;
iconPadding: pixels;
iconForegroundPadding: pixels;
descriptionTopSkip: pixels;
descriptionBottomSkip: pixels;
descriptionRightSkip: pixels;
}
detailedSettingsButtonStyle: DetailedSettingsButtonStyle {
button: SettingsButton(settingsButtonNoIconLocked) {
style: defaultTextStyle;
textBg: transparent;
textBgOver: transparent;
padding: margins(68px, 8px, 22px, 8px);
iconLeft: 17px;
}
description: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
iconRadius: 10px;
iconPadding: 6px;
iconForegroundPadding: 3px;
descriptionTopSkip: 0px;
descriptionBottomSkip: 8px;
descriptionRightSkip: 4px;
}