New top bar, sharing, internal IV links style.

This commit is contained in:
John Preston 2024-03-12 13:00:51 +04:00
parent 315859bf7b
commit e1b55b560a
11 changed files with 502 additions and 292 deletions

View file

@ -82,43 +82,24 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
opacity: 1;
}
}
#top_menu circle {
fill: var(--td-history-to-down-fg);
#top_shadow {
z-index: 999;
position: fixed;
top: 0;
height: 1px;
width: 100%;
left: 0;
background-color: var(--td-shadow-fg)
}
#top_menu:hover circle {
fill: var(--td-history-to-down-fg-over);
}
#top_menu {
top: 10px;
right: 10px;
}
#top_back path,
#top_back line,
#bottom_up path {
stroke: var(--td-history-to-down-fg);
}
#top_back path,
#top_back line {
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
}
#bottom_up path {
stroke-width: 1.4;
}
#top_back:hover path,
#top_back:hover line,
#bottom_up:hover path {
stroke: var(--td-history-to-down-fg-over);
}
#top_back {
top: 10px;
left: 10px;
transition: left 200ms linear;
}
#top_back.hidden {
left: -36px;
}
#bottom_up {
bottom: 10px;
right: 10px;
@ -210,6 +191,14 @@ article a[href] {
color: var(--td-window-active-text-fg);
text-decoration: none;
}
article a.internal-iv-link {
border-radius: 3px;
margin: 0px -3px;
padding: 0px 3px;
position: relative;
background: var(--td-light-button-bg-over);
color: var(--td-light-button-fg);
}
article span.reference {
border: dotted var(--td-window-sub-text-fg);
border-width: 1px 1px 1px 2px;

View file

@ -206,9 +206,6 @@ var IV = {
},
stopRipples: function (button) {
const id = button.id ? button.id : button;
if (IV.frozenRipple === id) {
return;
}
button = document.getElementById(id);
const ripples = button.getElementsByClassName('ripple');
for (var i = 0; i < ripples.length; ++i) {
@ -218,15 +215,6 @@ var IV = {
}
}
},
clearFrozenRipple: function () {
if (IV.frozenRipple) {
const button = document.getElementById(IV.frozenRipple);
IV.frozenRipple = null;
if (button) {
IV.stopRipples(button);
}
}
},
init: function () {
IV.platform = window.navigator.platform.toLowerCase();
IV.mac = IV.platform.startsWith('mac');
@ -310,12 +298,6 @@ var IV = {
behavior: instant ? 'instant' : 'smooth'
});
},
menu: function (button) {
IV.frozenRipple = button.id;
const state = this.computeCurrentState();
IV.notify({ event: 'menu', index: state.index, hash: state.hash });
},
computeCurrentState: function () {
var now = IV.findPageScroll();
return {
@ -490,13 +472,13 @@ var IV = {
was.classList.add(back ? 'hidden-right' : 'hidden-left');
now.classList.remove(back ? 'hidden-left' : 'hidden-right');
var topBack = document.getElementById('top_back');
if (!IV.position) {
topBack.classList.add('hidden');
} else {
topBack.classList.remove('hidden');
}
IV.index = index;
IV.notify({
event: 'location_change',
index: IV.index,
position: IV.position,
hash: IV.computeCurrentState().hash,
});
if (IV.cache[index].contentUpdated) {
IV.cache[index].contentUpdated = false;
IV.applyUpdatedContent(index);

View file

@ -21,25 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
QString SiteNameFromUrl(const QString &url) {
const auto u = QUrl(url);
QString pretty = u.isValid() ? u.toDisplayString() : url;
const auto m = QRegularExpression(u"^[a-zA-Z0-9]+://"_q).match(pretty);
if (m.hasMatch()) pretty = pretty.mid(m.capturedLength());
int32 slash = pretty.indexOf('/');
if (slash > 0) pretty = pretty.mid(0, slash);
QStringList components = pretty.split('.', Qt::SkipEmptyParts);
if (components.size() >= 2) {
components = components.mid(components.size() - 2);
return components.at(0).at(0).toUpper()
+ components.at(0).mid(1)
+ '.'
+ components.at(1);
}
return QString();
}
WebPageCollage ExtractCollage(
[[nodiscard]] WebPageCollage ExtractCollage(
not_null<Data::Session*> owner,
const QVector<MTPPageBlock> &items,
const QVector<MTPPhoto> &photos,
@ -256,7 +238,7 @@ bool WebPageData::applyChanges(
} else if (!newDescription.text.isEmpty()
&& viewTitleText.isEmpty()
&& !resultUrl.isEmpty()) {
return SiteNameFromUrl(resultUrl);
return Iv::SiteNameFromUrl(resultUrl);
}
return QString();
}();

View file

@ -8,94 +8,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
using "ui/basic.style";
using "ui/widgets/widgets.style";
ivTitleHeight: 24px;
ivTitleIconShift: point(0px, 0px);
ivTitleButton: IconButton(windowTitleButton) {
height: ivTitleHeight;
iconPosition: ivTitleIconShift;
}
ivTitleButtonClose: IconButton(windowTitleButtonClose) {
height: ivTitleHeight;
iconPosition: ivTitleIconShift;
}
ivMenuToggle: IconButton(defaultIconButton) {
width: 48px;
height: 48px;
ivTitleButtonSize: size(windowTitleButtonWidth, ivTitleHeight);
ivTitle: WindowTitle(defaultWindowTitle) {
height: ivTitleHeight;
style: TextStyle(defaultTextStyle) {
font: font(semibold 12px);
icon: icon {{ "title_menu_dots", menuIconColor }};
iconOver: icon {{ "title_menu_dots", menuIconColor }};
rippleAreaPosition: point(6px, 6px);
rippleAreaSize: 36px;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
shadow: false;
minimize: IconButton(ivTitleButton) {
icon: icon {
{ ivTitleButtonSize, titleButtonBg },
{ "title_button_minimize", titleButtonFg, ivTitleIconShift },
};
iconOver: icon {
{ ivTitleButtonSize, titleButtonBgOver },
{ "title_button_minimize", titleButtonFgOver, ivTitleIconShift },
};
}
minimizeIconActive: icon {
{ ivTitleButtonSize, titleButtonBgActive },
{ "title_button_minimize", titleButtonFgActive, ivTitleIconShift },
};
minimizeIconActiveOver: icon {
{ ivTitleButtonSize, titleButtonBgActiveOver },
{ "title_button_minimize", titleButtonFgActiveOver, ivTitleIconShift },
};
maximize: IconButton(windowTitleButton) {
icon: icon {
{ ivTitleButtonSize, titleButtonBg },
{ "title_button_maximize", titleButtonFg, ivTitleIconShift },
};
iconOver: icon {
{ ivTitleButtonSize, titleButtonBgOver },
{ "title_button_maximize", titleButtonFgOver, ivTitleIconShift },
};
}
maximizeIconActive: icon {
{ ivTitleButtonSize, titleButtonBgActive },
{ "title_button_maximize", titleButtonFgActive, ivTitleIconShift },
};
maximizeIconActiveOver: icon {
{ ivTitleButtonSize, titleButtonBgActiveOver },
{ "title_button_maximize", titleButtonFgActiveOver, ivTitleIconShift },
};
restoreIcon: icon {
{ ivTitleButtonSize, titleButtonBg },
{ "title_button_restore", titleButtonFg, ivTitleIconShift },
};
restoreIconOver: icon {
{ ivTitleButtonSize, titleButtonBgOver },
{ "title_button_restore", titleButtonFgOver, ivTitleIconShift },
};
restoreIconActive: icon {
{ ivTitleButtonSize, titleButtonBgActive },
{ "title_button_restore", titleButtonFgActive, ivTitleIconShift },
};
restoreIconActiveOver: icon {
{ ivTitleButtonSize, titleButtonBgActiveOver },
{ "title_button_restore", titleButtonFgActiveOver, ivTitleIconShift },
};
close: IconButton(windowTitleButtonClose) {
icon: icon {
{ ivTitleButtonSize, titleButtonCloseBg },
{ "title_button_close", titleButtonCloseFg, ivTitleIconShift },
};
iconOver: icon {
{ ivTitleButtonSize, titleButtonCloseBgOver },
{ "title_button_close", titleButtonCloseFgOver, ivTitleIconShift },
};
}
closeIconActive: icon {
{ ivTitleButtonSize, titleButtonCloseBgActive },
{ "title_button_close", titleButtonCloseFgActive, ivTitleIconShift },
};
closeIconActiveOver: icon {
{ ivTitleButtonSize, titleButtonCloseBgActiveOver },
{ "title_button_close", titleButtonCloseFgActiveOver, ivTitleIconShift },
};
}
ivTitleExpandedHeight: 76px;
ivMenuPosition: point(-8px, 36px);
ivMenuPosition: point(-2px, 40px);
ivBack: IconButton(ivMenuToggle) {
width: 60px;
icon: icon {{ "box_button_back", menuIconColor }};
iconOver: icon {{ "box_button_back", menuIconColor }};
rippleAreaPosition: point(12px, 6px);
}
ivSubtitleFont: font(16px semibold);
ivSubtitle: FlatLabel(defaultFlatLabel) {
textFg: boxTitleFg;
maxHeight: 26px;
style: TextStyle(defaultTextStyle) {
font: ivSubtitleFont;
}
}
ivSubtitleHeight: 48px;
ivSubtitleTop: 12px;
ivSubtitleLeft: 22px;
ivSubtitleSkip: 0px;

View file

@ -13,8 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "iv/iv_data.h"
#include "lang/lang_keys.h"
#include "ui/platform/ui_platform_window_title.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/rp_window.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/basic_click_handlers.h"
#include "ui/painter.h"
#include "webview/webview_data_stream_memory.h"
@ -35,11 +38,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QWindow>
#include <charconv>
#include "base/call_delayed.h"
namespace Iv {
namespace {
[[nodiscard]] QByteArray ComputeStyles() {
static const auto map = base::flat_map<QByteArray, const style::color*>{
{ "shadow-fg", &st::shadowFg },
{ "scroll-bg", &st::scrollBg },
{ "scroll-bg-over", &st::scrollBgOver },
{ "scroll-bar-bg", &st::scrollBarBg },
@ -54,6 +60,8 @@ namespace {
{ "window-shadow-fg", &st::windowShadowFg },
{ "box-divider-bg", &st::boxDividerBg },
{ "box-divider-fg", &st::boxDividerFg },
{ "light-button-fg", &st::lightButtonFg },
{ "light-button-bg-over", &st::lightButtonBgOver },
{ "menu-icon-fg", &st::menuIconFg },
{ "menu-icon-fg-over", &st::menuIconFgOver },
{ "menu-bg", &st::menuBg },
@ -164,19 +172,7 @@ namespace {
<link rel="stylesheet" href="/iv/page.css" />
</head>
<body>
<button class="fixed_button hidden" id="top_back" onclick="IV.back();">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<line x1="5.37464142" y1="12" x2="18.5" y2="12"></line>
<path d="M11.5,18.3 L5.27277119,12.0707223 C5.23375754,12.0316493 5.23375754,11.9683507 5.27277119,11.9292777 L11.5,5.7 L11.5,5.7"></path>
</svg>
</button>
<button class="fixed_button" id="top_menu" onclick="IV.menu(this);">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="17.4" r="1.7"></circle>
<circle cx="12" cy="12" r="1.7"></circle>
<circle cx="12" cy="6.6" r="1.7"></circle>
</svg>
</button>
<div id="top_shadow"></div>
<button class="fixed_button hidden" id="bottom_up" onclick="IV.scrollTo(0);">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M14.9972363,18 L9.13865768,12.1414214 C9.06055283,12.0633165 9.06055283,11.9366835 9.13865768,11.8585786 L14.9972363,6 L14.9972363,6" transform="translate(11.997236, 12.000000) scale(-1, -1) rotate(-90.000000) translate(-11.997236, -12.000000) "></path>
@ -198,26 +194,93 @@ namespace {
} // namespace
Controller::Controller()
Controller::Controller(Fn<ShareBoxResult(ShareBoxDescriptor)> showShareBox)
: _updateStyles([=] {
const auto str = EscapeForScriptString(ComputeStyles());
if (_webview) {
_webview->eval("IV.updateStyles('" + str + "');");
}
}) {
})
, _showShareBox(std::move(showShareBox)) {
createWindow();
}
Controller::~Controller() {
destroyShareMenu();
if (_window) {
_window->hide();
}
_ready = false;
_webview = nullptr;
_title = nullptr;
_back.destroy();
_menu = nullptr;
_menuToggle.destroy();
_subtitle = nullptr;
_subtitleWrap = nullptr;
_window = nullptr;
}
void Controller::updateTitleGeometry(int newWidth) const {
_subtitleWrap->setGeometry(
0,
st::windowTitleHeight,
newWidth,
st::ivSubtitleHeight);
_subtitleWrap->paintRequest() | rpl::start_with_next([=](QRect clip) {
QPainter(_subtitleWrap.get()).fillRect(clip, st::windowBg);
}, _subtitleWrap->lifetime());
const auto progress = _subtitleLeft.value(_back->toggled() ? 1. : 0.);
const auto left = anim::interpolate(
st::ivSubtitleLeft,
_back->width() + st::ivSubtitleSkip,
progress);
_subtitle->resizeToWidth(newWidth - left - _menuToggle->width());
_subtitle->moveToLeft(left, st::ivSubtitleTop);
_back->moveToLeft(0, 0);
_menuToggle->moveToRight(0, 0);
}
void Controller::initControls() {
_subtitleWrap = std::make_unique<Ui::RpWidget>(_window.get());
_subtitle = std::make_unique<Ui::FlatLabel>(
_subtitleWrap.get(),
_index.value() | rpl::filter(
rpl::mappers::_1 >= 0
) | rpl::map([=](int index) {
return _pages[index].name;
}),
st::ivSubtitle);
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
_menuToggle.create(_subtitleWrap.get(), st::ivMenuToggle);
_menuToggle->setClickedCallback([=] { showMenu(); });
_back.create(
_subtitleWrap.get(),
object_ptr<Ui::IconButton>(_subtitleWrap.get(), st::ivBack));
_back->entity()->setClickedCallback([=] {
if (_webview) {
_webview->eval("IV.back();");
} else {
_back->hide(anim::type::normal);
}
});
_back->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
_subtitleLeft.start(
[=] { updateTitleGeometry(_window->width()); },
toggled ? 0. : 1.,
toggled ? 1. : 0.,
st::fadeWrapDuration);
}, _back->lifetime());
_back->hide(anim::type::instant);
_subtitleLeft.stop();
}
bool Controller::showFast(const QString &url, const QString &hash) {
return false;
}
@ -227,8 +290,6 @@ void Controller::show(
Prepared page,
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
page.script = fillInChannelValuesScript(std::move(inChannelValues));
_titleText.setText(st::ivTitle.style, page.title);
_title->update();
InvokeQueued(_container, [=, page = std::move(page)]() mutable {
showInWindow(dataPath, std::move(page));
});
@ -277,35 +338,8 @@ QByteArray Controller::toggleInChannelScript(
return "IV.toggleChannelJoined('" + id + "', " + value + ");";
}
void Controller::updateTitleGeometry() {
_title->setGeometry(0, 0, _window->width(), st::ivTitle.height);
}
void Controller::paintTitle(Painter &p, QRect clip) {
const auto active = _window->isActiveWindow();
const auto full = _title->width();
p.setPen(active ? st::ivTitle.fgActive : st::ivTitle.fg);
const auto available = QRect(
_titleLeftSkip,
0,
full - _titleLeftSkip - _titleRightSkip,
_title->height());
const auto use = std::min(available.width(), _titleText.maxWidth());
const auto center = full
- 2 * std::max(_titleLeftSkip, _titleRightSkip);
const auto left = (use <= center)
? ((full - use) / 2)
: (use < available.width() && _titleLeftSkip < _titleRightSkip)
? (available.x() + available.width() - use)
: available.x();
const auto titleTextHeight = st::ivTitle.style.font->height;
const auto top = (st::ivTitle.height - titleTextHeight) / 2;
_titleText.drawLeftElided(p, left, top, available.width(), full);
}
void Controller::createWindow() {
_window = std::make_unique<Ui::RpWindow>();
_window->setTitleStyle(st::ivTitle);
const auto window = _window.get();
base::qt_signal_producer(
@ -314,61 +348,25 @@ void Controller::createWindow() {
) | rpl::filter([=] {
return _webview && window->window()->windowHandle()->isActive();
}) | rpl::start_with_next([=] {
_webview->focus();
setInnerFocus();
}, window->lifetime());
_title = std::make_unique<Ui::RpWidget>(window);
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
_title->paintRequest() | rpl::start_with_next([=](QRect clip) {
auto p = Painter(_title.get());
paintTitle(p, clip);
}, _title->lifetime());
window->widthValue() | rpl::start_with_next([=] {
updateTitleGeometry();
}, _title->lifetime());
initControls();
#ifdef Q_OS_MAC
_titleLeftSkip = 8 + 12 + 8 + 12 + 8 + 12 + 8;
_titleRightSkip = st::ivTitle.style.font->spacew;
#else // Q_OS_MAC
using namespace Ui::Platform;
TitleControlsLayoutValue(
) | rpl::start_with_next([=](TitleControls::Layout layout) {
const auto accumulate = [](const auto &list) {
auto result = 0;
for (const auto control : list) {
switch (control) {
case TitleControl::Close:
result += st::ivTitle.close.width;
break;
case TitleControl::Minimize:
result += st::ivTitle.minimize.width;
break;
case TitleControl::Maximize:
result += st::ivTitle.maximize.width;
break;
}
}
return result;
};
const auto space = st::ivTitle.style.font->spacew;
_titleLeftSkip = accumulate(layout.left) + space;
_titleRightSkip = accumulate(layout.right) + space;
_title->update();
}, _title->lifetime());
#endif // Q_OS_MAC
window->widthValue() | rpl::start_with_next([=](int width) {
updateTitleGeometry(width);
}, _subtitle->lifetime());
window->setGeometry({ 200, 200, 600, 800 });
window->setMinimumSize({ st::windowMinWidth, st::windowMinHeight });
_container = Ui::CreateChild<Ui::RpWidget>(window->body().get());
_container = Ui::CreateChild<Ui::RpWidget>(window->window());
rpl::combine(
window->body()->sizeValue(),
_title->heightValue()
window->sizeValue(),
_subtitleWrap->heightValue()
) | rpl::start_with_next([=](QSize size, int title) {
title -= window->body()->y();
_container->setGeometry(QRect(QPoint(), size).marginsRemoved(
{ 0, title, 0, 0 }));
{ 0, title + st::windowTitleHeight, 0, 0 }));
}, _container->lifetime());
_container->paintRequest() | rpl::start_with_next([=](QRect clip) {
@ -452,10 +450,12 @@ void Controller::createWebview(const QString &dataPath) {
if (!script.isEmpty()) {
_webview->eval(script);
}
} else if (event == u"menu"_q) {
menu(
object.value("index").toInt(),
object.value("hash").toString());
} else if (event == u"location_change"_q) {
_index = object.value("index").toInt();
_hash = object.value("hash").toString();
_back->toggle(
(object.value("position").toInt() > 0),
anim::type::normal);
}
});
});
@ -543,37 +543,47 @@ void Controller::showInWindow(const QString &dataPath, Prepared page) {
Expects(_container != nullptr);
const auto url = page.url;
const auto hash = page.hash;
_hash = page.hash;
auto i = _indices.find(url);
if (i == end(_indices)) {
_pages.push_back(std::move(page));
i = _indices.emplace(url, int(_pages.size() - 1)).first;
}
const auto index = i->second;
_index = index;
if (!_webview) {
createWebview(dataPath);
if (_webview && _webview->widget()) {
auto id = u"iv/page%1.html"_q.arg(index);
if (!hash.isEmpty()) {
id += '#' + hash;
if (!_hash.isEmpty()) {
id += '#' + _hash;
}
_webview->navigateToData(id);
_webview->focus();
activate();
} else {
_events.fire({ Event::Type::Close });
}
} else if (_ready) {
_webview->eval(navigateScript(index, hash));
_window->raise();
_window->activateWindow();
_window->setFocus();
_webview->focus();
_webview->eval(navigateScript(index, _hash));
activate();
} else {
_navigateToIndexWhenReady = index;
_navigateToHashWhenReady = hash;
_window->raise();
_window->activateWindow();
_window->setFocus();
_navigateToHashWhenReady = _hash;
activate();
}
}
void Controller::activate() {
_window->raise();
_window->activateWindow();
_window->setFocus();
setInnerFocus();
}
void Controller::setInnerFocus() {
if (const auto onstack = _shareFocus) {
onstack();
} else if (_webview) {
_webview->focus();
}
}
@ -657,8 +667,17 @@ void Controller::minimize() {
}
}
void Controller::menu(int index, const QString &hash) {
if (!_webview || _menu || index < 0 || index > _pages.size()) {
QString Controller::composeCurrentUrl() const {
const auto index = _index.current();
Assert(index >= 0 && index < _pages.size());
return _pages[index].url
+ (_hash.isEmpty() ? u""_q : ('#' + _hash));
}
void Controller::showMenu() {
const auto index = _index.current();
if (_menu || index < 0 || index > _pages.size()) {
return;
}
_menu = base::make_unique_q<Ui::PopupMenu>(
@ -666,14 +685,15 @@ void Controller::menu(int index, const QString &hash) {
st::popupMenuWithIcons);
_menu->setDestroyedCallback(crl::guard(_window.get(), [
this,
weakButton = Ui::MakeWeak(_menuToggle.data()),
menu = _menu.get()] {
if (_webview) {
_webview->eval("IV.clearFrozenRipple();");
if (_menu == menu && weakButton) {
weakButton->setForceRippled(false);
}
}));
_menuToggle->setForceRippled(true);
const auto url = _pages[index].url
+ (hash.isEmpty() ? u""_q : ('#' + hash));
const auto url = composeCurrentUrl();
const auto openInBrowser = crl::guard(_window.get(), [=] {
_events.fire({ .type = Event::Type::OpenLinkExternal, .url = url });
});
@ -683,6 +703,7 @@ void Controller::menu(int index, const QString &hash) {
&st::menuIconIpAddress);
_menu->addAction(tr::lng_iv_share(tr::now), [=] {
showShareMenu();
}, &st::menuIconShare);
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
@ -691,7 +712,11 @@ void Controller::menu(int index, const QString &hash) {
}
void Controller::escape() {
close();
if (const auto onstack = _shareHide) {
onstack();
} else {
close();
}
}
void Controller::close() {
@ -706,4 +731,56 @@ rpl::lifetime &Controller::lifetime() {
return _lifetime;
}
void Controller::destroyShareMenu() {
_shareHide = nullptr;
if (_shareFocus) {
_shareFocus = nullptr;
setInnerFocus();
}
if (_shareWrap) {
_shareWrap->windowHandle()->setParent(nullptr);
_shareWrap = nullptr;
_shareContainer = nullptr;
}
}
void Controller::showShareMenu() {
const auto index = _index.current();
if (_shareWrap || index < 0 || index > _pages.size()) {
return;
}
_shareWrap = std::make_unique<Ui::RpWidget>(nullptr);
const auto margins = QMargins(0, st::windowTitleHeight, 0, 0);
_shareWrap->setGeometry(_window->geometry().marginsRemoved(margins));
_shareWrap->setWindowFlag(Qt::FramelessWindowHint);
_shareWrap->setAttribute(Qt::WA_TranslucentBackground);
_shareWrap->setAttribute(Qt::WA_NoSystemBackground);
_shareWrap->createWinId();
_shareContainer.reset(QWidget::createWindowContainer(
_shareWrap->windowHandle(),
_window.get(),
Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint));
_window->sizeValue() | rpl::start_with_next([=](QSize size) {
_shareContainer->setGeometry(QRect(QPoint(), size).marginsRemoved(
margins));
}, _shareWrap->lifetime());
auto result = _showShareBox({
.parent = _shareWrap.get(),
.url = composeCurrentUrl(),
});
_shareFocus = result.focus;
_shareHide = result.hide;
std::move(result.destroyRequests) | rpl::start_with_next([=] {
destroyShareMenu();
}, _shareWrap->lifetime());
Ui::ForceFullRepaintSync(_shareWrap.get());
_shareContainer->show();
activate();
}
} // namespace Iv

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/invoke_queued.h"
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
#include "ui/effects/animations.h"
#include "ui/text/text.h"
@ -23,15 +24,29 @@ namespace Ui {
class RpWidget;
class RpWindow;
class PopupMenu;
class FlatLabel;
class IconButton;
template <typename Widget>
class FadeWrapScaled;
} // namespace Ui
namespace Iv {
struct Prepared;
struct ShareBoxResult {
Fn<void()> focus;
Fn<void()> hide;
rpl::producer<> destroyRequests;
};
struct ShareBoxDescriptor {
not_null<Ui::RpWidget*> parent;
QString url;
};
class Controller final {
public:
Controller();
explicit Controller(Fn<ShareBoxResult(ShareBoxDescriptor)> showShareBox);
~Controller();
struct Event {
@ -77,8 +92,6 @@ private:
[[nodiscard]] QByteArray navigateScript(int index, const QString &hash);
[[nodiscard]] QByteArray reloadScript(int index);
void updateTitleGeometry();
void paintTitle(Painter &p, QRect clip);
void showInWindow(const QString &dataPath, Prepared page);
[[nodiscard]] QByteArray fillInChannelValuesScript(
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
@ -89,17 +102,28 @@ private:
void processKey(const QString &key, const QString &modifier);
void processLink(const QString &url, const QString &context);
void menu(int index, const QString &hash);
void initControls();
void updateTitleGeometry(int newWidth) const;
void activate();
void setInnerFocus();
void showMenu();
void escape();
void close();
void quit();
[[nodiscard]] QString composeCurrentUrl() const;
void showShareMenu();
void destroyShareMenu();
std::unique_ptr<Ui::RpWindow> _window;
std::unique_ptr<Ui::RpWidget> _title;
std::unique_ptr<Ui::RpWidget> _subtitleWrap;
rpl::variable<QString> _subtitleText;
std::unique_ptr<Ui::FlatLabel> _subtitle;
Ui::Animations::Simple _subtitleLeft;
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _back = { nullptr };
base::unique_qptr<Ui::PopupMenu> _menu;
Ui::Text::String _titleText;
int _titleLeftSkip = 0;
int _titleRightSkip = 0;
Ui::RpWidget *_container = nullptr;
std::unique_ptr<Webview::Window> _webview;
rpl::event_stream<Webview::DataRequest> _dataRequests;
@ -111,6 +135,15 @@ private:
bool _subscribedToColors = false;
bool _ready = false;
rpl::variable<int> _index = -1;
QString _hash;
Fn<ShareBoxResult(ShareBoxDescriptor)> _showShareBox;
std::unique_ptr<Ui::RpWidget> _shareWrap;
std::unique_ptr<QWidget> _shareContainer;
Fn<void()> _shareFocus;
Fn<void()> _shareHide;
std::vector<Prepared> _pages;
base::flat_map<QString, int> _indices;
QString _navigateToHashWhenReady;

View file

@ -9,6 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "iv/iv_prepare.h"
#include <QtCore/QRegularExpression>
#include <QtCore/QUrl>
namespace Iv {
QByteArray GeoPointId(Geo point) {
@ -45,9 +48,9 @@ Data::Data(const MTPDwebPage &webpage, const MTPPage &page)
.webpageDocument = (webpage.vdocument()
? *webpage.vdocument()
: std::optional<MTPDocument>()),
.title = (webpage.vtitle()
? qs(*webpage.vtitle())
: qs(webpage.vauthor().value_or_empty()))
.name = (webpage.vsite_name()
? qs(*webpage.vsite_name())
: SiteNameFromUrl(qs(webpage.vurl())))
})) {
}
@ -67,4 +70,22 @@ void Data::prepare(const Options &options, Fn<void(Prepared)> done) const {
});
}
QString SiteNameFromUrl(const QString &url) {
const auto u = QUrl(url);
QString pretty = u.isValid() ? u.toDisplayString() : url;
const auto m = QRegularExpression(u"^[a-zA-Z0-9]+://"_q).match(pretty);
if (m.hasMatch()) pretty = pretty.mid(m.capturedLength());
int32 slash = pretty.indexOf('/');
if (slash > 0) pretty = pretty.mid(0, slash);
QStringList components = pretty.split('.', Qt::SkipEmptyParts);
if (components.size() >= 2) {
components = components.mid(components.size() - 2);
return components.at(0).at(0).toUpper()
+ components.at(0).mid(1)
+ '.'
+ components.at(1);
}
return QString();
}
} // namespace Iv

View file

@ -16,7 +16,8 @@ struct Options {
};
struct Prepared {
QString title;
QString name;
//QString title;
QByteArray content;
QByteArray script;
QString url;
@ -53,4 +54,6 @@ private:
};
[[nodiscard]] QString SiteNameFromUrl(const QString &url);
} // namespace Iv

View file

@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "iv/iv_instance.h"
#include "base/call_delayed.h"
#include "apiwrap.h"
#include "boxes/share_box.h"
#include "core/application.h"
#include "core/file_utilities.h"
#include "core/shortcuts.h"
@ -20,10 +20,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h"
#include "data/data_photo_media.h"
#include "data/data_session.h"
#include "data/data_thread.h"
#include "data/data_web_page.h"
#include "data/data_user.h"
#include "history/history_item_helpers.h"
#include "info/profile/info_profile_values.h"
#include "iv/iv_controller.h"
#include "iv/iv_data.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_common.h" // Lottie::ReadContent.
#include "main/main_account.h"
#include "main/main_domain.h"
@ -34,6 +38,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/file_download.h"
#include "storage/storage_domain.h"
#include "ui/boxes/confirm_box.h"
#include "ui/layers/layer_widget.h"
#include "ui/text/text_utilities.h"
#include "ui/basic_click_handlers.h"
#include "webview/webview_data_stream_memory.h"
#include "webview/webview_interface.h"
@ -42,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller_link_info.h"
#include <QtGui/QDesktopServices>
#include <QtGui/QGuiApplication>
namespace Iv {
namespace {
@ -124,6 +131,7 @@ private:
void showLocal(Prepared result);
void showWindowed(Prepared result);
[[nodiscard]] ShareBoxResult shareBox(ShareBoxDescriptor &&descriptor);
// Local.
void showProgress(int index);
@ -445,10 +453,185 @@ void Shown::writeEmbed(QString id, QString hash) {
}
}
ShareBoxResult Shown::shareBox(ShareBoxDescriptor &&descriptor) {
class Show final : public Ui::Show {
public:
Show(QPointer<QWidget> parent, Fn<Ui::LayerStackWidget*()> lookup)
: _parent(parent)
, _lookup(lookup) {
}
void showOrHideBoxOrLayer(
std::variant<
v::null_t,
object_ptr<Ui::BoxContent>,
std::unique_ptr<Ui::LayerWidget>> &&layer,
Ui::LayerOptions options,
anim::type animated) const {
using UniqueLayer = std::unique_ptr<Ui::LayerWidget>;
using ObjectBox = object_ptr<Ui::BoxContent>;
const auto stack = _lookup();
if (!stack) {
return;
} else if (auto layerWidget = std::get_if<UniqueLayer>(&layer)) {
stack->showLayer(std::move(*layerWidget), options, animated);
} else if (auto box = std::get_if<ObjectBox>(&layer)) {
stack->showBox(std::move(*box), options, animated);
} else {
stack->hideAll(animated);
}
}
not_null<QWidget*> toastParent() const {
return _parent.data();
}
bool valid() const override {
return _lookup() != nullptr;
}
operator bool() const override {
return valid();
}
private:
const QPointer<QWidget> _parent;
const Fn<Ui::LayerStackWidget*()> _lookup;
};
const auto url = descriptor.url;
const auto wrap = descriptor.parent;
struct State {
Ui::LayerStackWidget *stack = nullptr;
rpl::event_stream<> destroyRequests;
};
const auto state = wrap->lifetime().make_state<State>();
const auto weak = QPointer<Ui::RpWidget>(wrap);
const auto lookup = crl::guard(weak, [state] { return state->stack; });
const auto layer = Ui::CreateChild<Ui::LayerStackWidget>(
wrap.get(),
[=] { return std::make_shared<Show>(weak.data(), lookup); });
state->stack = layer;
const auto show = layer->showFactory()();
layer->setHideByBackgroundClick(false);
layer->move(0, 0);
wrap->sizeValue(
) | rpl::start_with_next([=](QSize size) {
layer->resize(size);
}, layer->lifetime());
layer->hideFinishEvents(
) | rpl::filter([=] {
return !!lookup(); // Last hide finish is sent from destructor.
}) | rpl::start_with_next([=] {
state->destroyRequests.fire({});
}, wrap->lifetime());
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
const auto sending = std::make_shared<bool>();
auto copyCallback = [=] {
QGuiApplication::clipboard()->setText(url);
show->showToast(tr::lng_background_link_copied(tr::now));
};
auto submitCallback = [=](
std::vector<not_null<::Data::Thread*>> &&result,
TextWithTags &&comment,
Api::SendOptions options,
::Data::ForwardOptions) {
if (*sending || result.empty()) {
return;
}
const auto error = [&] {
for (const auto thread : result) {
const auto error = GetErrorTextForSending(
thread,
{ .text = &comment });
if (!error.isEmpty()) {
return std::make_pair(error, thread);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->chatListName())
).append("\n\n");
}
text.append(error.first);
if (const auto weak = *box) {
weak->getDelegate()->show(Ui::MakeConfirmBox({
.text = text,
.inform = true,
}));
}
return;
}
*sending = true;
if (!comment.text.isEmpty()) {
comment.text = url + "\n" + comment.text;
const auto add = url.size() + 1;
for (auto &tag : comment.tags) {
tag.offset += add;
}
} else {
comment.text = url;
}
auto &api = _session->api();
for (const auto thread : result) {
auto message = Api::MessageToSend(
Api::SendAction(thread, options));
message.textWithTags = comment;
message.action.clearDraft = false;
api.sendMessage(std::move(message));
}
if (*box) {
(*box)->closeBox();
}
show->showToast(tr::lng_share_done(tr::now));
};
auto filterCallback = [](not_null<::Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) {
return true;
}
}
return ::Data::CanSend(thread, ChatRestriction::SendOther);
};
const auto focus = crl::guard(layer, [=] {
if (!layer->window()->isActiveWindow()) {
layer->window()->activateWindow();
layer->window()->setFocus();
}
layer->setInnerFocus();
});
auto result = ShareBoxResult{
.focus = focus,
.hide = [=] { show->hideLayer(); },
.destroyRequests = state->destroyRequests.events(),
};
*box = show->show(
Box<ShareBox>(ShareBox::Descriptor{
.session = _session,
.copyCallback = std::move(copyCallback),
.submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback),
.premiumRequiredError = SharePremiumRequiredError(),
}),
Ui::LayerOption::KeepOther,
anim::type::normal);
return result;
}
void Shown::createController() {
Expects(!_controller);
_controller = std::make_unique<Controller>();
const auto showShareBox = [=](ShareBoxDescriptor &&descriptor) {
return shareBox(std::move(descriptor));
};
_controller = std::make_unique<Controller>(std::move(showShareBox));
_controller->events(
) | rpl::start_to_stream(_events, _controller->lifetime());
@ -836,9 +1019,7 @@ void Instance::show(
const auto session = &show->session();
const auto guard = gsl::finally([&] {
if (data->partial()) {
base::call_delayed(10000, [=] {
requestFull(session, data->id());
});
requestFull(session, data->id());
}
});
if (_shown && _shownSession == session) {

View file

@ -204,7 +204,7 @@ private:
Parser::Parser(const Source &source, const Options &options)
: _options(options) {
process(source);
_result.title = source.title;
_result.name = source.name;
_result.rtl = source.page.data().is_rtl();
_result.content = list(source.page.data().vblocks());
}
@ -998,6 +998,7 @@ QByteArray Parser::rich(const MTPRichText &text) {
: QByteArray();
return tag("a", {
{ "href", utf(data.vurl()) },
{ "class", webpageId ? "internal-iv-link" : "" },
{ "data-context", context },
}, rich(data.vtext()));
}, [&](const MTPDtextEmail &data) {

View file

@ -16,7 +16,7 @@ struct Source {
MTPPage page;
std::optional<MTPPhoto> webpagePhoto;
std::optional<MTPDocument> webpageDocument;
QString title;
QString name;
};
[[nodiscard]] Prepared Prepare(