mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
New top bar, sharing, internal IV links style.
This commit is contained in:
parent
315859bf7b
commit
e1b55b560a
11 changed files with 502 additions and 292 deletions
|
@ -82,43 +82,24 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#top_menu circle {
|
#top_shadow {
|
||||||
fill: var(--td-history-to-down-fg);
|
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 {
|
#bottom_up path {
|
||||||
stroke: var(--td-history-to-down-fg);
|
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 {
|
#bottom_up path {
|
||||||
stroke-width: 1.4;
|
stroke-width: 1.4;
|
||||||
}
|
}
|
||||||
#top_back:hover path,
|
|
||||||
#top_back:hover line,
|
|
||||||
#bottom_up:hover path {
|
#bottom_up:hover path {
|
||||||
stroke: var(--td-history-to-down-fg-over);
|
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_up {
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
|
@ -210,6 +191,14 @@ article a[href] {
|
||||||
color: var(--td-window-active-text-fg);
|
color: var(--td-window-active-text-fg);
|
||||||
text-decoration: none;
|
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 {
|
article span.reference {
|
||||||
border: dotted var(--td-window-sub-text-fg);
|
border: dotted var(--td-window-sub-text-fg);
|
||||||
border-width: 1px 1px 1px 2px;
|
border-width: 1px 1px 1px 2px;
|
||||||
|
|
|
@ -206,9 +206,6 @@ var IV = {
|
||||||
},
|
},
|
||||||
stopRipples: function (button) {
|
stopRipples: function (button) {
|
||||||
const id = button.id ? button.id : button;
|
const id = button.id ? button.id : button;
|
||||||
if (IV.frozenRipple === id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
button = document.getElementById(id);
|
button = document.getElementById(id);
|
||||||
const ripples = button.getElementsByClassName('ripple');
|
const ripples = button.getElementsByClassName('ripple');
|
||||||
for (var i = 0; i < ripples.length; ++i) {
|
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 () {
|
init: function () {
|
||||||
IV.platform = window.navigator.platform.toLowerCase();
|
IV.platform = window.navigator.platform.toLowerCase();
|
||||||
IV.mac = IV.platform.startsWith('mac');
|
IV.mac = IV.platform.startsWith('mac');
|
||||||
|
@ -310,12 +298,6 @@ var IV = {
|
||||||
behavior: instant ? 'instant' : 'smooth'
|
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 () {
|
computeCurrentState: function () {
|
||||||
var now = IV.findPageScroll();
|
var now = IV.findPageScroll();
|
||||||
return {
|
return {
|
||||||
|
@ -490,13 +472,13 @@ var IV = {
|
||||||
was.classList.add(back ? 'hidden-right' : 'hidden-left');
|
was.classList.add(back ? 'hidden-right' : 'hidden-left');
|
||||||
now.classList.remove(back ? 'hidden-left' : 'hidden-right');
|
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.index = index;
|
||||||
|
IV.notify({
|
||||||
|
event: 'location_change',
|
||||||
|
index: IV.index,
|
||||||
|
position: IV.position,
|
||||||
|
hash: IV.computeCurrentState().hash,
|
||||||
|
});
|
||||||
if (IV.cache[index].contentUpdated) {
|
if (IV.cache[index].contentUpdated) {
|
||||||
IV.cache[index].contentUpdated = false;
|
IV.cache[index].contentUpdated = false;
|
||||||
IV.applyUpdatedContent(index);
|
IV.applyUpdatedContent(index);
|
||||||
|
|
|
@ -21,25 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
QString SiteNameFromUrl(const QString &url) {
|
[[nodiscard]] WebPageCollage ExtractCollage(
|
||||||
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(
|
|
||||||
not_null<Data::Session*> owner,
|
not_null<Data::Session*> owner,
|
||||||
const QVector<MTPPageBlock> &items,
|
const QVector<MTPPageBlock> &items,
|
||||||
const QVector<MTPPhoto> &photos,
|
const QVector<MTPPhoto> &photos,
|
||||||
|
@ -256,7 +238,7 @@ bool WebPageData::applyChanges(
|
||||||
} else if (!newDescription.text.isEmpty()
|
} else if (!newDescription.text.isEmpty()
|
||||||
&& viewTitleText.isEmpty()
|
&& viewTitleText.isEmpty()
|
||||||
&& !resultUrl.isEmpty()) {
|
&& !resultUrl.isEmpty()) {
|
||||||
return SiteNameFromUrl(resultUrl);
|
return Iv::SiteNameFromUrl(resultUrl);
|
||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
}();
|
}();
|
||||||
|
|
|
@ -8,94 +8,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
using "ui/basic.style";
|
using "ui/basic.style";
|
||||||
using "ui/widgets/widgets.style";
|
using "ui/widgets/widgets.style";
|
||||||
|
|
||||||
ivTitleHeight: 24px;
|
ivMenuToggle: IconButton(defaultIconButton) {
|
||||||
ivTitleIconShift: point(0px, 0px);
|
width: 48px;
|
||||||
ivTitleButton: IconButton(windowTitleButton) {
|
height: 48px;
|
||||||
height: ivTitleHeight;
|
|
||||||
iconPosition: ivTitleIconShift;
|
|
||||||
}
|
|
||||||
ivTitleButtonClose: IconButton(windowTitleButtonClose) {
|
|
||||||
height: ivTitleHeight;
|
|
||||||
iconPosition: ivTitleIconShift;
|
|
||||||
}
|
|
||||||
|
|
||||||
ivTitleButtonSize: size(windowTitleButtonWidth, ivTitleHeight);
|
icon: icon {{ "title_menu_dots", menuIconColor }};
|
||||||
ivTitle: WindowTitle(defaultWindowTitle) {
|
iconOver: icon {{ "title_menu_dots", menuIconColor }};
|
||||||
height: ivTitleHeight;
|
|
||||||
style: TextStyle(defaultTextStyle) {
|
rippleAreaPosition: point(6px, 6px);
|
||||||
font: font(semibold 12px);
|
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(-2px, 40px);
|
||||||
ivMenuPosition: point(-8px, 36px);
|
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;
|
||||||
|
|
|
@ -13,8 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "iv/iv_data.h"
|
#include "iv/iv_data.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "ui/platform/ui_platform_window_title.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/rp_window.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "ui/wrap/fade_wrap.h"
|
||||||
#include "ui/basic_click_handlers.h"
|
#include "ui/basic_click_handlers.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "webview/webview_data_stream_memory.h"
|
#include "webview/webview_data_stream_memory.h"
|
||||||
|
@ -35,11 +38,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <QtGui/QWindow>
|
#include <QtGui/QWindow>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
|
||||||
|
#include "base/call_delayed.h"
|
||||||
|
|
||||||
namespace Iv {
|
namespace Iv {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
[[nodiscard]] QByteArray ComputeStyles() {
|
[[nodiscard]] QByteArray ComputeStyles() {
|
||||||
static const auto map = base::flat_map<QByteArray, const style::color*>{
|
static const auto map = base::flat_map<QByteArray, const style::color*>{
|
||||||
|
{ "shadow-fg", &st::shadowFg },
|
||||||
{ "scroll-bg", &st::scrollBg },
|
{ "scroll-bg", &st::scrollBg },
|
||||||
{ "scroll-bg-over", &st::scrollBgOver },
|
{ "scroll-bg-over", &st::scrollBgOver },
|
||||||
{ "scroll-bar-bg", &st::scrollBarBg },
|
{ "scroll-bar-bg", &st::scrollBarBg },
|
||||||
|
@ -54,6 +60,8 @@ namespace {
|
||||||
{ "window-shadow-fg", &st::windowShadowFg },
|
{ "window-shadow-fg", &st::windowShadowFg },
|
||||||
{ "box-divider-bg", &st::boxDividerBg },
|
{ "box-divider-bg", &st::boxDividerBg },
|
||||||
{ "box-divider-fg", &st::boxDividerFg },
|
{ "box-divider-fg", &st::boxDividerFg },
|
||||||
|
{ "light-button-fg", &st::lightButtonFg },
|
||||||
|
{ "light-button-bg-over", &st::lightButtonBgOver },
|
||||||
{ "menu-icon-fg", &st::menuIconFg },
|
{ "menu-icon-fg", &st::menuIconFg },
|
||||||
{ "menu-icon-fg-over", &st::menuIconFgOver },
|
{ "menu-icon-fg-over", &st::menuIconFgOver },
|
||||||
{ "menu-bg", &st::menuBg },
|
{ "menu-bg", &st::menuBg },
|
||||||
|
@ -164,19 +172,7 @@ namespace {
|
||||||
<link rel="stylesheet" href="/iv/page.css" />
|
<link rel="stylesheet" href="/iv/page.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<button class="fixed_button hidden" id="top_back" onclick="IV.back();">
|
<div id="top_shadow"></div>
|
||||||
<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>
|
|
||||||
<button class="fixed_button hidden" id="bottom_up" onclick="IV.scrollTo(0);">
|
<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">
|
<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>
|
<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
|
} // namespace
|
||||||
|
|
||||||
Controller::Controller()
|
Controller::Controller(Fn<ShareBoxResult(ShareBoxDescriptor)> showShareBox)
|
||||||
: _updateStyles([=] {
|
: _updateStyles([=] {
|
||||||
const auto str = EscapeForScriptString(ComputeStyles());
|
const auto str = EscapeForScriptString(ComputeStyles());
|
||||||
if (_webview) {
|
if (_webview) {
|
||||||
_webview->eval("IV.updateStyles('" + str + "');");
|
_webview->eval("IV.updateStyles('" + str + "');");
|
||||||
}
|
}
|
||||||
}) {
|
})
|
||||||
|
, _showShareBox(std::move(showShareBox)) {
|
||||||
createWindow();
|
createWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller::~Controller() {
|
Controller::~Controller() {
|
||||||
|
destroyShareMenu();
|
||||||
if (_window) {
|
if (_window) {
|
||||||
_window->hide();
|
_window->hide();
|
||||||
}
|
}
|
||||||
_ready = false;
|
_ready = false;
|
||||||
_webview = nullptr;
|
_webview = nullptr;
|
||||||
_title = nullptr;
|
_back.destroy();
|
||||||
|
_menu = nullptr;
|
||||||
|
_menuToggle.destroy();
|
||||||
|
_subtitle = nullptr;
|
||||||
|
_subtitleWrap = nullptr;
|
||||||
_window = 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) {
|
bool Controller::showFast(const QString &url, const QString &hash) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -227,8 +290,6 @@ void Controller::show(
|
||||||
Prepared page,
|
Prepared page,
|
||||||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
||||||
page.script = fillInChannelValuesScript(std::move(inChannelValues));
|
page.script = fillInChannelValuesScript(std::move(inChannelValues));
|
||||||
_titleText.setText(st::ivTitle.style, page.title);
|
|
||||||
_title->update();
|
|
||||||
InvokeQueued(_container, [=, page = std::move(page)]() mutable {
|
InvokeQueued(_container, [=, page = std::move(page)]() mutable {
|
||||||
showInWindow(dataPath, std::move(page));
|
showInWindow(dataPath, std::move(page));
|
||||||
});
|
});
|
||||||
|
@ -277,35 +338,8 @@ QByteArray Controller::toggleInChannelScript(
|
||||||
return "IV.toggleChannelJoined('" + id + "', " + value + ");";
|
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() {
|
void Controller::createWindow() {
|
||||||
_window = std::make_unique<Ui::RpWindow>();
|
_window = std::make_unique<Ui::RpWindow>();
|
||||||
_window->setTitleStyle(st::ivTitle);
|
|
||||||
const auto window = _window.get();
|
const auto window = _window.get();
|
||||||
|
|
||||||
base::qt_signal_producer(
|
base::qt_signal_producer(
|
||||||
|
@ -314,61 +348,25 @@ void Controller::createWindow() {
|
||||||
) | rpl::filter([=] {
|
) | rpl::filter([=] {
|
||||||
return _webview && window->window()->windowHandle()->isActive();
|
return _webview && window->window()->windowHandle()->isActive();
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::start_with_next([=] {
|
||||||
_webview->focus();
|
setInnerFocus();
|
||||||
}, window->lifetime());
|
}, window->lifetime());
|
||||||
|
|
||||||
_title = std::make_unique<Ui::RpWidget>(window);
|
initControls();
|
||||||
_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());
|
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
window->widthValue() | rpl::start_with_next([=](int width) {
|
||||||
_titleLeftSkip = 8 + 12 + 8 + 12 + 8 + 12 + 8;
|
updateTitleGeometry(width);
|
||||||
_titleRightSkip = st::ivTitle.style.font->spacew;
|
}, _subtitle->lifetime());
|
||||||
#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->setGeometry({ 200, 200, 600, 800 });
|
window->setGeometry({ 200, 200, 600, 800 });
|
||||||
window->setMinimumSize({ st::windowMinWidth, st::windowMinHeight });
|
window->setMinimumSize({ st::windowMinWidth, st::windowMinHeight });
|
||||||
|
|
||||||
_container = Ui::CreateChild<Ui::RpWidget>(window->body().get());
|
_container = Ui::CreateChild<Ui::RpWidget>(window->window());
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
window->body()->sizeValue(),
|
window->sizeValue(),
|
||||||
_title->heightValue()
|
_subtitleWrap->heightValue()
|
||||||
) | rpl::start_with_next([=](QSize size, int title) {
|
) | rpl::start_with_next([=](QSize size, int title) {
|
||||||
title -= window->body()->y();
|
|
||||||
_container->setGeometry(QRect(QPoint(), size).marginsRemoved(
|
_container->setGeometry(QRect(QPoint(), size).marginsRemoved(
|
||||||
{ 0, title, 0, 0 }));
|
{ 0, title + st::windowTitleHeight, 0, 0 }));
|
||||||
}, _container->lifetime());
|
}, _container->lifetime());
|
||||||
|
|
||||||
_container->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
_container->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||||
|
@ -452,10 +450,12 @@ void Controller::createWebview(const QString &dataPath) {
|
||||||
if (!script.isEmpty()) {
|
if (!script.isEmpty()) {
|
||||||
_webview->eval(script);
|
_webview->eval(script);
|
||||||
}
|
}
|
||||||
} else if (event == u"menu"_q) {
|
} else if (event == u"location_change"_q) {
|
||||||
menu(
|
_index = object.value("index").toInt();
|
||||||
object.value("index").toInt(),
|
_hash = object.value("hash").toString();
|
||||||
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);
|
Expects(_container != nullptr);
|
||||||
|
|
||||||
const auto url = page.url;
|
const auto url = page.url;
|
||||||
const auto hash = page.hash;
|
_hash = page.hash;
|
||||||
auto i = _indices.find(url);
|
auto i = _indices.find(url);
|
||||||
if (i == end(_indices)) {
|
if (i == end(_indices)) {
|
||||||
_pages.push_back(std::move(page));
|
_pages.push_back(std::move(page));
|
||||||
i = _indices.emplace(url, int(_pages.size() - 1)).first;
|
i = _indices.emplace(url, int(_pages.size() - 1)).first;
|
||||||
}
|
}
|
||||||
const auto index = i->second;
|
const auto index = i->second;
|
||||||
|
_index = index;
|
||||||
if (!_webview) {
|
if (!_webview) {
|
||||||
createWebview(dataPath);
|
createWebview(dataPath);
|
||||||
if (_webview && _webview->widget()) {
|
if (_webview && _webview->widget()) {
|
||||||
auto id = u"iv/page%1.html"_q.arg(index);
|
auto id = u"iv/page%1.html"_q.arg(index);
|
||||||
if (!hash.isEmpty()) {
|
if (!_hash.isEmpty()) {
|
||||||
id += '#' + hash;
|
id += '#' + _hash;
|
||||||
}
|
}
|
||||||
_webview->navigateToData(id);
|
_webview->navigateToData(id);
|
||||||
_webview->focus();
|
activate();
|
||||||
} else {
|
} else {
|
||||||
_events.fire({ Event::Type::Close });
|
_events.fire({ Event::Type::Close });
|
||||||
}
|
}
|
||||||
} else if (_ready) {
|
} else if (_ready) {
|
||||||
_webview->eval(navigateScript(index, hash));
|
_webview->eval(navigateScript(index, _hash));
|
||||||
_window->raise();
|
activate();
|
||||||
_window->activateWindow();
|
|
||||||
_window->setFocus();
|
|
||||||
_webview->focus();
|
|
||||||
} else {
|
} else {
|
||||||
_navigateToIndexWhenReady = index;
|
_navigateToIndexWhenReady = index;
|
||||||
_navigateToHashWhenReady = hash;
|
_navigateToHashWhenReady = _hash;
|
||||||
_window->raise();
|
activate();
|
||||||
_window->activateWindow();
|
}
|
||||||
_window->setFocus();
|
}
|
||||||
|
|
||||||
|
void Controller::activate() {
|
||||||
|
_window->raise();
|
||||||
|
_window->activateWindow();
|
||||||
|
_window->setFocus();
|
||||||
|
setInnerFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::setInnerFocus() {
|
||||||
|
if (const auto onstack = _shareFocus) {
|
||||||
|
onstack();
|
||||||
|
} else if (_webview) {
|
||||||
_webview->focus();
|
_webview->focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -657,8 +667,17 @@ void Controller::minimize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::menu(int index, const QString &hash) {
|
QString Controller::composeCurrentUrl() const {
|
||||||
if (!_webview || _menu || index < 0 || index > _pages.size()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||||
|
@ -666,14 +685,15 @@ void Controller::menu(int index, const QString &hash) {
|
||||||
st::popupMenuWithIcons);
|
st::popupMenuWithIcons);
|
||||||
_menu->setDestroyedCallback(crl::guard(_window.get(), [
|
_menu->setDestroyedCallback(crl::guard(_window.get(), [
|
||||||
this,
|
this,
|
||||||
|
weakButton = Ui::MakeWeak(_menuToggle.data()),
|
||||||
menu = _menu.get()] {
|
menu = _menu.get()] {
|
||||||
if (_webview) {
|
if (_menu == menu && weakButton) {
|
||||||
_webview->eval("IV.clearFrozenRipple();");
|
weakButton->setForceRippled(false);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
_menuToggle->setForceRippled(true);
|
||||||
|
|
||||||
const auto url = _pages[index].url
|
const auto url = composeCurrentUrl();
|
||||||
+ (hash.isEmpty() ? u""_q : ('#' + hash));
|
|
||||||
const auto openInBrowser = crl::guard(_window.get(), [=] {
|
const auto openInBrowser = crl::guard(_window.get(), [=] {
|
||||||
_events.fire({ .type = Event::Type::OpenLinkExternal, .url = url });
|
_events.fire({ .type = Event::Type::OpenLinkExternal, .url = url });
|
||||||
});
|
});
|
||||||
|
@ -683,6 +703,7 @@ void Controller::menu(int index, const QString &hash) {
|
||||||
&st::menuIconIpAddress);
|
&st::menuIconIpAddress);
|
||||||
|
|
||||||
_menu->addAction(tr::lng_iv_share(tr::now), [=] {
|
_menu->addAction(tr::lng_iv_share(tr::now), [=] {
|
||||||
|
showShareMenu();
|
||||||
}, &st::menuIconShare);
|
}, &st::menuIconShare);
|
||||||
|
|
||||||
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
|
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
|
||||||
|
@ -691,7 +712,11 @@ void Controller::menu(int index, const QString &hash) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::escape() {
|
void Controller::escape() {
|
||||||
close();
|
if (const auto onstack = _shareHide) {
|
||||||
|
onstack();
|
||||||
|
} else {
|
||||||
|
close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::close() {
|
void Controller::close() {
|
||||||
|
@ -706,4 +731,56 @@ rpl::lifetime &Controller::lifetime() {
|
||||||
return _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
|
} // namespace Iv
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "base/invoke_queued.h"
|
#include "base/invoke_queued.h"
|
||||||
|
#include "base/object_ptr.h"
|
||||||
#include "base/unique_qptr.h"
|
#include "base/unique_qptr.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
|
@ -23,15 +24,29 @@ namespace Ui {
|
||||||
class RpWidget;
|
class RpWidget;
|
||||||
class RpWindow;
|
class RpWindow;
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
|
class FlatLabel;
|
||||||
|
class IconButton;
|
||||||
|
template <typename Widget>
|
||||||
|
class FadeWrapScaled;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Iv {
|
namespace Iv {
|
||||||
|
|
||||||
struct Prepared;
|
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 {
|
class Controller final {
|
||||||
public:
|
public:
|
||||||
Controller();
|
explicit Controller(Fn<ShareBoxResult(ShareBoxDescriptor)> showShareBox);
|
||||||
~Controller();
|
~Controller();
|
||||||
|
|
||||||
struct Event {
|
struct Event {
|
||||||
|
@ -77,8 +92,6 @@ private:
|
||||||
[[nodiscard]] QByteArray navigateScript(int index, const QString &hash);
|
[[nodiscard]] QByteArray navigateScript(int index, const QString &hash);
|
||||||
[[nodiscard]] QByteArray reloadScript(int index);
|
[[nodiscard]] QByteArray reloadScript(int index);
|
||||||
|
|
||||||
void updateTitleGeometry();
|
|
||||||
void paintTitle(Painter &p, QRect clip);
|
|
||||||
void showInWindow(const QString &dataPath, Prepared page);
|
void showInWindow(const QString &dataPath, Prepared page);
|
||||||
[[nodiscard]] QByteArray fillInChannelValuesScript(
|
[[nodiscard]] QByteArray fillInChannelValuesScript(
|
||||||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
|
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
|
||||||
|
@ -89,17 +102,28 @@ private:
|
||||||
void processKey(const QString &key, const QString &modifier);
|
void processKey(const QString &key, const QString &modifier);
|
||||||
void processLink(const QString &url, const QString &context);
|
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 escape();
|
||||||
void close();
|
void close();
|
||||||
void quit();
|
void quit();
|
||||||
|
|
||||||
|
[[nodiscard]] QString composeCurrentUrl() const;
|
||||||
|
void showShareMenu();
|
||||||
|
void destroyShareMenu();
|
||||||
|
|
||||||
std::unique_ptr<Ui::RpWindow> _window;
|
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;
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
Ui::Text::String _titleText;
|
|
||||||
int _titleLeftSkip = 0;
|
|
||||||
int _titleRightSkip = 0;
|
|
||||||
Ui::RpWidget *_container = nullptr;
|
Ui::RpWidget *_container = nullptr;
|
||||||
std::unique_ptr<Webview::Window> _webview;
|
std::unique_ptr<Webview::Window> _webview;
|
||||||
rpl::event_stream<Webview::DataRequest> _dataRequests;
|
rpl::event_stream<Webview::DataRequest> _dataRequests;
|
||||||
|
@ -111,6 +135,15 @@ private:
|
||||||
bool _subscribedToColors = false;
|
bool _subscribedToColors = false;
|
||||||
bool _ready = 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;
|
std::vector<Prepared> _pages;
|
||||||
base::flat_map<QString, int> _indices;
|
base::flat_map<QString, int> _indices;
|
||||||
QString _navigateToHashWhenReady;
|
QString _navigateToHashWhenReady;
|
||||||
|
|
|
@ -9,6 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "iv/iv_prepare.h"
|
#include "iv/iv_prepare.h"
|
||||||
|
|
||||||
|
#include <QtCore/QRegularExpression>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
|
||||||
namespace Iv {
|
namespace Iv {
|
||||||
|
|
||||||
QByteArray GeoPointId(Geo point) {
|
QByteArray GeoPointId(Geo point) {
|
||||||
|
@ -45,9 +48,9 @@ Data::Data(const MTPDwebPage &webpage, const MTPPage &page)
|
||||||
.webpageDocument = (webpage.vdocument()
|
.webpageDocument = (webpage.vdocument()
|
||||||
? *webpage.vdocument()
|
? *webpage.vdocument()
|
||||||
: std::optional<MTPDocument>()),
|
: std::optional<MTPDocument>()),
|
||||||
.title = (webpage.vtitle()
|
.name = (webpage.vsite_name()
|
||||||
? qs(*webpage.vtitle())
|
? qs(*webpage.vsite_name())
|
||||||
: qs(webpage.vauthor().value_or_empty()))
|
: 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
|
} // namespace Iv
|
||||||
|
|
|
@ -16,7 +16,8 @@ struct Options {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Prepared {
|
struct Prepared {
|
||||||
QString title;
|
QString name;
|
||||||
|
//QString title;
|
||||||
QByteArray content;
|
QByteArray content;
|
||||||
QByteArray script;
|
QByteArray script;
|
||||||
QString url;
|
QString url;
|
||||||
|
@ -53,4 +54,6 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] QString SiteNameFromUrl(const QString &url);
|
||||||
|
|
||||||
} // namespace Iv
|
} // namespace Iv
|
||||||
|
|
|
@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "iv/iv_instance.h"
|
#include "iv/iv_instance.h"
|
||||||
|
|
||||||
#include "base/call_delayed.h"
|
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "boxes/share_box.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
#include "core/shortcuts.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_file_origin.h"
|
||||||
#include "data/data_photo_media.h"
|
#include "data/data_photo_media.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_thread.h"
|
||||||
#include "data/data_web_page.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 "info/profile/info_profile_values.h"
|
||||||
#include "iv/iv_controller.h"
|
#include "iv/iv_controller.h"
|
||||||
#include "iv/iv_data.h"
|
#include "iv/iv_data.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
#include "lottie/lottie_common.h" // Lottie::ReadContent.
|
#include "lottie/lottie_common.h" // Lottie::ReadContent.
|
||||||
#include "main/main_account.h"
|
#include "main/main_account.h"
|
||||||
#include "main/main_domain.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/file_download.h"
|
||||||
#include "storage/storage_domain.h"
|
#include "storage/storage_domain.h"
|
||||||
#include "ui/boxes/confirm_box.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 "ui/basic_click_handlers.h"
|
||||||
#include "webview/webview_data_stream_memory.h"
|
#include "webview/webview_data_stream_memory.h"
|
||||||
#include "webview/webview_interface.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 "window/window_session_controller_link_info.h"
|
||||||
|
|
||||||
#include <QtGui/QDesktopServices>
|
#include <QtGui/QDesktopServices>
|
||||||
|
#include <QtGui/QGuiApplication>
|
||||||
|
|
||||||
namespace Iv {
|
namespace Iv {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -124,6 +131,7 @@ private:
|
||||||
|
|
||||||
void showLocal(Prepared result);
|
void showLocal(Prepared result);
|
||||||
void showWindowed(Prepared result);
|
void showWindowed(Prepared result);
|
||||||
|
[[nodiscard]] ShareBoxResult shareBox(ShareBoxDescriptor &&descriptor);
|
||||||
|
|
||||||
// Local.
|
// Local.
|
||||||
void showProgress(int index);
|
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() {
|
void Shown::createController() {
|
||||||
Expects(!_controller);
|
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(
|
_controller->events(
|
||||||
) | rpl::start_to_stream(_events, _controller->lifetime());
|
) | rpl::start_to_stream(_events, _controller->lifetime());
|
||||||
|
@ -836,9 +1019,7 @@ void Instance::show(
|
||||||
const auto session = &show->session();
|
const auto session = &show->session();
|
||||||
const auto guard = gsl::finally([&] {
|
const auto guard = gsl::finally([&] {
|
||||||
if (data->partial()) {
|
if (data->partial()) {
|
||||||
base::call_delayed(10000, [=] {
|
requestFull(session, data->id());
|
||||||
requestFull(session, data->id());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (_shown && _shownSession == session) {
|
if (_shown && _shownSession == session) {
|
||||||
|
|
|
@ -204,7 +204,7 @@ private:
|
||||||
Parser::Parser(const Source &source, const Options &options)
|
Parser::Parser(const Source &source, const Options &options)
|
||||||
: _options(options) {
|
: _options(options) {
|
||||||
process(source);
|
process(source);
|
||||||
_result.title = source.title;
|
_result.name = source.name;
|
||||||
_result.rtl = source.page.data().is_rtl();
|
_result.rtl = source.page.data().is_rtl();
|
||||||
_result.content = list(source.page.data().vblocks());
|
_result.content = list(source.page.data().vblocks());
|
||||||
}
|
}
|
||||||
|
@ -998,6 +998,7 @@ QByteArray Parser::rich(const MTPRichText &text) {
|
||||||
: QByteArray();
|
: QByteArray();
|
||||||
return tag("a", {
|
return tag("a", {
|
||||||
{ "href", utf(data.vurl()) },
|
{ "href", utf(data.vurl()) },
|
||||||
|
{ "class", webpageId ? "internal-iv-link" : "" },
|
||||||
{ "data-context", context },
|
{ "data-context", context },
|
||||||
}, rich(data.vtext()));
|
}, rich(data.vtext()));
|
||||||
}, [&](const MTPDtextEmail &data) {
|
}, [&](const MTPDtextEmail &data) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ struct Source {
|
||||||
MTPPage page;
|
MTPPage page;
|
||||||
std::optional<MTPPhoto> webpagePhoto;
|
std::optional<MTPPhoto> webpagePhoto;
|
||||||
std::optional<MTPDocument> webpageDocument;
|
std::optional<MTPDocument> webpageDocument;
|
||||||
QString title;
|
QString name;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] Prepared Prepare(
|
[[nodiscard]] Prepared Prepare(
|
||||||
|
|
Loading…
Add table
Reference in a new issue