// 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/settings/settings_ayu_utils.h" #include "lang_auto.h" #include "core/application.h" #include "lang/lang_text_entity.h" #include "settings/settings_common.h" #include "styles/style_ayu_styles.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_settings.h" #include "ui/painter.h" #include "ui/vertical_list.h" #include "ui/boxes/confirm_box.h" #include "ui/boxes/single_choice_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/labels.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" #include class PainterHighQualityEnabler; namespace Settings { void ShowRestartPrompt(not_null controller) { crl::on_main([=] { controller->show(Ui::MakeConfirmBox({ .text = tr::lng_settings_need_restart(), .confirmed = [] { Core::Restart(); }, .confirmText = tr::lng_settings_restart_now(), .cancelText = tr::lng_settings_restart_later(), })); }); } void AddBetaBadge(not_null parent) { const auto badge = Ui::CreateChild>( parent.get(), object_ptr( parent, rpl::single(QString("BETA")), st::settingsPremiumNewBadge), st::ayuBetaBadgePadding); badge->show(); badge->setAttribute(Qt::WA_TransparentForMouseEvents); badge->paintRequest() | rpl::on_next([=] { auto p = QPainter(badge); auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); p.setBrush(st::windowBgActive); const auto r = st::ayuBetaBadgePadding.left(); p.drawRoundedRect(badge->rect(), r, r); }, badge->lifetime()); parent->sizeValue( ) | rpl::on_next([=](QSize size) { const auto &st = parent->st(); badge->moveToLeft( st.padding.left() + parent->fullTextWidth() + st::settingsPremiumNewBadgePosition.x(), st.padding.top() + (st.style.font->height - badge->height()) / 2); }, badge->lifetime()); } not_null AddInnerToggle(not_null container, const style::SettingsButton &st, std::vector> innerCheckViews, not_null*> wrap, rpl::producer buttonLabel, bool toggledWhenAll, std::vector> lockChecks, rpl::event_stream<> *lockChanges) { const auto button = container->add(object_ptr( container, nullptr, st::settingsButtonNoIcon)); const auto toggleButton = Ui::CreateChild( container.get(), nullptr, st); struct State final { State(const style::Toggle &st, Fn c) : checkView(st, false, c) { } Ui::ToggleView checkView; Ui::Animations::Simple animation; rpl::event_stream<> anyChanges; std::vector> innerChecks; std::vector> lockChecks; }; const auto state = button->lifetime().make_state( st.toggle, [=] { toggleButton->update(); }); state->innerChecks = std::move(innerCheckViews); state->lockChecks = std::move(lockChecks); const auto countChecked = [=] { return ranges::count_if( state->innerChecks, [](const auto &v) { return v->checked(); }); }; const auto countUnlockedChecked = [=] { auto count = 0; for (auto i = 0u; i < state->innerChecks.size(); ++i) { if (i < state->lockChecks.size() && state->lockChecks[i] && state->lockChecks[i]()) { continue; } if (state->innerChecks[i]->checked()) { ++count; } } return count; }; const auto countTotal = [=] { auto total = static_cast(state->innerChecks.size()); for (auto i = 0u; i < state->lockChecks.size(); ++i) { if (state->lockChecks[i] && state->lockChecks[i]()) { --total; } } return total; }; for (const auto &innerCheck : state->innerChecks) { innerCheck->checkedChanges( ) | rpl::to_empty | start_to_stream( state->anyChanges, button->lifetime()); } if (lockChanges) { lockChanges->events( ) | start_to_stream( state->anyChanges, button->lifetime()); } const auto checkView = &state->checkView; { const auto separator = Ui::CreateChild(container.get()); separator->paintRequest( ) | on_next([=, bg = st.textBgOver] { auto p = QPainter(separator); p.fillRect(separator->rect(), bg); }, separator->lifetime()); const auto separatorHeight = 2 * st.toggle.border + st.toggle.diameter; button->geometryValue( ) | on_next([=](const QRect &r) { const auto w = st::rightsButtonToggleWidth; constexpr auto kLineWidth = 1; toggleButton->setGeometry( r.x() + r.width() - w, r.y(), w, r.height()); separator->setGeometry( toggleButton->x() - kLineWidth, r.y() + (r.height() - separatorHeight) / 2, kLineWidth, separatorHeight); }, toggleButton->lifetime()); const auto checkWidget = Ui::CreateChild(toggleButton); checkWidget->resize(checkView->getSize()); checkWidget->paintRequest( ) | on_next([=] { auto p = QPainter(checkWidget); checkView->paint(p, 0, 0, checkWidget->width()); }, checkWidget->lifetime()); toggleButton->sizeValue( ) | on_next([=](const QSize &s) { checkWidget->moveToRight( st.toggleSkip, (s.height() - checkWidget->height()) / 2); }, toggleButton->lifetime()); } const auto totalInnerChecks = state->innerChecks.size(); state->anyChanges.events_starting_with( rpl::empty_value() ) | rpl::map(countUnlockedChecked) | on_next([=](int count) { const auto total = countTotal(); if (toggledWhenAll) { checkView->setChecked(total > 0 && count == total, anim::type::normal); } else { checkView->setChecked(count != 0, anim::type::normal); } }, toggleButton->lifetime()); checkView->setLocked(false); checkView->finishAnimating(); const auto label = Ui::CreateChild( button, combine( std::move(buttonLabel), state->anyChanges.events_starting_with( rpl::empty_value() ) | rpl::map(countChecked) ) | rpl::map([=](const QString &t, int checked) { auto count = tr::bold(" " + QString::number(checked) + '/' + QString::number(totalInnerChecks)); return TextWithEntities::Simple(t).append(std::move(count)); }), st::boxLabel); label->setAttribute(Qt::WA_TransparentForMouseEvents); const auto arrow = Ui::CreateChild(button); { const auto &icon = st::permissionsExpandIcon; arrow->resize(icon.size()); arrow->paintRequest( ) | on_next([=, &icon] { auto p = QPainter(arrow); const auto center = QPointF( icon.width() / 2., icon.height() / 2.); const auto progress = state->animation.value( wrap->toggled() ? 1. : 0.); auto hq = std::optional(); if (progress > 0.) { hq.emplace(p); p.translate(center); p.rotate(progress * 180.); p.translate(-center); } icon.paint(p, 0, 0, arrow->width()); }, arrow->lifetime()); } button->sizeValue( ) | on_next([=, &st](const QSize &s) { const auto labelLeft = st.padding.left(); const auto labelRight = s.width() - toggleButton->width(); label->resizeToWidth(labelRight - labelLeft - arrow->width()); label->moveToLeft( labelLeft, (s.height() - label->height()) / 2); arrow->moveToLeft( std::min( labelLeft + label->naturalWidth(), labelRight - arrow->width()), (s.height() - arrow->height()) / 2); }, button->lifetime()); wrap->toggledValue( ) | rpl::skip(1) | on_next([=](bool toggled) { state->animation.start( [=] { arrow->update(); }, toggled ? 0. : 1., toggled ? 1. : 0., st::slideWrapDuration, anim::easeOutCubic); }, button->lifetime()); wrap->ease = anim::easeOutCubic; button->clicks( ) | on_next([=] { wrap->toggle(!wrap->toggled(), anim::type::normal); }, button->lifetime()); toggleButton->clicks( ) | on_next([=] { const auto checked = !checkView->checked(); for (auto i = 0u; i < state->innerChecks.size(); ++i) { if (i < state->lockChecks.size() && state->lockChecks[i] && state->lockChecks[i]()) { continue; } state->innerChecks[i]->setChecked(checked, anim::type::normal); } }, toggleButton->lifetime()); return button; } CollapsibleToggleResult AddCollapsibleToggle(not_null container, rpl::producer title, std::vector checkboxes, bool toggledWhenAll, QString description) { struct CheckboxEntry { not_null checkView; Ui::Checkbox *checkbox = nullptr; Ui::RippleButton *button = nullptr; }; struct CollapsibleState { std::vector entries; std::vector checkboxes; rpl::event_stream<> lockChanges; }; const auto cState = container->lifetime().make_state(); cState->checkboxes = std::move(checkboxes); auto wrap = object_ptr>( container, object_ptr(container)); const auto verticalLayout = wrap->entity(); auto innerChecks = std::vector>(); auto lockChecks = std::vector>(); const auto hasAnyLock = ranges::any_of(cState->checkboxes, [](const NestedEntry &e) { return e.lockGetter != nullptr; }); for (auto i = 0u; i < cState->checkboxes.size(); ++i) { const auto &entry = cState->checkboxes[i]; const auto checkbox = verticalLayout->add( object_ptr( verticalLayout, entry.checkboxLabel, entry.getter(), st::settingsCheckbox), st::powerSavingButton.padding); const auto button = Ui::CreateChild( verticalLayout, st::defaultRippleAnimation); button->stackUnder(checkbox); combine( verticalLayout->widthValue(), checkbox->geometryValue() ) | on_next([=](int w, const QRect &r) { button->setGeometry(0, r.y(), w, r.height()); }, button->lifetime()); checkbox->setAttribute(Qt::WA_TransparentForMouseEvents); const auto checkView = checkbox->checkView(); const auto idx = i; button->setClickedCallback([=] { const auto &e = cState->checkboxes[idx]; if (e.lockGetter && e.lockSetter) { if (button->clickModifiers() & Qt::ShiftModifier) { const auto currentlyLocked = e.lockGetter(); if (!currentlyLocked) { // Count already locked entries, deny if would lock all. auto lockedCount = 0; for (const auto &ce : cState->checkboxes) { if (ce.lockGetter && ce.lockGetter()) { ++lockedCount; } } if (lockedCount + 1 >= static_cast(cState->checkboxes.size())) { return; } } e.lockSetter(!currentlyLocked); // Update opacity. const auto &ce = cState->entries[idx]; if (!currentlyLocked) { auto *effect = new QGraphicsOpacityEffect(ce.checkbox); effect->setOpacity(0.4); ce.checkbox->setGraphicsEffect(effect); } else { ce.checkbox->setGraphicsEffect(nullptr); } cState->lockChanges.fire({}); return; } // Normal click on locked entry: ignore. if (e.lockGetter()) { return; } } checkView->setChecked( !checkView->checked(), anim::type::normal); }); checkView->checkedValue( ) | on_next([=](bool enabled) { cState->checkboxes[idx].setter(enabled); }, container->lifetime()); cState->entries.push_back(CheckboxEntry{ checkView, checkbox, button }); innerChecks.push_back(checkView); if (hasAnyLock) { lockChecks.push_back(entry.lockGetter ? entry.lockGetter : Fn(nullptr)); } } if (!description.isEmpty()) { Ui::AddDividerText(verticalLayout, rpl::single(std::move(description))); Ui::AddSkip(verticalLayout); } // Apply initial lock visuals. for (auto i = 0u; i < cState->entries.size(); ++i) { const auto &entry = cState->checkboxes[i]; if (entry.lockGetter && entry.lockGetter()) { auto *effect = new QGraphicsOpacityEffect(cState->entries[i].checkbox); effect->setOpacity(0.4); cState->entries[i].checkbox->setGraphicsEffect(effect); } } const auto raw = wrap.data(); raw->hide(anim::type::instant); const auto toggleWidget = AddInnerToggle( container, st::powerSavingButtonNoIcon, innerChecks, raw, std::move(title), toggledWhenAll, std::move(lockChecks), &cState->lockChanges); container->add(std::move(wrap)); container->widthValue( ) | on_next([=](int w) { raw->resizeToWidth(w); }, raw->lifetime()); return { .refresh = [cState] { for (auto i = 0u; i < cState->entries.size(); ++i) { cState->entries[i].checkView->setChecked( cState->checkboxes[i].getter(), anim::type::normal); // Refresh lock visuals. const auto &entry = cState->checkboxes[i]; if (entry.lockGetter) { if (entry.lockGetter()) { auto *effect = new QGraphicsOpacityEffect(cState->entries[i].checkbox); effect->setOpacity(0.4); cState->entries[i].checkbox->setGraphicsEffect(effect); } else { cState->entries[i].checkbox->setGraphicsEffect(nullptr); } } } }, .widget = toggleWidget, }; } void AddChooseButtonWithIconAndRightTextInner(not_null container, not_null controller, int initialState, std::vector options, rpl::producer text, rpl::producer boxTitle, const style::SettingsButton &st, Settings::IconDescriptor &&descriptor, const Fn &setter) { auto reactiveVal = container->lifetime().make_state>(initialState); rpl::producer rightTextReactive = reactiveVal->value() | rpl::map( [=](int val) { return options[val]; }); Settings::AddButtonWithLabel( container, std::move(text), rightTextReactive, st, std::move(descriptor))->addClickHandler( [=] { controller->show(Box( [=](not_null box) { const auto save = [=](int index) mutable { setter(index); reactiveVal->force_assign(index); }; SingleChoiceBox(box, { .title = boxTitle, .options = options, .initialSelection = reactiveVal->current(), .callback = save, }); })); }); } void AddChooseButtonWithIconAndRightText(not_null container, not_null controller, int initialState, std::vector options, rpl::producer text, rpl::producer boxTitle, const style::icon &icon, const Fn &setter) { AddChooseButtonWithIconAndRightTextInner( container, controller, initialState, options, std::move(text), std::move(boxTitle), st::settingsButton, {&icon}, setter); } void AddChooseButtonWithIconAndRightText(not_null container, not_null controller, int initialState, std::vector options, rpl::producer text, rpl::producer boxTitle, const Fn &setter) { AddChooseButtonWithIconAndRightTextInner( container, controller, initialState, options, std::move(text), std::move(boxTitle), st::settingsButtonNoIcon, {}, setter); } not_null AddToggleInner( not_null container, rpl::producer text, Fn getter, Fn setter, const style::SettingsButton &st, Settings::IconDescriptor &&descriptor) { const auto button = AddButtonWithIcon( container, std::move(text), st, std::move(descriptor)); button->toggleOn( rpl::single(getter()) )->toggledValue( ) | rpl::filter( [=](bool enabled) { return (enabled != getter()); }) | rpl::on_next( [=](bool enabled) { setter(enabled); }, container->lifetime()); return button; } not_null AddToggle( not_null container, rpl::producer text, Fn getter, Fn setter) { return AddToggleInner( container, std::move(text), std::move(getter), std::move(setter), st::settingsButtonNoIcon, {}); } not_null AddToggle( not_null container, rpl::producer text, Fn getter, Fn setter, const style::icon &icon) { return AddToggleInner( container, std::move(text), std::move(getter), std::move(setter), st::settingsButton, {&icon}); } void AddSectionDivider(not_null container) { AddSkip(container); AddDivider(container); AddSkip(container); } not_null AddSettingToggle( not_null container, rpl::producer text, BoolGetter getter, BoolSetter setter) { return AddToggle( container, std::move(text), [getter] { return (AyuSettings::getInstance().*getter)(); }, [setter](bool v) { (AyuSettings::getInstance().*setter)(v); }); } not_null AddSettingToggle( not_null container, rpl::producer text, BoolGetter getter, BoolSetter setter, const style::icon &icon) { return AddToggle( container, std::move(text), [getter] { return (AyuSettings::getInstance().*getter)(); }, [setter](bool v) { (AyuSettings::getInstance().*setter)(v); }, icon); } } // namespace Settings