Bug 1698763 - Part 2 - Draw treeheadercells with NSTableHeaderCell. r=mstange
authorHarry Twyford <htwyford@mozilla.com>
Fri, 07 May 2021 16:23:12 +0000
changeset 578883 c1188e42ad46a17a12fe783bd1b9ae39b7b8538a
parent 578882 f3f7f23f3ec0f54e525c8c7559c217377218c9cb
child 578884 52ebc9d40f371f853da03bf9fbb2cff418a7744a
push id38445
push userimoraru@mozilla.com
push dateFri, 07 May 2021 21:46:25 +0000
treeherdermozilla-central@950445712e58 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1698763
milestone90.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 1698763 - Part 2 - Draw treeheadercells with NSTableHeaderCell. r=mstange Differential Revision: https://phabricator.services.mozilla.com/D114409
widget/cocoa/nsNativeThemeCocoa.h
widget/cocoa/nsNativeThemeCocoa.mm
--- a/widget/cocoa/nsNativeThemeCocoa.h
+++ b/widget/cocoa/nsNativeThemeCocoa.h
@@ -435,13 +435,14 @@ class nsNativeThemeCocoa : private nsNat
   NSButtonCell* mRadioButtonCell;
   NSButtonCell* mCheckboxCell;
   NSTextFieldCell* mTextFieldCell;
   MOZSearchFieldCell* mSearchFieldCell;
   NSPopUpButtonCell* mDropdownCell;
   NSComboBoxCell* mComboBoxCell;
   NSProgressBarCell* mProgressBarCell;
   NSLevelIndicatorCell* mMeterBarCell;
+  NSTableHeaderCell* mTreeHeaderCell;
   MOZCellDrawWindow* mCellDrawWindow = nil;
   MOZCellDrawView* mCellDrawView;
 };
 
 #endif  // nsNativeThemeCocoa_h_
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsNativeThemeCocoa.h"
+#include <objc/NSObjCRuntime.h>
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "nsChildView.h"
 #include "nsDeviceContext.h"
 #include "nsLayoutUtils.h"
 #include "nsObjCExceptions.h"
@@ -105,17 +106,18 @@ void CUIDraw(CUIRendererRef r, CGRect re
 // NSView) will be called when drawing search fields, and we only provide it in
 // order to prevent "unrecognized selector" exceptions.
 // There's no need to pass the actual NSView that we're drawing into to
 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
 // invalidations as soon as we draw a focusring!
 // This class needs to be an NSControl so that NSTextFieldCell (and
 // NSSearchFieldCell, which is a subclass of NSTextFieldCell) draws a focus ring.
 @interface MOZCellDrawView : NSControl
-
+// Called by NSTreeHeaderCell during drawing.
+@property BOOL _drawingEndSeparator;
 @end
 
 @implementation MOZCellDrawView
 
 - (BOOL)isFlipped {
   return YES;
 }
 
@@ -446,16 +448,18 @@ nsNativeThemeCocoa::nsNativeThemeCocoa()
   [mComboBoxCell setEditable:YES];
   [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
 
   mProgressBarCell = [[NSProgressBarCell alloc] init];
 
   mMeterBarCell = [[NSLevelIndicatorCell alloc]
       initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
 
+  mTreeHeaderCell = [[NSTableHeaderCell alloc] init];
+
   mCellDrawView = [[MOZCellDrawView alloc] init];
 
   if (XRE_IsParentProcess()) {
     // Put the cell draw view into a window that is never shown.
     // This allows us to convince some NSCell implementations (such as NSButtonCell for default
     // buttons) to draw with the active appearance. Another benefit of putting the draw view in a
     // window is the fact that it lets NSTextFieldCell (and its subclass NSSearchFieldCell) inherit
     // the current NSApplication effectiveAppearance automatically, so the field adapts to Dark Mode
@@ -482,16 +486,17 @@ nsNativeThemeCocoa::~nsNativeThemeCocoa(
   [mHelpButtonCell release];
   [mPushButtonCell release];
   [mRadioButtonCell release];
   [mCheckboxCell release];
   [mTextFieldCell release];
   [mSearchFieldCell release];
   [mDropdownCell release];
   [mComboBoxCell release];
+  [mTreeHeaderCell release];
   [mCellDrawWindow release];
   [mCellDrawView release];
 
   NS_OBJC_END_TRY_IGNORE_BLOCK;
 }
 
 // Limit on the area of the target rect (in pixels^2) in
 // DrawCellWithScaling() and DrawButton() and above which we
@@ -1466,65 +1471,73 @@ nsNativeThemeCocoa::TreeHeaderCellParams
     nsIFrame* aFrame, EventStates aEventState) {
   TreeHeaderCellParams params;
   params.controlParams = ComputeControlParams(aFrame, aEventState);
   params.sortDirection = GetTreeSortDirection(aFrame);
   params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
   return params;
 }
 
+@interface NSTableHeaderCell (NSTableHeaderCell_setSortable)
+// This method has been present in the same form since at least macOS 10.4.
+- (void)_setSortable:(BOOL)arg1
+    showSortIndicator:(BOOL)arg2
+            ascending:(BOOL)arg3
+             priority:(NSInteger)arg4
+     highlightForSort:(BOOL)arg5;
+@end
+
 void nsNativeThemeCocoa::DrawTreeHeaderCell(CGContextRef cgContext, const HIRect& inBoxRect,
                                             const TreeHeaderCellParams& aParams) {
   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
 
-  HIThemeButtonDrawInfo bdi;
-  bdi.version = 0;
-  bdi.kind = kThemeListHeaderButton;
-  bdi.value = kThemeButtonOff;
-  bdi.adornment = kThemeAdornmentNone;
-
-  switch (aParams.sortDirection) {
-    case eTreeSortDirection_Natural:
-      break;
-    case eTreeSortDirection_Ascending:
-      bdi.value = kThemeButtonOn;
-      bdi.adornment = kThemeAdornmentHeaderButtonSortUp;
-      break;
-    case eTreeSortDirection_Descending:
-      bdi.value = kThemeButtonOn;
-      break;
+  // Without clearing the cell's title, it takes on a default value of "Field",
+  // which is displayed underneath the title set in the front-end.
+  NSCell* cell = (NSCell*)mTreeHeaderCell;
+  cell.title = @"";
+
+  if ([mTreeHeaderCell respondsToSelector:@selector
+                       (_setSortable:showSortIndicator:ascending:priority:highlightForSort:)]) {
+    switch (aParams.sortDirection) {
+      case eTreeSortDirection_Ascending:
+        [mTreeHeaderCell _setSortable:YES
+                    showSortIndicator:YES
+                            ascending:YES
+                             priority:0
+                     highlightForSort:YES];
+        break;
+      case eTreeSortDirection_Descending:
+        [mTreeHeaderCell _setSortable:YES
+                    showSortIndicator:YES
+                            ascending:NO
+                             priority:0
+                     highlightForSort:YES];
+        break;
+      default:
+        // eTreeSortDirection_Natural
+        [mTreeHeaderCell _setSortable:YES
+                    showSortIndicator:NO
+                            ascending:YES
+                             priority:0
+                     highlightForSort:NO];
+        break;
+    }
   }
 
-  if (aParams.controlParams.disabled) {
-    bdi.state = kThemeStateUnavailable;
-  } else if (aParams.controlParams.pressed) {
-    bdi.state = kThemeStatePressed;
-  } else if (!aParams.controlParams.insideActiveWindow) {
-    bdi.state = kThemeStateInactive;
-  } else {
-    bdi.state = kThemeStateActive;
-  }
-
-  CGContextClipToRect(cgContext, inBoxRect);
-
-  HIRect drawFrame = inBoxRect;
-  // Always remove the top border.
-  drawFrame.origin.y -= 1;
-  drawFrame.size.height += 1;
-  // Remove the left border in LTR mode and the right border in RTL mode.
-  drawFrame.size.width += 1;
-  if (aParams.lastTreeHeaderCell) {
-    drawFrame.size.width += 1;  // Also remove the other border.
-  }
-  if (!aParams.controlParams.rtl || aParams.lastTreeHeaderCell) {
-    drawFrame.origin.x -= 1;
-  }
-
-  RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
-                                  aParams.controlParams.rtl);
+  mTreeHeaderCell.enabled = !aParams.controlParams.disabled;
+  mTreeHeaderCell.state =
+      (mTreeHeaderCell.enabled && aParams.controlParams.pressed) ? NSOnState : NSOffState;
+
+  mCellDrawView._drawingEndSeparator = !aParams.lastTreeHeaderCell;
+
+  NSGraphicsContext* savedContext = NSGraphicsContext.currentContext;
+  NSGraphicsContext.currentContext = [NSGraphicsContext graphicsContextWithCGContext:cgContext
+                                                                             flipped:YES];
+  DrawCellIncludingFocusRing(mTreeHeaderCell, inBoxRect, mCellDrawView);
+  NSGraphicsContext.currentContext = savedContext;
 
 #if DRAW_IN_FRAME_DEBUG
   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
   CGContextFillRect(cgContext, inBoxRect);
 #endif
 
   NS_OBJC_END_TRY_IGNORE_BLOCK;
 }
@@ -2959,19 +2972,19 @@ bool nsNativeThemeCocoa::CreateWebRender
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistArrowButton:
     case StyleAppearance::Groupbox:
     case StyleAppearance::Textfield:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Searchfield:
     case StyleAppearance::ProgressBar:
     case StyleAppearance::Meter:
+    case StyleAppearance::Treeheadercell:
     case StyleAppearance::Treetwisty:
     case StyleAppearance::Treetwistyopen:
-    case StyleAppearance::Treeheadercell:
     case StyleAppearance::Treeitem:
     case StyleAppearance::Treeview:
     case StyleAppearance::Range:
     case StyleAppearance::ScrollbarthumbVertical:
     case StyleAppearance::ScrollbarthumbHorizontal:
       return false;
 
     case StyleAppearance::Scrollcorner:
@@ -3332,17 +3345,17 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
       *aIsOverridable = false;
       break;
     }
 
     case StyleAppearance::Treeheader:
     case StyleAppearance::Treeheadercell: {
       SInt32 headerHeight = 0;
       ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
-      aResult->SizeTo(0, headerHeight - 1);  // We don't need the top border.
+      aResult->SizeTo(0, headerHeight);
       break;
     }
 
     case StyleAppearance::Tab: {
       aResult->SizeTo(0, tabHeights[miniControlSize]);
       break;
     }