Bug 1562032 - Use NSDockTile to implement the dock download progress bar. r=spohl
authorMarkus Stange <mstange@themasta.com>
Fri, 28 Jun 2019 14:17:47 +0000
changeset 480547 9823d19217e209619c3f8d0e52d88119ba238fef
parent 480546 0c6e149ba01d55dfa0e21e0eed6cfa2ec9aad90f
child 480548 1b5308f24205634af9d3aeafa3a84854394ca758
push id36215
push usershindli@mozilla.com
push dateFri, 28 Jun 2019 23:19:25 +0000
treeherdermozilla-central@900a0b127043 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersspohl
bugs1562032
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1562032 - Use NSDockTile to implement the dock download progress bar. r=spohl Differential Revision: https://phabricator.services.mozilla.com/D36244
widget/cocoa/nsMacDockSupport.h
widget/cocoa/nsMacDockSupport.mm
--- a/widget/cocoa/nsMacDockSupport.h
+++ b/widget/cocoa/nsMacDockSupport.h
@@ -4,37 +4,33 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIMacDockSupport.h"
 #include "nsIStandaloneNativeMenu.h"
 #include "nsITaskbarProgress.h"
 #include "nsITimer.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
-#include "nsNativeThemeCocoa.h"
+
+@class MOZProgressDockOverlayView;
 
 class nsMacDockSupport : public nsIMacDockSupport, public nsITaskbarProgress {
  public:
   nsMacDockSupport();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMACDOCKSUPPORT
   NS_DECL_NSITASKBARPROGRESS
 
  protected:
   virtual ~nsMacDockSupport();
 
   nsCOMPtr<nsIStandaloneNativeMenu> mDockMenu;
   nsString mBadgeText;
 
-  NSImage *mAppIcon, *mProgressBackground;
+  NSView* mDockTileWrapperView;
+  MOZProgressDockOverlayView* mProgressDockOverlayView;
 
-  HIRect mProgressBounds;
   nsTaskbarProgressState mProgressState;
   double mProgressFraction;
-  nsCOMPtr<nsITimer> mProgressTimer;
-  RefPtr<nsNativeThemeCocoa> mTheme;
 
-  static void RedrawIconCallback(nsITimer* aTimer, void* aClosure);
-
-  bool InitProgress();
-  nsresult RedrawIcon();
+  nsresult UpdateDockTile();
 };
--- a/widget/cocoa/nsMacDockSupport.mm
+++ b/widget/cocoa/nsMacDockSupport.mm
@@ -6,37 +6,83 @@
 #import <Cocoa/Cocoa.h>
 
 #include "nsComponentManagerUtils.h"
 #include "nsMacDockSupport.h"
 #include "nsObjCExceptions.h"
 
 NS_IMPL_ISUPPORTS(nsMacDockSupport, nsIMacDockSupport, nsITaskbarProgress)
 
+// This view is used in the dock tile when we're downloading a file.
+// It draws a progress bar that looks similar to the native progress bar on
+// 10.12. This style of progress bar is not animated, unlike the pre-10.10
+// progress bar look which had to redrawn multiple times per second.
+@interface MOZProgressDockOverlayView : NSView {
+  double mFractionValue;
+}
+@property double fractionValue;
+
+@end
+
+@implementation MOZProgressDockOverlayView
+
+@synthesize fractionValue = mFractionValue;
+
+- (void)drawRect:(NSRect)aRect {
+  // Erase the background behind this view, i.e. cut a rectangle hole in the icon.
+  [[NSColor clearColor] set];
+  NSRectFill(self.bounds);
+
+  // Split the height of this view into four quarters. The middle two quarters
+  // will be covered by the actual progress bar.
+  CGFloat radius = self.bounds.size.height / 4;
+  NSRect barBounds = NSInsetRect(self.bounds, 0, radius);
+
+  NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:barBounds
+                                                       xRadius:radius
+                                                       yRadius:radius];
+
+  // Draw a grayish background first.
+  [[NSColor colorWithDeviceWhite:0 alpha:0.1] setFill];
+  [path fill];
+
+  // Draw a blue or gray fill (depending on graphite or not) for the progress part.
+  NSRect progressFillRect = self.bounds;
+  progressFillRect.size.width *= mFractionValue;
+  [NSGraphicsContext saveGraphicsState];
+  [NSBezierPath clipRect:progressFillRect];
+  [[NSColor keyboardFocusIndicatorColor] setFill];
+  [path fill];
+  [NSGraphicsContext restoreGraphicsState];
+
+  // Add a shadowy stroke on top.
+  [NSGraphicsContext saveGraphicsState];
+  [path addClip];
+  [[NSColor colorWithDeviceWhite:0 alpha:0.2] setStroke];
+  path.lineWidth = barBounds.size.height / 10;
+  [path stroke];
+  [NSGraphicsContext restoreGraphicsState];
+}
+
+@end
+
 nsMacDockSupport::nsMacDockSupport()
-    : mAppIcon(nil),
-      mProgressBackground(nil),
-      mProgressBounds(),
+    : mDockTileWrapperView(nil),
+      mProgressDockOverlayView(nil),
       mProgressState(STATE_NO_PROGRESS),
-      mProgressFraction(0.0) {
-  mProgressTimer = NS_NewTimer();
-}
+      mProgressFraction(0.0) {}
 
 nsMacDockSupport::~nsMacDockSupport() {
-  if (mAppIcon) {
-    [mAppIcon release];
-    mAppIcon = nil;
+  if (mDockTileWrapperView) {
+    [mDockTileWrapperView release];
+    mDockTileWrapperView = nil;
   }
-  if (mProgressBackground) {
-    [mProgressBackground release];
-    mProgressBackground = nil;
-  }
-  if (mProgressTimer) {
-    mProgressTimer->Cancel();
-    mProgressTimer = nullptr;
+  if (mProgressDockOverlayView) {
+    [mProgressDockOverlayView release];
+    mProgressDockOverlayView = nil;
   }
 }
 
 NS_IMETHODIMP
 nsMacDockSupport::GetDockMenu(nsIStandaloneNativeMenu** aDockMenu) {
   nsCOMPtr<nsIStandaloneNativeMenu> dockMenu(mDockMenu);
   dockMenu.forget(aDockMenu);
   return NS_OK;
@@ -95,74 +141,57 @@ nsMacDockSupport::SetProgressState(nsTas
 
   mProgressState = aState;
   if (aMaxValue == 0) {
     mProgressFraction = 0;
   } else {
     mProgressFraction = (double)aCurrentValue / aMaxValue;
   }
 
-  if (mProgressState == STATE_NORMAL || mProgressState == STATE_INDETERMINATE) {
-    int perSecond = 8;  // Empirically determined, see bug 848792
-    mProgressTimer->InitWithNamedFuncCallback(RedrawIconCallback, this, 1000 / perSecond,
-                                              nsITimer::TYPE_REPEATING_SLACK,
-                                              "nsMacDockSupport::RedrawIconCallback");
-    return NS_OK;
-  } else {
-    mProgressTimer->Cancel();
-    return RedrawIcon();
-  }
-}
-
-// static
-void nsMacDockSupport::RedrawIconCallback(nsITimer* aTimer, void* aClosure) {
-  static_cast<nsMacDockSupport*>(aClosure)->RedrawIcon();
+  return UpdateDockTile();
 }
 
-// Return whether to draw progress
-bool nsMacDockSupport::InitProgress() {
-  if (mProgressState != STATE_NORMAL && mProgressState != STATE_INDETERMINATE) {
-    return false;
-  }
-
-  if (!mAppIcon) {
-    mProgressTimer = NS_NewTimer();
-    mAppIcon = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
-    mProgressBackground = [mAppIcon copyWithZone:nil];
-    mTheme = new nsNativeThemeCocoa();
-
-    NSSize sz = [mProgressBackground size];
-    mProgressBounds =
-        CGRectMake(sz.width * 1 / 32, sz.height * 3 / 32, sz.width * 30 / 32, sz.height * 4 / 32);
-    [mProgressBackground lockFocus];
-    [[NSColor clearColor] set];
-    NSRectFill(NSRectFromCGRect(mProgressBounds));
-    [mProgressBackground unlockFocus];
-  }
-  return true;
-}
-
-nsresult nsMacDockSupport::RedrawIcon() {
+nsresult nsMacDockSupport::UpdateDockTile() {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
-  if (InitProgress()) {
-    // TODO: - Implement ERROR and PAUSED states?
-    NSImage* icon = [mProgressBackground copyWithZone:nil];
+  if (mProgressState == STATE_NORMAL || mProgressState == STATE_INDETERMINATE) {
+    if (!mDockTileWrapperView) {
+      // Create the following NSView hierarchy:
+      // * mDockTileWrapperView (NSView)
+      //    * imageView (NSImageView) <- has the application icon
+      //    * mProgressDockOverlayView (MOZProgressDockOverlayView) <- draws the progress bar
+
+      mDockTileWrapperView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 32, 32)];
+      mDockTileWrapperView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
+
+      NSImageView* imageView = [[NSImageView alloc] initWithFrame:[mDockTileWrapperView bounds]];
+      imageView.image = [NSImage imageNamed:@"NSApplicationIcon"];
+      imageView.imageScaling = NSImageScaleAxesIndependently;
+      imageView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
+      [mDockTileWrapperView addSubview:imageView];
 
-    [icon lockFocus];
-    CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
-    nsNativeThemeCocoa::ProgressParams params;
-    params.value = mProgressFraction;
-    params.max = 1.0;
-    params.insideActiveWindow = true;
-    params.indeterminate = (mProgressState != STATE_NORMAL);
-    params.horizontal = true;
-    mTheme->DrawProgress(ctx, mProgressBounds, params);
-    [icon unlockFocus];
-    [NSApp setApplicationIconImage:icon];
-    [icon release];
-  } else {
-    [NSApp setApplicationIconImage:mAppIcon];
+      mProgressDockOverlayView =
+          [[MOZProgressDockOverlayView alloc] initWithFrame:NSMakeRect(1, 3, 30, 4)];
+      mProgressDockOverlayView.autoresizingMask = NSViewMinXMargin | NSViewWidthSizable |
+                                                  NSViewMaxXMargin | NSViewMinYMargin |
+                                                  NSViewHeightSizable | NSViewMaxYMargin;
+      [mDockTileWrapperView addSubview:mProgressDockOverlayView];
+    }
+    if (NSApp.dockTile.contentView != mDockTileWrapperView) {
+      NSApp.dockTile.contentView = mDockTileWrapperView;
+    }
+
+    if (mProgressState == STATE_NORMAL) {
+      mProgressDockOverlayView.fractionValue = mProgressFraction;
+    } else {
+      // Indeterminate states are rare. Just fill the entire progress bar in
+      // that case.
+      mProgressDockOverlayView.fractionValue = 1.0;
+    }
+    [NSApp.dockTile display];
+  } else if (NSApp.dockTile.contentView) {
+    NSApp.dockTile.contentView = nil;
+    [NSApp.dockTile display];
   }
 
   return NS_OK;
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }