/* 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 */ #include "ui/boxes/auto_delete_settings.h" #include "ui/widgets/checkbox.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" #include "styles/style_layers.h" namespace Ui { namespace { object_ptr CreateSliderForTTL( not_null parent, std::vector labels, int dashedAfterIndex, int selected, Fn callback) { Expects(labels.size() > 1); Expects(selected >= 0 && selected < labels.size()); Expects(dashedAfterIndex >= 0 && dashedAfterIndex < labels.size()); struct State { std::vector points; std::vector labels; int selected = 0; }; static const auto st = &st::defaultSliderForTTL; const auto height = st->font->height + st->skip + st->chosenSize; const auto count = int(labels.size()); auto result = object_ptr(parent.get(), height); const auto raw = result.data(); const auto slider = Ui::CreateChild( raw, st->chosenSize); slider->setCursor(style::cur_pointer); slider->move(0, height - slider->height()); auto &lifetime = raw->lifetime(); const auto state = lifetime.make_state(State{ .labels = std::move(labels), .selected = selected }); state->points.resize(count, 0); raw->widthValue( ) | rpl::start_with_next([=](int width) { for (auto i = 0; i != count; ++i) { state->points[i] = (width * i) / (count - 1); } slider->resize(width, slider->height()); }, lifetime); raw->paintRequest( ) | rpl::start_with_next([=] { auto p = QPainter(raw); p.setFont(st->font); for (auto i = 0; i != count; ++i) { // Label p.setPen(st->textFg); const auto &text = state->labels[i]; const auto textWidth = st->font->width(text); const auto shift = (i == count - 1) ? textWidth : (i > 0) ? (textWidth / 2) : 0; const auto x = state->points[i] - shift; const auto y = st->font->ascent; p.drawText(x, y, text); } }, lifetime); slider->paintRequest( ) | rpl::start_with_next([=] { auto p = QPainter(slider); auto hq = PainterHighQualityEnabler(p); p.setFont(st->font); for (auto i = 0; i != count; ++i) { const auto middle = (st->chosenSize / 2.); // Point const auto size = (i == state->selected) ? st->chosenSize : st->pointSize; const auto pointfg = (i <= state->selected) ? st->activeFg : st->inactiveFg; const auto shift = (i == count - 1) ? float64(size) : (i > 0) ? (size / 2.) : 0.; const auto pointx = state->points[i] - shift; const auto pointy = middle - (size / 2.); p.setPen(Qt::NoPen); p.setBrush(pointfg); p.drawEllipse(QRectF{ pointx, pointy, size * 1., size * 1. }); // Line if (i + 1 == count) { break; } const auto nextSize = (i + 1 == state->selected) ? st->chosenSize : st->pointSize; const auto nextShift = (i + 1 == count - 1) ? float64(nextSize) : (nextSize / 2.); const auto &linefg = (i + 1 <= state->selected) ? st->activeFg : st->inactiveFg; const auto from = pointx + size + st->stroke * 1.5; const auto till = state->points[i + 1] - nextShift - st->stroke * 1.5; auto pen = linefg->p; pen.setWidthF(st->stroke); if (i >= dashedAfterIndex) { // Try to fill the line with exact number of dash segments. // UPD Doesn't work so well because it changes when clicking. //const auto length = till - from; //const auto offSegmentsCount = int(std::round( // (length - st->dashOn) / (st->dashOn + st->dashOff))); //const auto onSegmentsCount = offSegmentsCount + 1; //const auto idealLength = offSegmentsCount * st->dashOff // + onSegmentsCount * st->dashOn; //const auto multiplier = length / float64(idealLength); const auto multiplier = 1.; auto dashPattern = QVector{ st->dashOn * multiplier / st->stroke, st->dashOff * multiplier / st->stroke }; pen.setDashPattern(dashPattern); } pen.setCapStyle(Qt::RoundCap); p.setPen(pen); p.setBrush(Qt::NoBrush); p.drawLine(QPointF(from, middle), QPointF(till, middle)); } }, lifetime); slider->events( ) | rpl::filter([=](not_null e) { return (e->type() == QEvent::MouseButtonPress) && (static_cast(e.get())->button() == Qt::LeftButton) && (state->points[1] > 0); }) | rpl::map([=](not_null e) { return rpl::single( static_cast(e.get())->pos() ) | rpl::then(slider->events( ) | rpl::take_while([=](not_null e) { return (e->type() != QEvent::MouseButtonRelease) || (static_cast(e.get())->button() != Qt::LeftButton); }) | rpl::filter([=](not_null e) { return (e->type() == QEvent::MouseMove); }) | rpl::map([=](not_null e) { return static_cast(e.get())->pos(); })); }) | rpl::flatten_latest( ) | rpl::start_with_next([=](QPoint position) { state->selected = std::clamp( (position.x() + (state->points[1] / 2)) / state->points[1], 0, count - 1); slider->update(); callback(state->selected); }, lifetime); return result; } } // namespace void AutoDeleteSettingsBox( not_null box, TimeId ttlMyPeriod, TimeId ttlPeerPeriod, bool ttlOneSide, std::optional userFirstName, Fn callback) { box->setTitle(tr::lng_manage_messages_ttl_title()); box->setWidth(st::boxWideWidth); struct State { TimeId my = 0; bool oneSide = false; rpl::event_stream> aboutTexts; Fn update; }; const auto state = box->lifetime().make_state(State{ .my = ttlMyPeriod, .oneSide = ttlOneSide, }); const auto options = std::vector{ u"5 seconds"_q, AssertIsDebug() tr::lng_manage_messages_ttl_after1(tr::now), tr::lng_manage_messages_ttl_after2(tr::now), tr::lng_manage_messages_ttl_never(tr::now), }; const auto periodToIndex = [&](TimeId period) { return !period ? 3 : (period == 5) AssertIsDebug() ? 0 AssertIsDebug() : (period < 3 * 86400) ? 1 : 2; }; const auto indexToPeriod = [&](int index) { return !index ? 5 AssertIsDebug() : (index == 1) AssertIsDebug() ? 86400 : (index == 2) ? 7 * 86400 : 0; }; const auto sliderCallback = [=](int index) { state->my = indexToPeriod(index); state->update(); }; const auto slider = box->addRow( CreateSliderForTTL( box, options | ranges::to_vector, periodToIndex(ttlPeerPeriod), periodToIndex(ttlMyPeriod), sliderCallback), { st::boxRowPadding.left(), 0, st::boxRowPadding.right(), st::boxMediumSkip }); const auto bothSides = userFirstName ? box->addRow( object_ptr( box, tr::lng_ttl_also_checkbox(tr::now, lt_user, *userFirstName), !ttlOneSide), { st::boxRowPadding.left(), 0, st::boxRowPadding.right(), st::boxMediumSkip }) : nullptr; const auto description = box->addRow( object_ptr( box, object_ptr( box, state->aboutTexts.events() | rpl::flatten_latest(), st::boxDividerLabel), st::ttlDividerLabelPadding), style::margins()); if (bothSides) { bothSides->checkedChanges( ) | rpl::start_with_next([=](bool checked) { state->oneSide = !checked; state->update(); }, bothSides->lifetime()); } state->update = [=] { const auto his = ttlPeerPeriod; const auto wrap = [](TimeId period) { Expects(period > 0); return (period == 5) AssertIsDebug() ? rpl::single(u"5 seconds"_q) AssertIsDebug() : (period < 3 * 86400) ? tr::lng_ttl_about_duration1() : tr::lng_ttl_about_duration2(); }; state->aboutTexts.fire(((!state->my && !his) || !userFirstName) ? tr::lng_ttl_edit_about() : (his > 0 && (!state->my || his < state->my)) ? tr::lng_ttl_edit_about_other( lt_user, rpl::single(*userFirstName), lt_duration, wrap(his)) : state->oneSide ? tr::lng_ttl_edit_about_you_only(lt_duration, wrap(state->my)) : tr::lng_ttl_edit_about_you( lt_duration, wrap(state->my), lt_user, rpl::single(*userFirstName))); }; state->update(); box->addButton(tr::lng_settings_save(), [=] { const auto period = state->my; const auto oneSide = state->oneSide; box->closeBox(); callback(period, oneSide); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } } // namespace Ui