From 8cc90c3373811549c4bb50f8d1239c7d00f5f3f0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Jul 2023 12:30:52 +0400 Subject: [PATCH] Fix media viewer with MacBook top notch. --- .../stories/media_stories_controller.cpp | 5 +- .../media/stories/media_stories_delegate.h | 1 + .../media/view/media_view_overlay_opengl.cpp | 27 ++++- .../media/view/media_view_overlay_raster.cpp | 10 +- .../media/view/media_view_overlay_widget.cpp | 101 ++++++++++++++---- .../media/view/media_view_overlay_widget.h | 5 + .../platform/mac/overlay_widget_mac.h | 1 + .../platform/mac/overlay_widget_mac.mm | 8 ++ .../platform/platform_overlay_widget.h | 3 + 9 files changed, 135 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index b77eeafa3..384770741 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -388,6 +388,8 @@ void Controller::initLayout() { _layout = _wrap->sizeValue( ) | rpl::map([=](QSize size) { + const auto topNotchSkip = _delegate->storiesTopNotchSkip(); + size = QSize( std::max(size.width(), st::mediaviewMinWidth), std::max(size.height(), st::mediaviewMinHeight)); @@ -397,7 +399,8 @@ void Controller::initLayout() { ? HeaderLayout::Outside : HeaderLayout::Normal; - const auto topSkip = st::storiesFieldMargin.bottom() + const auto topSkip = topNotchSkip + + st::storiesFieldMargin.bottom() + (layout.headerLayout == HeaderLayout::Outside ? outsideHeaderHeight : 0); diff --git a/Telegram/SourceFiles/media/stories/media_stories_delegate.h b/Telegram/SourceFiles/media/stories/media_stories_delegate.h index ca663e69c..d1a06a52e 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_delegate.h +++ b/Telegram/SourceFiles/media/stories/media_stories_delegate.h @@ -64,6 +64,7 @@ public: virtual void storiesVolumeToggle() = 0; virtual void storiesVolumeChanged(float64 volume) = 0; virtual void storiesVolumeChangeFinished() = 0; + [[nodiscard]] virtual int storiesTopNotchSkip() = 0; }; } // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp index 7174aef59..b3b26aeed 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp @@ -21,7 +21,8 @@ namespace { using namespace Ui::GL; -constexpr auto kRadialLoadingOffset = 4; +constexpr auto kNotchOffset = 4; +constexpr auto kRadialLoadingOffset = kNotchOffset + 4; constexpr auto kThemePreviewOffset = kRadialLoadingOffset + 4; constexpr auto kDocumentBubbleOffset = kThemePreviewOffset + 4; constexpr auto kSaveMsgOffset = kDocumentBubbleOffset + 4; @@ -129,7 +130,7 @@ OverlayWidget::RendererGL::RendererGL(not_null owner) void OverlayWidget::RendererGL::init( not_null widget, QOpenGLFunctions &f) { - constexpr auto kQuads = 8; + constexpr auto kQuads = 9; constexpr auto kQuadVertices = kQuads * 4; constexpr auto kQuadValues = kQuadVertices * 4; constexpr auto kControlsValues = kControlsCount * kControlValues; @@ -291,6 +292,28 @@ bool OverlayWidget::RendererGL::handleHideWorkaround(QOpenGLFunctions &f) { void OverlayWidget::RendererGL::paintBackground() { _contentBuffer->bind(); + if (const auto notch = _owner->topNotchSkip()) { + const auto top = transformRect(QRect(0, 0, _owner->width(), notch)); + const GLfloat coords[] = { + top.left(), top.top(), + top.right(), top.top(), + top.right(), top.bottom(), + top.left(), top.bottom(), + }; + const auto offset = kNotchOffset; + _contentBuffer->write( + offset * 4 * sizeof(GLfloat), + coords, + sizeof(coords)); + + _fillProgram->bind(); + _fillProgram->setUniformValue("viewport", _uniformViewport); + FillRectangle( + *_f, + &*_fillProgram, + offset, + QColor(0, 0, 0)); + } } void OverlayWidget::RendererGL::paintTransformedVideoFrame( diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp index 277d39d96..4ca702d39 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp @@ -45,6 +45,12 @@ void OverlayWidget::RendererSW::paintBackground() { for (const auto &rect : region) { _p->fillRect(rect, bg); } + if (const auto notch = _owner->topNotchSkip()) { + const auto top = QRect(0, 0, _owner->width(), notch); + if (const auto black = top.intersected(_clipOuter); !black.isEmpty()) { + _p->fillRect(black, Qt::black); + } + } _p->setCompositionMode(m); } @@ -101,8 +107,8 @@ void OverlayWidget::RendererSW::paintTransformedStaticContent( } void OverlayWidget::RendererSW::paintControlsFade( - QRect content, - const ContentGeometry &geometry) { + QRect content, + const ContentGeometry &geometry) { auto opacity = geometry.controlsOpacity; if (geometry.fade > 0.) { _p->setOpacity(geometry.fade); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 0a8be2dc6..aa225a91f 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -587,6 +587,16 @@ OverlayWidget::OverlayWidget() update(); }, lifetime()); + _helper->topNotchSkipValue( + ) | rpl::start_with_next([=](int notch) { + if (_topNotchSize != notch) { + _topNotchSize = notch; + if (_fullscreen) { + updateControlsGeometry(); + } + } + }, lifetime()); + _window->setTitle(tr::lng_mediaview_title(tr::now)); _window->setTitleStyle(st::mediaviewTitle); @@ -673,7 +683,11 @@ void OverlayWidget::showSaveMsgToastWith( const auto h = st::mediaviewSaveMsgStyle.font->height + st::mediaviewSaveMsgPadding.top() + st::mediaviewSaveMsgPadding.bottom(); - _saveMsg = QRect((width() - w) / 2, (height() - h) / 2, w, h); + _saveMsg = QRect( + (width() - w) / 2, + _minUsedTop + (_maxUsedHeight - h) / 2, + w, + h); const auto callback = [=](float64 value) { updateSaveMsg(); if (!_saveMsgAnimation.animating()) { @@ -703,7 +717,7 @@ void OverlayWidget::setupWindow() { && _streamed->controls && _streamed->controls->dragging())) { return Flag::None | Flag(0); - } else if ((_w > _widget->width() || _h > _widget->height()) + } else if ((_w > _widget->width() || _h > _maxUsedHeight) && (widgetPoint.y() > st::mediaviewHeaderTop) && QRect(_x, _y, _w, _h).contains(widgetPoint)) { return Flag::None | Flag(0); @@ -940,8 +954,14 @@ void OverlayWidget::updateGeometryToScreen(bool inMove) { void OverlayWidget::updateControlsGeometry() { updateNavigationControlsGeometry(); - _saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2); - _photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize); + _saveMsg.moveTo( + (width() - _saveMsg.width()) / 2, + _minUsedTop + (_maxUsedHeight - _saveMsg.height()) / 2); + _photoRadialRect = QRect( + QPoint( + (width() - st::radialSize.width()) / 2, + _minUsedTop + (_maxUsedHeight - st::radialSize.height()) / 2), + st::radialSize); const auto bottom = st::mediaviewShadowBottom.height(); const auto top = st::mediaviewShadowTop.size(); @@ -960,6 +980,9 @@ void OverlayWidget::updateControlsGeometry() { } void OverlayWidget::updateNavigationControlsGeometry() { + _minUsedTop = topNotchSkip(); + _maxUsedHeight = height() - _minUsedTop; + const auto overRect = QRect( QPoint(), QSize(st::mediaviewIconOver, st::mediaviewIconOver)); @@ -969,14 +992,22 @@ void OverlayWidget::updateNavigationControlsGeometry() { const auto navSkip = st::mediaviewHeaderTop; const auto xLeft = _stories ? (_x - navSize) : 0; const auto xRight = _stories ? (_x + _w) : (width() - navSize); - _leftNav = QRect(xLeft, navSkip, navSize, height() - 2 * navSkip); + _leftNav = QRect( + xLeft, + _minUsedTop + navSkip, + navSize, + _maxUsedHeight - 2 * navSkip); _leftNavOver = _stories ? QRect() : style::centerrect(_leftNav, overRect); _leftNavIcon = style::centerrect( _leftNav, _stories ? st::storiesLeft : st::mediaviewLeft); - _rightNav = QRect(xRight, navSkip, navSize, height() - 2 * navSkip); + _rightNav = QRect( + xRight, + _minUsedTop + navSkip, + navSize, + _maxUsedHeight - 2 * navSkip); _rightNavOver = _stories ? QRect() : style::centerrect(_rightNav, overRect); @@ -1213,7 +1244,7 @@ void OverlayWidget::updateControls() { if (_document && documentBubbleShown()) { _docRect = QRect( (width() - st::mediaviewFileSize.width()) / 2, - (height() - st::mediaviewFileSize.height()) / 2, + _minUsedTop + (_maxUsedHeight - st::mediaviewFileSize.height()) / 2, st::mediaviewFileSize.width(), st::mediaviewFileSize.height()); _docIconRect = QRect( @@ -1244,7 +1275,7 @@ void OverlayWidget::updateControls() { } else { _docIconRect = QRect( (width() - st::mediaviewFileIconSize) / 2, - (height() - st::mediaviewFileIconSize) / 2, + _minUsedTop + (_maxUsedHeight - st::mediaviewFileIconSize) / 2, st::mediaviewFileIconSize, st::mediaviewFileIconSize); _docDownload->hide(); @@ -1263,7 +1294,8 @@ void OverlayWidget::updateControls() { _shareVisible = story && story->canShare(); _rotateVisible = !_themePreviewShown && !story; const auto navRect = [&](int i) { - return QRect(width() - st::mediaviewIconSize.width() * i, + return QRect( + width() - st::mediaviewIconSize.width() * i, height() - st::mediaviewIconSize.height(), st::mediaviewIconSize.width(), st::mediaviewIconSize.height()); @@ -1302,12 +1334,27 @@ void OverlayWidget::updateControls() { }(); _dateText = d.isValid() ? Ui::FormatDateTime(d) : QString(); if (!_fromName.isEmpty()) { - _fromNameLabel.setText(st::mediaviewTextStyle, _fromName, Ui::NameTextOptions()); - _nameNav = QRect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, qMin(_fromNameLabel.maxWidth(), width() / 3), st::mediaviewFont->height); - _dateNav = QRect(st::mediaviewTextLeft + _nameNav.width() + st::mediaviewTextSkip, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height); + _fromNameLabel.setText( + st::mediaviewTextStyle, + _fromName, + Ui::NameTextOptions()); + _nameNav = QRect( + st::mediaviewTextLeft, + height() - st::mediaviewTextTop, + qMin(_fromNameLabel.maxWidth(), width() / 3), + st::mediaviewFont->height); + _dateNav = QRect( + st::mediaviewTextLeft + _nameNav.width() + st::mediaviewTextSkip, + height() - st::mediaviewTextTop, + st::mediaviewFont->width(_dateText), + st::mediaviewFont->height); } else { _nameNav = QRect(); - _dateNav = QRect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height); + _dateNav = QRect( + st::mediaviewTextLeft, + height() - st::mediaviewTextTop, + st::mediaviewFont->width(_dateText), + st::mediaviewFont->height); } updateHeader(); refreshNavVisibility(); @@ -1363,9 +1410,9 @@ void OverlayWidget::refreshCaptionGeometry() { _caption.maxWidth()); const auto maxExpandedOuterHeight = (_stories ? (_h - st::storiesShadowTop.height()) - : height()); + : _maxUsedHeight); const auto maxCollapsedOuterHeight = !_stories - ? (height() / 4) + ? (_maxUsedHeight / 4) : (_h / 3); const auto maxExpandedHeight = maxExpandedOuterHeight - st::mediaviewCaptionPadding.top() @@ -1777,7 +1824,7 @@ void OverlayWidget::recountSkipTop() { ? height() : (_streamed->controls->y() - st::mediaviewCaptionPadding.bottom()); const auto skipHeightBottom = (height() - bottom); - _skipTop = std::min( + _skipTop = _minUsedTop + std::min( std::max( st::mediaviewCaptionMargin.height(), height() - _height - skipHeightBottom), @@ -1785,7 +1832,7 @@ void OverlayWidget::recountSkipTop() { _availableHeight = height() - skipHeightBottom - _skipTop; if (_fullScreenVideo && skipHeightBottom > 0 && _width > 0) { const auto h = width() * _height / _width; - const auto topAllFit = height() - skipHeightBottom - h; + const auto topAllFit = _maxUsedHeight - skipHeightBottom - h; if (_skipTop > topAllFit) { _skipTop = std::max(topAllFit, 0); } @@ -1818,12 +1865,12 @@ void OverlayWidget::resizeContentByScreenSize() { }; if (_width > 0 && _height > 0) { _zoomToDefault = countZoomFor(availableWidth, _availableHeight); - _zoomToScreen = countZoomFor(width(), height()); + _zoomToScreen = countZoomFor(width(), _maxUsedHeight); } else { _zoomToDefault = _zoomToScreen = 0; } const auto usew = _fullScreenVideo ? width() : availableWidth; - const auto useh = _fullScreenVideo ? height() : _availableHeight; + const auto useh = _fullScreenVideo ? _maxUsedHeight : _availableHeight; if ((_width > usew) || (_height > useh) || _fullScreenVideo) { const auto use = _fullScreenVideo ? _zoomToScreen : _zoomToDefault; _zoom = kZoomToScreenLevel; @@ -3156,6 +3203,10 @@ void OverlayWidget::show(OpenRequest request) { } } } + if (isHidden() || isMinimized()) { + // Count top notch on macOS before counting geometry. + _helper->beforeShow(_fullscreen); + } if (photo) { if (contextItem && contextPeer) { return; @@ -4240,6 +4291,14 @@ void OverlayWidget::storiesVolumeChangeFinished() { playbackControlsVolumeChangeFinished(); } +int OverlayWidget::topNotchSkip() const { + return _fullscreen ? _topNotchSize : 0; +} + +int OverlayWidget::storiesTopNotchSkip() { + return topNotchSkip(); +} + void OverlayWidget::playbackToggleFullScreen() { Expects(_streamed != nullptr); @@ -5366,7 +5425,7 @@ bool OverlayWidget::handleDoubleClick( void OverlayWidget::snapXY() { auto xmin = width() - _w, xmax = 0; - auto ymin = height() - _h, ymax = 0; + auto ymin = height() - _h, ymax = _minUsedTop; accumulate_min(xmin, (width() - _w) / 2); accumulate_max(xmax, (width() - _w) / 2); accumulate_min(ymin, _skipTop + (_availableHeight - _h) / 2); @@ -5390,7 +5449,7 @@ void OverlayWidget::handleMouseMove(QPoint position) { >= QApplication::startDragDistance())) { _dragging = QRect(_x, _y, _w, _h).contains(_mStart) ? 1 : -1; if (_dragging > 0) { - if (_w > width() || _h > height()) { + if (_w > width() || _h > _maxUsedHeight) { setCursor(style::cur_sizeall); } else { setCursor(style::cur_default); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index b0feb4f1d..b4a243f84 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -243,6 +243,7 @@ private: void playbackResumeOnCall(); void playbackPauseMusic(); void switchToPip(); + [[nodiscard]] int topNotchSkip() const; not_null storiesWrap() override; std::shared_ptr storiesShow() override; @@ -264,6 +265,7 @@ private: void storiesVolumeToggle() override; void storiesVolumeChanged(float64 volume) override; void storiesVolumeChangeFinished() override; + int storiesTopNotchSkip() override; void hideControls(bool force = false); void subscribeToScreenGeometry(); @@ -589,10 +591,13 @@ private: bool _captionFitsIfExpanded = false; bool _captionExpanded = false; + int _topNotchSize = 0; int _width = 0; int _height = 0; int _skipTop = 0; int _availableHeight = 0; + int _minUsedTop = 0; // Geometry without top notch on macOS. + int _maxUsedHeight = 0; int _x = 0, _y = 0, _w = 0, _h = 0; int _xStart = 0, _yStart = 0; int _zoom = 0; // < 0 - out, 0 - none, > 0 - in diff --git a/Telegram/SourceFiles/platform/mac/overlay_widget_mac.h b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.h index 1e292891f..2c83b5612 100644 --- a/Telegram/SourceFiles/platform/mac/overlay_widget_mac.h +++ b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.h @@ -36,6 +36,7 @@ public: void clearState() override; void setControlsOpacity(float64 opacity) override; rpl::producer controlsSideRightValue() override; + rpl::producer topNotchSkipValue() override; private: using Control = Ui::Platform::TitleControl; diff --git a/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm index 4f9160e7a..ef544803e 100644 --- a/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm +++ b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm @@ -35,6 +35,7 @@ struct MacOverlayWidgetHelper::Data { rpl::event_stream<> clearStateRequests; bool anyOver = false; NSWindow * __weak native = nil; + rpl::variable topNotchSkip; }; MacOverlayWidgetHelper::MacOverlayWidgetHelper( @@ -96,6 +97,9 @@ void MacOverlayWidgetHelper::updateStyles(bool fullscreen) { [window setTitleVisibility:NSWindowTitleHidden]; [window setTitlebarAppearsTransparent:YES]; [window setStyleMask:[window styleMask] | NSWindowStyleMaskFullSizeContentView]; + if (@available(macOS 12.0, *)) { + _data->topNotchSkip = [[window screen] safeAreaInsets].top; + } } void MacOverlayWidgetHelper::refreshButtons(bool fullscreen) { @@ -153,6 +157,10 @@ rpl::producer MacOverlayWidgetHelper::controlsSideRightValue() { return rpl::single(false); } +rpl::producer MacOverlayWidgetHelper::topNotchSkipValue() { + return _data->topNotchSkip.value(); +} + object_ptr MacOverlayWidgetHelper::create( not_null parent, Control control) { diff --git a/Telegram/SourceFiles/platform/platform_overlay_widget.h b/Telegram/SourceFiles/platform/platform_overlay_widget.h index 97b6ef7ef..2de3687d0 100644 --- a/Telegram/SourceFiles/platform/platform_overlay_widget.h +++ b/Telegram/SourceFiles/platform/platform_overlay_widget.h @@ -58,6 +58,9 @@ public: -> rpl::producer> { return rpl::never>(); } + [[nodiscard]] virtual rpl::producer topNotchSkipValue() { + return rpl::single(0); + } }; [[nodiscard]] std::unique_ptr CreateOverlayWidgetHelper(