Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Eric Kotato 2020-05-01 18:00:11 +03:00
commit 966cf4d23d
10 changed files with 218 additions and 91 deletions

View file

@ -268,17 +268,6 @@ inlineRowFileDescriptionTop: 23px;
inlineResultsMinWidth: 48px; inlineResultsMinWidth: 48px;
inlineDurationMargin: 3px; inlineDurationMargin: 3px;
toastTextStyle: defaultTextStyle;
toastTextPalette: TextPalette(defaultTextPalette) {
linkFg: mediaviewTextLinkFg;
monoFg: mediaviewCaptionFg;
}
toastMaxWidth: 480px;
toastMinMargin: 13px;
toastPadding: margins(19px, 13px, 19px, 12px);
toastFadeInDuration: 200;
toastFadeOutDuration: 1000;
historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }}; historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }};
historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }}; historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }};
boxTitleCloseIcon: icon {{ "box_button_close", boxTitleCloseFg }}; boxTitleCloseIcon: icon {{ "box_button_close", boxTitleCloseFg }};

View file

@ -32,7 +32,7 @@ constexpr auto kImagesPerRow = 32;
constexpr auto kImageRowsPerSprite = 16; constexpr auto kImageRowsPerSprite = 16;
constexpr auto kSetVersion = uint32(1); constexpr auto kSetVersion = uint32(1);
constexpr auto kCacheVersion = uint32(3); constexpr auto kCacheVersion = uint32(4);
constexpr auto kMaxId = uint32(1 << 8); constexpr auto kMaxId = uint32(1 << 8);
constexpr auto kScaleForTouchBar = 150; constexpr auto kScaleForTouchBar = 150;

View file

@ -85,13 +85,13 @@ boxTitle: FlatLabel(defaultFlatLabel) {
linkFontOver: font(17px semibold underline); linkFontOver: font(17px semibold underline);
} }
} }
boxTitlePosition: point(23px, 16px); boxTitlePosition: point(22px, 16px);
boxTitleHeight: 56px; boxTitleHeight: 56px;
boxTitleAdditionalSkip: 9px; boxTitleAdditionalSkip: 9px;
boxTitleAdditionalFont: normalFont; boxTitleAdditionalFont: normalFont;
boxScroll: defaultSolidScroll; boxScroll: defaultSolidScroll;
boxRowPadding: margins(23px, 0px, 23px, 0px); boxRowPadding: margins(22px, 0px, 22px, 0px);
boxTopMargin: 6px; boxTopMargin: 6px;
@ -121,7 +121,7 @@ boxOptionInputSkip: 6px;
boxVerticalMargin: 10px; boxVerticalMargin: 10px;
boxWidth: 320px; boxWidth: 320px;
boxWideWidth: 364px; boxWideWidth: 364px;
boxPadding: margins(23px, 30px, 23px, 8px); boxPadding: margins(22px, 30px, 22px, 8px);
boxMaxListHeight: 492px; boxMaxListHeight: 492px;
boxLittleSkip: 10px; boxLittleSkip: 10px;
boxMediumSkip: 20px; boxMediumSkip: 20px;

View file

@ -8,6 +8,7 @@
#include "ui/toast/toast_manager.h" #include "ui/toast/toast_manager.h"
#include "ui/toast/toast_widget.h" #include "ui/toast/toast_widget.h"
#include "styles/style_widgets.h"
namespace Ui { namespace Ui {
namespace Toast { namespace Toast {
@ -21,49 +22,51 @@ Instance::Instance(
const Config &config, const Config &config,
not_null<QWidget*> widgetParent, not_null<QWidget*> widgetParent,
const Private &) const Private &)
: _hideAtMs(crl::now() + config.durationMs) { : _st(config.st)
_widget = std::make_unique<internal::Widget>(widgetParent, config); , _hideAt(crl::now() + config.durationMs)
_a_opacity.start( , _sliding(config.slideSide != RectPart::None)
[=] { opacityAnimationCallback(); }, , _widget(std::make_unique<internal::Widget>(widgetParent, config)) {
_shownAnimation.start(
[=] { shownAnimationCallback(); },
0., 0.,
1., 1.,
st::toastFadeInDuration); _sliding ? _st->durationSlide : _st->durationFadeIn);
} }
void SetDefaultParent(not_null<QWidget*> parent) { void SetDefaultParent(not_null<QWidget*> parent) {
DefaultParent = parent; DefaultParent = parent;
} }
void Show(not_null<QWidget*> parent, const Config &config) { base::weak_ptr<Instance> Show(
not_null<QWidget*> parent,
const Config &config) {
const auto manager = internal::Manager::instance(parent); const auto manager = internal::Manager::instance(parent);
manager->addToast(std::make_unique<Instance>( return manager->addToast(std::make_unique<Instance>(
config, config,
parent, parent,
Instance::Private())); Instance::Private()));
} }
void Show(const Config &config) { base::weak_ptr<Instance> Show(const Config &config) {
if (const auto parent = DefaultParent.data()) { if (const auto parent = DefaultParent.data()) {
Show(parent, config); return Show(parent, config);
} }
return nullptr;
} }
void Show(not_null<QWidget*> parent, const QString &text) { base::weak_ptr<Instance> Show(
auto config = Config(); not_null<QWidget*> parent,
config.text = { text }; const QString &text) {
Show(parent, config); return Show(parent, Config{ .text = { text }, .st = &st::defaultToast });
} }
void Show(const QString &text) { base::weak_ptr<Instance> Show(const QString &text) {
auto config = Config(); return Show(Config{ .text = { text }, .st = &st::defaultToast });
config.text = { text };
Show(config);
} }
void Instance::opacityAnimationCallback() { void Instance::shownAnimationCallback() {
_widget->setShownLevel(_a_opacity.value(_hiding ? 0. : 1.)); _widget->setShownLevel(_shownAnimation.value(_hiding ? 0. : 1.));
_widget->update(); if (!_shownAnimation.animating()) {
if (!_a_opacity.animating()) {
if (_hiding) { if (_hiding) {
hide(); hide();
} }
@ -72,7 +75,11 @@ void Instance::opacityAnimationCallback() {
void Instance::hideAnimated() { void Instance::hideAnimated() {
_hiding = true; _hiding = true;
_a_opacity.start([this] { opacityAnimationCallback(); }, 1., 0., st::toastFadeOutDuration); _shownAnimation.start(
[=] { shownAnimationCallback(); },
1.,
0.,
_sliding ? _st->durationSlide : _st->durationFadeOut);
} }
void Instance::hide() { void Instance::hide() {
@ -80,5 +87,9 @@ void Instance::hide() {
_widget->deleteLater(); _widget->deleteLater();
} }
not_null<RpWidget*> Instance::widget() const {
return _widget.get();
}
} // namespace Toast } // namespace Toast
} // namespace Ui } // namespace Ui

View file

@ -9,6 +9,13 @@
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/text/text_entity.h" #include "ui/text/text_entity.h"
#include "ui/click_handler.h" #include "ui/click_handler.h"
#include "ui/rect_part.h"
#include "ui/rp_widget.h"
#include "base/weak_ptr.h"
namespace style {
struct Toast;
} // namespace style
namespace Ui { namespace Ui {
namespace Toast { namespace Toast {
@ -23,22 +30,18 @@ using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>;
inline constexpr auto kDefaultDuration = crl::time(1500); inline constexpr auto kDefaultDuration = crl::time(1500);
struct Config { struct Config {
TextWithEntities text; TextWithEntities text;
QMargins padding; not_null<const style::Toast*> st;
crl::time durationMs = kDefaultDuration; crl::time durationMs = kDefaultDuration;
int minWidth = 0;
int maxWidth = 0;
int maxLines = 16; int maxLines = 16;
bool multiline = false; bool multiline = false;
bool dark = false; bool dark = false;
RectPart slideSide = RectPart::None;
ClickHandlerFilter filter; ClickHandlerFilter filter;
}; };
void SetDefaultParent(not_null<QWidget*> parent);
void Show(not_null<QWidget*> parent, const Config &config);
void Show(const Config &config);
void Show(not_null<QWidget*> parent, const QString &text);
void Show(const QString &text);
class Instance { void SetDefaultParent(not_null<QWidget*> parent);
class Instance final : public base::has_weak_ptr {
struct Private { struct Private {
}; };
@ -53,20 +56,35 @@ public:
void hideAnimated(); void hideAnimated();
void hide(); void hide();
[[nodiscard]] not_null<RpWidget*> widget() const;
private: private:
void opacityAnimationCallback(); void shownAnimationCallback();
const not_null<const style::Toast*> _st;
const crl::time _hideAt = 0;
Ui::Animations::Simple _shownAnimation;
bool _hiding = false; bool _hiding = false;
Ui::Animations::Simple _a_opacity; bool _sliding = false;
const crl::time _hideAtMs;
// ToastManager should reset _widget pointer if _widget is destroyed. // ToastManager should reset _widget pointer if _widget is destroyed.
friend class internal::Manager; friend class internal::Manager;
friend void Show(not_null<QWidget*> parent, const Config &config); friend base::weak_ptr<Instance> Show(
not_null<QWidget*> parent,
const Config &config);
std::unique_ptr<internal::Widget> _widget; std::unique_ptr<internal::Widget> _widget;
}; };
base::weak_ptr<Instance> Show(
not_null<QWidget*> parent,
const Config &config);
base::weak_ptr<Instance> Show(const Config &config);
base::weak_ptr<Instance> Show(
not_null<QWidget*> parent,
const QString &text);
base::weak_ptr<Instance> Show(const QString &text);
} // namespace Toast } // namespace Toast
} // namespace Ui } // namespace Ui

View file

@ -44,13 +44,16 @@ not_null<Manager*> Manager::instance(not_null<QWidget*> parent) {
return i->second; return i->second;
} }
void Manager::addToast(std::unique_ptr<Instance> &&toast) { base::weak_ptr<Instance> Manager::addToast(
std::unique_ptr<Instance> &&toast) {
_toasts.push_back(std::move(toast)); _toasts.push_back(std::move(toast));
const auto t = _toasts.back().get(); const auto t = _toasts.back().get();
const auto widget = t->_widget.get(); const auto widget = t->_widget.get();
_toastByWidget.emplace(widget, t); _toastByWidget.emplace(widget, t);
connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(onToastWidgetDestroyed(QObject*))); connect(widget, &QObject::destroyed, [=] {
toastWidgetDestroyed(widget);
});
if (const auto parent = widget->parentWidget()) { if (const auto parent = widget->parentWidget()) {
auto found = false; auto found = false;
for (auto i = _toastParents.begin(); i != _toastParents.cend();) { for (auto i = _toastParents.begin(); i != _toastParents.cend();) {
@ -72,10 +75,11 @@ void Manager::addToast(std::unique_ptr<Instance> &&toast) {
const auto nearestHide = _toastByHideTime.empty() const auto nearestHide = _toastByHideTime.empty()
? 0LL ? 0LL
: _toastByHideTime.begin()->first; : _toastByHideTime.begin()->first;
_toastByHideTime.emplace(t->_hideAtMs, t); _toastByHideTime.emplace(t->_hideAt, t);
if (!nearestHide || _toastByHideTime.begin()->first < nearestHide) { if (!nearestHide || _toastByHideTime.begin()->first < nearestHide) {
startNextHideTimer(); startNextHideTimer();
} }
return make_weak(t);
} }
void Manager::hideByTimer() { void Manager::hideByTimer() {
@ -92,7 +96,7 @@ void Manager::hideByTimer() {
startNextHideTimer(); startNextHideTimer();
} }
void Manager::onToastWidgetDestroyed(QObject *widget) { void Manager::toastWidgetDestroyed(QObject *widget) {
const auto i = _toastByWidget.find(static_cast<Widget*>(widget)); const auto i = _toastByWidget.find(static_cast<Widget*>(widget));
if (i == _toastByWidget.cend()) { if (i == _toastByWidget.cend()) {
return; return;
@ -101,6 +105,13 @@ void Manager::onToastWidgetDestroyed(QObject *widget) {
_toastByWidget.erase(i); _toastByWidget.erase(i);
toast->_widget.release(); toast->_widget.release();
for (auto i = begin(_toastByHideTime); i != end(_toastByHideTime); ++i) {
if (i->second == toast) {
_toastByHideTime.erase(i);
break;
}
}
const auto j = ranges::find( const auto j = ranges::find(
_toasts, _toasts,
toast.get(), toast.get(),
@ -111,7 +122,9 @@ void Manager::onToastWidgetDestroyed(QObject *widget) {
} }
void Manager::startNextHideTimer() { void Manager::startNextHideTimer() {
if (_toastByHideTime.empty()) return; if (_toastByHideTime.empty()) {
return;
}
auto ms = crl::now(); auto ms = crl::now();
if (ms >= _toastByHideTime.begin()->first) { if (ms >= _toastByHideTime.begin()->first) {

View file

@ -14,9 +14,7 @@ namespace Toast {
namespace internal { namespace internal {
class Widget; class Widget;
class Manager : public QObject { class Manager final : public QObject {
Q_OBJECT
struct CreateTag { struct CreateTag {
}; };
@ -28,20 +26,17 @@ public:
static not_null<Manager*> instance(not_null<QWidget*> parent); static not_null<Manager*> instance(not_null<QWidget*> parent);
void addToast(std::unique_ptr<Instance> &&toast); base::weak_ptr<Instance> addToast(std::unique_ptr<Instance> &&toast);
protected: protected:
bool eventFilter(QObject *o, QEvent *e); bool eventFilter(QObject *o, QEvent *e);
private slots:
void onToastWidgetDestroyed(QObject *widget);
private: private:
void toastWidgetDestroyed(QObject *widget);
void startNextHideTimer(); void startNextHideTimer();
void hideByTimer(); void hideByTimer();
base::Timer _hideTimer; base::Timer _hideTimer;
crl::time _nextHide = 0;
base::flat_multi_map<crl::time, not_null<Instance*>> _toastByHideTime; base::flat_multi_map<crl::time, not_null<Instance*>> _toastByHideTime;
base::flat_map<not_null<Widget*>, not_null<Instance*>> _toastByWidget; base::flat_map<not_null<Widget*>, not_null<Instance*>> _toastByWidget;

View file

@ -8,6 +8,7 @@
#include "ui/image/image_prepare.h" #include "ui/image/image_prepare.h"
#include "styles/palette.h" #include "styles/palette.h"
#include "styles/style_widgets.h"
#include <QtGui/QtEvents> #include <QtGui/QtEvents>
@ -16,16 +17,16 @@ namespace Toast {
namespace internal { namespace internal {
Widget::Widget(QWidget *parent, const Config &config) Widget::Widget(QWidget *parent, const Config &config)
: TWidget(parent) : RpWidget(parent)
, _st(config.st)
, _roundRect(ImageRoundRadius::Large, st::toastBg) , _roundRect(ImageRoundRadius::Large, st::toastBg)
, _slideSide(config.slideSide)
, _multiline(config.multiline) , _multiline(config.multiline)
, _dark(config.dark) , _dark(config.dark)
, _maxWidth((config.maxWidth > 0) ? config.maxWidth : st::toastMaxWidth) , _maxTextWidth(widthWithoutPadding(_st->maxWidth))
, _padding((config.padding.left() > 0) ? config.padding : st::toastPadding)
, _maxTextWidth(widthWithoutPadding(_maxWidth))
, _maxTextHeight( , _maxTextHeight(
st::toastTextStyle.font->height * (_multiline ? config.maxLines : 1)) config.st->style.font->height * (_multiline ? config.maxLines : 1))
, _text(_multiline ? widthWithoutPadding(config.minWidth) : QFIXED_MAX) , _text(_multiline ? widthWithoutPadding(config.st->minWidth) : QFIXED_MAX)
, _clickHandlerFilter(config.filter) { , _clickHandlerFilter(config.filter) {
const auto toastOptions = TextParseOptions{ const auto toastOptions = TextParseOptions{
TextParseMultiline, TextParseMultiline,
@ -34,7 +35,7 @@ Widget::Widget(QWidget *parent, const Config &config)
Qt::LayoutDirectionAuto Qt::LayoutDirectionAuto
}; };
_text.setMarkedText( _text.setMarkedText(
st::toastTextStyle, _st->style,
_multiline ? config.text : TextUtilities::SingleLine(config.text), _multiline ? config.text : TextUtilities::SingleLine(config.text),
toastOptions); toastOptions);
@ -49,36 +50,102 @@ Widget::Widget(QWidget *parent, const Config &config)
} }
void Widget::onParentResized() { void Widget::onParentResized() {
auto newWidth = _maxWidth; updateGeometry();
accumulate_min(newWidth, _padding.left() + _text.maxWidth() + _padding.right()); }
accumulate_min(newWidth, parentWidget()->width() - 2 * st::toastMinMargin);
_textWidth = widthWithoutPadding(newWidth); void Widget::updateGeometry() {
const auto textHeight = _multiline auto width = _st->maxWidth;
accumulate_min(
width,
_st->padding.left() + _text.maxWidth() + _st->padding.right());
accumulate_min(
width,
parentWidget()->width() - _st->margin.left() - _st->margin.right());
_textWidth = widthWithoutPadding(width);
_textHeight = _multiline
? qMin(_text.countHeight(_textWidth), _maxTextHeight) ? qMin(_text.countHeight(_textWidth), _maxTextHeight)
: _text.minHeight(); : _text.minHeight();
const auto newHeight = _padding.top() + textHeight + _padding.bottom(); const auto minHeight = _st->icon.empty()
setGeometry((parentWidget()->width() - newWidth) / 2, (parentWidget()->height() - newHeight) / 2, newWidth, newHeight); ? 0
: (_st->icon.height() + 2 * _st->iconPosition.y());
const auto normalHeight = _st->padding.top()
+ _textHeight
+ _st->padding.bottom();
const auto height = std::max(minHeight, normalHeight);
_textTop = _st->padding.top() + ((height - normalHeight) / 2);
const auto rect = QRect(0, 0, width, height);
const auto outer = parentWidget()->size();
const auto full = QPoint(outer.width(), outer.height());
const auto middle = QPoint(
(outer.width() - width) / 2,
(outer.height() - height) / 2);
const auto interpolated = [&](int from, int to) {
return anim::interpolate(from, to, _shownLevel);
};
setGeometry(rect.translated([&] {
switch (_slideSide) {
case RectPart::None:
return middle;
case RectPart::Left:
return QPoint(
interpolated(-width, _st->margin.left()),
middle.y());
case RectPart::Top:
return QPoint(
middle.x(),
interpolated(-height, _st->margin.top()));
case RectPart::Right:
return QPoint(
full.x() - interpolated(0, width + _st->margin.right()),
middle.y());
case RectPart::Bottom:
return QPoint(
middle.x(),
full.y() - interpolated(0, height + _st->margin.bottom()));
}
Unexpected("Slide side in Toast::Widget::updateGeometry.");
}()));
} }
void Widget::setShownLevel(float64 shownLevel) { void Widget::setShownLevel(float64 shownLevel) {
_shownLevel = shownLevel; _shownLevel = shownLevel;
if (_slideSide != RectPart::None) {
updateGeometry();
} else {
update();
}
} }
void Widget::paintEvent(QPaintEvent *e) { void Widget::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
PainterHighQualityEnabler hq(p); PainterHighQualityEnabler hq(p);
p.setOpacity(_shownLevel); if (_slideSide == RectPart::None) {
p.setOpacity(_shownLevel);
}
_roundRect.paint(p, rect()); _roundRect.paint(p, rect());
if (_dark) { if (_dark) {
_roundRect.paint(p, rect()); _roundRect.paint(p, rect());
} }
p.setTextPalette(st::toastTextPalette); if (!_st->icon.empty()) {
_st->icon.paint(
p,
_st->iconPosition.x(),
_st->iconPosition.y(),
width());
}
const auto lines = _maxTextHeight / st::toastTextStyle.font->height; p.setTextPalette(_st->palette);
const auto lines = _maxTextHeight / _st->style.font->height;
p.setPen(st::toastFg); p.setPen(st::toastFg);
_text.drawElided(p, _padding.left(), _padding.top(), _textWidth + 1, lines); _text.drawElided(
p,
_st->padding.left(),
_textTop,
_textWidth + 1,
lines);
} }
void Widget::leaveEventHook(QEvent *e) { void Widget::leaveEventHook(QEvent *e) {
@ -96,8 +163,9 @@ void Widget::mouseMoveEvent(QMouseEvent *e) {
if (!_text.hasLinks()) { if (!_text.hasLinks()) {
return; return;
} }
const auto point = e->pos() - QPoint(_padding.left(), _padding.top()); const auto point = e->pos()
const auto lines = _maxTextHeight / st::toastTextStyle.font->height; - QPoint(_st->padding.left(), _textTop);
const auto lines = _maxTextHeight / _st->style.font->height;
const auto state = _text.getStateElided(point, _textWidth + 1); const auto state = _text.getStateElided(point, _textWidth + 1);
const auto was = ClickHandler::getActive(); const auto was = ClickHandler::getActive();
if (was != state.link) { if (was != state.link) {
@ -129,6 +197,10 @@ void Widget::mouseReleaseEvent(QMouseEvent *e) {
} }
} }
int Widget::widthWithoutPadding(int w) const {
return w - _st->padding.left() - _st->padding.right();
}
} // namespace internal } // namespace internal
} // namespace Toast } // namespace Toast
} // namespace Ui } // namespace Ui

View file

@ -15,7 +15,7 @@ namespace Ui {
namespace Toast { namespace Toast {
namespace internal { namespace internal {
class Widget : public TWidget { class Widget final : public RpWidget {
public: public:
Widget(QWidget *parent, const Config &config); Widget(QWidget *parent, const Config &config);
@ -33,21 +33,22 @@ protected:
void mouseReleaseEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override;
private: private:
int widthWithoutPadding(int w) { [[nodiscard]] int widthWithoutPadding(int w) const;
return w - _padding.left() - _padding.right(); void updateGeometry();
}
const not_null<const style::Toast*> _st;
RoundRect _roundRect; RoundRect _roundRect;
RectPart _slideSide = RectPart::None;
float64 _shownLevel = 0; float64 _shownLevel = 0;
bool _multiline = false; bool _multiline = false;
bool _dark = false; bool _dark = false;
int _maxWidth = 0;
QMargins _padding;
int _maxTextWidth = 0; int _maxTextWidth = 0;
int _maxTextHeight = 0; int _maxTextHeight = 0;
int _textWidth = 0; int _textWidth = 0;
int _textHeight = 0;
int _textTop = 0;
Text::String _text; Text::String _text;
ClickHandlerFilter _clickHandlerFilter; ClickHandlerFilter _clickHandlerFilter;

View file

@ -568,6 +568,20 @@ SideBarButton {
ripple: RippleAnimation; ripple: RippleAnimation;
} }
Toast {
style: TextStyle;
icon: icon;
iconPosition: point;
palette: TextPalette;
padding: margins;
margin: margins;
minWidth: pixels;
maxWidth: pixels;
durationFadeIn: int;
durationFadeOut: int;
durationSlide: int;
}
defaultLabelSimple: LabelSimple { defaultLabelSimple: LabelSimple {
font: normalFont; font: normalFont;
maxWidth: 0px; maxWidth: 0px;
@ -1248,7 +1262,7 @@ defaultSettingsButton: SettingsButton {
toggle: defaultSettingsToggle; toggle: defaultSettingsToggle;
toggleOver: defaultSettingsToggleOver; toggleOver: defaultSettingsToggleOver;
toggleSkip: 23px; toggleSkip: 22px;
ripple: defaultRippleAnimation; ripple: defaultRippleAnimation;
} }
@ -1266,6 +1280,20 @@ defaultSideBarButton: SideBarButton {
} }
} }
defaultToast: Toast {
style: defaultTextStyle;
palette: TextPalette(defaultTextPalette) {
linkFg: mediaviewTextLinkFg;
monoFg: mediaviewCaptionFg;
}
padding: margins(19px, 13px, 19px, 12px);
margin: margins(13px, 13px, 13px, 13px);
maxWidth: 480px;
durationFadeIn: 200;
durationFadeOut: 1000;
durationSlide: 160;
}
// Windows specific title // Windows specific title
windowTitleButton: IconButton { windowTitleButton: IconButton {