diff --git a/docs/changes.txt b/docs/changes.txt index 5a34ed263b..b853af9f33 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -455,6 +455,7 @@ GTK: Mac: +- Implement wxWindow::ShowWithEffect() in wxOSX/Cocoa. - Correct min/max pages display in the print dialog (Auria). MSW: diff --git a/include/wx/osx/cocoa/private.h b/include/wx/osx/cocoa/private.h index 7b8b32008d..a6ac933584 100644 --- a/include/wx/osx/cocoa/private.h +++ b/include/wx/osx/cocoa/private.h @@ -73,6 +73,17 @@ public : virtual bool IsVisible() const ; virtual void SetVisibility(bool); + // we provide a static function which can be reused from + // wxNonOwnedWindowCocoaImpl too + static bool ShowViewOrWindowWithEffect(wxWindow *win, + bool show, + wxShowEffect effect, + unsigned timeout); + + virtual bool ShowWithEffect(bool show, + wxShowEffect effect, + unsigned timeout); + virtual void Raise(); virtual void Lower(); @@ -190,7 +201,10 @@ public : void Raise(); void Lower(); bool Show(bool show); - bool ShowWithEffect(bool show, wxShowEffect effect, unsigned timeout); + + virtual bool ShowWithEffect(bool show, + wxShowEffect effect, + unsigned timeout); void Update(); bool SetTransparent(wxByte alpha); diff --git a/include/wx/osx/core/private.h b/include/wx/osx/core/private.h index b6df304840..418b6f8e4a 100644 --- a/include/wx/osx/core/private.h +++ b/include/wx/osx/core/private.h @@ -188,6 +188,13 @@ public : // set the visibility of this widget (maybe latent) virtual void SetVisibility( bool visible ) = 0; + virtual bool ShowWithEffect(bool WXUNUSED(show), + wxShowEffect WXUNUSED(effect), + unsigned WXUNUSED(timeout)) + { + return false; + } + virtual void Raise() = 0; virtual void Lower() = 0; diff --git a/include/wx/osx/nonownedwnd.h b/include/wx/osx/nonownedwnd.h index b7b73e4e71..e650d7e094 100644 --- a/include/wx/osx/nonownedwnd.h +++ b/include/wx/osx/nonownedwnd.h @@ -85,12 +85,6 @@ public: virtual void Lower(); virtual bool Show( bool show = true ); - virtual bool ShowWithEffect(wxShowEffect effect, - unsigned timeout = 0) ; - - virtual bool HideWithEffect(wxShowEffect effect, - unsigned timeout = 0) ; - virtual void SetExtraStyle(long exStyle) ; virtual bool SetBackgroundColour( const wxColour &colour ); @@ -118,6 +112,10 @@ protected: virtual void DoMoveWindow(int x, int y, int width, int height); virtual void DoGetClientSize(int *width, int *height) const; + virtual bool OSXShowWithEffect(bool show, + wxShowEffect effect, + unsigned timeout); + wxNonOwnedWindowImpl* m_nowpeer ; // wxWindowMac* m_macFocus ; diff --git a/include/wx/osx/window.h b/include/wx/osx/window.h index c7398555c9..bdf48d8f74 100644 --- a/include/wx/osx/window.h +++ b/include/wx/osx/window.h @@ -63,6 +63,16 @@ public: virtual void Lower(); virtual bool Show( bool show = true ); + virtual bool ShowWithEffect(wxShowEffect effect, + unsigned timeout = 0) + { + return OSXShowWithEffect(true, effect, timeout); + } + virtual bool HideWithEffect(wxShowEffect effect, + unsigned timeout = 0) + { + return OSXShowWithEffect(false, effect, timeout); + } virtual bool IsShownOnScreen() const; @@ -341,6 +351,11 @@ protected: virtual void DoSetToolTip( wxToolTip *tip ); #endif + // common part of Show/HideWithEffect() + virtual bool OSXShowWithEffect(bool show, + wxShowEffect effect, + unsigned timeout); + private: // common part of all ctors void Init(); @@ -349,7 +364,6 @@ private: // AlwaysShowScrollbars() void DoUpdateScrollbarVisibility(); - wxDECLARE_NO_COPY_CLASS(wxWindowMac); DECLARE_EVENT_TABLE() }; diff --git a/interface/wx/window.h b/interface/wx/window.h index c3a1924e3c..f0d02c7558 100644 --- a/interface/wx/window.h +++ b/interface/wx/window.h @@ -2185,8 +2185,10 @@ public: milliseconds. If the default value of 0 is used, the default animation time for the current platform is used. - @note Currently this function is only implemented in wxMSW and does the - same thing as Show() in the other ports. + @note Currently this function is only implemented in wxMSW and wxOSX + (for wxTopLevelWindows only in Carbon version and for any kind of + windows in Cocoa) and does the same thing as Show() in the other + ports. @since 2.9.0 diff --git a/src/osx/cocoa/nonownedwnd.mm b/src/osx/cocoa/nonownedwnd.mm index 6e617f9bfd..c44f4eba89 100644 --- a/src/osx/cocoa/nonownedwnd.mm +++ b/src/osx/cocoa/nonownedwnd.mm @@ -488,9 +488,12 @@ bool wxNonOwnedWindowCocoaImpl::Show(bool show) return true; } -bool wxNonOwnedWindowCocoaImpl::ShowWithEffect(bool show, wxShowEffect WXUNUSED(effect), unsigned WXUNUSED(timeout)) +bool wxNonOwnedWindowCocoaImpl::ShowWithEffect(bool show, + wxShowEffect effect, + unsigned timeout) { - return Show(show); + return wxWidgetCocoaImpl:: + ShowViewOrWindowWithEffect(m_wxPeer, show, effect, timeout); } void wxNonOwnedWindowCocoaImpl::Update() diff --git a/src/osx/cocoa/window.mm b/src/osx/cocoa/window.mm index 9ce10cc02c..274eb12099 100644 --- a/src/osx/cocoa/window.mm +++ b/src/osx/cocoa/window.mm @@ -21,6 +21,8 @@ #include "wx/osx/private.h" #endif +#include "wx/hashmap.h" + #if wxUSE_CARET #include "wx/caret.h" #endif @@ -31,6 +33,15 @@ #include +namespace +{ + +// stop animation of this window if one is in progress +void StopAnimation(wxWindow *win); + +} // anonymous namespace + + // Get the window with the focus NSView* GetViewFromResponder( NSResponder* responder ) @@ -1169,6 +1180,8 @@ void wxWidgetCocoaImpl::Init() wxWidgetCocoaImpl::~wxWidgetCocoaImpl() { + StopAnimation(m_wxPeer); + RemoveAssociations( this ); if ( !IsRootControl() ) @@ -1192,6 +1205,286 @@ void wxWidgetCocoaImpl::SetVisibility( bool visible ) [m_osxView setHidden:(visible ? NO:YES)]; } +// ---------------------------------------------------------------------------- +// window animation stuff +// ---------------------------------------------------------------------------- + +namespace +{ + +WX_DECLARE_VOIDPTR_HASH_MAP(NSViewAnimation *, wxNSViewAnimations); + +// all currently active animations +// +// this is MT-safe because windows can only be animated from the main +// thread anyhow +wxNSViewAnimations gs_activeAnimations; + +void StopAnimation(wxWindow *win) +{ + wxNSViewAnimations::iterator it = gs_activeAnimations.find(win); + if ( it != gs_activeAnimations.end() ) + { + [it->second stopAnimation]; + } +} + +} // anonymous namespace + +// define a delegate used to detect the end of the window animation +@interface wxNSAnimationDelegate : NSObject +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + +#endif +{ + // can't use wxRect here as it has a user-defined ctor and so can't be used + // as an Objective-C field + struct + { + int x, y, w, h; + } m_origRect; + wxWindow *m_win; + bool m_show; +} + +- (id)initWithWindow:(wxWindow *)win show:(bool)show; + +// NSAnimationDelegate methods +- (void)animation:(NSAnimation*)animation + didReachProgressMark:(NSAnimationProgress)progress; +- (void)animationDidStop:(NSAnimation *)animation; +- (void)animationDidEnd:(NSAnimation *)animation; + +// private helpers +- (void)finishAnimation:(NSAnimation *)animation; +@end + +@implementation wxNSAnimationDelegate + +- (id)initWithWindow:(wxWindow *)win show:(bool)show +{ + [super init]; + + m_win = win; + m_show = show; + if ( !show ) + { + m_win->GetPosition(&m_origRect.x, &m_origRect.y); + m_win->GetSize(&m_origRect.w, &m_origRect.h); + } + return self; +} + +- (void)animation:(NSAnimation*)animation + didReachProgressMark:(NSAnimationProgress)progress +{ + wxUnusedVar(animation); + wxUnusedVar(progress); + + m_win->SendSizeEvent(); +} + +- (void)animationDidStop:(NSAnimation *)animation +{ + [self finishAnimation:animation]; +} + +- (void)animationDidEnd:(NSAnimation *)animation +{ + [self finishAnimation:animation]; +} + +- (void)finishAnimation:(NSAnimation *)animation +{ + if ( m_show ) + { + // the window expects to be sent a size event when it is shown normally + // and so it should also get one when it is shown with effect + m_win->SendSizeEvent(); + } + else // window was being hidden + { + // NSViewAnimation is smart enough to hide the NSView itself but we + // also need to ensure that it's considered to be hidden at wx level + m_win->Hide(); + + // and we also need to restore its original size which was changed by + // the animation + m_win->SetSize(m_origRect.x, m_origRect.y, m_origRect.w, m_origRect.h); + } + + wxNSViewAnimations::iterator it = gs_activeAnimations.find(m_win); + wxASSERT_MSG( it != gs_activeAnimations.end() && it->second == animation, + "corrupted active animations list?" ); + + gs_activeAnimations.erase(it); + + // we don't dare to release it immediately as we're called from its code + // but schedule the animation itself for deletion soon + [animation autorelease]; + + // ensure that this delegate is definitely not needed any more before + // destroying it + [animation setDelegate:nil]; + [self release]; +} + +@end + +/* static */ +bool +wxWidgetCocoaImpl::ShowViewOrWindowWithEffect(wxWindow *win, + bool show, + wxShowEffect effect, + unsigned timeout) +{ + // first of all, check if this window is not being already animated and + // cancel the previous animation if it is as performing more than one + // animation on the same window at the same time results in some really + // unexpected effects + StopAnimation(win); + + + // create the dictionary describing the animation to perform on this view + NSObject * const + viewOrWin = static_cast(win->OSXGetViewOrWindow()); + NSMutableDictionary * const + dict = [NSMutableDictionary dictionaryWithCapacity:4]; + [dict setObject:viewOrWin forKey:NSViewAnimationTargetKey]; + + // determine the start and end rectangles assuming we're hiding the window + wxRect rectStart, + rectEnd; + rectStart = + rectEnd = win->GetRect(); + + if ( show ) + { + if ( effect == wxSHOW_EFFECT_ROLL_TO_LEFT || + effect == wxSHOW_EFFECT_SLIDE_TO_LEFT ) + effect = wxSHOW_EFFECT_ROLL_TO_RIGHT; + else if ( effect == wxSHOW_EFFECT_ROLL_TO_RIGHT || + effect == wxSHOW_EFFECT_SLIDE_TO_RIGHT ) + effect = wxSHOW_EFFECT_ROLL_TO_LEFT; + else if ( effect == wxSHOW_EFFECT_ROLL_TO_TOP || + effect == wxSHOW_EFFECT_SLIDE_TO_TOP ) + effect = wxSHOW_EFFECT_ROLL_TO_BOTTOM; + else if ( effect == wxSHOW_EFFECT_ROLL_TO_BOTTOM || + effect == wxSHOW_EFFECT_SLIDE_TO_BOTTOM ) + effect = wxSHOW_EFFECT_ROLL_TO_TOP; + } + + switch ( effect ) + { + case wxSHOW_EFFECT_ROLL_TO_LEFT: + case wxSHOW_EFFECT_SLIDE_TO_LEFT: + rectEnd.width = 0; + break; + + case wxSHOW_EFFECT_ROLL_TO_RIGHT: + case wxSHOW_EFFECT_SLIDE_TO_RIGHT: + rectEnd.x = rectStart.GetRight(); + rectEnd.width = 0; + break; + + case wxSHOW_EFFECT_ROLL_TO_TOP: + case wxSHOW_EFFECT_SLIDE_TO_TOP: + rectEnd.height = 0; + break; + + case wxSHOW_EFFECT_ROLL_TO_BOTTOM: + case wxSHOW_EFFECT_SLIDE_TO_BOTTOM: + rectEnd.y = rectStart.GetBottom(); + rectEnd.height = 0; + break; + + case wxSHOW_EFFECT_EXPAND: + rectEnd.x = rectStart.x + rectStart.width / 2; + rectEnd.y = rectStart.y + rectStart.height / 2; + rectEnd.width = + rectEnd.height = 0; + break; + + case wxSHOW_EFFECT_BLEND: + [dict setObject:(show ? NSViewAnimationFadeInEffect + : NSViewAnimationFadeOutEffect) + forKey:NSViewAnimationEffectKey]; + break; + + case wxSHOW_EFFECT_NONE: + case wxSHOW_EFFECT_MAX: + wxFAIL_MSG( "unexpected animation effect" ); + return false; + + default: + wxFAIL_MSG( "unknown animation effect" ); + return false; + }; + + if ( show ) + { + // we need to restore it to the original rectangle instead of making it + // disappear + wxSwap(rectStart, rectEnd); + + // and as the window is currently hidden, we need to show it for the + // animation to be visible at all (but don't restore it at its full + // rectangle as it shouldn't appear immediately) + win->SetSize(rectStart); + win->Show(true); + } + + NSView * const parentView = [viewOrWin isKindOfClass:[NSView class]] + ? [(NSView *)viewOrWin superview] + : nil; + const NSRect rStart = wxToNSRect(parentView, rectStart); + const NSRect rEnd = wxToNSRect(parentView, rectEnd); + + [dict setObject:[NSValue valueWithRect:rStart] + forKey:NSViewAnimationStartFrameKey]; + [dict setObject:[NSValue valueWithRect:rEnd] + forKey:NSViewAnimationEndFrameKey]; + + // create an animation using the values in the above dictionary + // + // notice that it will be released when it is removed from + // gs_activeAnimations + NSViewAnimation * const + anim = [[NSViewAnimation alloc] + initWithViewAnimations:[NSArray arrayWithObject:dict]]; + gs_activeAnimations[win] = anim; + + if ( !timeout ) + { + // what is a good default duration? Windows uses 200ms, Web frameworks + // use anything from 250ms to 1s... choose something in the middle + timeout = 500; + } + + [anim setDuration:timeout/1000.]; // duration is in seconds here + + // if the window being animated changes its layout depending on its size + // (which is almost always the case) we need to redo it during animation + // + // the number of layouts here is arbitrary, but 10 seems like too few (e.g. + // controls in wxInfoBar visibly jump around) + const int NUM_LAYOUTS = 20; + for ( float f = 1./NUM_LAYOUTS; f < 1.; f += 1./NUM_LAYOUTS ) + [anim addProgressMark:f]; + + [anim setDelegate:[[wxNSAnimationDelegate alloc] initWithWindow:win show:show]]; + [anim startAnimation]; + + return true; +} + +bool wxWidgetCocoaImpl::ShowWithEffect(bool show, + wxShowEffect effect, + unsigned timeout) +{ + return ShowViewOrWindowWithEffect(m_wxPeer, show, effect, timeout); +} + void wxWidgetCocoaImpl::Raise() { // Not implemented diff --git a/src/osx/nonownedwnd_osx.cpp b/src/osx/nonownedwnd_osx.cpp index 7b8f08cedd..fc50ea4091 100644 --- a/src/osx/nonownedwnd_osx.cpp +++ b/src/osx/nonownedwnd_osx.cpp @@ -169,28 +169,32 @@ wxNonOwnedWindow::~wxNonOwnedWindow() // wxNonOwnedWindow misc // ---------------------------------------------------------------------------- -bool wxNonOwnedWindow::ShowWithEffect(wxShowEffect effect, - unsigned timeout ) +bool wxNonOwnedWindow::OSXShowWithEffect(bool show, + wxShowEffect effect, + unsigned timeout) { - if ( !wxWindow::Show(true) ) + // Cocoa code needs to manage window visibility on its own and so calls + // wxWindow::Show() as needed but if we already changed the internal + // visibility flag here, Show() would do nothing, so avoid doing it +#if wxOSX_USE_CARBON + if ( !wxWindow::Show(show) ) return false; +#endif // Carbon - // because apps expect a size event to occur at this moment - wxSizeEvent event(GetSize() , m_windowId); - event.SetEventObject(this); - HandleWindowEvent(event); + if ( effect == wxSHOW_EFFECT_NONE || + !m_nowpeer || !m_nowpeer->ShowWithEffect(show, effect, timeout) ) + return Show(show); + if ( show ) + { + // as apps expect a size event to occur when the window is shown, + // generate one when it is shown with effect too + wxSizeEvent event(GetSize(), m_windowId); + event.SetEventObject(this); + HandleWindowEvent(event); + } - return m_nowpeer->ShowWithEffect(true, effect, timeout); -} - -bool wxNonOwnedWindow::HideWithEffect(wxShowEffect effect, - unsigned timeout ) -{ - if ( !wxWindow::Show(false) ) - return false; - - return m_nowpeer->ShowWithEffect(false, effect, timeout); + return true; } wxPoint wxNonOwnedWindow::GetClientAreaOrigin() const diff --git a/src/osx/window_osx.cpp b/src/osx/window_osx.cpp index 3d355fde51..42e856aaec 100644 --- a/src/osx/window_osx.cpp +++ b/src/osx/window_osx.cpp @@ -1063,6 +1063,17 @@ bool wxWindowMac::Show(bool show) return true; } +bool wxWindowMac::OSXShowWithEffect(bool show, + wxShowEffect effect, + unsigned timeout) +{ + if ( effect == wxSHOW_EFFECT_NONE || + !m_peer || !m_peer->ShowWithEffect(show, effect, timeout) ) + return Show(show); + + return true; +} + void wxWindowMac::DoEnable(bool enable) { m_peer->Enable( enable ) ;