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