1 /*
2  * ttkMacOSXTheme.c --
3  *
4  *      Tk theme engine for Mac OSX, using the Appearance Manager API.
5  *
6  * Copyright © 2004 Joe English
7  * Copyright © 2005 Neil Madden
8  * Copyright © 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
9  * Copyright © 2008-2009 Apple Inc.
10  * Copyright © 2009 Kevin Walzer/WordTech Communications LLC.
11  * Copyright © 2019 Marc Culler
12  *
13  * See the file "license.terms" for information on usage and redistribution
14  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
15  *
16  * See also:
17  *
18  * <URL: http://developer.apple.com/documentation/Carbon/Reference/
19  *      Appearance_Manager/appearance_manager/APIIndex.html >
20  *
21  * Notes:
22  *      "Active" means different things in Mac and Tk terminology --
23  *      On Aqua, widgets are "Active" if they belong to the foreground window,
24  *      "Inactive" if they are in a background window.  Tk uses the term
25  *      "active" to mean that the mouse cursor is over a widget; aka "hover",
26  *      "prelight", or "hot-tracked".  Aqua doesn't use this kind of feedback.
27  *
28  *      The QuickDraw/Carbon coordinate system is relative to the top-level
29  *      window, not to the Tk_Window.  BoxToRect() accounts for this.
30  */
31 
32 #include "tkMacOSXPrivate.h"
33 #include "ttk/ttkTheme.h"
34 
35 /*
36  * Macros for handling drawing contexts.
37  */
38 
39 #define BEGIN_DRAWING(d) {				    \
40     TkMacOSXDrawingContext dc;				    \
41     if (!TkMacOSXSetupDrawingContext((d), NULL, &dc)) {	    \
42 	return;						    \
43     }							    \
44 
45 #define END_DRAWING				\
46     TkMacOSXRestoreDrawingContext(&dc);}
47 
48 #define HIOrientation kHIThemeOrientationNormal
49 #define NoThemeMetric 0xFFFFFFFF
50 
51 #ifdef __LP64__
52 #define RangeToFactor(maximum) (((double) (INT_MAX >> 1)) / (maximum))
53 #else
54 #define RangeToFactor(maximum) (((double) (LONG_MAX >> 1)) / (maximum))
55 #endif /* __LP64__ */
56 
57 #define TTK_STATE_FIRST_TAB     TTK_STATE_USER1
58 #define TTK_STATE_LAST_TAB      TTK_STATE_USER2
59 #define TTK_TREEVIEW_STATE_SORTARROW    TTK_STATE_USER1
60 
61 /*
62  * Colors and gradients used in Dark Mode.
63  */
64 
65 static CGFloat darkButtonFace[4] = {
66     112.0 / 255, 113.0 / 255, 115.0 / 255, 1.0
67 };
68 static CGFloat darkPressedBevelFace[4] = {
69     135.0 / 255, 136.0 / 255, 138.0 / 255, 1.0
70 };
71 static CGFloat darkSelectedBevelFace[4] = {
72     162.0 / 255, 163.0 / 255, 165.0 / 255, 1.0
73 };
74 static CGFloat darkDisabledButtonFace[4] = {
75     86.0 / 255, 87.0 / 255, 89.0 / 255, 1.0
76 };
77 static CGFloat darkInactiveSelectedTab[4] = {
78     159.0 / 255, 160.0 / 255, 161.0 / 255, 1.0
79 };
80 static CGFloat darkFocusRing[4] = {
81     38.0 / 255, 113.0 / 255, 159.0 / 255, 1.0
82 };
83 static CGFloat darkFocusRingTop[4] = {
84     50.0 / 255, 124.0 / 255, 171.0 / 255, 1.0
85 };
86 static CGFloat darkFocusRingBottom[4] = {
87     57.0 / 255, 130.0 / 255, 176.0 / 255, 1.0
88 };
89 static CGFloat darkTabSeparator[4] = {0.0, 0.0, 0.0, 0.25};
90 static CGFloat darkTrack[4] = {1.0, 1.0, 1.0, 0.25};
91 static CGFloat darkFrameTop[4] = {1.0, 1.0, 1.0, 0.0625};
92 static CGFloat darkFrameBottom[4] = {1.0, 1.0, 1.0, 0.125};
93 static CGFloat darkFrameAccent[4] = {0.0, 0.0, 0.0, 0.0625};
94 static CGFloat darkTopGradient[8] = {
95     1.0, 1.0, 1.0, 0.3,
96     1.0, 1.0, 1.0, 0.0
97 };
98 static CGFloat darkBackgroundGradient[8] = {
99     0.0, 0.0, 0.0, 0.1,
100     0.0, 0.0, 0.0, 0.25
101 };
102 static CGFloat darkInactiveGradient[8] = {
103     89.0 / 255, 90.0 / 255, 93.0 / 255, 1.0,
104     119.0 / 255, 120.0 / 255, 122.0 / 255, 1.0
105 };
106 static CGFloat darkSelectedGradient[8] = {
107     23.0 / 255, 111.0 / 255, 232.0 / 255, 1.0,
108     20.0 / 255, 94.0 / 255,  206.0 / 255, 1.0
109 };
110 static CGFloat pressedPushButtonGradient[8] = {
111     35.0 / 255, 123.0 / 255, 244.0 / 255, 1.0,
112     30.0 / 255, 114.0 / 255, 235.0 / 255, 1.0
113 };
114 
115 /*
116  * When building on systems earlier than 10.8 there is no reasonable way to
117  * convert an NSColor to a CGColor.  We do run-time checking of the OS version,
118  * and never need the CGColor property on older systems, so we can use this
119  * CGCOLOR macro, which evaluates to NULL without raising compiler warnings.
120  * Similarly, we never draw rounded rectangles on older systems which did not
121  * have CGPathCreateWithRoundedRect, so we just redefine it to return NULL.
122  */
123 
124 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
125 #define CGCOLOR(nscolor) nscolor.CGColor
126 #else
127 #define CGCOLOR(nscolor) (0 ? (CGColorRef) nscolor : NULL)
128 #define CGPathCreateWithRoundedRect(w, x, y, z) NULL
129 #endif
130 
131 /*
132  * If we try to draw a rounded rectangle with too large of a radius
133  * CoreGraphics will raise a fatal exception.  This macro returns if
134  * the width or height is less than twice the radius.  Presumably this
135  * only happens when a widget has not yet been configured and has size
136  * 1x1.
137  */
138 
139 #define CHECK_RADIUS(radius, bounds)                                         \
140     if (radius > bounds.size.width / 2 || radius > bounds.size.height / 2) { \
141         return;                                                              \
142     }
143 
144 /*----------------------------------------------------------------------
145  * +++ Utilities.
146  */
147 
148 /*
149  * BoxToRect --
150  *    Convert a Ttk_Box in Tk coordinates relative to the given Drawable
151  *    to a native Rect relative to the containing port.
152  */
153 
BoxToRect(Drawable d,Ttk_Box b)154 static inline CGRect BoxToRect(
155     Drawable d,
156     Ttk_Box b)
157 {
158     MacDrawable *md = (MacDrawable *)d;
159     CGRect rect;
160 
161     rect.origin.y       = b.y + md->yOff;
162     rect.origin.x       = b.x + md->xOff;
163     rect.size.height    = b.height;
164     rect.size.width     = b.width;
165 
166     return rect;
167 }
168 
169 /*
170  * Table mapping Tk states to Appearance manager ThemeStates
171  */
172 
173 static Ttk_StateTable ThemeStateTable[] = {
174     {kThemeStateActive, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
175     {kThemeStateUnavailable, TTK_STATE_DISABLED, 0},
176     {kThemeStatePressed, TTK_STATE_PRESSED, 0},
177     {kThemeStateInactive, TTK_STATE_BACKGROUND, 0},
178     {kThemeStateActive, 0, 0}
179 
180     /* Others: Not sure what these are supposed to mean.  Up/Down have
181      * something to do with "little arrow" increment controls...  Dunno what
182      * a "Rollover" is.
183      * NEM: Rollover is TTK_STATE_ACTIVE... but we don't handle that yet, by
184      * the looks of things
185      *
186      * {kThemeStateRollover, 0, 0},
187      * {kThemeStateUnavailableInactive, 0, 0}
188      * {kThemeStatePressedUp, 0, 0},
189      * {kThemeStatePressedDown, 0, 0}
190      */
191 };
192 
193 /*----------------------------------------------------------------------
194  * NormalizeButtonBounds --
195  *
196  *      Apple's Human Interface Guidelines only allow three specific heights
197  *      for most buttons: Regular, small and mini. We always use the regular
198  *      size.  However, Ttk may provide an arbitrary bounding rectangle.  We
199  *      always draw the button centered vertically on the rectangle, and
200  *      having the same width as the rectangle.  This function returns the
201  *      actual bounding rectangle that will be used in drawing the button.
202  *
203  *      The BevelButton is allowed to have arbitrary size, and also has
204  *      external padding.  This is handled separately here.
205  */
206 
NormalizeButtonBounds(SInt32 heightMetric,CGRect bounds)207 static CGRect NormalizeButtonBounds(
208     SInt32 heightMetric,
209     CGRect bounds)
210 {
211     SInt32 height;
212 
213     if (heightMetric != (SInt32) NoThemeMetric) {
214 	ChkErr(GetThemeMetric, heightMetric, &height);
215 	bounds.origin.y += (bounds.size.height - height) / 2;
216 	bounds.size.height = height;
217     }
218     return bounds;
219 }
220 
221 /*----------------------------------------------------------------------
222  * +++ Backgrounds
223  *
224  * Support for contrasting background colors when GroupBoxes or Tabbed
225  * panes are nested inside each other.  Early versions of macOS used ridged
226  * borders, so do not need contrasting backgrounds.
227  */
228 
229 /*
230  * For systems older than 10.14, [NSColor windowBackGroundColor] generates
231  * garbage when called from this function.  In 10.14 it works correctly, and
232  * must be used in order to have a background color which responds to Dark
233  * Mode.  So we use this hard-wired RGBA color on the older systems which don't
234  * support Dark Mode anyway.
235  */
236 
237 static const CGFloat WINDOWBACKGROUND[4] = {
238     235.0 / 255, 235.0 / 255, 235.0 / 255, 1.0
239 };
240 static const CGFloat WHITERGBA[4] = {1.0, 1.0, 1.0, 1.0};
241 static const CGFloat BLACKRGBA[4] = {0.0, 0.0, 0.0, 1.0};
242 
243 /*----------------------------------------------------------------------
244  * GetBackgroundColor --
245  *
246  *      Fills the array rgba with the color coordinates for a background color.
247  *      Start with the background color of a window's geometry container, or
248  *      the standard ttk window background if there is no container. If the
249  *      contrast parameter is nonzero, modify this color to be darker, for the
250  *      aqua appearance, or lighter for the DarkAqua appearance.  This is
251  *      primarily used by the Fill and Background elements.
252  */
253 
GetBackgroundColor(TCL_UNUSED (CGContextRef),Tk_Window tkwin,int contrast,CGFloat * rgba)254 static void GetBackgroundColor(
255     TCL_UNUSED(CGContextRef),
256     Tk_Window tkwin,
257     int contrast,
258     CGFloat *rgba)
259 {
260     TkWindow *winPtr = (TkWindow *)tkwin;
261     TkWindow *containerPtr = (TkWindow *)TkGetContainer(tkwin);
262 
263     while (containerPtr && containerPtr->privatePtr) {
264 	if (containerPtr->privatePtr->flags & TTK_HAS_CONTRASTING_BG) {
265 	    break;
266 	}
267 	containerPtr = (TkWindow *)TkGetContainer(containerPtr);
268     }
269     if (containerPtr && containerPtr->privatePtr) {
270 	for (int i = 0; i < 4; i++) {
271 	    rgba[i] = containerPtr->privatePtr->fillRGBA[i];
272 	}
273     } else {
274 	if ([NSApp macOSVersion] > 101300) {
275 	    NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
276 	    NSColor *windowColor = [[NSColor windowBackgroundColor]
277 		colorUsingColorSpace: deviceRGB];
278 	    [windowColor getComponents: rgba];
279 	} else {
280 	    for (int i = 0; i < 4; i++) {
281 		rgba[i] = WINDOWBACKGROUND[i];
282 	    }
283 	}
284     }
285     if (contrast) {
286 	int isDark = (rgba[0] + rgba[1] + rgba[2] < 1.5);
287 
288 	if (isDark) {
289 	    for (int i = 0; i < 3; i++) {
290 		rgba[i] += 8.0 / 255.0;
291 	    }
292 	} else {
293 	    for (int i = 0; i < 3; i++) {
294 		rgba[i] -= 8.0 / 255.0;
295 	    }
296 	}
297         if (winPtr->privatePtr) {
298             winPtr->privatePtr->flags |= TTK_HAS_CONTRASTING_BG;
299             for (int i = 0; i < 4; i++) {
300                 winPtr->privatePtr->fillRGBA[i] = rgba[i];
301             }
302         }
303     }
304 }
305 
306 
307 /*----------------------------------------------------------------------
308  * +++ Single Arrow Images --
309  *
310  * Used in ListHeaders and Comboboxes as well as disclosure triangles in
311  * macOS 11.
312  */
313 
DrawDownArrow(CGContextRef context,CGRect bounds,CGFloat inset,CGFloat size,const CGFloat * rgba)314 static void DrawDownArrow(
315     CGContextRef context,
316     CGRect bounds,
317     CGFloat inset,
318     CGFloat size,
319     const CGFloat *rgba)
320 {
321     CGFloat x, y;
322 
323     CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
324     CGContextSetLineWidth(context, 1.5);
325     x = bounds.origin.x + inset;
326     y = bounds.origin.y + trunc(bounds.size.height / 2);
327     CGContextBeginPath(context);
328     CGPoint arrow[3] = {
329 	{x, y - size / 4}, {x + size / 2, y + size / 4},
330 	{x + size, y - size / 4}
331     };
332     CGContextAddLines(context, arrow, 3);
333     CGContextStrokePath(context);
334 }
335 
DrawUpArrow(CGContextRef context,CGRect bounds,CGFloat inset,CGFloat size,const CGFloat * rgba)336 static void DrawUpArrow(
337     CGContextRef context,
338     CGRect bounds,
339     CGFloat inset,
340     CGFloat size,
341     const CGFloat *rgba)
342 {
343     CGFloat x, y;
344 
345     CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
346     CGContextSetLineWidth(context, 1.5);
347     x = bounds.origin.x + inset;
348     y = bounds.origin.y + trunc(bounds.size.height / 2);
349     CGContextBeginPath(context);
350     CGPoint arrow[3] = {
351 	{x, y + size / 4}, {x + size / 2, y - size / 4},
352 	{x + size, y + size / 4}
353     };
354     CGContextAddLines(context, arrow, 3);
355     CGContextStrokePath(context);
356 }
357 
DrawClosedDisclosure(CGContextRef context,CGRect bounds,CGFloat inset,CGFloat size,CGFloat * rgba)358 static void DrawClosedDisclosure(
359     CGContextRef context,
360     CGRect bounds,
361     CGFloat inset,
362     CGFloat size,
363     CGFloat *rgba)
364 {
365     CGFloat x, y;
366 
367     CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
368     CGContextSetLineWidth(context, 1.5);
369     x = bounds.origin.x + inset;
370     y = bounds.origin.y + trunc(bounds.size.height / 2);
371     CGContextBeginPath(context);
372     CGPoint arrow[3] = {
373 	{x, y - size / 4 - 1}, {x + size / 2, y}, {x, y + size / 4 + 1}
374     };
375     CGContextAddLines(context, arrow, 3);
376     CGContextStrokePath(context);
377 }
378 
DrawOpenDisclosure(CGContextRef context,CGRect bounds,CGFloat inset,CGFloat size,CGFloat * rgba)379 static void DrawOpenDisclosure(
380     CGContextRef context,
381     CGRect bounds,
382     CGFloat inset,
383     CGFloat size,
384     CGFloat *rgba)
385 {
386     CGFloat x, y;
387 
388     CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
389     CGContextSetLineWidth(context, 1.5);
390     x = bounds.origin.x + inset;
391     y = bounds.origin.y + trunc(bounds.size.height / 2);
392     CGContextBeginPath(context);
393     CGPoint arrow[3] = {
394 	{x, y - size / 4}, {x + size / 2, y + size / 2}, {x + size, y - size / 4}
395     };
396     CGContextAddLines(context, arrow, 3);
397     CGContextStrokePath(context);
398 }
399 
400 /*----------------------------------------------------------------------
401  * +++ Double Arrow Buttons --
402  *
403  * Used in MenuButtons and SpinButtons.
404  */
405 
DrawUpDownArrows(CGContextRef context,CGRect bounds,CGFloat inset,CGFloat size,const CGFloat * rgba)406 static void DrawUpDownArrows(
407     CGContextRef context,
408     CGRect bounds,
409     CGFloat inset,
410     CGFloat size,
411     const CGFloat *rgba)
412 {
413     CGFloat x, y;
414 
415     CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
416     CGContextSetLineWidth(context, 1.5);
417     x = bounds.origin.x + inset;
418     y = bounds.origin.y + trunc(bounds.size.height / 2);
419     CGContextBeginPath(context);
420     CGPoint bottomArrow[3] =
421     {{x, y + 2}, {x + size / 2, y + 2 + size / 2}, {x + size, y + 2}};
422     CGContextAddLines(context, bottomArrow, 3);
423     CGPoint topArrow[3] =
424     {{x, y - 2}, {x + size / 2, y - 2 - size / 2}, {x + size, y - 2}};
425     CGContextAddLines(context, topArrow, 3);
426     CGContextStrokePath(context);
427 }
428 
429 
430 /*----------------------------------------------------------------------
431  * +++ FillButtonBackground --
432  *
433  *      Fills a rounded rectangle with a transparent black gradient.
434  *      This is a no-op if building on 10.8 or older.
435  */
436 
FillButtonBackground(CGContextRef context,CGRect bounds,CGFloat radius)437 static void FillButtonBackground(
438     CGContextRef context,
439     CGRect bounds,
440     CGFloat radius)
441 {
442     CHECK_RADIUS(radius, bounds)
443 
444     CGPathRef path;
445     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
446     CGGradientRef backgroundGradient = CGGradientCreateWithColorComponents(
447 	deviceRGB.CGColorSpace, darkBackgroundGradient, NULL, 2);
448     CGPoint backgroundEnd = {
449 	bounds.origin.x,
450 	bounds.origin.y + bounds.size.height
451     };
452     CGContextBeginPath(context);
453     path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
454     CGContextAddPath(context, path);
455     CGContextClip(context);
456     CGContextDrawLinearGradient(context, backgroundGradient,
457 	bounds.origin, backgroundEnd, 0);
458     CFRelease(path);
459     CFRelease(backgroundGradient);
460 }
461 
462 /*----------------------------------------------------------------------
463  * +++ HighlightButtonBorder --
464  *
465  * Accent the top border of a rounded rectangle with a transparent
466  * white gradient.
467  */
468 
HighlightButtonBorder(CGContextRef context,CGRect bounds)469 static void HighlightButtonBorder(
470     CGContextRef context,
471     CGRect bounds)
472 {
473     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
474     CGPoint topEnd = {bounds.origin.x, bounds.origin.y + 3};
475     CGGradientRef topGradient = CGGradientCreateWithColorComponents(
476 	deviceRGB.CGColorSpace, darkTopGradient, NULL, 2);
477 
478     CGContextSaveGState(context);
479     CGContextBeginPath(context);
480     CGContextAddArc(context, bounds.origin.x + 4, bounds.origin.y + 4,
481 	4, PI, 3 * PI / 2, 0);
482     CGContextAddArc(context, bounds.origin.x + bounds.size.width - 4,
483 	bounds.origin.y + 4, 4, 3 * PI / 2, 0, 0);
484     CGContextReplacePathWithStrokedPath(context);
485     CGContextClip(context);
486     CGContextDrawLinearGradient(context, topGradient, bounds.origin, topEnd,
487 	0.0);
488     CGContextRestoreGState(context);
489     CFRelease(topGradient);
490 }
491 
492 /*----------------------------------------------------------------------
493  * DrawGroupBox --
494  *
495  *      This is a standalone drawing procedure which draws the contrasting
496  *      rounded rectangular box for LabelFrames and Notebook panes used in
497  *      more recent versions of macOS.
498  */
499 
DrawGroupBox(CGRect bounds,CGContextRef context,Tk_Window tkwin)500 static void DrawGroupBox(
501     CGRect bounds,
502     CGContextRef context,
503     Tk_Window tkwin)
504 {
505     CHECK_RADIUS(4, bounds)
506 
507     CGPathRef path;
508     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
509     NSColor *borderColor, *bgColor;
510     static CGFloat border[4] = {1.0, 1.0, 1.0, 0.25};
511     CGFloat fill[4];
512 
513     GetBackgroundColor(context, tkwin, 1, fill);
514     bgColor = [NSColor colorWithColorSpace: deviceRGB components: fill
515 	count: 4];
516     CGContextSetFillColorSpace(context, deviceRGB.CGColorSpace);
517     CGContextSetFillColorWithColor(context, CGCOLOR(bgColor));
518     path = CGPathCreateWithRoundedRect(bounds, 4, 4, NULL);
519     CGContextClipToRect(context, bounds);
520     CGContextBeginPath(context);
521     CGContextAddPath(context, path);
522     CGContextFillPath(context);
523     borderColor = [NSColor colorWithColorSpace: deviceRGB components: border
524 	count: 4];
525     CGContextSetFillColorWithColor(context, CGCOLOR(borderColor));
526     [borderColor getComponents: fill];
527     CGContextSetRGBFillColor(context, fill[0], fill[1], fill[2], fill[3]);
528 
529     CGContextBeginPath(context);
530     CGContextAddPath(context, path);
531     CGContextReplacePathWithStrokedPath(context);
532     CGContextFillPath(context);
533     CFRelease(path);
534 }
535 
536 /*----------------------------------------------------------------------
537  * SolidFillRoundedRectangle --
538  *
539  *      Fill a rounded rectangle with a specified solid color.
540  */
541 
SolidFillRoundedRectangle(CGContextRef context,CGRect bounds,CGFloat radius,NSColor * color)542 static void SolidFillRoundedRectangle(
543     CGContextRef context,
544     CGRect bounds,
545     CGFloat radius,
546     NSColor *color)
547 {
548     CGPathRef path;
549 
550     CHECK_RADIUS(radius, bounds)
551     path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
552     if (!path) {
553 	return;
554     }
555     CGContextSetFillColorWithColor(context, CGCOLOR(color));
556     CGContextBeginPath(context);
557     CGContextAddPath(context, path);
558     CGContextFillPath(context);
559     CFRelease(path);
560 }
561 
562 /*----------------------------------------------------------------------
563  * +++ DrawListHeader --
564  *
565  *      This is a standalone drawing procedure which draws column headers for
566  *      a Treeview in the Aqua appearance.  The HITheme headers have not
567  *      matched the native ones since OSX 10.8.  Note that the header image is
568  *      ignored, but we draw arrows according to the state.
569  */
570 
DrawListHeader(CGRect bounds,CGContextRef context,Tk_Window tkwin,int state)571 static void DrawListHeader(
572     CGRect bounds,
573     CGContextRef context,
574     Tk_Window tkwin,
575     int state)
576 {
577     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
578     NSColor *strokeColor, *bgColor;
579     static CGFloat borderRGBA[4] = {
580 	200.0 / 255, 200.0 / 255, 200.0 / 255, 1.0
581     };
582     static CGFloat separatorRGBA[4] = {
583 	220.0 / 255, 220.0 / 255, 220.0 / 255, 1.0
584     };
585     static CGFloat activeBgRGBA[4] = {
586 	238.0 / 255, 238.0 / 255, 238.0 / 255, 1.0
587     };
588     static CGFloat inactiveBgRGBA[4] = {
589 	246.0 / 255, 246.0 / 255, 246.0 / 255, 1.0
590     };
591 
592     /*
593      * Apple changes the background of a list header when the window is not
594      * active.  But Ttk does not indicate that in the state of a TreeHeader.
595      * So we have to query the Apple window manager.
596      */
597 
598     NSWindow *win = TkMacOSXGetNSWindowForDrawable(Tk_WindowId(tkwin));
599     CGFloat *bgRGBA = [win isKeyWindow] ? activeBgRGBA : inactiveBgRGBA;
600     CGFloat x = bounds.origin.x, y = bounds.origin.y;
601     CGFloat w = bounds.size.width, h = bounds.size.height;
602     CGPoint top[2] = {{x, y + 1}, {x + w, y + 1}};
603     CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
604     CGPoint separator[2] = {{x + w - 1, y + 3}, {x + w - 1, y + h - 3}};
605 
606     bgColor = [NSColor colorWithColorSpace: deviceRGB
607 	components: bgRGBA
608 	count: 4];
609     CGContextSaveGState(context);
610     CGContextSetShouldAntialias(context, false);
611     CGContextSetFillColorSpace(context, deviceRGB.CGColorSpace);
612     CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
613     CGContextBeginPath(context);
614     CGContextSetFillColorWithColor(context, CGCOLOR(bgColor));
615     CGContextAddRect(context, bounds);
616     CGContextFillPath(context);
617     strokeColor = [NSColor colorWithColorSpace: deviceRGB
618 	components: separatorRGBA
619 	count: 4];
620     CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
621     CGContextAddLines(context, separator, 2);
622     CGContextStrokePath(context);
623     strokeColor = [NSColor colorWithColorSpace: deviceRGB
624 	components: borderRGBA
625 	count: 4];
626     CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
627     CGContextAddLines(context, top, 2);
628     CGContextStrokePath(context);
629     CGContextAddLines(context, bottom, 2);
630     CGContextStrokePath(context);
631     CGContextRestoreGState(context);
632 
633     if (state & TTK_TREEVIEW_STATE_SORTARROW) {
634 	CGRect arrowBounds = bounds;
635 	arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16;
636 	arrowBounds.size.width = 16;
637 	if (state & TTK_STATE_ALTERNATE) {
638 	    DrawUpArrow(context, arrowBounds, 3, 8, BLACKRGBA);
639 	} else if (state & TTK_STATE_SELECTED) {
640 	    DrawDownArrow(context, arrowBounds, 3, 8, BLACKRGBA);
641 	}
642     }
643 }
644 
645 /*----------------------------------------------------------------------
646  * +++ Drawing procedures for widgets in Apple's "Dark Mode" (10.14 and up).
647  *
648  *      The HIToolbox does not support Dark Mode, and apparently never will,
649  *      so to make widgets look "native" we have to provide analogues of the
650  *      HITheme drawing functions to be used in DarkAqua.  We continue to use
651  *      HITheme in Aqua, since it understands earlier versions of the OS.
652  *
653  *      Drawing the dark widgets requires NSColors that were introduced in OSX
654  *      10.14, so we make some of these functions be no-ops when building on
655  *      systems older than 10.14.
656  */
657 
658 /*----------------------------------------------------------------------
659  * GradientFillRoundedRectangle --
660  *
661  *      Fill a rounded rectangle with a specified gradient.
662  */
663 
GradientFillRoundedRectangle(CGContextRef context,CGRect bounds,CGFloat radius,CGFloat * colors,int numColors)664 static void GradientFillRoundedRectangle(
665     CGContextRef context,
666     CGRect bounds,
667     CGFloat radius,
668     CGFloat *colors,
669     int numColors)
670 {
671     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
672     CGPathRef path;
673     CHECK_RADIUS(radius, bounds)
674 
675     CGPoint end = {
676 	bounds.origin.x,
677 	bounds.origin.y + bounds.size.height
678     };
679     CGGradientRef gradient = CGGradientCreateWithColorComponents(
680 	deviceRGB.CGColorSpace, colors, NULL, numColors);
681 
682     path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
683     CGContextBeginPath(context);
684     CGContextAddPath(context, path);
685     CGContextClip(context);
686     CGContextDrawLinearGradient(context, gradient, bounds.origin, end, 0);
687     CFRelease(path);
688     CFRelease(gradient);
689 }
690 
691 /*----------------------------------------------------------------------
692  * +++ DrawDarkButton --
693  *
694  *      This is a standalone drawing procedure which draws PushButtons and
695  *      PopupButtons in the Dark Mode style.
696  */
697 
DrawDarkButton(CGRect bounds,ThemeButtonKind kind,Ttk_State state,CGContextRef context)698 static void DrawDarkButton(
699     CGRect bounds,
700     ThemeButtonKind kind,
701     Ttk_State state,
702     CGContextRef context)
703 {
704     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
705     NSColor *faceColor;
706 
707     /*
708      * To match the appearance of Apple's buttons we need to increase the
709      * height by 1 pixel.
710      */
711 
712     bounds.size.height += 1;
713 
714     CGContextClipToRect(context, bounds);
715     FillButtonBackground(context, bounds, 5);
716 
717     /*
718      * Fill the button face with the appropriate color.
719      */
720 
721     bounds = CGRectInset(bounds, 1, 1);
722     if (kind == kThemePushButton && (state & TTK_STATE_PRESSED)) {
723 	GradientFillRoundedRectangle(context, bounds, 4,
724 	    pressedPushButtonGradient, 2);
725     } else if (kind == kThemePushButton &&
726 	       (state & TTK_STATE_ALTERNATE) &&
727 	       !(state & TTK_STATE_BACKGROUND)) {
728 	GradientFillRoundedRectangle(context, bounds, 4,
729 	    darkSelectedGradient, 2);
730     } else {
731 	if (state & TTK_STATE_DISABLED) {
732 	    faceColor = [NSColor colorWithColorSpace: deviceRGB
733 		components: darkDisabledButtonFace
734 		count: 4];
735 	} else {
736 	    faceColor = [NSColor colorWithColorSpace: deviceRGB
737 		components: darkButtonFace
738 		count: 4];
739 	}
740 	SolidFillRoundedRectangle(context, bounds, 4, faceColor);
741     }
742 
743     /*
744      * If this is a popup, draw the arrow button.
745      */
746 
747     if ((kind == kThemePopupButton) | (kind == kThemeComboBox)) {
748 	CGRect arrowBounds = bounds;
749 	arrowBounds.size.width = 16;
750 	arrowBounds.origin.x += bounds.size.width - 16;
751 
752         /*
753          * If the toplevel is front, paint the button blue.
754          */
755 
756 	if (!(state & TTK_STATE_BACKGROUND) &&
757 	    !(state & TTK_STATE_DISABLED)) {
758 	    GradientFillRoundedRectangle(context, arrowBounds, 4,
759 		darkSelectedGradient, 2);
760 	}
761 	if (kind == kThemePopupButton) {
762 	    DrawUpDownArrows(context, arrowBounds, 3, 7, WHITERGBA);
763 	} else {
764 	    DrawDownArrow(context, arrowBounds, 4, 8, WHITERGBA);
765 	}
766     }
767 
768     HighlightButtonBorder(context, bounds);
769 }
770 
771 /*----------------------------------------------------------------------
772  * +++ DrawDarkIncDecButton --
773  *
774  *      This is a standalone drawing procedure which draws an IncDecButton
775  *      (as used in a Spinbox) in the Dark Mode style.
776  */
777 
DrawDarkIncDecButton(CGRect bounds,ThemeDrawState drawState,Ttk_State state,CGContextRef context)778 static void DrawDarkIncDecButton(
779     CGRect bounds,
780     ThemeDrawState drawState,
781     Ttk_State state,
782     CGContextRef context)
783 {
784     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
785     NSColor *faceColor;
786 
787     bounds = CGRectInset(bounds, 0, -1);
788     CGContextClipToRect(context, bounds);
789     FillButtonBackground(context, bounds, 6);
790 
791     /*
792      * Fill the button face with the appropriate color.
793      */
794 
795     bounds = CGRectInset(bounds, 1, 1);
796     if (state & TTK_STATE_DISABLED) {
797 	faceColor = [NSColor colorWithColorSpace: deviceRGB
798 	    components: darkDisabledButtonFace
799 	    count: 4];
800     } else {
801 	faceColor = [NSColor colorWithColorSpace: deviceRGB
802 	    components: darkButtonFace
803 	    count: 4];
804     }
805     SolidFillRoundedRectangle(context, bounds, 4, faceColor);
806 
807     /*
808      * If pressed, paint the appropriate half blue.
809      */
810 
811     if (state & TTK_STATE_PRESSED) {
812 	CGRect clip = bounds;
813 	clip.size.height /= 2;
814 	CGContextSaveGState(context);
815 	if (drawState == kThemeStatePressedDown) {
816 	    clip.origin.y += clip.size.height;
817 	}
818 	CGContextClipToRect(context, clip);
819 	GradientFillRoundedRectangle(context, bounds, 5,
820 	    darkSelectedGradient, 2);
821 	CGContextRestoreGState(context);
822     }
823     DrawUpDownArrows(context, bounds, 3, 5, WHITERGBA);
824     HighlightButtonBorder(context, bounds);
825 }
826 
827 /*----------------------------------------------------------------------
828  * +++ DrawDarkBevelButton --
829  *
830  *      This is a standalone drawing procedure which draws RoundedBevelButtons
831  *      in the Dark Mode style.
832  */
833 
DrawDarkBevelButton(CGRect bounds,Ttk_State state,CGContextRef context)834 static void DrawDarkBevelButton(
835     CGRect bounds,
836     Ttk_State state,
837     CGContextRef context)
838 {
839     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
840     NSColor *faceColor;
841 
842     CGContextClipToRect(context, bounds);
843     FillButtonBackground(context, bounds, 5);
844 
845     /*
846      * Fill the button face with the appropriate color.
847      */
848 
849     bounds = CGRectInset(bounds, 1, 1);
850     if (state & TTK_STATE_PRESSED) {
851 	faceColor = [NSColor colorWithColorSpace: deviceRGB
852 	    components: darkPressedBevelFace
853 	    count: 4];
854     } else if ((state & TTK_STATE_DISABLED) ||
855 	(state & TTK_STATE_ALTERNATE)) {
856 	faceColor = [NSColor colorWithColorSpace: deviceRGB
857 	    components: darkDisabledButtonFace
858 	    count: 4];
859     } else if (state & TTK_STATE_SELECTED) {
860 	faceColor = [NSColor colorWithColorSpace: deviceRGB
861 	    components: darkSelectedBevelFace
862 	    count: 4];
863     } else {
864 	faceColor = [NSColor colorWithColorSpace: deviceRGB
865 	    components: darkButtonFace
866 	    count: 4];
867     }
868     SolidFillRoundedRectangle(context, bounds, 4, faceColor);
869     HighlightButtonBorder(context, bounds);
870 }
871 
872 /*----------------------------------------------------------------------
873  * +++ DrawDarkCheckBox --
874  *
875  *      This is a standalone drawing procedure which draws Checkboxes in the
876  *      Dark Mode style.
877  */
878 
DrawDarkCheckBox(CGRect bounds,Ttk_State state,CGContextRef context)879 static void DrawDarkCheckBox(
880     CGRect bounds,
881     Ttk_State state,
882     CGContextRef context)
883 {
884     CGRect checkbounds = {{0, bounds.size.height / 2 - 8}, {16, 16}};
885     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
886     NSColor *stroke;
887     CGFloat x, y;
888 
889     bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y);
890     x = bounds.origin.x;
891     y = bounds.origin.y;
892 
893     CGContextClipToRect(context, bounds);
894     FillButtonBackground(context, bounds, 4);
895     bounds = CGRectInset(bounds, 1, 1);
896     if (!(state & TTK_STATE_BACKGROUND) &&
897 	!(state & TTK_STATE_DISABLED) &&
898 	((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE))) {
899 	GradientFillRoundedRectangle(context, bounds, 3,
900 	    darkSelectedGradient, 2);
901     } else {
902 	GradientFillRoundedRectangle(context, bounds, 3,
903 	    darkInactiveGradient, 2);
904     }
905     HighlightButtonBorder(context, bounds);
906     if ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE)) {
907 	CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
908 	if (state & TTK_STATE_DISABLED) {
909 	    stroke = [NSColor disabledControlTextColor];
910 	} else {
911 	    stroke = [NSColor controlTextColor];
912 	}
913 	CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
914     }
915     if (state & TTK_STATE_SELECTED) {
916 	CGContextSetLineWidth(context, 1.5);
917 	CGContextBeginPath(context);
918 	CGPoint check[3] = {{x + 4, y + 8}, {x + 7, y + 11}, {x + 11, y + 4}};
919 	CGContextAddLines(context, check, 3);
920 	CGContextStrokePath(context);
921     } else if (state & TTK_STATE_ALTERNATE) {
922 	CGContextSetLineWidth(context, 2.0);
923 	CGContextBeginPath(context);
924 	CGPoint bar[2] = {{x + 4, y + 8}, {x + 12, y + 8}};
925 	CGContextAddLines(context, bar, 2);
926 	CGContextStrokePath(context);
927     }
928 }
929 
930 /*----------------------------------------------------------------------
931  * +++ DrawDarkRadioButton --
932  *
933  *    This is a standalone drawing procedure which draws RadioButtons
934  *    in the Dark Mode style.
935  */
936 
DrawDarkRadioButton(CGRect bounds,Ttk_State state,CGContextRef context)937 static void DrawDarkRadioButton(
938     CGRect bounds,
939     Ttk_State state,
940     CGContextRef context)
941 {
942     CGRect checkbounds = {{0, bounds.size.height / 2 - 9}, {18, 18}};
943     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
944     NSColor *fill;
945     CGFloat x, y;
946 
947     bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y);
948     x = bounds.origin.x;
949     y = bounds.origin.y;
950 
951     CGContextClipToRect(context, bounds);
952     FillButtonBackground(context, bounds, 9);
953     bounds = CGRectInset(bounds, 1, 1);
954     if (!(state & TTK_STATE_BACKGROUND) &&
955 	!(state & TTK_STATE_DISABLED) &&
956 	((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE))) {
957 	GradientFillRoundedRectangle(context, bounds, 8,
958 	    darkSelectedGradient, 2);
959     } else {
960 	GradientFillRoundedRectangle(context, bounds, 8,
961 	    darkInactiveGradient, 2);
962     }
963     HighlightButtonBorder(context, bounds);
964     if ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE)) {
965 	CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
966 	if (state & TTK_STATE_DISABLED) {
967 	    fill = [NSColor disabledControlTextColor];
968 	} else {
969 	    fill = [NSColor controlTextColor];
970 	}
971 	CGContextSetFillColorWithColor(context, CGCOLOR(fill));
972     }
973     if (state & TTK_STATE_SELECTED) {
974 	CGContextBeginPath(context);
975 	CGRect dot = {{x + 6, y + 6}, {6, 6}};
976 	CGContextAddEllipseInRect(context, dot);
977 	CGContextFillPath(context);
978     } else if (state & TTK_STATE_ALTERNATE) {
979 	CGRect bar = {{x + 5, y + 8}, {8, 2}};
980 	CGContextFillRect(context, bar);
981     }
982 }
983 
984 /*----------------------------------------------------------------------
985  * +++ DrawDarkTab --
986  *
987  *      This is a standalone drawing procedure which draws Tabbed Pane
988  *      Tabs in the Dark Mode style.
989  */
990 
DrawDarkTab(CGRect bounds,Ttk_State state,CGContextRef context)991 static void DrawDarkTab(
992     CGRect bounds,
993     Ttk_State state,
994     CGContextRef context)
995 {
996     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
997     NSColor *faceColor, *stroke;
998     CGRect originalBounds = bounds;
999 
1000     CGContextSetLineWidth(context, 1.0);
1001     CGContextClipToRect(context, bounds);
1002 
1003     /*
1004      * Extend the bounds to one or both sides so the rounded part will be
1005      * clipped off.
1006      */
1007 
1008     if (!(state & TTK_STATE_FIRST_TAB)) {
1009 	bounds.origin.x -= 10;
1010 	bounds.size.width += 10;
1011     }
1012 
1013     if (!(state & TTK_STATE_LAST_TAB)) {
1014 	bounds.size.width += 10;
1015     }
1016 
1017     /*
1018      * Fill the tab face with the appropriate color or gradient.  Use a solid
1019      * color if the tab is not selected, otherwise use a blue or gray
1020      * gradient.
1021      */
1022 
1023     bounds = CGRectInset(bounds, 1, 1);
1024     if (!(state & TTK_STATE_SELECTED)) {
1025 	if (state & TTK_STATE_DISABLED) {
1026 	    faceColor = [NSColor colorWithColorSpace: deviceRGB
1027 		components: darkDisabledButtonFace
1028 		count: 4];
1029 	} else {
1030 	    faceColor = [NSColor colorWithColorSpace: deviceRGB
1031 		components: darkButtonFace
1032 		count: 4];
1033 	}
1034 	SolidFillRoundedRectangle(context, bounds, 4, faceColor);
1035 
1036         /*
1037          * Draw a separator line on the left side of the tab if it
1038          * not first.
1039          */
1040 
1041 	if (!(state & TTK_STATE_FIRST_TAB)) {
1042 	    CGContextSaveGState(context);
1043 	    CGContextSetShouldAntialias(context, false);
1044 	    stroke = [NSColor colorWithColorSpace: deviceRGB
1045 		components: darkTabSeparator
1046 		count: 4];
1047 	    CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
1048 	    CGContextBeginPath(context);
1049 	    CGContextMoveToPoint(context, originalBounds.origin.x,
1050 		originalBounds.origin.y + 1);
1051 	    CGContextAddLineToPoint(context, originalBounds.origin.x,
1052 		originalBounds.origin.y + originalBounds.size.height - 1);
1053 	    CGContextStrokePath(context);
1054 	    CGContextRestoreGState(context);
1055 	}
1056     } else {
1057 
1058         /*
1059          * This is the selected tab; paint it blue.  If it is first, cover up
1060          * the separator line drawn by the second one.  (The selected tab is
1061          * always drawn last.)
1062          */
1063 
1064 	if ((state & TTK_STATE_FIRST_TAB) && !(state & TTK_STATE_LAST_TAB)) {
1065 	    bounds.size.width += 1;
1066 	}
1067 	if (!(state & TTK_STATE_BACKGROUND)) {
1068 	    GradientFillRoundedRectangle(context, bounds, 4,
1069 		darkSelectedGradient, 2);
1070 	} else {
1071 	    faceColor = [NSColor colorWithColorSpace: deviceRGB
1072 		components: darkInactiveSelectedTab
1073 		count: 4];
1074 	    SolidFillRoundedRectangle(context, bounds, 4, faceColor);
1075 	}
1076 	HighlightButtonBorder(context, bounds);
1077     }
1078 }
1079 
1080 /*----------------------------------------------------------------------
1081  * +++ DrawDarkSeparator --
1082  *
1083  *      This is a standalone drawing procedure which draws a separator widget
1084  *      in Dark Mode.
1085  */
1086 
DrawDarkSeparator(CGRect bounds,CGContextRef context,TCL_UNUSED (Tk_Window))1087 static void DrawDarkSeparator(
1088     CGRect bounds,
1089     CGContextRef context,
1090     TCL_UNUSED(Tk_Window))
1091 {
1092     static CGFloat fill[4] = {1.0, 1.0, 1.0, 0.3};
1093     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
1094     NSColor *fillColor = [NSColor colorWithColorSpace: deviceRGB
1095 	components: fill
1096 	count:4];
1097 
1098     CGContextSetFillColorWithColor(context, CGCOLOR(fillColor));
1099     CGContextFillRect(context, bounds);
1100 }
1101 
1102 /*----------------------------------------------------------------------
1103  * +++ DrawDarkFocusRing --
1104  *
1105  *      This is a standalone drawing procedure which draws a focus ring around
1106  *      an Entry widget in Dark Mode.
1107  */
1108 
DrawDarkFocusRing(CGRect bounds,CGContextRef context)1109 static void DrawDarkFocusRing(
1110     CGRect bounds,
1111     CGContextRef context)
1112 {
1113     CGRect insetBounds = CGRectInset(bounds, -3, -3);
1114     CHECK_RADIUS(4, insetBounds)
1115 
1116     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
1117     NSColor *strokeColor;
1118     NSColor *fillColor = [NSColor colorWithColorSpace:deviceRGB
1119 					   components:darkFocusRing
1120 						count:4];
1121     CGFloat x = bounds.origin.x, y = bounds.origin.y;
1122     CGFloat w = bounds.size.width, h = bounds.size.height;
1123     CGPoint topPart[4] = {
1124 	{x, y + h}, {x, y + 1}, {x + w - 1, y + 1}, {x + w - 1, y + h}
1125     };
1126     CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
1127 
1128     CGContextSaveGState(context);
1129     CGContextSetShouldAntialias(context, false);
1130     CGContextBeginPath(context);
1131     strokeColor = [NSColor colorWithColorSpace: deviceRGB
1132 				    components: darkFocusRingTop
1133 					 count: 4];
1134     CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
1135     CGContextAddLines(context, topPart, 4);
1136     CGContextStrokePath(context);
1137     strokeColor = [NSColor colorWithColorSpace: deviceRGB
1138 				    components: darkFocusRingBottom
1139 					 count: 4];
1140     CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
1141     CGContextAddLines(context, bottom, 2);
1142     CGContextStrokePath(context);
1143     CGContextSetShouldAntialias(context, true);
1144     CGContextSetFillColorWithColor(context, CGCOLOR(fillColor));
1145     CGPathRef path = CGPathCreateWithRoundedRect(insetBounds, 4, 4, NULL);
1146     CGContextBeginPath(context);
1147     CGContextAddPath(context, path);
1148     CGContextAddRect(context, bounds);
1149     CGContextEOFillPath(context);
1150     CGContextRestoreGState(context);
1151 }
1152 /*----------------------------------------------------------------------
1153  * +++ DrawDarkFrame --
1154  *
1155  *      This is a standalone drawing procedure which draws various
1156  *      types of borders in Dark Mode.
1157  */
1158 
DrawDarkFrame(CGRect bounds,CGContextRef context,HIThemeFrameKind kind)1159 static void DrawDarkFrame(
1160     CGRect bounds,
1161     CGContextRef context,
1162     HIThemeFrameKind kind)
1163 {
1164     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
1165     NSColor *stroke;
1166 
1167     CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
1168     CGFloat x = bounds.origin.x, y = bounds.origin.y;
1169     CGFloat w = bounds.size.width, h = bounds.size.height;
1170     CGPoint topPart[4] = {
1171 	{x, y + h - 1}, {x, y + 1}, {x + w, y + 1}, {x + w, y + h - 1}
1172     };
1173     CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
1174     CGPoint accent[2] = {{x, y + 1}, {x + w, y + 1}};
1175 
1176     switch (kind) {
1177     case kHIThemeFrameTextFieldSquare:
1178 	CGContextSaveGState(context);
1179 	CGContextSetShouldAntialias(context, false);
1180 	CGContextBeginPath(context);
1181 	stroke = [NSColor colorWithColorSpace: deviceRGB
1182 	    components: darkFrameTop
1183 	    count: 4];
1184 	CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
1185 	CGContextAddLines(context, topPart, 4);
1186 	CGContextStrokePath(context);
1187 	stroke = [NSColor colorWithColorSpace: deviceRGB
1188 	    components: darkFrameBottom
1189 	    count: 4];
1190 	CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
1191 	CGContextAddLines(context, bottom, 2);
1192 	CGContextStrokePath(context);
1193 	stroke = [NSColor colorWithColorSpace: deviceRGB
1194 	    components: darkFrameAccent
1195 	    count: 4];
1196 	CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
1197 	CGContextAddLines(context, accent, 2);
1198 	CGContextStrokePath(context);
1199 	CGContextRestoreGState(context);
1200 	break;
1201     default:
1202 	break;
1203     }
1204 }
1205 
1206 /*----------------------------------------------------------------------
1207  * +++ DrawListHeader --
1208  *
1209  *      This is a standalone drawing procedure which draws column
1210  *      headers for a Treeview in the Dark Mode.
1211  */
1212 
DrawDarkListHeader(CGRect bounds,CGContextRef context,TCL_UNUSED (Tk_Window),int state)1213 static void DrawDarkListHeader(
1214     CGRect bounds,
1215     CGContextRef context,
1216     TCL_UNUSED(Tk_Window),
1217     int state)
1218 {
1219     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
1220     NSColor *stroke;
1221 
1222     CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
1223     CGFloat x = bounds.origin.x, y = bounds.origin.y;
1224     CGFloat w = bounds.size.width, h = bounds.size.height;
1225 
1226     CGPoint top[2] = {{x, y + 1}, {x + w, y + 1}};
1227     CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
1228     CGPoint separator[2] = {{x + w - 1, y + 3}, {x + w - 1, y + h - 3}};
1229     CGContextSaveGState(context);
1230     CGContextSetShouldAntialias(context, false);
1231     stroke = [NSColor colorWithColorSpace: deviceRGB
1232 	components: darkFrameBottom
1233 	count: 4];
1234     CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
1235     CGContextBeginPath(context);
1236     CGContextAddLines(context, top, 2);
1237     CGContextStrokePath(context);
1238     CGContextAddLines(context, bottom, 2);
1239     CGContextStrokePath(context);
1240     CGContextAddLines(context, separator, 2);
1241     CGContextStrokePath(context);
1242     CGContextRestoreGState(context);
1243 
1244     if (state & TTK_TREEVIEW_STATE_SORTARROW) {
1245 	CGRect arrowBounds = bounds;
1246 
1247 	arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16;
1248 	arrowBounds.size.width = 16;
1249 	if (state & TTK_STATE_ALTERNATE) {
1250 	    DrawUpArrow(context, arrowBounds, 3, 8, WHITERGBA);
1251 	} else if (state & TTK_STATE_SELECTED) {
1252 	    DrawDownArrow(context, arrowBounds, 3, 8, WHITERGBA);
1253 	}
1254     }
1255 }
1256 
1257 /*----------------------------------------------------------------------
1258  * +++ Button element: Used for elements drawn with DrawThemeButton.
1259  */
1260 
1261 /*
1262  * When Ttk draws the various types of buttons, a pointer to one of these
1263  * is passed as the clientData.
1264  */
1265 
1266 typedef struct {
1267     ThemeButtonKind kind;
1268     ThemeMetric heightMetric;
1269 } ThemeButtonParams;
1270 static ThemeButtonParams
1271     PushButtonParams =  {kThemePushButton, kThemeMetricPushButtonHeight},
1272     CheckBoxParams =    {kThemeCheckBox, kThemeMetricCheckBoxHeight},
1273     RadioButtonParams = {kThemeRadioButton, kThemeMetricRadioButtonHeight},
1274     BevelButtonParams = {kThemeRoundedBevelButton, NoThemeMetric},
1275     PopupButtonParams = {kThemePopupButton, kThemeMetricPopupButtonHeight},
1276     DisclosureParams =  {
1277     kThemeDisclosureButton, kThemeMetricDisclosureTriangleHeight
1278 },
1279     ListHeaderParams =
1280 {kThemeListHeaderButton, kThemeMetricListHeaderHeight};
1281 static Ttk_StateTable ButtonValueTable[] = {
1282     {kThemeButtonOff, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
1283     {kThemeButtonMixed, TTK_STATE_ALTERNATE, 0},
1284     {kThemeButtonOn, TTK_STATE_SELECTED, 0},
1285     {kThemeButtonOff, 0, 0}
1286 
1287     /*
1288      * Others: kThemeDisclosureRight, kThemeDisclosureDown,
1289      * kThemeDisclosureLeft
1290      */
1291 
1292 };
1293 static Ttk_StateTable ButtonAdornmentTable[] = {
1294     {kThemeAdornmentNone, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
1295     {kThemeAdornmentDefault | kThemeAdornmentFocus,
1296      TTK_STATE_ALTERNATE | TTK_STATE_FOCUS, 0},
1297     {kThemeAdornmentFocus, TTK_STATE_FOCUS, 0},
1298     {kThemeAdornmentDefault, TTK_STATE_ALTERNATE, 0},
1299     {kThemeAdornmentNone, 0, 0}
1300 };
1301 
1302 /*----------------------------------------------------------------------
1303  * +++ computeButtonDrawInfo --
1304  *
1305  *      Fill in an appearance manager HIThemeButtonDrawInfo record.
1306  */
1307 
computeButtonDrawInfo(ThemeButtonParams * params,Ttk_State state,TCL_UNUSED (Tk_Window))1308 static inline HIThemeButtonDrawInfo computeButtonDrawInfo(
1309     ThemeButtonParams *params,
1310     Ttk_State state,
1311     TCL_UNUSED(Tk_Window))
1312 {
1313 
1314     /*
1315      * See ButtonElementDraw for the explanation of why we always draw
1316      * PushButtons in the active state.
1317      */
1318 
1319     SInt32 HIThemeState;
1320 
1321     HIThemeState = Ttk_StateTableLookup(ThemeStateTable, state);
1322     switch (params->kind) {
1323     case kThemePushButton:
1324 	HIThemeState &= ~kThemeStateInactive;
1325 	HIThemeState |= kThemeStateActive;
1326 	break;
1327     default:
1328 	break;
1329     }
1330 
1331     const HIThemeButtonDrawInfo info = {
1332 	.version = 0,
1333 	.state = HIThemeState,
1334 	.kind = params ? params->kind : 0,
1335 	.value = Ttk_StateTableLookup(ButtonValueTable, state),
1336 	.adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state),
1337     };
1338     return info;
1339 }
1340 
1341 /*----------------------------------------------------------------------
1342  * +++ Button elements.
1343  */
1344 
ButtonElementMinSize(void * clientData,TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))1345 static void ButtonElementMinSize(
1346     void *clientData,
1347     TCL_UNUSED(void *),
1348     TCL_UNUSED(Tk_Window),
1349     int *minWidth,
1350     int *minHeight,
1351     TCL_UNUSED(Ttk_Padding *))
1352 {
1353     ThemeButtonParams *params = (ThemeButtonParams *)clientData;
1354 
1355     if (params->heightMetric != NoThemeMetric) {
1356 	ChkErr(GetThemeMetric, params->heightMetric, (SInt *) minHeight);
1357 
1358         /*
1359          * The theme height does not include the 1-pixel border around
1360          * the button, although it does include the 1-pixel shadow at
1361          * the bottom.
1362          */
1363 
1364 	*minHeight += 2;
1365 
1366         /*
1367          * The minwidth must be 0 to force the generic ttk code to compute the
1368          * correct text layout.  For example, a non-zero value will cause the
1369          * text to be left justified, no matter what -anchor setting is used in
1370          * the style.
1371          */
1372 
1373 	*minWidth = 0;
1374     }
1375 }
1376 
ButtonElementSize(void * clientData,void * elementRecord,Tk_Window tkwin,int * minWidth,int * minHeight,Ttk_Padding * paddingPtr)1377 static void ButtonElementSize(
1378     void *clientData,
1379     void *elementRecord,
1380     Tk_Window tkwin,
1381     int *minWidth,
1382     int *minHeight,
1383     Ttk_Padding *paddingPtr)
1384 {
1385     ThemeButtonParams *params = (ThemeButtonParams *)clientData;
1386     const HIThemeButtonDrawInfo info =
1387 	computeButtonDrawInfo(params, 0, tkwin);
1388     static const CGRect scratchBounds = {{0, 0}, {100, 100}};
1389     CGRect contentBounds, backgroundBounds;
1390     int verticalPad;
1391 
1392     ButtonElementMinSize(clientData, elementRecord, tkwin,
1393 	minWidth, minHeight, paddingPtr);
1394 
1395     /*
1396      * Given a hypothetical bounding rectangle for a button, HIToolbox will
1397      * compute a bounding rectangle for the button contents and a bounding
1398      * rectangle for the button background.  The background bounds are large
1399      * enough to contain the image of the button in any state, which might
1400      * include highlight borders, shadows, etc.  The content rectangle is not
1401      * centered vertically within the background rectangle, presumably because
1402      * shadows only appear on the bottom.  Nonetheless, when HITools is asked
1403      * to draw a button with a certain bounding rectangle it draws the button
1404      * centered within the rectangle.
1405      *
1406      * To compute the effective padding around a button we request the
1407      * content and bounding rectangles for a 100x100 button and use the
1408      * padding between those.  However, we symmetrize the padding on the
1409      * top and bottom, because that is how the button will be drawn.
1410      */
1411 
1412     ChkErr(HIThemeGetButtonContentBounds,
1413 	&scratchBounds, &info, &contentBounds);
1414     ChkErr(HIThemeGetButtonBackgroundBounds,
1415 	&scratchBounds, &info, &backgroundBounds);
1416     paddingPtr->left = contentBounds.origin.x - backgroundBounds.origin.x;
1417     paddingPtr->right =
1418 	CGRectGetMaxX(backgroundBounds) - CGRectGetMaxX(contentBounds);
1419     verticalPad = backgroundBounds.size.height - contentBounds.size.height;
1420     paddingPtr->top = paddingPtr->bottom = verticalPad / 2;
1421 }
1422 
ButtonElementDraw(void * clientData,TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)1423 static void ButtonElementDraw(
1424     void *clientData,
1425     TCL_UNUSED(void *),
1426     Tk_Window tkwin,
1427     Drawable d,
1428     Ttk_Box b,
1429     Ttk_State state)
1430 {
1431     ThemeButtonParams *params = (ThemeButtonParams *)clientData;
1432     CGRect bounds = BoxToRect(d, b);
1433     HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, state, tkwin);
1434 
1435     bounds = NormalizeButtonBounds(params->heightMetric, bounds);
1436 
1437     BEGIN_DRAWING(d)
1438     if (TkMacOSXInDarkMode(tkwin)) {
1439 	switch (info.kind) {
1440 	case kThemePushButton:
1441 	case kThemePopupButton:
1442 	    DrawDarkButton(bounds, info.kind, state, dc.context);
1443 	    break;
1444 	case kThemeCheckBox:
1445 	    DrawDarkCheckBox(bounds, state, dc.context);
1446 	    break;
1447 	case kThemeRadioButton:
1448 	    DrawDarkRadioButton(bounds, state, dc.context);
1449 	    break;
1450 	case kThemeRoundedBevelButton:
1451 	    DrawDarkBevelButton(bounds, state, dc.context);
1452 	    break;
1453 	default:
1454 	    ChkErr(HIThemeDrawButton, &bounds, &info, dc.context,
1455 		HIOrientation, NULL);
1456 	}
1457     } else if (info.kind == kThemePushButton &&
1458 	       (state & TTK_STATE_PRESSED)) {
1459 	bounds.size.height += 2;
1460 	if ([NSApp macOSVersion] > 100800) {
1461 	    GradientFillRoundedRectangle(dc.context, bounds, 4,
1462 					 pressedPushButtonGradient, 2);
1463 	}
1464     } else {
1465 
1466         /*
1467          * Apple's PushButton and PopupButton do not change their fill color
1468          * when the window is inactive.  However, except in 10.7 (Lion), the
1469          * color of the arrow button on a PopupButton does change.  For some
1470          * reason HITheme fills inactive buttons with a transparent color that
1471          * allows the window background to show through, leading to
1472          * inconsistent behavior.  We work around this by filling behind an
1473          * inactive PopupButton with a text background color before asking
1474          * HIToolbox to draw it. For PushButtons, we simply draw them in the
1475          * active state.
1476          */
1477 
1478 	if (info.kind == kThemePopupButton &&
1479 	    (state & TTK_STATE_BACKGROUND)) {
1480 	    CGRect innerBounds = CGRectInset(bounds, 1, 1);
1481 	    NSColor *whiteRGBA = [NSColor whiteColor];
1482 	    SolidFillRoundedRectangle(dc.context, innerBounds, 4, whiteRGBA);
1483 	}
1484 
1485         /*
1486          * A BevelButton with mixed value is drawn borderless, which does make
1487          * much sense for us.
1488          */
1489 
1490 	if (info.kind == kThemeRoundedBevelButton &&
1491 	    info.value == kThemeButtonMixed) {
1492 	    info.value = kThemeButtonOff;
1493 	    info.state = kThemeStateInactive;
1494 	}
1495 	ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
1496 	    NULL);
1497     }
1498     END_DRAWING
1499 }
1500 
1501 static Ttk_ElementSpec ButtonElementSpec = {
1502     TK_STYLE_VERSION_2,
1503     sizeof(NullElement),
1504     TtkNullElementOptions,
1505     ButtonElementSize,
1506     ButtonElementDraw
1507 };
1508 
1509 /*----------------------------------------------------------------------
1510  * +++ Notebook elements.
1511  */
1512 
1513 /* Tab position logic, c.f. ttkNotebook.c TabState() */
1514 static Ttk_StateTable TabStyleTable[] = {
1515     {kThemeTabFrontInactive, TTK_STATE_SELECTED | TTK_STATE_BACKGROUND, 0},
1516     {kThemeTabNonFrontInactive, TTK_STATE_BACKGROUND, 0},
1517     {kThemeTabFrontUnavailable, TTK_STATE_DISABLED | TTK_STATE_SELECTED, 0},
1518     {kThemeTabNonFrontUnavailable, TTK_STATE_DISABLED, 0},
1519     {kThemeTabFront, TTK_STATE_SELECTED, 0},
1520     {kThemeTabNonFrontPressed, TTK_STATE_PRESSED, 0},
1521     {kThemeTabNonFront, 0, 0}
1522 };
1523 static Ttk_StateTable TabAdornmentTable[] = {
1524     {kHIThemeTabAdornmentNone, TTK_STATE_FIRST_TAB | TTK_STATE_LAST_TAB, 0},
1525     {kHIThemeTabAdornmentTrailingSeparator, TTK_STATE_FIRST_TAB, 0},
1526     {kHIThemeTabAdornmentNone, TTK_STATE_LAST_TAB, 0},
1527     {kHIThemeTabAdornmentTrailingSeparator, 0, 0},
1528 };
1529 static Ttk_StateTable TabPositionTable[] = {
1530     {kHIThemeTabPositionOnly, TTK_STATE_FIRST_TAB | TTK_STATE_LAST_TAB, 0},
1531     {kHIThemeTabPositionFirst, TTK_STATE_FIRST_TAB, 0},
1532     {kHIThemeTabPositionLast, TTK_STATE_LAST_TAB, 0},
1533     {kHIThemeTabPositionMiddle, 0, 0},
1534 };
1535 
1536 /*
1537  * Apple XHIG Tab View Specifications:
1538  *
1539  * Control sizes: Tab views are available in regular, small, and mini sizes.
1540  * The tab height is fixed for each size, but you control the size of the pane
1541  * area. The tab heights for each size are listed below:
1542  *  - Regular size: 20 pixels.
1543  *  - Small: 17 pixels.
1544  *  - Mini: 15 pixels.
1545  *
1546  * Label spacing and fonts: The tab labels should be in a font that’s
1547  * proportional to the size of the tab view control. In addition, the label
1548  * should be placed so that there are equal margins of space before and after
1549  * it. The guidelines below provide the specifications you should use for tab
1550  * labels:
1551  *  - Regular size: System font. Center in tab, leaving 12 pixels on each
1552  *side.
1553  *  - Small: Small system font. Center in tab, leaving 10 pixels on each side.
1554  *  - Mini: Mini system font. Center in tab, leaving 8 pixels on each side.
1555  *
1556  * Control spacing: Whether you decide to inset a tab view in a window or
1557  * extend its edges to the window sides and bottom, you should place the top
1558  * edge of the tab view 12 or 14 pixels below the bottom edge of the title bar
1559  * (or toolbar, if there is one). If you choose to inset a tab view in a
1560  * window, you should leave a margin of 20 pixels between the sides and bottom
1561  * of the tab view and the sides and bottom of the window (although 16 pixels
1562  * is also an acceptable margin-width). If you need to provide controls below
1563  * the tab view, leave enough space below the tab view so the controls are 20
1564  * pixels above the bottom edge of the window and 12 pixels between the tab
1565  * view and the controls.
1566  *
1567  * If you choose to extend the tab view sides and bottom so that they meet the
1568  * window sides and bottom, you should leave a margin of at least 20 pixels
1569  * between the content in the tab view and the tab-view edges.
1570  *
1571  * <URL: http://developer.apple.com/documentation/userexperience/Conceptual/
1572  *       AppleHIGuidelines/XHIGControls/XHIGControls.html#//apple_ref/doc/uid/
1573  *       TP30000359-TPXREF116>
1574  */
1575 
TabElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),TCL_UNUSED (int *),int * minHeight,Ttk_Padding * paddingPtr)1576 static void TabElementSize(
1577     TCL_UNUSED(void *),
1578     TCL_UNUSED(void *),
1579     TCL_UNUSED(Tk_Window),
1580     TCL_UNUSED(int *),
1581     int *minHeight,
1582     Ttk_Padding *paddingPtr)
1583 {
1584     GetThemeMetric(kThemeMetricLargeTabHeight, (SInt32 *) minHeight);
1585     *paddingPtr = Ttk_MakePadding(0, 0, 0, 2);
1586 
1587 }
1588 
TabElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)1589 static void TabElementDraw(
1590     TCL_UNUSED(void *),
1591     TCL_UNUSED(void *),
1592     Tk_Window tkwin,
1593     Drawable d,
1594     Ttk_Box b,
1595     Ttk_State state)
1596 {
1597     CGRect bounds = BoxToRect(d, b);
1598     HIThemeTabDrawInfo info = {
1599 	.version = 1,
1600 	.style = Ttk_StateTableLookup(TabStyleTable, state),
1601 	.direction = kThemeTabNorth,
1602 	.size = kHIThemeTabSizeNormal,
1603 	.adornment = Ttk_StateTableLookup(TabAdornmentTable, state),
1604 	.kind = kHIThemeTabKindNormal,
1605 	.position = Ttk_StateTableLookup(TabPositionTable, state),
1606     };
1607 
1608     BEGIN_DRAWING(d)
1609     if (TkMacOSXInDarkMode(tkwin)) {
1610 	DrawDarkTab(bounds, state, dc.context);
1611     } else {
1612 	ChkErr(HIThemeDrawTab, &bounds, &info, dc.context, HIOrientation,
1613 	    NULL);
1614     }
1615     END_DRAWING
1616 }
1617 
1618 static Ttk_ElementSpec TabElementSpec = {
1619     TK_STYLE_VERSION_2,
1620     sizeof(NullElement),
1621     TtkNullElementOptions,
1622     TabElementSize,
1623     TabElementDraw
1624 };
1625 
1626 /*
1627  * Notebook panes:
1628  */
1629 
PaneElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),TCL_UNUSED (int *),TCL_UNUSED (int *),Ttk_Padding * paddingPtr)1630 static void PaneElementSize(
1631     TCL_UNUSED(void *),
1632     TCL_UNUSED(void *),
1633     TCL_UNUSED(Tk_Window),
1634     TCL_UNUSED(int *),
1635     TCL_UNUSED(int *),
1636     Ttk_Padding *paddingPtr)
1637 {
1638     *paddingPtr = Ttk_MakePadding(9, 5, 9, 9);
1639 }
1640 
PaneElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)1641 static void PaneElementDraw(
1642     TCL_UNUSED(void *),
1643     TCL_UNUSED(void *),
1644     Tk_Window tkwin,
1645     Drawable d,
1646     Ttk_Box b,
1647     Ttk_State state)
1648 {
1649     CGRect bounds = BoxToRect(d, b);
1650 
1651     bounds.origin.y -= kThemeMetricTabFrameOverlap;
1652     bounds.size.height += kThemeMetricTabFrameOverlap;
1653     BEGIN_DRAWING(d)
1654     if ([NSApp macOSVersion] > 100800) {
1655 	DrawGroupBox(bounds, dc.context, tkwin);
1656     } else {
1657 	HIThemeTabPaneDrawInfo info = {
1658 	    .version = 1,
1659 	    .state = Ttk_StateTableLookup(ThemeStateTable, state),
1660 	    .direction = kThemeTabNorth,
1661 	    .size = kHIThemeTabSizeNormal,
1662 	    .kind = kHIThemeTabKindNormal,
1663 	    .adornment = kHIThemeTabPaneAdornmentNormal,
1664 	    };
1665 	bounds.origin.y -= kThemeMetricTabFrameOverlap;
1666 	bounds.size.height += kThemeMetricTabFrameOverlap;
1667 	ChkErr(HIThemeDrawTabPane, &bounds, &info, dc.context, HIOrientation);
1668     }
1669     END_DRAWING
1670 }
1671 
1672 static Ttk_ElementSpec PaneElementSpec = {
1673     TK_STYLE_VERSION_2,
1674     sizeof(NullElement),
1675     TtkNullElementOptions,
1676     PaneElementSize,
1677     PaneElementDraw
1678 };
1679 
1680 /*----------------------------------------------------------------------
1681  * +++ Labelframe elements --
1682  *
1683  * Labelframe borders: Use "primary group box ..."  Quoth
1684  * DrawThemePrimaryGroup reference: "The primary group box frame is drawn
1685  * inside the specified rectangle and is a maximum of 2 pixels thick."
1686  *
1687  * "Maximum of 2 pixels thick" is apparently a lie; looks more like 4 to me
1688  * with shading.
1689  */
1690 
GroupElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),TCL_UNUSED (int *),TCL_UNUSED (int *),Ttk_Padding * paddingPtr)1691 static void GroupElementSize(
1692     TCL_UNUSED(void *),
1693     TCL_UNUSED(void *),
1694     TCL_UNUSED(Tk_Window),
1695     TCL_UNUSED(int *),
1696     TCL_UNUSED(int *),
1697     Ttk_Padding *paddingPtr)
1698 {
1699     *paddingPtr = Ttk_UniformPadding(4);
1700 }
1701 
GroupElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)1702 static void GroupElementDraw(
1703     TCL_UNUSED(void *),
1704     TCL_UNUSED(void *),
1705     Tk_Window tkwin,
1706     Drawable d,
1707     Ttk_Box b,
1708     Ttk_State state)
1709 {
1710     CGRect bounds = BoxToRect(d, b);
1711 
1712     BEGIN_DRAWING(d)
1713     if ([NSApp macOSVersion] > 100800) {
1714 	DrawGroupBox(bounds, dc.context, tkwin);
1715     } else {
1716 	const HIThemeGroupBoxDrawInfo info = {
1717 	    .version = 0,
1718 	    .state = Ttk_StateTableLookup(ThemeStateTable, state),
1719 	    .kind = kHIThemeGroupBoxKindPrimaryOpaque,
1720 	    };
1721 	ChkErr(HIThemeDrawGroupBox, &bounds, &info, dc.context, HIOrientation);
1722     }
1723     END_DRAWING
1724 }
1725 
1726 static Ttk_ElementSpec GroupElementSpec = {
1727     TK_STYLE_VERSION_2,
1728     sizeof(NullElement),
1729     TtkNullElementOptions,
1730     GroupElementSize,
1731     GroupElementDraw
1732 };
1733 
1734 /*----------------------------------------------------------------------
1735  * +++ Entry elements --
1736  *
1737  *    3 pixels padding for focus rectangle
1738  *    2 pixels padding for EditTextFrame
1739  */
1740 
1741 typedef struct {
1742     Tcl_Obj     *backgroundObj;
1743     Tcl_Obj     *fieldbackgroundObj;
1744 } EntryElement;
1745 
1746 #define ENTRY_DEFAULT_BACKGROUND "systemTextBackgroundColor"
1747 
1748 static Ttk_ElementOptionSpec EntryElementOptions[] = {
1749     {"-background", TK_OPTION_BORDER,
1750      offsetof(EntryElement, backgroundObj), ENTRY_DEFAULT_BACKGROUND},
1751     {"-fieldbackground", TK_OPTION_BORDER,
1752      offsetof(EntryElement, fieldbackgroundObj), ENTRY_DEFAULT_BACKGROUND},
1753     {NULL, TK_OPTION_BOOLEAN, 0, NULL}
1754 };
1755 
EntryElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),TCL_UNUSED (int *),TCL_UNUSED (int *),Ttk_Padding * paddingPtr)1756 static void EntryElementSize(
1757     TCL_UNUSED(void *),
1758     TCL_UNUSED(void *),
1759     TCL_UNUSED(Tk_Window),
1760     TCL_UNUSED(int *),
1761     TCL_UNUSED(int *),
1762     Ttk_Padding *paddingPtr)
1763 {
1764     *paddingPtr = Ttk_MakePadding(7, 5, 7, 6);
1765 }
1766 
EntryElementDraw(TCL_UNUSED (void *),void * elementRecord,Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)1767 static void EntryElementDraw(
1768     TCL_UNUSED(void *),
1769     void *elementRecord,
1770     Tk_Window tkwin,
1771     Drawable d,
1772     Ttk_Box b,
1773     Ttk_State state)
1774 {
1775     EntryElement *e = (EntryElement *)elementRecord;
1776     Ttk_Box inner = Ttk_PadBox(b, Ttk_UniformPadding(3));
1777     CGRect bounds = BoxToRect(d, inner);
1778     NSColor *background;
1779     Tk_3DBorder backgroundPtr = NULL;
1780     static const char *defaultBG = ENTRY_DEFAULT_BACKGROUND;
1781 
1782     if (TkMacOSXInDarkMode(tkwin)) {
1783 	BEGIN_DRAWING(d)
1784 	NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
1785 	CGFloat fill[4];
1786 	GetBackgroundColor(dc.context, tkwin, 1, fill);
1787 
1788 	/*
1789 	 * Lighten the background to provide contrast.
1790 	 */
1791 
1792 	for (int i = 0; i < 3; i++) {
1793 		fill[i] += 9.0 / 255.0;
1794 	    }
1795 	background = [NSColor colorWithColorSpace: deviceRGB
1796 	    components: fill
1797 	    count: 4];
1798 	CGContextSetFillColorWithColor(dc.context, CGCOLOR(background));
1799 	CGContextFillRect(dc.context, bounds);
1800 	if (state & TTK_STATE_FOCUS) {
1801 	    DrawDarkFocusRing(bounds, dc.context);
1802 	} else {
1803 	    DrawDarkFrame(bounds, dc.context, kHIThemeFrameTextFieldSquare);
1804 	}
1805 	END_DRAWING
1806     } else {
1807 	const HIThemeFrameDrawInfo info = {
1808 	    .version = 0,
1809 	    .kind = kHIThemeFrameTextFieldSquare,
1810 	    .state = Ttk_StateTableLookup(ThemeStateTable, state),
1811 	    .isFocused = state & TTK_STATE_FOCUS,
1812 	};
1813 
1814         /*
1815          * Earlier versions of the Aqua theme ignored the -fieldbackground
1816          * option and used the -background as if it were -fieldbackground.
1817          * Here we are enabling -fieldbackground.  For backwards
1818          * compatibility, if -fieldbackground is set to the default color and
1819          * -background is set to a different color then we use -background as
1820          * -fieldbackground.
1821          */
1822 
1823 	if (0 != strcmp(Tcl_GetString(e->fieldbackgroundObj), defaultBG)) {
1824 	    backgroundPtr =
1825 		Tk_Get3DBorderFromObj(tkwin, e->fieldbackgroundObj);
1826 	} else if (0 != strcmp(Tcl_GetString(e->backgroundObj), defaultBG)) {
1827 	    backgroundPtr = Tk_Get3DBorderFromObj(tkwin, e->backgroundObj);
1828 	}
1829 	if (backgroundPtr != NULL) {
1830 	    XFillRectangle(Tk_Display(tkwin), d,
1831 		Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC),
1832 		inner.x, inner.y, inner.width, inner.height);
1833 	}
1834 	BEGIN_DRAWING(d)
1835 	if (backgroundPtr == NULL) {
1836 	    if ([NSApp macOSVersion] > 100800) {
1837 		background = [NSColor textBackgroundColor];
1838 		CGContextSetFillColorWithColor(dc.context, CGCOLOR(background));
1839 	    } else {
1840 		CGContextSetRGBFillColor(dc.context, 1.0, 1.0, 1.0, 1.0);
1841 	    }
1842 	    CGContextFillRect(dc.context, bounds);
1843 	}
1844 	ChkErr(HIThemeDrawFrame, &bounds, &info, dc.context, HIOrientation);
1845 	END_DRAWING
1846     }
1847 }
1848 
1849 static Ttk_ElementSpec EntryElementSpec = {
1850     TK_STYLE_VERSION_2,
1851     sizeof(EntryElement),
1852     EntryElementOptions,
1853     EntryElementSize,
1854     EntryElementDraw
1855 };
1856 
1857 /*----------------------------------------------------------------------
1858  * +++ Combobox elements --
1859  *
1860  * NOTES:
1861  *      The HIToolbox has incomplete and inconsistent support for ComboBoxes.
1862  *      There is no constant available to get the height of a ComboBox with
1863  *      GetThemeMetric. In fact, ComboBoxes are the same (fixed) height as
1864  *      PopupButtons and PushButtons, but they have no shadow at the bottom.
1865  *      As a result, they are drawn 1 pixel above the center of the bounds
1866  *      rectangle rather than being centered like the other buttons.  One can
1867  *      request background bounds for a ComboBox, and it is reported with
1868  *      height 23, while the actual button face, including its 1-pixel border
1869  *      has height 21. Attempting to request the content bounds returns a 0x0
1870  *      rectangle.  Measurement indicates that the arrow button has width 18.
1871  *
1872  *      With no help available from HIToolbox, we have to use hard-wired
1873  *      constants for the padding. We shift the bounding rectangle downward by
1874  *      1 pixel to account for the fact that the button is not centered.
1875  */
1876 
1877 static Ttk_Padding ComboboxPadding = {4, 4, 20, 4};
1878 static Ttk_Padding DarkComboboxPadding = {6, 6, 22, 6};
1879 
ComboboxElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,int * minWidth,int * minHeight,Ttk_Padding * paddingPtr)1880 static void ComboboxElementSize(
1881     TCL_UNUSED(void *),
1882     TCL_UNUSED(void *),
1883     Tk_Window tkwin,
1884     int *minWidth,
1885     int *minHeight,
1886     Ttk_Padding *paddingPtr)
1887 {
1888     *minWidth = 24;
1889     *minHeight = 23;
1890     if (TkMacOSXInDarkMode(tkwin)) {
1891 	*paddingPtr = DarkComboboxPadding;
1892     } else {
1893 	*paddingPtr = ComboboxPadding;
1894     }
1895 }
1896 
ComboboxElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)1897 static void ComboboxElementDraw(
1898     TCL_UNUSED(void *),
1899     TCL_UNUSED(void *),
1900     Tk_Window tkwin,
1901     Drawable d,
1902     Ttk_Box b,
1903     Ttk_State state)
1904 {
1905     CGRect bounds = BoxToRect(d, b);
1906     const HIThemeButtonDrawInfo info = {
1907 	.version = 0,
1908 	.state = Ttk_StateTableLookup(ThemeStateTable, state),
1909 	.kind = kThemeComboBox,
1910 	.value = Ttk_StateTableLookup(ButtonValueTable, state),
1911 	.adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state),
1912     };
1913 
1914     BEGIN_DRAWING(d)
1915     if (TkMacOSXInDarkMode(tkwin)) {
1916 	bounds = CGRectInset(bounds, 3, 3);
1917 	if (state & TTK_STATE_FOCUS) {
1918 	    DrawDarkFocusRing(bounds, dc.context);
1919 	}
1920 	DrawDarkButton(bounds, info.kind, state, dc.context);
1921     } else {
1922 	if ([NSApp macOSVersion] > 100800) {
1923 	    if ((state & TTK_STATE_BACKGROUND) &&
1924 		!(state & TTK_STATE_DISABLED)) {
1925 		NSColor *background = [NSColor textBackgroundColor];
1926 		CGRect innerBounds = CGRectInset(bounds, 1, 4);
1927 		bounds.origin.y += 1;
1928 		SolidFillRoundedRectangle(dc.context, innerBounds, 4, background);
1929 	    }
1930 	}
1931 	ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL);
1932     }
1933     END_DRAWING
1934 }
1935 
1936 static Ttk_ElementSpec ComboboxElementSpec = {
1937     TK_STYLE_VERSION_2,
1938     sizeof(NullElement),
1939     TtkNullElementOptions,
1940     ComboboxElementSize,
1941     ComboboxElementDraw
1942 };
1943 
1944 /*----------------------------------------------------------------------
1945  * +++ Spinbutton elements --
1946  *
1947  *      From Apple HIG, part III, section "Controls", "The Stepper Control":
1948  *      there should be 2 pixels of space between the stepper control (AKA
1949  *      IncDecButton, AKA "little arrows") and the text field it modifies.
1950  *
1951  *      Ttk expects the up and down arrows to be distinct elements but
1952  *      HIToolbox draws them as one widget with two different pressed states.
1953  *      We work around this by defining them as separate elements in the
1954  *      layout, but making each one have a drawing method which also draws the
1955  *      other one.  The down button does no drawing when not pressed, and when
1956  *      pressed draws the entire IncDecButton in its "pressed down" state.
1957  *      The up button draws the entire IncDecButton when not pressed and when
1958  *      pressed draws the IncDecButton in its "pressed up" state.  NOTE: This
1959  *      means that when the down button is pressed the IncDecButton will be
1960  *      drawn twice, first in unpressed state by the up arrow and then in
1961  *      "pressed down" state by the down button.  The drawing must be done in
1962  *      that order.  So the up button must be listed first in the layout.
1963  */
1964 
1965 static Ttk_Padding SpinbuttonMargins = {0, 0, 2, 0};
1966 
SpinButtonUpElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))1967 static void SpinButtonUpElementSize(
1968     TCL_UNUSED(void *),
1969     TCL_UNUSED(void *),
1970     TCL_UNUSED(Tk_Window),
1971     int *minWidth,
1972     int *minHeight,
1973     TCL_UNUSED(Ttk_Padding *))
1974 {
1975     SInt32 s;
1976 
1977     ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s);
1978     *minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins);
1979     ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s);
1980     *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2;
1981 }
1982 
SpinButtonUpElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)1983 static void SpinButtonUpElementDraw(
1984     TCL_UNUSED(void *),
1985     TCL_UNUSED(void *),
1986     Tk_Window tkwin,
1987     Drawable d,
1988     Ttk_Box b,
1989     Ttk_State state)
1990 {
1991     CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins));
1992     int infoState;
1993 
1994     bounds.size.height *= 2;
1995     if (state & TTK_STATE_PRESSED) {
1996 	infoState = kThemeStatePressedUp;
1997     } else {
1998 	infoState = Ttk_StateTableLookup(ThemeStateTable, state);
1999     }
2000     const HIThemeButtonDrawInfo info = {
2001 	.version = 0,
2002 	.state = infoState,
2003 	.kind = kThemeIncDecButton,
2004 	.value = Ttk_StateTableLookup(ButtonValueTable, state),
2005 	.adornment = kThemeAdornmentNone,
2006     };
2007     BEGIN_DRAWING(d)
2008     if (TkMacOSXInDarkMode(tkwin)) {
2009 	DrawDarkIncDecButton(bounds, infoState, state, dc.context);
2010     } else {
2011 	ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
2012 	       NULL);
2013     }
2014     END_DRAWING
2015 }
2016 
2017 static Ttk_ElementSpec SpinButtonUpElementSpec = {
2018     TK_STYLE_VERSION_2,
2019     sizeof(NullElement),
2020     TtkNullElementOptions,
2021     SpinButtonUpElementSize,
2022     SpinButtonUpElementDraw
2023 };
SpinButtonDownElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))2024 static void SpinButtonDownElementSize(
2025     TCL_UNUSED(void *),
2026     TCL_UNUSED(void *),
2027     TCL_UNUSED(Tk_Window),
2028     int *minWidth,
2029     int *minHeight,
2030     TCL_UNUSED(Ttk_Padding *))
2031 {
2032     SInt32 s;
2033 
2034     ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s);
2035     *minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins);
2036     ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s);
2037     *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2;
2038 }
2039 
SpinButtonDownElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)2040 static void SpinButtonDownElementDraw(
2041     TCL_UNUSED(void *),
2042     TCL_UNUSED(void *),
2043     Tk_Window tkwin,
2044     Drawable d,
2045     Ttk_Box b,
2046     Ttk_State state)
2047 {
2048     CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins));
2049     int infoState = 0;
2050 
2051     bounds.origin.y -= bounds.size.height;
2052     bounds.size.height *= 2;
2053     if (state & TTK_STATE_PRESSED) {
2054 	infoState = kThemeStatePressedDown;
2055     } else {
2056 	return;
2057     }
2058     const HIThemeButtonDrawInfo info = {
2059 	.version = 0,
2060 	.state = infoState,
2061 	.kind = kThemeIncDecButton,
2062 	.value = Ttk_StateTableLookup(ButtonValueTable, state),
2063 	.adornment = kThemeAdornmentNone,
2064     };
2065 
2066     BEGIN_DRAWING(d)
2067     if (TkMacOSXInDarkMode(tkwin)) {
2068 	DrawDarkIncDecButton(bounds, infoState, state, dc.context);
2069     } else {
2070 	ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
2071 	       NULL);
2072     }
2073     END_DRAWING
2074 }
2075 
2076 static Ttk_ElementSpec SpinButtonDownElementSpec = {
2077     TK_STYLE_VERSION_2,
2078     sizeof(NullElement),
2079     TtkNullElementOptions,
2080     SpinButtonDownElementSize,
2081     SpinButtonDownElementDraw
2082 };
2083 
2084 /*----------------------------------------------------------------------
2085  * +++ DrawThemeTrack-based elements --
2086  *
2087  *    Progress bars and scales. (See also: <<NOTE-TRACKS>>)
2088  */
2089 
2090 /*
2091  * Apple does not change the appearance of a slider when the window becomes
2092  * inactive.  So we shouldn't either.
2093  */
2094 
2095 static Ttk_StateTable ThemeTrackEnableTable[] = {
2096     {kThemeTrackDisabled, TTK_STATE_DISABLED, 0},
2097     {kThemeTrackActive, TTK_STATE_BACKGROUND, 0},
2098     {kThemeTrackActive, 0, 0}
2099     /* { kThemeTrackNothingToScroll, ?, ? }, */
2100 };
2101 
2102 typedef struct {        /* TrackElement client data */
2103     ThemeTrackKind kind;
2104     SInt32 thicknessMetric;
2105 } TrackElementData;
2106 
2107 static TrackElementData ScaleData = {
2108     kThemeSlider, kThemeMetricHSliderHeight
2109 };
2110 
2111 typedef struct {
2112     Tcl_Obj *fromObj;           /* minimum value */
2113     Tcl_Obj *toObj;             /* maximum value */
2114     Tcl_Obj *valueObj;          /* current value */
2115     Tcl_Obj *orientObj;         /* horizontal / vertical */
2116 } TrackElement;
2117 
2118 static Ttk_ElementOptionSpec TrackElementOptions[] = {
2119     {"-from", TK_OPTION_DOUBLE, offsetof(TrackElement, fromObj), NULL},
2120     {"-to", TK_OPTION_DOUBLE, offsetof(TrackElement, toObj), NULL},
2121     {"-value", TK_OPTION_DOUBLE, offsetof(TrackElement, valueObj), NULL},
2122     {"-orient", TK_OPTION_STRING, offsetof(TrackElement, orientObj), NULL},
2123     {NULL, TK_OPTION_BOOLEAN, 0, NULL}
2124 };
TrackElementSize(void * clientData,TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))2125 static void TrackElementSize(
2126     void *clientData,
2127     TCL_UNUSED(void *),
2128     TCL_UNUSED(Tk_Window),
2129     int *minWidth,
2130     int *minHeight,
2131     TCL_UNUSED(Ttk_Padding *))
2132 {
2133     TrackElementData *data = (TrackElementData *)clientData;
2134     SInt32 size = 24;   /* reasonable default ... */
2135 
2136     ChkErr(GetThemeMetric, data->thicknessMetric, &size);
2137     *minWidth = *minHeight = size;
2138 }
2139 
TrackElementDraw(void * clientData,void * elementRecord,Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)2140 static void TrackElementDraw(
2141     void *clientData,
2142     void *elementRecord,
2143     Tk_Window tkwin,
2144     Drawable d,
2145     Ttk_Box b,
2146     Ttk_State state)
2147 {
2148     TrackElementData *data = (TrackElementData *)clientData;
2149     TrackElement *elem = (TrackElement *)elementRecord;
2150     Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
2151     double from = 0, to = 100, value = 0, factor;
2152     CGRect bounds;
2153 
2154     TtkGetOrientFromObj(NULL, elem->orientObj, &orientation);
2155     Tcl_GetDoubleFromObj(NULL, elem->fromObj, &from);
2156     Tcl_GetDoubleFromObj(NULL, elem->toObj, &to);
2157     Tcl_GetDoubleFromObj(NULL, elem->valueObj, &value);
2158     factor = RangeToFactor(to);
2159 
2160     /*
2161      * HIThemeTrackDrawInfo uses 2-byte alignment; assigning to a separate
2162      * bounds variable avoids UBSan (-fsanitize=alignment) complaints.
2163      */
2164 
2165     bounds = BoxToRect(d, b);
2166     HIThemeTrackDrawInfo info = {
2167 	.version = 0,
2168 	.kind = data->kind,
2169 	.bounds = bounds,
2170 	.min = from * factor,
2171 	.max = to * factor,
2172 	.value = value * factor,
2173 	.attributes = kThemeTrackShowThumb |
2174 	    (orientation == TTK_ORIENT_HORIZONTAL ?
2175 	    kThemeTrackHorizontal : 0),
2176 	.enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state),
2177 	.trackInfo.progress.phase = 0,
2178     };
2179 
2180     if (info.kind == kThemeSlider) {
2181 	info.trackInfo.slider.pressState = state & TTK_STATE_PRESSED ?
2182 	    kThemeThumbPressed : 0;
2183 	if (state & TTK_STATE_ALTERNATE) {
2184 	    info.trackInfo.slider.thumbDir = kThemeThumbDownward;
2185 	} else {
2186 	    info.trackInfo.slider.thumbDir = kThemeThumbPlain;
2187 	}
2188     }
2189     BEGIN_DRAWING(d)
2190     if (TkMacOSXInDarkMode(tkwin)) {
2191 	bounds = BoxToRect(d, b);
2192 	NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
2193 	NSColor *trackColor = [NSColor colorWithColorSpace: deviceRGB
2194 	    components: darkTrack
2195 	    count: 4];
2196 	if (orientation == TTK_ORIENT_HORIZONTAL) {
2197 	    bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 2);
2198 	} else {
2199 	    bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 2);
2200 	}
2201 	SolidFillRoundedRectangle(dc.context, bounds, 2, trackColor);
2202     }
2203     ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
2204     END_DRAWING
2205 }
2206 
2207 static Ttk_ElementSpec TrackElementSpec = {
2208     TK_STYLE_VERSION_2,
2209     sizeof(TrackElement),
2210     TrackElementOptions,
2211     TrackElementSize,
2212     TrackElementDraw
2213 };
2214 
2215 /*----------------------------------------------------------------------
2216  * Slider elements -- <<NOTE-TRACKS>>
2217  *
2218  * Has geometry only. The Scale widget adjusts the position of this element,
2219  * and uses it for hit detection. In the Aqua theme, the slider is actually
2220  * drawn as part of the trough element.
2221  *
2222  */
2223 
SliderElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))2224 static void SliderElementSize(
2225     TCL_UNUSED(void *),
2226     TCL_UNUSED(void *),
2227     TCL_UNUSED(Tk_Window),
2228     int *minWidth,
2229     int *minHeight,
2230     TCL_UNUSED(Ttk_Padding *))
2231 {
2232     *minWidth = *minHeight = 24;
2233 }
2234 
2235 static Ttk_ElementSpec SliderElementSpec = {
2236     TK_STYLE_VERSION_2,
2237     sizeof(NullElement),
2238     TtkNullElementOptions,
2239     SliderElementSize,
2240     TtkNullElementDraw
2241 };
2242 
2243 /*----------------------------------------------------------------------
2244  * +++ Progress bar elements --
2245  *
2246  * @@@ NOTE: According to an older revision of the Aqua reference docs,
2247  * @@@ the 'phase' field is between 0 and 4. Newer revisions say
2248  * @@@ that it can be any UInt8 value.
2249  */
2250 
2251 typedef struct {
2252     Tcl_Obj *orientObj;         /* horizontal / vertical */
2253     Tcl_Obj *valueObj;          /* current value */
2254     Tcl_Obj *maximumObj;        /* maximum value */
2255     Tcl_Obj *phaseObj;          /* animation phase */
2256     Tcl_Obj *modeObj;           /* progress bar mode */
2257 } PbarElement;
2258 
2259 static Ttk_ElementOptionSpec PbarElementOptions[] = {
2260     {"-orient", TK_OPTION_STRING,
2261      offsetof(PbarElement, orientObj), "horizontal"},
2262     {"-value", TK_OPTION_DOUBLE,
2263      offsetof(PbarElement, valueObj), "0"},
2264     {"-maximum", TK_OPTION_DOUBLE,
2265      offsetof(PbarElement, maximumObj), "100"},
2266     {"-phase", TK_OPTION_INT,
2267      offsetof(PbarElement, phaseObj), "0"},
2268     {"-mode", TK_OPTION_STRING,
2269      offsetof(PbarElement, modeObj), "determinate"},
2270     {NULL, TK_OPTION_BOOLEAN, 0, NULL}
2271 };
PbarElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))2272 static void PbarElementSize(
2273     TCL_UNUSED(void *),
2274     TCL_UNUSED(void *),
2275     TCL_UNUSED(Tk_Window),
2276     int *minWidth,
2277     int *minHeight,
2278     TCL_UNUSED(Ttk_Padding *))
2279 {
2280     SInt32 size = 24;           /* @@@ Check HIG for correct default */
2281 
2282     ChkErr(GetThemeMetric, kThemeMetricLargeProgressBarThickness, &size);
2283     *minWidth = *minHeight = size;
2284 }
2285 
PbarElementDraw(TCL_UNUSED (void *),void * elementRecord,Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)2286 static void PbarElementDraw(
2287     TCL_UNUSED(void *),
2288     void *elementRecord,
2289     Tk_Window tkwin,
2290     Drawable d,
2291     Ttk_Box b,
2292     Ttk_State state)
2293 {
2294     PbarElement *pbar = (PbarElement *)elementRecord;
2295     Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
2296 
2297     /*
2298      * Using 1000 as the maximum should give better than 1 pixel
2299      * resolution for most progress bars.
2300      */
2301 
2302     int kind, phase = 0, ivalue, imaximum = 1000;
2303     CGRect bounds;
2304 
2305     TtkGetOrientFromObj(NULL, pbar->orientObj, &orientation);
2306     kind = !strcmp("indeterminate", Tcl_GetString(pbar->modeObj)) ?
2307 	kThemeIndeterminateBar : kThemeProgressBar;
2308     if (kind == kThemeIndeterminateBar) {
2309 	Tcl_GetIntFromObj(NULL, pbar->phaseObj, &phase);
2310 
2311 	/*
2312 	 * On macOS 11 the fraction of an indeterminate progress bar which is
2313 	 * traversed by the oscillating thumb is value / maximum.  The phase
2314 	 * determines the position of the moving thumb in that range and is
2315 	 * apparently expected to vary between 0 and 120.  On earlier systems
2316 	 * it is unclear how the phase is used in generating the animation.
2317 	 */
2318 
2319 	ivalue = imaximum;
2320     } else {
2321 	double value, maximum;
2322 	Tcl_GetDoubleFromObj(NULL, pbar->valueObj, &value);
2323 	Tcl_GetDoubleFromObj(NULL, pbar->maximumObj, &maximum);
2324 	ivalue = (value / maximum)*1000;
2325     }
2326 
2327     /*
2328      * HIThemeTrackDrawInfo uses 2-byte alignment; assigning to a separate
2329      * bounds variable avoids UBSan (-fsanitize=alignment) complaints.
2330      */
2331 
2332     bounds = BoxToRect(d, b);
2333     HIThemeTrackDrawInfo info = {
2334 	.version = 0,
2335 	.kind = kind,
2336 	.bounds = bounds,
2337 	.min = 0,
2338 	.max = imaximum,
2339 	.value = ivalue,
2340 	.attributes = kThemeTrackShowThumb |
2341 	    (orientation == TTK_ORIENT_HORIZONTAL ? kThemeTrackHorizontal : 0),
2342 	.enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state),
2343 	.trackInfo.progress.phase = phase
2344     };
2345     BEGIN_DRAWING(d)
2346     if (TkMacOSXInDarkMode(tkwin)) {
2347 	bounds = BoxToRect(d, b);
2348 	NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
2349 	NSColor *trackColor = [NSColor colorWithColorSpace: deviceRGB
2350 	    components: darkTrack
2351 	    count: 4];
2352 	if (orientation == TTK_ORIENT_HORIZONTAL) {
2353 	    bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 3);
2354 	} else {
2355 	    bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 1);
2356 	}
2357 	SolidFillRoundedRectangle(dc.context, bounds, 3, trackColor);
2358     }
2359     ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
2360     END_DRAWING
2361 }
2362 
2363 static Ttk_ElementSpec PbarElementSpec = {
2364     TK_STYLE_VERSION_2,
2365     sizeof(PbarElement),
2366     PbarElementOptions,
2367     PbarElementSize,
2368     PbarElementDraw
2369 };
2370 
2371 /*----------------------------------------------------------------------
2372  * +++ Scrollbar elements
2373  */
2374 
2375 typedef struct
2376 {
2377     Tcl_Obj *orientObj;
2378 } ScrollbarElement;
2379 
2380 static Ttk_ElementOptionSpec ScrollbarElementOptions[] = {
2381     {"-orient", TK_OPTION_STRING,
2382      offsetof(ScrollbarElement, orientObj), "horizontal"},
2383     {NULL, TK_OPTION_BOOLEAN, 0, NULL}
2384 };
TroughElementSize(TCL_UNUSED (void *),void * elementRecord,TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,Ttk_Padding * paddingPtr)2385 static void TroughElementSize(
2386     TCL_UNUSED(void *),
2387     void *elementRecord,
2388     TCL_UNUSED(Tk_Window),
2389     int *minWidth,
2390     int *minHeight,
2391     Ttk_Padding *paddingPtr)
2392 {
2393     ScrollbarElement *scrollbar = (ScrollbarElement *)elementRecord;
2394     Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
2395     SInt32 thickness = 15;
2396 
2397     TtkGetOrientFromObj(NULL, scrollbar->orientObj, &orientation);
2398     ChkErr(GetThemeMetric, kThemeMetricScrollBarWidth, &thickness);
2399     if (orientation == TTK_ORIENT_HORIZONTAL) {
2400 	*minHeight = thickness;
2401 	if ([NSApp macOSVersion] > 100700) {
2402 	    *paddingPtr = Ttk_MakePadding(4, 4, 4, 3);
2403 	}
2404     } else {
2405 	*minWidth = thickness;
2406 	if ([NSApp macOSVersion] > 100700) {
2407 	    *paddingPtr = Ttk_MakePadding(4, 4, 3, 4);
2408 	}
2409     }
2410 }
2411 
2412 static CGFloat lightTrough[4] = {250.0 / 255, 250.0 / 255, 250.0 / 255, 1.0};
2413 static CGFloat darkTrough[4] = {45.0 / 255, 46.0 / 255, 49.0 / 255, 1.0};
2414 static CGFloat lightInactiveThumb[4] = {
2415     200.0 / 255, 200.0 / 255, 200.0 / 255, 1.0
2416 };
2417 static CGFloat lightActiveThumb[4] = {
2418     133.0 / 255, 133.0 / 255, 133.0 / 255, 1.0
2419 };
2420 static CGFloat darkInactiveThumb[4] = {
2421     116.0 / 255, 117.0 / 255, 118.0 / 255, 1.0
2422 };
2423 static CGFloat darkActiveThumb[4] = {
2424     158.0 / 255, 158.0 / 255, 159.0 / 255, 1.0
2425 };
TroughElementDraw(TCL_UNUSED (void *),void * elementRecord,Tk_Window tkwin,Drawable d,Ttk_Box b,TCL_UNUSED (Ttk_State))2426 static void TroughElementDraw(
2427     TCL_UNUSED(void *),
2428     void *elementRecord,
2429     Tk_Window tkwin,
2430     Drawable d,
2431     Ttk_Box b,
2432     TCL_UNUSED(Ttk_State))
2433 {
2434     ScrollbarElement *scrollbar = (ScrollbarElement *)elementRecord;
2435     Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
2436     CGRect bounds = BoxToRect(d, b);
2437     NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
2438     NSColor *troughColor;
2439     CGFloat *rgba = TkMacOSXInDarkMode(tkwin) ? darkTrough : lightTrough;
2440 
2441     TtkGetOrientFromObj(NULL, scrollbar->orientObj, &orientation);
2442     if (orientation == TTK_ORIENT_HORIZONTAL) {
2443 	bounds = CGRectInset(bounds, 0, 1);
2444     } else {
2445 	bounds = CGRectInset(bounds, 1, 0);
2446     }
2447     troughColor = [NSColor colorWithColorSpace: deviceRGB
2448 	components: rgba
2449 	count: 4];
2450     BEGIN_DRAWING(d)
2451     if ([NSApp macOSVersion] > 100800) {
2452 	CGContextSetFillColorWithColor(dc.context, CGCOLOR(troughColor));
2453     } else {
2454 	ChkErr(HIThemeSetFill, kThemeBrushDocumentWindowBackground, NULL,
2455 	    dc.context, HIOrientation);
2456     }
2457     CGContextFillRect(dc.context, bounds);
2458     END_DRAWING
2459 }
2460 
2461 static Ttk_ElementSpec TroughElementSpec = {
2462     TK_STYLE_VERSION_2,
2463     sizeof(ScrollbarElement),
2464     ScrollbarElementOptions,
2465     TroughElementSize,
2466     TroughElementDraw
2467 };
ThumbElementSize(TCL_UNUSED (void *),void * elementRecord,TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))2468 static void ThumbElementSize(
2469     TCL_UNUSED(void *),
2470     void *elementRecord,
2471     TCL_UNUSED(Tk_Window),
2472     int *minWidth,
2473     int *minHeight,
2474     TCL_UNUSED(Ttk_Padding *))
2475 {
2476     ScrollbarElement *scrollbar = (ScrollbarElement *)elementRecord;
2477     Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
2478 
2479     TtkGetOrientFromObj(NULL, scrollbar->orientObj, &orientation);
2480     if (orientation == TTK_ORIENT_VERTICAL) {
2481 	*minHeight = 18;
2482 	*minWidth = 8;
2483     } else {
2484 	*minHeight = 8;
2485 	*minWidth = 18;
2486     }
2487 }
2488 
ThumbElementDraw(TCL_UNUSED (void *),void * elementRecord,Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)2489 static void ThumbElementDraw(
2490     TCL_UNUSED(void *),
2491     void *elementRecord,
2492     Tk_Window tkwin,
2493     Drawable d,
2494     Ttk_Box b,
2495     Ttk_State state)
2496 {
2497     ScrollbarElement *scrollbar = (ScrollbarElement *)elementRecord;
2498     Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
2499 
2500     TtkGetOrientFromObj(NULL, scrollbar->orientObj, &orientation);
2501 
2502     /*
2503      * In order to make ttk scrollbars work correctly it is necessary to be
2504      * able to display the thumb element at the size and location which the ttk
2505      * scrollbar widget requests.  The algorithm that HIToolbox uses to
2506      * determine the thumb geometry from the input values of min, max, value
2507      * and viewSize is undocumented.  A seemingly natural algorithm is
2508      * implemented below.  This code uses that algorithm for older OS versions,
2509      * because using HITools also handles drawing the buttons and 3D thumb used
2510      * on those systems.  For newer systems the cleanest approach is to just
2511      * draw the thumb directly.
2512      */
2513 
2514     if ([NSApp macOSVersion] > 100800) {
2515 	CGRect thumbBounds = BoxToRect(d, b);
2516 	NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
2517 	NSColor *thumbColor;
2518 	CGFloat *rgba;
2519 	if ((orientation == TTK_ORIENT_HORIZONTAL &&
2520 	    thumbBounds.size.width >= Tk_Width(tkwin) - 8) ||
2521 	    (orientation == TTK_ORIENT_VERTICAL &&
2522 	    thumbBounds.size.height >= Tk_Height(tkwin) - 8)) {
2523 	    return;
2524 	}
2525 	int isDark = TkMacOSXInDarkMode(tkwin);
2526 	if ((state & TTK_STATE_PRESSED) ||
2527 	    (state & TTK_STATE_HOVER)) {
2528 	    rgba = isDark ? darkActiveThumb : lightActiveThumb;
2529 	} else {
2530 	    rgba = isDark ? darkInactiveThumb : lightInactiveThumb;
2531 	}
2532 	thumbColor = [NSColor colorWithColorSpace: deviceRGB
2533 	    components: rgba
2534 	    count: 4];
2535 	BEGIN_DRAWING(d)
2536 	SolidFillRoundedRectangle(dc.context, thumbBounds, 4, thumbColor);
2537 	END_DRAWING
2538     } else {
2539 	double thumbSize, trackSize, visibleSize, factor, fraction;
2540 	MacDrawable *macWin = (MacDrawable *)Tk_WindowId(tkwin);
2541 	CGRect troughBounds = {{macWin->xOff, macWin->yOff},
2542 			       {Tk_Width(tkwin), Tk_Height(tkwin)}};
2543 
2544         /*
2545          * The info struct has integer fields, which will be converted to
2546          * floats in the drawing routine.  All of values provided in the info
2547          * struct, namely min, max, value, and viewSize are only defined up to
2548          * an arbitrary scale factor.  To avoid roundoff error we scale so
2549          * that the viewSize is a large float which is smaller than the
2550          * largest int.
2551          */
2552 
2553 	HIThemeTrackDrawInfo info = {
2554 	    .version = 0,
2555 	    .bounds = troughBounds,
2556 	    .min = 0,
2557 	    .attributes = kThemeTrackShowThumb |
2558 		kThemeTrackThumbRgnIsNotGhost,
2559 	    .enableState = kThemeTrackActive
2560 	};
2561 	factor = RangeToFactor(100.0);
2562 	if (orientation == TTK_ORIENT_HORIZONTAL) {
2563 	    trackSize = troughBounds.size.width;
2564 	    thumbSize = b.width;
2565 	    fraction = b.x / trackSize;
2566 	} else {
2567 	    trackSize = troughBounds.size.height;
2568 	    thumbSize = b.height;
2569 	    fraction = b.y / trackSize;
2570 	}
2571 	visibleSize = (thumbSize / trackSize) * factor;
2572 	info.max = factor - visibleSize;
2573 	info.trackInfo.scrollbar.viewsize = visibleSize;
2574 	if ([NSApp macOSVersion] < 100800 ||
2575 	    orientation == TTK_ORIENT_HORIZONTAL) {
2576 	    info.value = factor * fraction;
2577 	} else {
2578 	    info.value = info.max - factor * fraction;
2579 	}
2580 	if ((state & TTK_STATE_PRESSED) ||
2581 	    (state & TTK_STATE_HOVER)) {
2582 	    info.trackInfo.scrollbar.pressState = kThemeThumbPressed;
2583 	} else {
2584 	    info.trackInfo.scrollbar.pressState = 0;
2585 	}
2586 	if (orientation == TTK_ORIENT_HORIZONTAL) {
2587 	    info.attributes |= kThemeTrackHorizontal;
2588 	} else {
2589 	    info.attributes &= ~kThemeTrackHorizontal;
2590 	}
2591 	BEGIN_DRAWING(d)
2592 	HIThemeDrawTrack(&info, 0, dc.context, kHIThemeOrientationNormal);
2593 	END_DRAWING
2594     }
2595 }
2596 
2597 static Ttk_ElementSpec ThumbElementSpec = {
2598     TK_STYLE_VERSION_2,
2599     sizeof(ScrollbarElement),
2600     ScrollbarElementOptions,
2601     ThumbElementSize,
2602     ThumbElementDraw
2603 };
ArrowElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))2604 static void ArrowElementSize(
2605     TCL_UNUSED(void *),
2606     TCL_UNUSED(void *),
2607     TCL_UNUSED(Tk_Window),
2608     int *minWidth,
2609     int *minHeight,
2610     TCL_UNUSED(Ttk_Padding *))
2611 {
2612     if ([NSApp macOSVersion] < 100800) {
2613 	*minHeight = *minWidth = 14;
2614     } else {
2615 	*minHeight = *minWidth = -1;
2616     }
2617 }
2618 
2619 static Ttk_ElementSpec ArrowElementSpec = {
2620     TK_STYLE_VERSION_2,
2621     sizeof(ScrollbarElement),
2622     ScrollbarElementOptions,
2623     ArrowElementSize,
2624     TtkNullElementDraw
2625 };
2626 
2627 /*----------------------------------------------------------------------
2628  * +++ Separator element.
2629  *
2630  *    DrawThemeSeparator() guesses the orientation of the line from the width
2631  *    and height of the rectangle, so the same element can can be used for
2632  *    horizontal, vertical, and general separators.
2633  */
2634 
SeparatorElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))2635 static void SeparatorElementSize(
2636     TCL_UNUSED(void *),
2637     TCL_UNUSED(void *),
2638     TCL_UNUSED(Tk_Window),
2639     int *minWidth,
2640     int *minHeight,
2641     TCL_UNUSED(Ttk_Padding *))
2642 {
2643     *minWidth = *minHeight = 1;
2644 }
2645 
SeparatorElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,unsigned int state)2646 static void SeparatorElementDraw(
2647     TCL_UNUSED(void *),
2648     TCL_UNUSED(void *),
2649     Tk_Window tkwin,
2650     Drawable d,
2651     Ttk_Box b,
2652     unsigned int state)
2653 {
2654     CGRect bounds = BoxToRect(d, b);
2655     const HIThemeSeparatorDrawInfo info = {
2656 	.version = 0,
2657         /* Separator only supports kThemeStateActive, kThemeStateInactive */
2658 	.state = Ttk_StateTableLookup(ThemeStateTable,
2659 	    state & TTK_STATE_BACKGROUND),
2660     };
2661 
2662     BEGIN_DRAWING(d)
2663     if (TkMacOSXInDarkMode(tkwin)) {
2664 	DrawDarkSeparator(bounds, dc.context, tkwin);
2665     } else {
2666 	ChkErr(HIThemeDrawSeparator, &bounds, &info, dc.context,
2667 	    HIOrientation);
2668     }
2669     END_DRAWING
2670 }
2671 
2672 static Ttk_ElementSpec SeparatorElementSpec = {
2673     TK_STYLE_VERSION_2,
2674     sizeof(NullElement),
2675     TtkNullElementOptions,
2676     SeparatorElementSize,
2677     SeparatorElementDraw
2678 };
2679 
2680 /*----------------------------------------------------------------------
2681  * +++ Size grip elements -- (obsolete)
2682  */
2683 
2684 static const ThemeGrowDirection sizegripGrowDirection
2685     = kThemeGrowRight | kThemeGrowDown;
2686 
SizegripElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))2687 static void SizegripElementSize(
2688     TCL_UNUSED(void *),
2689     TCL_UNUSED(void *),
2690     TCL_UNUSED(Tk_Window),
2691     int *minWidth,
2692     int *minHeight,
2693     TCL_UNUSED(Ttk_Padding *))
2694 {
2695     HIThemeGrowBoxDrawInfo info = {
2696 	.version = 0,
2697 	.state = kThemeStateActive,
2698 	.kind = kHIThemeGrowBoxKindNormal,
2699 	.direction = sizegripGrowDirection,
2700 	.size = kHIThemeGrowBoxSizeNormal,
2701     };
2702     CGRect bounds = CGRectZero;
2703 
2704     ChkErr(HIThemeGetGrowBoxBounds, &bounds.origin, &info, &bounds);
2705     *minWidth = bounds.size.width;
2706     *minHeight = bounds.size.height;
2707 }
2708 
SizegripElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),Drawable d,Ttk_Box b,unsigned int state)2709 static void SizegripElementDraw(
2710     TCL_UNUSED(void *),
2711     TCL_UNUSED(void *),
2712     TCL_UNUSED(Tk_Window),
2713     Drawable d,
2714     Ttk_Box b,
2715     unsigned int state)
2716 {
2717     CGRect bounds = BoxToRect(d, b);
2718     HIThemeGrowBoxDrawInfo info = {
2719 	.version = 0,
2720         /* Grow box only supports kThemeStateActive, kThemeStateInactive */
2721 	.state = Ttk_StateTableLookup(ThemeStateTable,
2722 	    state & TTK_STATE_BACKGROUND),
2723 	.kind = kHIThemeGrowBoxKindNormal,
2724 	.direction = sizegripGrowDirection,
2725 	.size = kHIThemeGrowBoxSizeNormal,
2726     };
2727 
2728     BEGIN_DRAWING(d)
2729     ChkErr(HIThemeDrawGrowBox, &bounds.origin, &info, dc.context,
2730 	HIOrientation);
2731     END_DRAWING
2732 }
2733 
2734 static Ttk_ElementSpec SizegripElementSpec = {
2735     TK_STYLE_VERSION_2,
2736     sizeof(NullElement),
2737     TtkNullElementOptions,
2738     SizegripElementSize,
2739     SizegripElementDraw
2740 };
2741 
2742 /*----------------------------------------------------------------------
2743  * +++ Background and fill elements --
2744  *
2745  *      Before drawing any ttk widget, its bounding rectangle is filled with a
2746  *      background color.  This color must match the background color of the
2747  *      containing widget to avoid looking ugly. The need for care when doing
2748  *      this is exacerbated by the fact that ttk enforces its "native look" by
2749  *      not allowing user control of the background or highlight colors of ttk
2750  *      widgets.
2751  *
2752  *      This job is made more complicated in recent versions of macOS by the
2753  *      fact that the Appkit GroupBox (used for ttk LabelFrames) and
2754  *      TabbedPane (used for the Notebook widget) both place their content
2755  *      inside a rectangle with rounded corners that has a color which
2756  *      contrasts with the dialog background color.  Moreover, although the
2757  *      Apple human interface guidelines recommend against doing so, there are
2758  *      times when one wants to nest these widgets, for example placing a
2759  *      GroupBox inside of a TabbedPane.  To have the right contrast, each
2760  *      level of nesting requires a different color.
2761  *
2762  *      Previous Tk releases used the HIThemeDrawGroupBox routine to draw
2763  *      GroupBoxes and TabbedPanes. This meant that the best that could be
2764  *      done was to set the GroupBox to be of kind
2765  *      kHIThemeGroupBoxKindPrimaryOpaque, and set its fill color to be the
2766  *      system background color.  If widgets inside the box were drawn with
2767  *      the system background color the backgrounds would match.  But this
2768  *      produces a GroupBox with no contrast, the only visual clue being a
2769  *      faint highlighting around the top of the GroupBox.  Moreover, the
2770  *      TabbedPane does not have an Opaque version, so while it is drawn
2771  *      inside a contrasting rounded rectangle, the widgets inside the pane
2772  *      needed to be enclosed in a frame with the system background
2773  *      color. This added a visual artifact since the frame's background color
2774  *      does not match the Pane's background color.  That code has now been
2775  *      replaced with the standalone drawing procedure macOSXDrawGroupBox,
2776  *      which draws a rounded rectangle with an appropriate contrasting
2777  *      background color.
2778  *
2779  *      Patterned backgrounds, which are now obsolete, should be aligned with
2780  *      the coordinate system of the top-level window.  Apparently failing to
2781  *      do this used to cause graphics anomalies when drawing into an
2782  *      off-screen graphics port.  The code for handling this is currently
2783  *      commented out.
2784  */
2785 
FillElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)2786 static void FillElementDraw(
2787     TCL_UNUSED(void *),
2788     TCL_UNUSED(void *),
2789     Tk_Window tkwin,
2790     Drawable d,
2791     Ttk_Box b,
2792     Ttk_State state)
2793 {
2794     CGRect bounds = BoxToRect(d, b);
2795 
2796     if ([NSApp macOSVersion] > 100800) {
2797 	NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
2798 	NSColor *bgColor;
2799 	CGFloat fill[4];
2800 	BEGIN_DRAWING(d)
2801 	GetBackgroundColor(dc.context, tkwin, 0, fill);
2802 	bgColor = [NSColor colorWithColorSpace: deviceRGB components: fill
2803 					 count: 4];
2804 	CGContextSetFillColorSpace(dc.context, deviceRGB.CGColorSpace);
2805 	CGContextSetFillColorWithColor(dc.context, CGCOLOR(bgColor));
2806 	CGContextFillRect(dc.context, bounds);
2807 	END_DRAWING
2808     } else {
2809 	ThemeBrush brush = (state & TTK_STATE_BACKGROUND)
2810 	    ? kThemeBrushModelessDialogBackgroundInactive
2811 	    : kThemeBrushModelessDialogBackgroundActive;
2812 	BEGIN_DRAWING(d)
2813 	ChkErr(HIThemeSetFill, brush, NULL, dc.context, HIOrientation);
2814 	//QDSetPatternOrigin(PatternOrigin(tkwin, d));
2815 	CGContextFillRect(dc.context, bounds);
2816 	END_DRAWING
2817     }
2818 }
2819 
BackgroundElementDraw(void * clientData,void * elementRecord,Tk_Window tkwin,Drawable d,TCL_UNUSED (Ttk_Box),unsigned int state)2820 static void BackgroundElementDraw(
2821     void *clientData,
2822     void *elementRecord,
2823     Tk_Window tkwin,
2824     Drawable d,
2825     TCL_UNUSED(Ttk_Box),
2826     unsigned int state)
2827 {
2828     FillElementDraw(clientData, elementRecord, tkwin, d, Ttk_WinBox(tkwin),
2829 	state);
2830 }
2831 
2832 static Ttk_ElementSpec FillElementSpec = {
2833     TK_STYLE_VERSION_2,
2834     sizeof(NullElement),
2835     TtkNullElementOptions,
2836     TtkNullElementSize,
2837     FillElementDraw
2838 };
2839 static Ttk_ElementSpec BackgroundElementSpec = {
2840     TK_STYLE_VERSION_2,
2841     sizeof(NullElement),
2842     TtkNullElementOptions,
2843     TtkNullElementSize,
2844     BackgroundElementDraw
2845 };
2846 
2847 /*----------------------------------------------------------------------
2848  * +++ ToolbarBackground element -- toolbar style for frames.
2849  *
2850  *    This is very similar to the normal background element, but uses a
2851  *    different ThemeBrush in order to get the lighter pinstripe effect
2852  *    used in toolbars. We use SetThemeBackground() rather than
2853  *    ApplyThemeBackground() in order to get the right style.
2854  *
2855  *    <URL: http://developer.apple.com/documentation/Carbon/Reference/
2856  *    Appearance_Manager/appearance_manager/constant_7.html#/
2857  *    /apple_ref/doc/uid/TP30000243/C005321>
2858  *
2859  */
2860 
ToolbarBackgroundElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,TCL_UNUSED (Ttk_Box),TCL_UNUSED (Ttk_State))2861 static void ToolbarBackgroundElementDraw(
2862     TCL_UNUSED(void *),
2863     TCL_UNUSED(void *),
2864     Tk_Window tkwin,
2865     Drawable d,
2866     TCL_UNUSED(Ttk_Box),
2867     TCL_UNUSED(Ttk_State))
2868 {
2869     ThemeBrush brush = kThemeBrushToolbarBackground;
2870     CGRect bounds = BoxToRect(d, Ttk_WinBox(tkwin));
2871 
2872     BEGIN_DRAWING(d)
2873     ChkErr(HIThemeSetFill, brush, NULL, dc.context, HIOrientation);
2874     //QDSetPatternOrigin(PatternOrigin(tkwin, d));
2875     CGContextFillRect(dc.context, bounds);
2876     END_DRAWING
2877 }
2878 
2879 static Ttk_ElementSpec ToolbarBackgroundElementSpec = {
2880     TK_STYLE_VERSION_2,
2881     sizeof(NullElement),
2882     TtkNullElementOptions,
2883     TtkNullElementSize,
2884     ToolbarBackgroundElementDraw
2885 };
2886 
2887 /*----------------------------------------------------------------------
2888  * +++ Field elements --
2889  *
2890  *      Used for the Treeview widget. This is like the BackgroundElement
2891  *      except that the fieldbackground color is configurable.
2892  */
2893 
2894 typedef struct {
2895     Tcl_Obj     *backgroundObj;
2896 } FieldElement;
2897 
2898 static Ttk_ElementOptionSpec FieldElementOptions[] = {
2899     {"-fieldbackground", TK_OPTION_BORDER,
2900      offsetof(FieldElement, backgroundObj), "white"},
2901     {NULL, TK_OPTION_BOOLEAN, 0, NULL}
2902 };
2903 
FieldElementDraw(TCL_UNUSED (void *),void * elementRecord,Tk_Window tkwin,Drawable d,Ttk_Box b,TCL_UNUSED (Ttk_State))2904 static void FieldElementDraw(
2905     TCL_UNUSED(void *),
2906     void *elementRecord,
2907     Tk_Window tkwin,
2908     Drawable d,
2909     Ttk_Box b,
2910     TCL_UNUSED(Ttk_State))
2911 {
2912     FieldElement *e = (FieldElement *)elementRecord;
2913     Tk_3DBorder backgroundPtr =
2914 	Tk_Get3DBorderFromObj(tkwin, e->backgroundObj);
2915 
2916     XFillRectangle(Tk_Display(tkwin), d,
2917 	Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC),
2918 	b.x, b.y, b.width, b.height);
2919 }
2920 
2921 static Ttk_ElementSpec FieldElementSpec = {
2922     TK_STYLE_VERSION_2,
2923     sizeof(FieldElement),
2924     FieldElementOptions,
2925     TtkNullElementSize,
2926     FieldElementDraw
2927 };
2928 
2929 /*----------------------------------------------------------------------
2930  * +++ Treeview headers --
2931  *
2932  *    On systems older than 10.9 The header is a kThemeListHeaderButton drawn
2933  *    by HIToolbox.  On newer systems those buttons do not match the Apple
2934  *    buttons, so we draw them from scratch.
2935  */
2936 
2937 static Ttk_StateTable TreeHeaderValueTable[] = {
2938     {kThemeButtonOn, TTK_STATE_ALTERNATE, 0},
2939     {kThemeButtonOn, TTK_STATE_SELECTED, 0},
2940     {kThemeButtonOff, 0, 0}
2941 };
2942 
2943 static Ttk_StateTable TreeHeaderAdornmentTable[] = {
2944     {kThemeAdornmentHeaderButtonSortUp,
2945      TTK_STATE_ALTERNATE | TTK_TREEVIEW_STATE_SORTARROW, 0},
2946     {kThemeAdornmentDefault,
2947      TTK_STATE_SELECTED | TTK_TREEVIEW_STATE_SORTARROW, 0},
2948     {kThemeAdornmentHeaderButtonNoSortArrow, TTK_STATE_ALTERNATE, 0},
2949     {kThemeAdornmentHeaderButtonNoSortArrow, TTK_STATE_SELECTED, 0},
2950     {kThemeAdornmentFocus, TTK_STATE_FOCUS, 0},
2951     {kThemeAdornmentNone, 0, 0}
2952 };
2953 
TreeAreaElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),TCL_UNUSED (int *),TCL_UNUSED (int *),Ttk_Padding * paddingPtr)2954 static void TreeAreaElementSize (
2955     TCL_UNUSED(void *),
2956     TCL_UNUSED(void *),
2957     TCL_UNUSED(Tk_Window),
2958     TCL_UNUSED(int *),
2959     TCL_UNUSED(int *),
2960     Ttk_Padding *paddingPtr)
2961 {
2962 
2963     /*
2964      * Padding is needed to get the heading text to align correctly, since the
2965      * widget expects the heading to be the same height as a row.
2966      */
2967 
2968     if ([NSApp macOSVersion] > 100800) {
2969 	paddingPtr->top = 4;
2970     }
2971 }
2972 
2973 static Ttk_ElementSpec TreeAreaElementSpec = {
2974     TK_STYLE_VERSION_2,
2975     sizeof(NullElement),
2976     TtkNullElementOptions,
2977     TreeAreaElementSize,
2978     TtkNullElementDraw
2979 };
TreeHeaderElementSize(void * clientData,void * elementRecord,Tk_Window tkwin,int * minWidth,int * minHeight,Ttk_Padding * paddingPtr)2980 static void TreeHeaderElementSize(
2981     void *clientData,
2982     void *elementRecord,
2983     Tk_Window tkwin,
2984     int *minWidth,
2985     int *minHeight,
2986     Ttk_Padding *paddingPtr)
2987 {
2988     if ([NSApp macOSVersion] > 100800) {
2989 	*minHeight = 24;
2990     } else {
2991 	ButtonElementSize(clientData, elementRecord, tkwin, minWidth,
2992 	    minHeight, paddingPtr);
2993     }
2994 }
2995 
TreeHeaderElementDraw(void * clientData,TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)2996 static void TreeHeaderElementDraw(
2997     void *clientData,
2998     TCL_UNUSED(void *),
2999     Tk_Window tkwin,
3000     Drawable d,
3001     Ttk_Box b,
3002     Ttk_State state)
3003 {
3004     ThemeButtonParams *params = (ThemeButtonParams *)clientData;
3005     CGRect bounds = BoxToRect(d, b);
3006     const HIThemeButtonDrawInfo info = {
3007 	.version = 0,
3008 	.state = Ttk_StateTableLookup(ThemeStateTable, state),
3009 	.kind = params->kind,
3010 	.value = Ttk_StateTableLookup(TreeHeaderValueTable, state),
3011 	.adornment = Ttk_StateTableLookup(TreeHeaderAdornmentTable, state),
3012     };
3013 
3014     BEGIN_DRAWING(d)
3015     if ([NSApp macOSVersion] > 100800) {
3016 
3017         /*
3018          * Compensate for the padding added in TreeHeaderElementSize, so
3019          * the larger heading will be drawn at the top of the widget.
3020          */
3021 
3022 	bounds.origin.y -= 4;
3023 	if (TkMacOSXInDarkMode(tkwin)) {
3024 	    DrawDarkListHeader(bounds, dc.context, tkwin, state);
3025 	} else {
3026 	    DrawListHeader(bounds, dc.context, tkwin, state);
3027 	}
3028     } else {
3029 	ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
3030 	    NULL);
3031     }
3032     END_DRAWING
3033 }
3034 
3035 static Ttk_ElementSpec TreeHeaderElementSpec = {
3036     TK_STYLE_VERSION_2,
3037     sizeof(NullElement),
3038     TtkNullElementOptions,
3039     TreeHeaderElementSize,
3040     TreeHeaderElementDraw
3041 };
3042 
3043 /*----------------------------------------------------------------------
3044  * +++ Disclosure triangles --
3045  */
3046 
3047 #define TTK_TREEVIEW_STATE_OPEN         TTK_STATE_USER1
3048 #define TTK_TREEVIEW_STATE_LEAF         TTK_STATE_USER2
3049 static Ttk_StateTable DisclosureValueTable[] = {
3050     {kThemeDisclosureDown, TTK_TREEVIEW_STATE_OPEN, 0},
3051     {kThemeDisclosureRight, 0, 0},
3052 };
DisclosureElementSize(TCL_UNUSED (void *),TCL_UNUSED (void *),TCL_UNUSED (Tk_Window),int * minWidth,int * minHeight,TCL_UNUSED (Ttk_Padding *))3053 static void DisclosureElementSize(
3054     TCL_UNUSED(void *),
3055     TCL_UNUSED(void *),
3056     TCL_UNUSED(Tk_Window),
3057     int *minWidth,
3058     int *minHeight,
3059     TCL_UNUSED(Ttk_Padding *))
3060 {
3061     SInt32 s;
3062 
3063     ChkErr(GetThemeMetric, kThemeMetricDisclosureTriangleWidth, &s);
3064     *minWidth = s;
3065     ChkErr(GetThemeMetric, kThemeMetricDisclosureTriangleHeight, &s);
3066     *minHeight = s;
3067 }
3068 
DisclosureElementDraw(TCL_UNUSED (void *),TCL_UNUSED (void *),Tk_Window tkwin,Drawable d,Ttk_Box b,Ttk_State state)3069 static void DisclosureElementDraw(
3070     TCL_UNUSED(void *),
3071     TCL_UNUSED(void *),
3072     Tk_Window tkwin,
3073     Drawable d,
3074     Ttk_Box b,
3075     Ttk_State state)
3076 {
3077     if (!(state & TTK_TREEVIEW_STATE_LEAF)) {
3078 	int triangleState = TkMacOSXInDarkMode(tkwin) ?
3079 	    kThemeStateInactive : kThemeStateActive;
3080 	CGRect bounds = BoxToRect(d, b);
3081 	const HIThemeButtonDrawInfo info = {
3082 	    .version = 0,
3083 	    .state = triangleState,
3084 	    .kind = kThemeDisclosureTriangle,
3085 	    .value = Ttk_StateTableLookup(DisclosureValueTable, state),
3086 	    .adornment = kThemeAdornmentDrawIndicatorOnly,
3087 	};
3088 
3089 	BEGIN_DRAWING(d)
3090 	if ([NSApp macOSVersion] >= 110000) {
3091 	    CGFloat rgba[4];
3092 	    NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
3093 	    NSColor *stroke = [[NSColor textColor]
3094 		colorUsingColorSpace: deviceRGB];
3095 	    [stroke getComponents: rgba];
3096 	    if (state & TTK_TREEVIEW_STATE_OPEN) {
3097 		DrawOpenDisclosure(dc.context, bounds, 2, 8, rgba);
3098 	    } else {
3099 		DrawClosedDisclosure(dc.context, bounds, 2, 12, rgba);
3100 	    }
3101 	} else {
3102 	    ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
3103 	    NULL);
3104 	}
3105 	END_DRAWING
3106     }
3107 }
3108 
3109 static Ttk_ElementSpec DisclosureElementSpec = {
3110     TK_STYLE_VERSION_2,
3111     sizeof(NullElement),
3112     TtkNullElementOptions,
3113     DisclosureElementSize,
3114     DisclosureElementDraw
3115 };
3116 
3117 /*----------------------------------------------------------------------
3118  * +++ Widget layouts --
3119  */
3120 
3121 TTK_BEGIN_LAYOUT_TABLE(LayoutTable)
3122 
3123 TTK_LAYOUT("Toolbar",
3124     TTK_NODE("Toolbar.background", TTK_FILL_BOTH))
3125 
3126 TTK_LAYOUT("TButton",
3127     TTK_GROUP("Button.button", TTK_FILL_BOTH,
3128     TTK_GROUP("Button.padding", TTK_FILL_BOTH,
3129     TTK_NODE("Button.label", TTK_FILL_BOTH))))
3130 
3131 TTK_LAYOUT("TRadiobutton",
3132     TTK_GROUP("Radiobutton.button", TTK_FILL_BOTH,
3133     TTK_GROUP("Radiobutton.padding", TTK_FILL_BOTH,
3134     TTK_NODE("Radiobutton.label", TTK_PACK_LEFT))))
3135 
3136 TTK_LAYOUT("TCheckbutton",
3137     TTK_GROUP("Checkbutton.button", TTK_FILL_BOTH,
3138     TTK_GROUP("Checkbutton.padding", TTK_FILL_BOTH,
3139     TTK_NODE("Checkbutton.label", TTK_PACK_LEFT))))
3140 
3141 TTK_LAYOUT("TMenubutton",
3142     TTK_GROUP("Menubutton.button", TTK_FILL_BOTH,
3143     TTK_GROUP("Menubutton.padding", TTK_FILL_BOTH,
3144     TTK_NODE("Menubutton.label", TTK_PACK_LEFT))))
3145 
3146 TTK_LAYOUT("TCombobox",
3147     TTK_GROUP("Combobox.button", TTK_FILL_BOTH,
3148     TTK_GROUP("Combobox.padding", TTK_FILL_BOTH,
3149     TTK_NODE("Combobox.textarea", TTK_FILL_BOTH))))
3150 
3151 /* Notebook tabs -- no focus ring */
3152 TTK_LAYOUT("Tab",
3153     TTK_GROUP("Notebook.tab", TTK_FILL_BOTH,
3154     TTK_GROUP("Notebook.padding", TTK_FILL_BOTH,
3155     TTK_NODE("Notebook.label", TTK_FILL_BOTH))))
3156 
3157 /* Spinbox -- buttons 2px to the right of the field. */
3158 TTK_LAYOUT("TSpinbox",
3159     TTK_GROUP("Spinbox.buttons", TTK_PACK_RIGHT,
3160     TTK_NODE("Spinbox.uparrow", TTK_PACK_TOP | TTK_STICK_E)
3161     TTK_NODE("Spinbox.downarrow", TTK_PACK_BOTTOM | TTK_STICK_E))
3162     TTK_GROUP("Spinbox.field", TTK_FILL_X,
3163     TTK_NODE("Spinbox.textarea", TTK_FILL_X)))
3164 
3165 /* Progress bars -- track only */
3166 TTK_LAYOUT("TProgressbar",
3167     TTK_NODE("Progressbar.track", TTK_FILL_BOTH))
3168 
3169 /* Treeview -- no border. */
3170 TTK_LAYOUT("Treeview",
3171     TTK_GROUP("Treeview.field", TTK_FILL_BOTH,
3172     TTK_GROUP("Treeview.padding", TTK_FILL_BOTH,
3173     TTK_NODE("Treeview.treearea", TTK_FILL_BOTH))))
3174 
3175 /* Tree heading -- no border, fixed height */
3176 TTK_LAYOUT("Heading",
3177     TTK_NODE("Treeheading.cell", TTK_FILL_BOTH)
3178     TTK_NODE("Treeheading.image", TTK_PACK_RIGHT)
3179     TTK_NODE("Treeheading.text", TTK_PACK_TOP))
3180 
3181 /* Tree items -- omit focus ring */
3182 TTK_LAYOUT("Item",
3183     TTK_GROUP("Treeitem.padding", TTK_FILL_BOTH,
3184     TTK_NODE("Treeitem.indicator", TTK_PACK_LEFT)
3185     TTK_NODE("Treeitem.image", TTK_PACK_LEFT)
3186     TTK_NODE("Treeitem.text", TTK_PACK_LEFT)))
3187 
3188 /* Scrollbar Layout -- Buttons at the bottom (Snow Leopard and Lion only) */
3189 
3190 TTK_LAYOUT("Vertical.TScrollbar",
3191     TTK_GROUP("Vertical.Scrollbar.trough", TTK_FILL_Y,
3192     TTK_NODE("Vertical.Scrollbar.thumb", TTK_FILL_BOTH)
3193     TTK_NODE("Vertical.Scrollbar.downarrow", TTK_PACK_BOTTOM)
3194     TTK_NODE("Vertical.Scrollbar.uparrow", TTK_PACK_BOTTOM)))
3195 
3196 TTK_LAYOUT("Horizontal.TScrollbar",
3197     TTK_GROUP("Horizontal.Scrollbar.trough", TTK_FILL_X,
3198     TTK_NODE("Horizontal.Scrollbar.thumb", TTK_FILL_BOTH)
3199     TTK_NODE("Horizontal.Scrollbar.rightarrow", TTK_PACK_RIGHT)
3200     TTK_NODE("Horizontal.Scrollbar.leftarrow", TTK_PACK_RIGHT)))
3201 
3202 TTK_END_LAYOUT_TABLE
3203 
3204 /*----------------------------------------------------------------------
3205  * +++ Initialization --
3206  */
3207 
AquaTheme_Init(Tcl_Interp * interp)3208 static int AquaTheme_Init(
3209     Tcl_Interp *interp)
3210 {
3211     Ttk_Theme themePtr = Ttk_CreateTheme(interp, "aqua", NULL);
3212 
3213     if (!themePtr) {
3214 	return TCL_ERROR;
3215     }
3216 
3217     /*
3218      * Elements:
3219      */
3220 
3221     Ttk_RegisterElementSpec(themePtr, "background", &BackgroundElementSpec,
3222 	0);
3223     Ttk_RegisterElementSpec(themePtr, "fill", &FillElementSpec, 0);
3224     Ttk_RegisterElementSpec(themePtr, "field", &FieldElementSpec, 0);
3225     Ttk_RegisterElementSpec(themePtr, "Toolbar.background",
3226 	&ToolbarBackgroundElementSpec, 0);
3227 
3228     Ttk_RegisterElementSpec(themePtr, "Button.button",
3229 	&ButtonElementSpec, &PushButtonParams);
3230     Ttk_RegisterElementSpec(themePtr, "Checkbutton.button",
3231 	&ButtonElementSpec, &CheckBoxParams);
3232     Ttk_RegisterElementSpec(themePtr, "Radiobutton.button",
3233 	&ButtonElementSpec, &RadioButtonParams);
3234     Ttk_RegisterElementSpec(themePtr, "Toolbutton.border",
3235 	&ButtonElementSpec, &BevelButtonParams);
3236     Ttk_RegisterElementSpec(themePtr, "Menubutton.button",
3237 	&ButtonElementSpec, &PopupButtonParams);
3238     Ttk_RegisterElementSpec(themePtr, "Spinbox.uparrow",
3239 	&SpinButtonUpElementSpec, 0);
3240     Ttk_RegisterElementSpec(themePtr, "Spinbox.downarrow",
3241 	&SpinButtonDownElementSpec, 0);
3242     Ttk_RegisterElementSpec(themePtr, "Combobox.button",
3243 	&ComboboxElementSpec, 0);
3244     Ttk_RegisterElementSpec(themePtr, "Treeitem.indicator",
3245 	&DisclosureElementSpec, &DisclosureParams);
3246     Ttk_RegisterElementSpec(themePtr, "Treeheading.cell",
3247 	&TreeHeaderElementSpec, &ListHeaderParams);
3248 
3249     Ttk_RegisterElementSpec(themePtr, "Treeview.treearea",
3250 	&TreeAreaElementSpec, 0);
3251     Ttk_RegisterElementSpec(themePtr, "Notebook.tab", &TabElementSpec, 0);
3252     Ttk_RegisterElementSpec(themePtr, "Notebook.client", &PaneElementSpec, 0);
3253 
3254     Ttk_RegisterElementSpec(themePtr, "Labelframe.border", &GroupElementSpec,
3255 	0);
3256     Ttk_RegisterElementSpec(themePtr, "Entry.field", &EntryElementSpec, 0);
3257     Ttk_RegisterElementSpec(themePtr, "Spinbox.field", &EntryElementSpec, 0);
3258 
3259     Ttk_RegisterElementSpec(themePtr, "separator", &SeparatorElementSpec, 0);
3260     Ttk_RegisterElementSpec(themePtr, "hseparator", &SeparatorElementSpec, 0);
3261     Ttk_RegisterElementSpec(themePtr, "vseparator", &SeparatorElementSpec, 0);
3262 
3263     Ttk_RegisterElementSpec(themePtr, "sizegrip", &SizegripElementSpec, 0);
3264 
3265     /*
3266      * <<NOTE-TRACKS>>
3267      * In some themes the Layouts for a progress bar has a trough element and a
3268      * pbar element.  But in our case the appearance manager draws both parts
3269      * of the progress bar, so we just have a single element called ".track".
3270      */
3271 
3272     Ttk_RegisterElementSpec(themePtr, "Progressbar.track", &PbarElementSpec,
3273 	0);
3274 
3275     Ttk_RegisterElementSpec(themePtr, "Scale.trough", &TrackElementSpec,
3276 	&ScaleData);
3277     Ttk_RegisterElementSpec(themePtr, "Scale.slider", &SliderElementSpec, 0);
3278 
3279     Ttk_RegisterElementSpec(themePtr, "Vertical.Scrollbar.trough",
3280 	&TroughElementSpec, 0);
3281     Ttk_RegisterElementSpec(themePtr, "Vertical.Scrollbar.thumb",
3282 	&ThumbElementSpec, 0);
3283     Ttk_RegisterElementSpec(themePtr, "Horizontal.Scrollbar.trough",
3284 	&TroughElementSpec, 0);
3285     Ttk_RegisterElementSpec(themePtr, "Horizontal.Scrollbar.thumb",
3286 	&ThumbElementSpec, 0);
3287 
3288     /*
3289      * If we are not in Snow Leopard or Lion the arrows won't actually be
3290      * displayed.
3291      */
3292 
3293     Ttk_RegisterElementSpec(themePtr, "Vertical.Scrollbar.uparrow",
3294 	&ArrowElementSpec, 0);
3295     Ttk_RegisterElementSpec(themePtr, "Vertical.Scrollbar.downarrow",
3296 	&ArrowElementSpec, 0);
3297     Ttk_RegisterElementSpec(themePtr, "Horizontal.Scrollbar.leftarrow",
3298 	&ArrowElementSpec, 0);
3299     Ttk_RegisterElementSpec(themePtr, "Horizontal.Scrollbar.rightarrow",
3300 	&ArrowElementSpec, 0);
3301 
3302     /*
3303      * Layouts:
3304      */
3305 
3306     Ttk_RegisterLayouts(themePtr, LayoutTable);
3307 
3308     Tcl_PkgProvide(interp, "ttk::theme::aqua", TTK_VERSION);
3309     return TCL_OK;
3310 }
3311 
3312 MODULE_SCOPE
Ttk_MacOSXPlatformInit(Tcl_Interp * interp)3313 int Ttk_MacOSXPlatformInit(
3314     Tcl_Interp *interp)
3315 {
3316     return AquaTheme_Init(interp);
3317 }
3318 
3319 /*
3320  * Local Variables:
3321  * mode: objc
3322  * c-basic-offset: 4
3323  * fill-column: 79
3324  * coding: utf-8
3325  * End:
3326  */
3327