From bcbc31e97f246564e4b19ea2d060ae859c302b44 Mon Sep 17 00:00:00 2001 From: Stefan Csomor Date: Mon, 20 Nov 2023 10:07:25 +0100 Subject: [PATCH] Fix clipping of scrolled windows under macOS Sonoma We need to use a native clip view for things to behave correctly under this OS version, otherwise scrollbars can be overdrawn by the window contents. Closes #24067. Closes #24073. --- include/wx/osx/cocoa/private.h | 7 +++- include/wx/osx/core/private.h | 8 +++++ include/wx/osx/window.h | 2 +- src/osx/cocoa/window.mm | 62 ++++++++++++++++++++++++++++++++-- src/osx/window_osx.cpp | 23 +++++++++++++ 5 files changed, 98 insertions(+), 4 deletions(-) diff --git a/include/wx/osx/cocoa/private.h b/include/wx/osx/cocoa/private.h index ca485c3a27..add141eb0e 100644 --- a/include/wx/osx/cocoa/private.h +++ b/include/wx/osx/cocoa/private.h @@ -220,9 +220,14 @@ public : // from the same pimpl class. virtual void controlTextDidChange(); + virtual void AdjustClippingView(wxScrollBar* horizontal, wxScrollBar* vertical) override; + virtual void UseClippingView(bool clip) override; + virtual WXWidget GetContainer() const override { return m_osxClipView ? m_osxClipView : m_osxView; } + protected: WXWidget m_osxView; - + WXWidget m_osxClipView; + // begins processing of native key down event, storing the native event for later wx event generation void BeginNativeKeyDownEvent( NSEvent* event ); // done with the current native key down event diff --git a/include/wx/osx/core/private.h b/include/wx/osx/core/private.h index 1421ba6f56..6c16eaf783 100644 --- a/include/wx/osx/core/private.h +++ b/include/wx/osx/core/private.h @@ -365,6 +365,14 @@ public : virtual bool EnableTouchEvents(int eventsMask) = 0; + // scrolling views need a clip subview that acts as parent for native children + // (except for the scollbars) which are children of the view itself + virtual void AdjustClippingView(wxScrollBar* horizontal, wxScrollBar* vertical); + virtual void UseClippingView(bool clip); + + // returns native view which acts as a parent for native children + virtual WXWidget GetContainer() const; + // Mechanism used to keep track of whether a change should send an event // Do SendEvents(false) when starting actions that would trigger programmatic events // and SendEvents(true) at the end of the block. diff --git a/include/wx/osx/window.h b/include/wx/osx/window.h index dff2181a70..e19241d430 100644 --- a/include/wx/osx/window.h +++ b/include/wx/osx/window.h @@ -224,7 +224,7 @@ public: // returns true if children have to clipped to the content area // (e.g., scrolled windows) bool MacClipChildren() const { return m_clipChildren ; } - void MacSetClipChildren( bool clip ) { m_clipChildren = clip ; } + void MacSetClipChildren( bool clip ); // returns true if the grandchildren need to be clipped to the children's content area // (e.g., splitter windows) diff --git a/src/osx/cocoa/window.mm b/src/osx/cocoa/window.mm index 446732ced4..d8e863bce1 100644 --- a/src/osx/cocoa/window.mm +++ b/src/osx/cocoa/window.mm @@ -16,6 +16,7 @@ #include "wx/textctrl.h" #include "wx/combobox.h" #include "wx/radiobut.h" + #include "wx/scrolbar.h" #endif #ifdef __WXMAC__ @@ -2562,7 +2563,8 @@ wxWidgetImpl( peer, flags ) { Init(); m_osxView = w; - + m_osxClipView = nil; + // check if the user wants to create the control initially hidden if ( !peer->IsShown() ) SetVisibility(false); @@ -3215,6 +3217,21 @@ bool wxWidgetCocoaImpl::CanFocus() const return canFocus; } +@interface wxNSClipView : NSClipView + +@end + +@implementation wxNSClipView + +#if wxOSX_USE_NATIVE_FLIPPED +- (BOOL)isFlipped +{ + return YES; +} +#endif + +@end + bool wxWidgetCocoaImpl::HasFocus() const { NSView* targetView = m_osxView; @@ -3305,7 +3322,13 @@ void wxWidgetCocoaImpl::RemoveFromParent() void wxWidgetCocoaImpl::Embed( wxWidgetImpl *parent ) { - NSView* container = parent->GetWXWidget() ; + NSView* container = nil; + + if ( m_wxPeer->MacIsWindowScrollbar( parent->GetWXPeer())) + container = parent->GetWXWidget(); + else + container = parent->GetContainer(); + wxASSERT_MSG( container != nullptr , wxT("No valid mac container control") ) ; [container addSubview:m_osxView]; @@ -4034,6 +4057,41 @@ void wxWidgetCocoaImpl::SetDrawingEnabled(bool enabled) [[m_osxView window] disableFlushWindow]; } } + +void wxWidgetCocoaImpl::AdjustClippingView(wxScrollBar* horizontal, wxScrollBar* vertical) +{ + if( m_osxClipView ) + { + NSRect bounds = m_osxView.bounds; + if( horizontal && horizontal->IsShown() ) + { + int sz = horizontal->GetSize().y; + bounds.size.height -= sz; + } + if( vertical && vertical->IsShown() ) + { + int sz = vertical->GetSize().x; + bounds.size.width -= sz; + } + m_osxClipView.frame = bounds; + } +} + +void wxWidgetCocoaImpl::UseClippingView(bool clip) +{ + wxWindow* peer = m_wxPeer; + + if ( peer && m_osxClipView == nil) + { + m_osxClipView = [[wxNSClipView alloc] initWithFrame: m_osxView.bounds]; + [(NSClipView*)m_osxClipView setDrawsBackground: NO]; + [m_osxView addSubview:m_osxClipView]; + + // TODO check for additional subwindows which might have to be moved to the clip view ? + } +} + + // // Factory methods // diff --git a/src/osx/window_osx.cpp b/src/osx/window_osx.cpp index 6154fd8757..448e360bb7 100644 --- a/src/osx/window_osx.cpp +++ b/src/osx/window_osx.cpp @@ -261,6 +261,13 @@ wxWindowMac::~wxWindowMac() delete GetPeer() ; } +void wxWindowMac::MacSetClipChildren( bool clip ) +{ + m_clipChildren = clip ; + if ( m_peer ) + m_peer->UseClippingView(clip); +} + WXWidget wxWindowMac::GetHandle() const { if ( GetPeer() ) @@ -386,6 +393,8 @@ bool wxWindowMac::Create(wxWindowMac *parent, { SetPeer(wxWidgetImpl::CreateUserPane( this, parent, id, pos, size , style, GetExtraStyle() )); MacPostControlCreate(pos, size) ; + if ( m_clipChildren ) + m_peer->UseClippingView(m_clipChildren); } wxWindowCreateEvent event((wxWindow*)this); @@ -2139,6 +2148,7 @@ void wxWindowMac::MacRepositionScrollBars() m_growBox->Hide(); } } + m_peer->AdjustClippingView(m_hScrollBar, m_vScrollBar); #endif } @@ -2700,3 +2710,16 @@ bool wxWidgetImpl::NeedsFrame() const void wxWidgetImpl::SetDrawingEnabled(bool WXUNUSED(enabled)) { } + +void wxWidgetImpl::AdjustClippingView(wxScrollBar* WXUNUSED(horizontal), wxScrollBar* WXUNUSED(vertical)) +{ +} + +void wxWidgetImpl::UseClippingView(bool WXUNUSED(clip)) +{ +} + +WXWidget wxWidgetImpl::GetContainer() const +{ + return GetWXWidget(); +}