summaryrefslogtreecommitdiffstats
path: root/macosx
diff options
context:
space:
mode:
Diffstat (limited to 'macosx')
-rw-r--r--macosx/tkMacOSXConstants.h3
-rw-r--r--macosx/tkMacOSXDraw.c2
-rw-r--r--macosx/tkMacOSXImage.c623
-rw-r--r--macosx/tkMacOSXInit.c118
-rw-r--r--macosx/tkMacOSXMenu.c3
-rw-r--r--macosx/tkMacOSXPrivate.h3
-rw-r--r--macosx/tkMacOSXRegion.c12
-rw-r--r--macosx/tkMacOSXSubwindows.c2
-rw-r--r--macosx/ttkMacOSXTheme.c2957
-rw-r--r--macosx/ttkMacOSXTheme.h570
10 files changed, 2920 insertions, 1373 deletions
diff --git a/macosx/tkMacOSXConstants.h b/macosx/tkMacOSXConstants.h
index 2341cb8..29bc391 100644
--- a/macosx/tkMacOSXConstants.h
+++ b/macosx/tkMacOSXConstants.h
@@ -79,6 +79,9 @@ typedef NSInteger NSModalResponse;
#define NSCursorUpdate NSEventTypeCursorUpdate
#define NSTexturedBackgroundWindowMask NSWindowStyleMaskTexturedBackground
#define NSCompositeCopy NSCompositingOperationCopy
+#define NSCompositeSourceOver NSCompositingOperationSourceOver
+#define NSCompositeSourceAtop NSCompositingOperationSourceAtop
+#define NSCompositeDestinationIn NSCompositingOperationDestinationIn
#define NSWarningAlertStyle NSAlertStyleWarning
#define NSInformationalAlertStyle NSAlertStyleInformational
#define NSCriticalAlertStyle NSAlertStyleCritical
diff --git a/macosx/tkMacOSXDraw.c b/macosx/tkMacOSXDraw.c
index 9f6966a..e8fb9dd 100644
--- a/macosx/tkMacOSXDraw.c
+++ b/macosx/tkMacOSXDraw.c
@@ -133,7 +133,7 @@ TkMacOSXGetNSImageFromTkImage(
{
Pixmap pixmap;
NSImage *nsImage;
- if (width == 0 || height == 0) {
+ if (width <= 0 || height <= 0) {
return nsImage = [[NSImage alloc] initWithSize:NSMakeSize(0,0)];
}
pixmap = Tk_GetPixmap(display, None, width, height, 0);
diff --git a/macosx/tkMacOSXImage.c b/macosx/tkMacOSXImage.c
index 28c73df..ad317b1 100644
--- a/macosx/tkMacOSXImage.c
+++ b/macosx/tkMacOSXImage.c
@@ -1,7 +1,8 @@
/*
* tkMacOSXImage.c --
*
- * The code in this file provides an interface for XImages,
+ * The code in this file provides an interface for XImages, and
+ * implements the nsimage image type.
*
* Copyright (c) 1995-1997 Sun Microsystems, Inc.
* Copyright (c) 2001-2009, Apple Inc.
@@ -114,7 +115,7 @@ _XInitImageFuncPtrs(
static void ReleaseData(
void *info,
TCL_UNUSED(const void *), /* data */
- TCL_UNUSED(size_t)) /* size */
+ TCL_UNUSED(size_t)) /* size */
{
ckfree(info);
}
@@ -384,7 +385,7 @@ ImagePutPixel(
XImage *
XCreateImage(
Display* display,
- TCL_UNUSED(Visual*), /* visual */
+ TCL_UNUSED(Visual*), /* visual */
unsigned int depth,
int format,
int offset,
@@ -727,7 +728,7 @@ XGetImage(
int y,
unsigned int width,
unsigned int height,
- TCL_UNUSED(unsigned long), /* plane_mask */
+ TCL_UNUSED(unsigned long), /* plane_mask */
int format)
{
NSBitmapImageRep* bitmapRep = nil;
@@ -1006,6 +1007,620 @@ XCopyPlane(
}
}
+/* ---------------------------------------------------------------------------*/
+
+/*
+ * Implementation of a Tk image type which provide access to NSImages
+ * for use in buttons etc.
+ */
+
+/*
+ * Forward declarations.
+ */
+
+typedef struct TkNSImageInstance TkNSImageInstance;
+typedef struct TkNSImageMaster TkNSImageMaster;
+
+/*
+ * The following data structure represents a particular use of an nsimage
+ * in a widget.
+ */
+
+struct TkNSImageInstance {
+ TkNSImageMaster *masterPtr; /* Pointer to the master for the image. */
+ NSImage *image; /* Pointer to a named NSImage.*/
+ TkNSImageInstance *nextPtr; /* First in the list of instances associated
+ * with this master. */
+};
+
+/*
+ * The following data structure represents the master for an nsimage:
+ */
+
+struct TkNSImageMaster {
+ Tk_ImageMaster tkMaster; /* Tk's token for image master. */
+ Tcl_Interp *interp; /* Interpreter for application. */
+ int width, height; /* Dimensions of the image. */
+ double alpha; /* Transparency, between 0.0 and 1.0*/
+ bool pressed; /* Image is for use in a pressed button.*/
+ char *imageName ; /* Malloc'ed image name. */
+ char *source; /* Malloc'ed name of the NSimage. */
+ char *as; /* Malloc'ed description of source */
+ int flags; /* Sundry flags, defined below. */
+ TkNSImageInstance *instancePtr; /* Start of list of instances associated
+ * with this master. */
+ NSImage *image; /* The underlying NSImage object. */
+ NSImage *darkModeImage; /* A modified image to use in Dark Mode. */
+};
+
+/*
+ * Bit definitions for the flags field of a TkNSImageMaster.
+ * IMAGE_CHANGED: 1 means that the instances of this image need
+ * to be redisplayed.
+ */
+
+#define IMAGE_CHANGED 1
+
+/*
+ * The type record for nsimage images:
+ */
+
+static int TkNSImageCreate(Tcl_Interp *interp,
+ const char *name, int argc, Tcl_Obj *const objv[],
+ const Tk_ImageType *typePtr, Tk_ImageMaster master,
+ ClientData *clientDataPtr);
+static ClientData TkNSImageGet(Tk_Window tkwin, ClientData clientData);
+static void TkNSImageDisplay(ClientData clientData,
+ Display *display, Drawable drawable,
+ int imageX, int imageY, int width,
+ int height, int drawableX,
+ int drawableY);
+static void TkNSImageFree(ClientData clientData, Display *display);
+static void TkNSImageDelete(ClientData clientData);
+
+static Tk_ImageType TkNSImageType = {
+ "nsimage", /* name of image type */
+ TkNSImageCreate, /* createProc */
+ TkNSImageGet, /* getProc */
+ TkNSImageDisplay, /* displayProc */
+ TkNSImageFree, /* freeProc */
+ TkNSImageDelete, /* deleteProc */
+ NULL, /* postscriptPtr */
+ NULL, /* nextPtr */
+ NULL
+};
+
+/*
+ * Information used for parsing configuration specifications:
+ */
+#define DEF_SOURCE ""
+#define DEF_AS "name"
+#define DEF_HEIGHT "32"
+#define DEF_WIDTH "32"
+#define DEF_ALPHA "1.0"
+#define DEF_PRESSED "0"
+
+static const Tk_OptionSpec systemImageOptions[] = {
+ {TK_OPTION_STRING, "-source", NULL, NULL, DEF_SOURCE,
+ -1, Tk_Offset(TkNSImageMaster, source), 0, NULL, 0},
+ {TK_OPTION_STRING, "-as", NULL, NULL, DEF_AS,
+ -1, Tk_Offset(TkNSImageMaster, as), 0, NULL, 0},
+ {TK_OPTION_INT, "-width", NULL, NULL, DEF_WIDTH,
+ -1, Tk_Offset(TkNSImageMaster, width), 0, NULL, 0},
+ {TK_OPTION_INT, "-height", NULL, NULL, DEF_HEIGHT,
+ -1, Tk_Offset(TkNSImageMaster, height), 0, NULL, 0},
+ {TK_OPTION_DOUBLE, "-alpha", NULL, NULL, DEF_ALPHA,
+ -1, Tk_Offset(TkNSImageMaster, alpha), 0, NULL, 0},
+ {TK_OPTION_BOOLEAN, "-pressed", NULL, NULL, DEF_PRESSED,
+ -1, Tk_Offset(TkNSImageMaster, pressed), 0, NULL, 0},
+ {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, -1, 0, NULL, 0}
+};
+
+/*
+ * The -as option specifies how the string provided in the -source
+ * option should be interpreted as a description of an NSImage.
+ * Below are the possible values and their meanings. (The last two
+ * provide the macOS icon for a particular file type.)
+ */
+
+static const char *sourceInterpretations[] = {
+ "name", /* A name for a named NSImage. */
+ "file", /* A path to an image file. */
+ "path", /* A path to a file whose type should be examined. */
+ "filetype", /* A file extension or 4-byte OSCode. */
+};
+
+enum {NAME_SOURCE, FILE_SOURCE, PATH_SOURCE, FILETYPE_SOURCE};
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TintImage --
+ *
+ * Modify an NSImage by blending it with a color. The transparent part of
+ * the image remains transparent. The opaque part of the image is painted
+ * with the color, using the specified alpha value for the transparency of
+ * the color.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The appearance of the NSImage changes.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void TintImage(
+ NSImage *image,
+ NSColor *color,
+ double alpha)
+{
+ NSSize size = [image size];
+ NSRect rect = {NSZeroPoint, size};
+ NSImage *mask = [[[NSImage alloc] initWithSize:size] retain];
+ [mask lockFocus];
+ [color set];
+ NSRectFillUsingOperation(rect, NSCompositeCopy);
+ [image drawInRect:rect
+ fromRect:rect
+ operation:NSCompositeDestinationIn
+ fraction:1.0];
+ [mask unlockFocus];
+ [image lockFocus];
+ [mask drawInRect:rect
+ fromRect:rect
+ operation:NSCompositeSourceOver
+ fraction:alpha];
+ [image unlockFocus];
+ [mask release];
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkNSImageConfigureMaster --
+ *
+ * This function is called when an nsimage image is created or
+ * reconfigured. It processes configuration options and resets any
+ * instances of the image.
+ *
+ * Results:
+ * A standard Tcl return value. If TCL_ERROR is returned then an error
+ * message is left in the masterPtr->interp's result.
+ *
+ * Side effects:
+ * Existing instances of the image will be redisplayed to match the new
+ * configuration options.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TkNSImageConfigureMaster(
+ Tcl_Interp *interp, /* Interpreter to use for reporting errors. */
+ TkNSImageMaster *masterPtr, /* Pointer to data structure describing
+ * overall photo image to (re)configure. */
+ int objc, /* Number of entries in objv. */
+ Tcl_Obj *const objv[]) /* Pairs of configuration options for image. */
+{
+ Tk_OptionTable optionTable = Tk_CreateOptionTable(interp, systemImageOptions);
+ NSImage *newImage;
+ Tcl_Obj *objPtr;
+ static Tcl_Obj *asOption = NULL;
+ int sourceInterpretation;
+
+ if (asOption == NULL) {
+ asOption = Tcl_NewStringObj("-as", -1);
+ Tcl_IncrRefCount(asOption);
+ }
+
+ if (Tk_SetOptions(interp, (char *) masterPtr, optionTable, objc, objv,
+ NULL, NULL, NULL) != TCL_OK){
+ goto errorExit;
+ }
+
+ if (masterPtr->source == NULL || masterPtr->source[0] == '0') {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("-source is required.", -1));
+ Tcl_SetErrorCode(interp, "TK", "IMAGE", "SYSTEM", "BAD_VALUE", NULL);
+ goto errorExit;
+ }
+
+ objPtr = Tk_GetOptionValue(interp, (char *) masterPtr, optionTable,
+ asOption, NULL);
+ if (Tcl_GetIndexFromObj(interp, objPtr, sourceInterpretations, "option",
+ 0, &sourceInterpretation) != TCL_OK) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "Unknown interpretation for source in -as option. "
+ "Should be name, file, path, or filetype.", -1));
+ Tcl_SetErrorCode(interp, "TK", "IMAGE", "SYSTEM", "BAD_VALUE", NULL);
+ goto errorExit;
+ }
+
+ NSString *source = [[NSString alloc] initWithUTF8String: masterPtr->source];
+ switch (sourceInterpretation) {
+ case NAME_SOURCE:
+ newImage = [[NSImage imageNamed:source] copy];
+ break;
+ case FILE_SOURCE:
+ newImage = [[NSImage alloc] initWithContentsOfFile:source];
+ break;
+ case PATH_SOURCE:
+ newImage = [[NSWorkspace sharedWorkspace] iconForFile:source];
+ break;
+ case FILETYPE_SOURCE:
+ newImage = [[NSWorkspace sharedWorkspace] iconForFileType:source];
+ break;
+ default:
+ newImage = NULL;
+ break;
+ }
+ [source release];
+ if (newImage) {
+ NSSize size = NSMakeSize(masterPtr->width, masterPtr->height);
+ [newImage setSize:size];
+ [masterPtr->image release];
+ [masterPtr->darkModeImage release];
+ masterPtr->image = [newImage retain];
+ masterPtr->darkModeImage = [[masterPtr->image copy] retain];
+ if ([masterPtr->darkModeImage isTemplate]) {
+
+ /*
+ * For a template image the Dark Mode version should be white.
+ */
+
+ NSRect rect = {NSZeroPoint, size};
+ [masterPtr->darkModeImage lockFocus];
+ [[NSColor whiteColor] set];
+ NSRectFillUsingOperation(rect, NSCompositeSourceAtop);
+ [masterPtr->darkModeImage unlockFocus];
+ } else if (masterPtr->pressed) {
+
+ /*
+ * Non-template pressed images are darker in Light Mode and lighter
+ * in Dark Mode.
+ */
+
+ TintImage(masterPtr->image, [NSColor blackColor], 0.2);
+ TintImage(masterPtr->darkModeImage, [NSColor whiteColor], 0.5);
+ }
+ } else {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("Unknown named NSImage.\n"
+ "Try omitting ImageName, "
+ "e.g. use NSCaution for NSImageNameCaution.", -1));
+ Tcl_SetErrorCode(interp, "TK", "IMAGE", "SYSTEM", "BAD_VALUE", NULL);
+ goto errorExit;
+ }
+
+ /*
+ * Inform the generic image code that the image has (potentially) changed.
+ */
+
+ Tk_ImageChanged(masterPtr->tkMaster, 0, 0, masterPtr->width,
+ masterPtr->height, masterPtr->width, masterPtr->height);
+ masterPtr->flags &= ~IMAGE_CHANGED;
+
+ return TCL_OK;
+
+ errorExit:
+ return TCL_ERROR;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkNSImageObjCmd --
+ *
+ * This function implements the configure and cget commands for an
+ * nsimage instance.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * The image may be reconfigured.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TkNSImageObjCmd(
+ ClientData clientData, /* Information about the image master. */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. */
+{
+ TkNSImageMaster *masterPtr = clientData;
+ Tk_OptionTable optionTable = Tk_CreateOptionTable(interp, systemImageOptions);
+ static const char *const options[] = {"cget", "configure", NULL};
+ enum {CGET, CONFIGURE};
+ Tcl_Obj *objPtr;
+ int index;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObjStruct(interp, objv[1], options,
+ sizeof(char *), "option", 0, &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ Tcl_Preserve(masterPtr);
+ switch (index) {
+ case CGET:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "option");
+ return TCL_ERROR;
+ }
+ objPtr = Tk_GetOptionValue(interp, (char *)masterPtr, optionTable,
+ objv[2], NULL);
+ if (objPtr == NULL) {
+ goto error;
+ }
+ Tcl_SetObjResult(interp, objPtr);
+ break;
+ case CONFIGURE:
+ if (objc == 2) {
+ objPtr = Tk_GetOptionInfo(interp, (char *)masterPtr, optionTable,
+ NULL, NULL);
+ if (objPtr == NULL) {
+ goto error;
+ }
+ Tcl_SetObjResult(interp, objPtr);
+ break;
+ } else if (objc == 3) {
+ objPtr = Tk_GetOptionInfo(interp, (char *)masterPtr, optionTable,
+ objv[2], NULL);
+ if (objPtr == NULL) {
+ goto error;
+ }
+ Tcl_SetObjResult(interp, objPtr);
+ break;
+ } else {
+ TkNSImageConfigureMaster(interp, masterPtr, objc - 2, objv + 2);
+ break;
+ }
+ default:
+ break;
+ }
+
+ Tcl_Release(masterPtr);
+ return TCL_OK;
+
+ error:
+ Tcl_Release(masterPtr);
+ return TCL_ERROR;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkNSImageCreate --
+ *
+ * Allocate and initialize an nsimage master.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * The data structure for a new image is allocated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TkNSImageCreate(
+ Tcl_Interp *interp, /* Interpreter for application using image. */
+ const char *name, /* Name to use for image. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[], /* Argument strings for options (not
+ * including image name or type). */
+ TCL_UNUSED(const Tk_ImageType *), /* typePtr */
+ Tk_ImageMaster master, /* Token for image, to be used in callbacks. */
+ ClientData *clientDataPtr) /* Store manager's token for image here; it
+ * will be returned in later callbacks. */
+{
+ TkNSImageMaster *masterPtr;
+ Tk_OptionTable optionTable = Tk_CreateOptionTable(interp, systemImageOptions);
+
+ masterPtr = ckalloc(sizeof(TkNSImageMaster));
+ masterPtr->tkMaster = master;
+ masterPtr->interp = interp;
+ masterPtr->imageName = ckalloc(strlen(name) + 1);
+ strcpy(masterPtr->imageName, name);
+ masterPtr->flags = 0;
+ masterPtr->instancePtr = NULL;
+ masterPtr->image = NULL;
+ masterPtr->darkModeImage = NULL;
+ masterPtr->source = NULL;
+ masterPtr->as = NULL;
+
+ /*
+ * Process configuration options given in the image create command.
+ */
+
+ if (Tk_InitOptions(interp, (char *) masterPtr, optionTable, NULL) != TCL_OK
+ || TkNSImageConfigureMaster(interp, masterPtr, objc, objv) != TCL_OK) {
+ TkNSImageDelete(masterPtr);
+ return TCL_ERROR;
+ }
+
+ Tcl_CreateObjCommand(interp, name, TkNSImageObjCmd, masterPtr, NULL);
+
+ *clientDataPtr = masterPtr;
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkNSImageGet --
+ *
+ * Allocate and initialize an nsimage instance.
+ *
+ * Results:
+ * The return value is a token for the image instance, which is used in
+ * future callbacks to ImageDisplay and ImageFree.
+ *
+ * Side effects:
+ * A new new nsimage instance is created.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static ClientData
+TkNSImageGet(
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ ClientData clientData) /* Pointer to TkNSImageMaster for image. */
+{
+ TkNSImageMaster *masterPtr = (TkNSImageMaster *) clientData;
+ TkNSImageInstance *instPtr;
+
+ instPtr = ckalloc(sizeof(TkNSImageInstance));
+ instPtr->masterPtr = masterPtr;
+ return instPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkNSImageDisplay --
+ *
+ * Display or redisplay an nsimage in the given drawable.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The image gets drawn.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TkNSImageDisplay(
+ ClientData clientData, /* Pointer to TkNSImageInstance for image. */
+ TCL_UNUSED(Display *), /* display */
+ Drawable drawable, /* Where to draw or redraw image. */
+ int imageX, int imageY, /* Origin of area to redraw, relative to
+ * origin of image. */
+ int width, int height, /* Dimensions of area to redraw. */
+ int drawableX, int drawableY)
+ /* Coordinates in drawable corresponding to
+ * imageX and imageY. */
+{
+ MacDrawable *macWin = (MacDrawable *) drawable;
+ Tk_Window tkwin = (Tk_Window) macWin->winPtr;
+ TkNSImageInstance *instPtr = (TkNSImageInstance *) clientData;
+ TkNSImageMaster *masterPtr = instPtr->masterPtr;
+ TkMacOSXDrawingContext dc;
+ NSRect dstRect = NSMakeRect(macWin->xOff + drawableX,
+ macWin->yOff + drawableY, width, height);
+ NSRect srcRect = NSMakeRect(imageX, imageY, width, height);
+ NSImage *image = TkMacOSXInDarkMode(tkwin) ? masterPtr->darkModeImage :
+ masterPtr->image;
+
+ if (TkMacOSXSetupDrawingContext(drawable, NULL, &dc)) {
+ if (dc.context) {
+ NSGraphicsContext *savedContext = NSGraphicsContext.currentContext;
+ NSGraphicsContext.currentContext = GET_NSCONTEXT(dc.context, YES);
+ [image drawInRect:dstRect
+ fromRect:srcRect
+ operation:NSCompositeSourceOver
+ fraction:masterPtr->alpha
+ respectFlipped:YES
+ hints:nil];
+ NSGraphicsContext.currentContext = savedContext;
+ }
+ TkMacOSXRestoreDrawingContext(&dc);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkNSImageFree --
+ *
+ * Deallocate an instance of an nsimage.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Information related to the instance is freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TkNSImageFree(
+ ClientData clientData, /* Pointer to TkNSImageInstance for instance. */
+ TCL_UNUSED(Display *)) /* display */
+{
+ TkNSImageInstance *instPtr = (TkNSImageInstance *) clientData;
+ ckfree(instPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkNSImageDelete --
+ *
+ * Deallocate an nsimage master.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * NSImages are released and memory is freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TkNSImageDelete(
+ ClientData clientData) /* Pointer to TkNSImageMaster for image. When
+ * this function is called, no more instances
+ * exist. */
+{
+ TkNSImageMaster *masterPtr = (TkNSImageMaster *) clientData;
+
+ Tcl_DeleteCommand(masterPtr->interp, masterPtr->imageName);
+ ckfree(masterPtr->imageName);
+ ckfree(masterPtr->source);
+ ckfree(masterPtr->as);
+ [masterPtr->image release];
+ [masterPtr->darkModeImage release];
+ ckfree(masterPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXNSImage_Init --
+ *
+ * Adds the TkNSImage type to Tk.
+ *
+ * Results:
+ * Returns a standard Tcl completion code, and leaves an error message in
+ * the interp's result if an error occurs.
+ *
+ * Side effects:
+ * Creates the command:
+ * image create system -source ?-width? ?-height? ?-alpha? ?-pressed?
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkMacOSXNSImage_Init(
+ TCL_UNUSED(Tcl_Interp *)) /* interp */
+{
+ Tk_CreateImageType(&TkNSImageType);
+ return 1;
+}
+
/*
* Local Variables:
* mode: objc
diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c
index dfdde24..375d20e 100644
--- a/macosx/tkMacOSXInit.c
+++ b/macosx/tkMacOSXInit.c
@@ -33,7 +33,9 @@ static char scriptPath[PATH_MAX + 1] = "";
* Forward declarations...
*/
-static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
+static int TkMacOSXGetAppPathObjCmd(TCL_UNUSED(void *), Tcl_Interp *ip,
+ int objc, Tcl_Obj *const objv[]);
+static int TkMacOSVersionObjCmd(ClientData cd, Tcl_Interp *ip,
int objc, Tcl_Obj *const objv[]);
#pragma mark TKApplication(TKInit)
@@ -42,7 +44,6 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
@synthesize poolLock = _poolLock;
@synthesize macOSVersion = _macOSVersion;
@synthesize isDrawing = _isDrawing;
-@synthesize needsToDraw = _needsToDraw;
@synthesize isSigned = _isSigned;
@synthesize tkLiveResizeEnded = _tkLiveResizeEnded;
@synthesize tkPointerWindow = _tkPointerWindow;
@@ -145,6 +146,7 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
/*
* Initialize the graphics context.
*/
+
TkMacOSXUseAntialiasedText(_eventInterp, -1);
TkMacOSXInitCGDrawing(_eventInterp, TRUE, 0);
@@ -156,6 +158,12 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
[self _setupMenus];
/*
+ * Run initialization routines that depend on the OS version.
+ */
+
+ Ttk_MacOSXInit();
+
+ /*
* It is not safe to force activation of the NSApp until this method is
* called. Activating too early can cause the menu bar to be unresponsive.
* The call to activateIgnoringOtherApps was moved here to avoid this.
@@ -563,12 +571,13 @@ TkpInit(
}
/*
- * Initialize the NSServices object here. Apple's docs say to do this
- * in applicationDidFinishLaunching, but the Tcl interpreter is not
- * initialized until this function call.
+ * Now we can run initialization routines which require that both the
+ * NSApplication and the Tcl interpreter have been created and
+ * initialized.
*/
TkMacOSXServices_Init(interp);
+ TkMacOSXNSImage_Init(interp);
/*
* The root window has been created and mapped, but XMapWindow deferred its
@@ -604,7 +613,6 @@ TkpInit(
signal(SIGHUP, TkMacOSXSignalHandler);
signal(SIGTERM, TkMacOSXSignalHandler);
}
-
/*
* Initialization steps that are needed for all interpreters.
*/
@@ -612,18 +620,66 @@ TkpInit(
if (tkLibPath[0] != '\0') {
Tcl_SetVar2(interp, "tk_library", NULL, tkLibPath, TCL_GLOBAL_ONLY);
}
-
if (scriptPath[0] != '\0') {
Tcl_SetVar2(interp, "auto_path", NULL, scriptPath,
TCL_GLOBAL_ONLY|TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
}
-
Tcl_CreateObjCommand(interp, "::tk::mac::standardAboutPanel",
TkMacOSXStandardAboutPanelObjCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "::tk::mac::iconBitmap",
TkMacOSXIconBitmapObjCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "::tk::mac::GetAppPath",
- TkMacOSXGetAppPathCmd, NULL, NULL);
+ TkMacOSXGetAppPathObjCmd, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tk::mac::macOSVersion",
+ TkMacOSVersionObjCmd, NULL, NULL);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXGetAppPathObjCmd --
+ *
+ * Returns the path of the Wish application bundle.
+ *
+ * Results:
+ * Returns the application path.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TkMacOSXGetAppPathObjCmd(
+ TCL_UNUSED(void *),
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ if (objc != 1) {
+ Tcl_WrongNumArgs(interp, 1, objv, NULL);
+ return TCL_ERROR;
+ }
+
+ /*
+ * Get the application path URL and convert it to a string path reference.
+ */
+
+ CFURLRef mainBundleURL = CFBundleCopyBundleURL(CFBundleGetMainBundle());
+ CFStringRef appPath =
+ CFURLCopyFileSystemPath(mainBundleURL, kCFURLPOSIXPathStyle);
+
+ /*
+ * Convert (and copy) the string reference into a Tcl result.
+ */
+
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ CFStringGetCStringPtr(appPath, CFStringGetSystemEncoding()), -1));
+
+ CFRelease(mainBundleURL);
+ CFRelease(appPath);
MacSystrayInit(interp);
MacPrint_Init(interp);
@@ -670,48 +726,37 @@ TkpGetAppName(
/*
*----------------------------------------------------------------------
*
- * TkMacOSXGetAppPathCmd --
+ * TkMacOSVersionObjCmd --
*
- * Returns the path of the Wish application bundle.
+ * Tcl command which returns an integer encoding the major and minor
+ * version numbers of the currently running operating system in the
+ * form 10000*majorVersion + 100*minorVersion.
*
* Results:
- * Returns the application path.
+ * Returns the OS version.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
-
+
static int
-TkMacOSXGetAppPathCmd(
- TCL_UNUSED(void *),
+TkMacOSVersionObjCmd(
+ TCL_UNUSED(void *), /* ClientData */
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
- if (objc != 1) {
+ static char version[16] = "";
+ if (objc > 1) {
Tcl_WrongNumArgs(interp, 1, objv, NULL);
return TCL_ERROR;
}
-
- /*
- * Get the application path URL and convert it to a string path reference.
- */
-
- CFURLRef mainBundleURL = CFBundleCopyBundleURL(CFBundleGetMainBundle());
- CFStringRef appPath =
- CFURLCopyFileSystemPath(mainBundleURL, kCFURLPOSIXPathStyle);
-
- /*
- * Convert (and copy) the string reference into a Tcl result.
- */
-
- Tcl_SetObjResult(interp, Tcl_NewStringObj(
- CFStringGetCStringPtr(appPath, CFStringGetSystemEncoding()), -1));
-
- CFRelease(mainBundleURL);
- CFRelease(appPath);
+ if (version[0] == '\0') {
+ snprintf(version, 16, "%d", [NSApp macOSVersion]);
+ }
+ Tcl_SetResult(interp, version, NULL);
return TCL_OK;
}
@@ -818,10 +863,11 @@ TkMacOSXDefaultStartupScript(void)
MODULE_SCOPE void*
TkMacOSXGetNamedSymbol(
- TCL_UNUSED(const char *),
- const char *symbol)
+ const char* module,
+ const char* symbol)
{
void *addr = dlsym(RTLD_NEXT, symbol);
+ (void)module;
if (!addr) {
(void) dlerror(); /* Clear dlfcn error state */
diff --git a/macosx/tkMacOSXMenu.c b/macosx/tkMacOSXMenu.c
index 346ee47..8ffccfa 100644
--- a/macosx/tkMacOSXMenu.c
+++ b/macosx/tkMacOSXMenu.c
@@ -164,7 +164,6 @@ static int ModifierCharWidth(Tk_Font tkfont);
TKBackgroundLoop *backgroundLoop = nil;
-
#pragma mark TKMenu
/*
@@ -490,8 +489,6 @@ TKBackgroundLoop *backgroundLoop = nil;
}
backgroundLoop = [[TKBackgroundLoop alloc] init];
[backgroundLoop start];
- //TkMacOSXClearMenubarActive();
- //TkMacOSXPreprocessMenu();
}
- (void) menuEndTracking: (NSNotification *) notification
diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h
index d546c49..03bbdb6 100644
--- a/macosx/tkMacOSXPrivate.h
+++ b/macosx/tkMacOSXPrivate.h
@@ -292,10 +292,12 @@ MODULE_SCOPE int TkMacOSXServices_Init(Tcl_Interp *interp);
MODULE_SCOPE int TkMacOSXRegisterServiceWidgetObjCmd(ClientData clientData,
Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
MODULE_SCOPE unsigned TkMacOSXAddVirtual(unsigned int keycode);
+MODULE_SCOPE int TkMacOSXNSImage_Init(Tcl_Interp *interp);
MODULE_SCOPE void TkMacOSXWinNSBounds(TkWindow *winPtr, NSView *view,
NSRect *bounds);
MODULE_SCOPE Bool TkMacOSXInDarkMode(Tk_Window tkwin);
MODULE_SCOPE void TkMacOSXDrawAllViews(ClientData clientData);
+MODULE_SCOPE void Ttk_MacOSXInit(void);
MODULE_SCOPE unsigned long TkMacOSXClearPixel(void);
MODULE_SCOPE int MacSystrayInit(Tcl_Interp *);
MODULE_SCOPE int MacPrint_Init(Tcl_Interp *);
@@ -334,6 +336,7 @@ VISIBILITY_HIDDEN
Bool _tkLiveResizeEnded;
#ifdef __i386__
+
/* The Objective C runtime used on i386 requires this. */
int _poolLock;
int _macOSVersion; /* 10000 * major + 100*minor */
diff --git a/macosx/tkMacOSXRegion.c b/macosx/tkMacOSXRegion.c
index b44073a..0537c7b 100644
--- a/macosx/tkMacOSXRegion.c
+++ b/macosx/tkMacOSXRegion.c
@@ -586,9 +586,9 @@ TkMacOSXCountRectsInRegion(
{
int rect_count = 0;
if (!HIShapeIsEmpty(shape)) {
- ChkErr(HIShapeEnumerate, shape,
- kHIShapeParseFromBottom|kHIShapeParseFromLeft,
- rectCounter, &rect_count);
+ HIShapeEnumerate(shape,
+ kHIShapeParseFromBottom|kHIShapeParseFromLeft,
+ (HIShapeEnumerateProcPtr) rectCounter, (void *) &rect_count);
}
return rect_count;
}
@@ -598,9 +598,9 @@ TkMacOSXPrintRectsInRegion(
HIShapeRef shape)
{
if (!HIShapeIsEmpty(shape)) {
- ChkErr(HIShapeEnumerate, shape,
- kHIShapeParseFromBottom|kHIShapeParseFromLeft,
- rectPrinter, NULL);
+ HIShapeEnumerate( shape,
+ kHIShapeParseFromBottom|kHIShapeParseFromLeft,
+ (HIShapeEnumerateProcPtr) rectPrinter, NULL);
}
}
diff --git a/macosx/tkMacOSXSubwindows.c b/macosx/tkMacOSXSubwindows.c
index e8d8b22..afadb32 100644
--- a/macosx/tkMacOSXSubwindows.c
+++ b/macosx/tkMacOSXSubwindows.c
@@ -361,7 +361,7 @@ XUnmapWindow(
if (parentPtr && parentPtr->privatePtr->visRgn) {
TkMacOSXInvalidateViewRegion(
- TkMacOSXGetNSViewForDrawable(parentPtr->privatePtr),
+ TkMacOSXGetNSViewForDrawable(parentPtr->window),
parentPtr->privatePtr->visRgn);
}
TkMacOSXInvalClipRgns((Tk_Window)parentPtr);
diff --git a/macosx/ttkMacOSXTheme.c b/macosx/ttkMacOSXTheme.c
index 863657b..7c8628b 100644
--- a/macosx/ttkMacOSXTheme.c
+++ b/macosx/ttkMacOSXTheme.c
@@ -3,12 +3,12 @@
*
* Tk theme engine for Mac OSX, using the Appearance Manager API.
*
- * Copyright © 2004 Joe English
- * Copyright © 2005 Neil Madden
- * Copyright © 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
- * Copyright © 2008-2009 Apple Inc.
- * Copyright © 2009 Kevin Walzer/WordTech Communications LLC.
- * Copyright © 2019 Marc Culler
+ * Copyright (c) 2004 Joe English
+ * Copyright (c) 2005 Neil Madden
+ * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
+ * Copyright (c) 2008-2009 Apple Inc.
+ * Copyright (c) 2009 Kevin Walzer/WordTech Communications LLC.
+ * Copyright (c) 2019 Marc Culler
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -31,133 +31,157 @@
#include "tkMacOSXPrivate.h"
#include "ttk/ttkTheme.h"
+#include "ttkMacOSXTheme.h"
+#include "tkColor.h"
+#include <math.h>
+
+static NSColor *controlAccentColor(void) {
+ static int accentPixel = -1;
+ if (accentPixel == -1) {
+ TkColor *temp = TkpGetColor(NULL, "systemControlAccentColor");
+ accentPixel = temp->color.pixel;
+ ckfree(temp);
+ }
+ return TkMacOSXGetNSColor(NULL, accentPixel);
+}
/*
- * Macros for handling drawing contexts.
+ * Values which depend on the OS version. These are initialized
+ * in Ttk_MacOSXInit.
*/
-#define BEGIN_DRAWING(d) { \
- TkMacOSXDrawingContext dc; \
- if (!TkMacOSXSetupDrawingContext((d), NULL, &dc)) { \
- return; \
- } \
+static Ttk_Padding entryElementPadding;
+static CGFloat Ttk_ContrastDelta;
-#define END_DRAWING \
- TkMacOSXRestoreDrawingContext(&dc);}
+/*----------------------------------------------------------------------
+ * +++ ComputeButtonDrawInfo --
+ *
+ * Fill in an appearance manager HIThemeButtonDrawInfo record
+ * from a Ttk state and the ThemeButtonParams used as the
+ * clientData.
+ */
-#define HIOrientation kHIThemeOrientationNormal
-#define NoThemeMetric 0xFFFFFFFF
+static inline HIThemeButtonDrawInfo ComputeButtonDrawInfo(
+ ThemeButtonParams *params,
+ Ttk_State state,
+ TCL_UNUSED(Tk_Window))
+{
+ /*
+ * See ButtonElementDraw for the explanation of why we always draw
+ * some buttons in the active state.
+ */
-#ifdef __LP64__
-#define RangeToFactor(maximum) (((double) (INT_MAX >> 1)) / (maximum))
-#else
-#define RangeToFactor(maximum) (((double) (LONG_MAX >> 1)) / (maximum))
-#endif /* __LP64__ */
+ SInt32 HIThemeState;
+ int adornment = 0;
-#define TTK_STATE_FIRST_TAB TTK_STATE_USER1
-#define TTK_STATE_LAST_TAB TTK_STATE_USER2
-#define TTK_TREEVIEW_STATE_SORTARROW TTK_STATE_USER1
+ HIThemeState = Ttk_StateTableLookup(ThemeStateTable, state);
-/*
- * Colors and gradients used in Dark Mode.
- */
+ /*
+ * HITheme uses the adornment to decide the direction of the
+ * arrow on a Disclosure Button. Also HITheme draws inactive
+ * (TTK_STATE_BACKGROUND) buttons in a gray color but macOS
+ * no longer does that. So we adjust the HIThemeState.
+ */
-static CGFloat darkButtonFace[4] = {
- 90.0 / 255, 86.0 / 255, 95.0 / 255, 1.0
-};
-static CGFloat darkPressedButtonFace[4] = {
- 114.0 / 255, 110.0 / 255, 118.0 / 255, 1.0
-};
-static CGFloat darkPressedBevelFace[4] = {
- 135.0 / 255, 136.0 / 255, 138.0 / 255, 1.0
-};
-static CGFloat darkSelectedBevelFace[4] = {
- 162.0 / 255, 163.0 / 255, 165.0 / 255, 1.0
-};
-static CGFloat darkDisabledButtonFace[4] = {
- 86.0 / 255, 87.0 / 255, 89.0 / 255, 1.0
-};
-static CGFloat darkInactiveSelectedTab[4] = {
- 159.0 / 255, 160.0 / 255, 161.0 / 255, 1.0
-};
-static CGFloat darkSelectedTab[4] = {
- 97.0 / 255, 94.0 / 255, 102.0 / 255, 1.0
-};
-static CGFloat darkTab[4] = {
- 44.0 / 255, 41.0 / 255, 50.0 / 255, 1.0
-};
-static CGFloat darkFocusRing[4] = {
- 38.0 / 255, 113.0 / 255, 159.0 / 255, 1.0
-};
-static CGFloat darkFocusRingTop[4] = {
- 50.0 / 255, 124.0 / 255, 171.0 / 255, 1.0
-};
-static CGFloat darkFocusRingBottom[4] = {
- 57.0 / 255, 130.0 / 255, 176.0 / 255, 1.0
-};
-static CGFloat darkTabSeparator[4] = {0.0, 0.0, 0.0, 0.25};
-static CGFloat darkTrack[4] = {1.0, 1.0, 1.0, 0.25};
-static CGFloat darkFrameTop[4] = {1.0, 1.0, 1.0, 0.0625};
-static CGFloat darkFrameBottom[4] = {1.0, 1.0, 1.0, 0.125};
-static CGFloat darkFrameAccent[4] = {0.0, 0.0, 0.0, 0.0625};
-static CGFloat darkTopGradient[8] = {
- 1.0, 1.0, 1.0, 0.3,
- 1.0, 1.0, 1.0, 0.0
-};
-static CGFloat darkBackgroundGradient[8] = {
- 0.0, 0.0, 0.0, 0.1,
- 0.0, 0.0, 0.0, 0.25
-};
-static CGFloat darkInactiveGradient[8] = {
- 89.0 / 255, 90.0 / 255, 93.0 / 255, 1.0,
- 119.0 / 255, 120.0 / 255, 122.0 / 255, 1.0
-};
-static CGFloat darkSelectedGradient[8] = {
- 23.0 / 255, 111.0 / 255, 232.0 / 255, 1.0,
- 20.0 / 255, 94.0 / 255, 206.0 / 255, 1.0
-};
-static CGFloat pressedPushButtonGradient[8] = {
- 35.0 / 255, 123.0 / 255, 244.0 / 255, 1.0,
- 30.0 / 255, 114.0 / 255, 235.0 / 255, 1.0
-};
+ switch (params->kind) {
+ case kThemeArrowButton:
+ adornment = kThemeAdornmentDrawIndicatorOnly;
+ if (state & TTK_STATE_SELECTED) {
+ adornment |= kThemeAdornmentArrowUpArrow;
+ }
+ /* Fall through. */
+ case kThemeRadioButton:
+ /*
+ * The gray color is better than the blue color for a
+ * background selected Radio Button.
+ */
+
+ if (state & TTK_STATE_SELECTED) {
+ break;
+ }
+ default:
+ if (state & TTK_STATE_BACKGROUND) {
+ HIThemeState |= kThemeStateActive;
+ }
+ break;
+ }
+
+ const HIThemeButtonDrawInfo info = {
+ .version = 0,
+ .state = HIThemeState,
+ .kind = params ? params->kind : 0,
+ .value = Ttk_StateTableLookup(ButtonValueTable, state),
+ .adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state) | adornment,
+ };
+ return info;
+}
/*
- * When building on systems earlier than 10.8 there is no reasonable way to
- * convert an NSColor to a CGColor. We do run-time checking of the OS version,
- * and never need the CGColor property on older systems, so we can use this
- * CGCOLOR macro, which evaluates to NULL without raising compiler warnings.
- * Similarly, we never draw rounded rectangles on older systems which did not
- * have CGPathCreateWithRoundedRect, so we just redefine it to return NULL.
+ * When we draw simulated Apple widgets we use the Core Graphics framework.
+ * Core Graphics uses CGColorRefs, not NSColors. A CGColorRef must be retained
+ * and released explicitly while an NSColor is autoreleased. In version 10.8
+ * of macOS Apple introduced a CGColor property of an NSColor which is guaranteed
+ * to be a valid CGColorRef for (approximately) the same color and is released
+ * when the NSColor is autoreleased.
+ *
+ * When building on systems earlier than 10.8 there is no painless way to
+ * convert an NSColor to a CGColor. On the other hand, on those systems we use
+ * the HIToolbox to draw all widgets, so we never need to call Core Graphics
+ * drawing routines directly. This means that the functions and macros below
+ * which construct CGColorRefs can be defined to return nil on systems before
+ * 10.8.
+ *
+ * Similarly, those older systems did not have CGPathCreateWithRoundedRect, but
+ * since we never need to draw rounded rectangles on those systems we can just
+ * define it to return nil.
*/
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
+static CGColorRef
+CGColorFromRGBA(
+ CGFloat *rgba)
+{
+ NSColorSpace *colorSpace = [NSColorSpace sRGBColorSpace];
+ NSColor *nscolor = [NSColor colorWithColorSpace: colorSpace
+ components: rgba
+ count: 4];
+ return nscolor.CGColor;
+}
+
+static CGColorRef
+CGColorFromGray(
+ GrayColor g)
+{
+ CGFloat rgba[4] = {g.grayscale, g.grayscale, g.grayscale, g.alpha};
+ NSColorSpace *colorSpace = [NSColorSpace sRGBColorSpace];
+ NSColor *nscolor = [NSColor colorWithColorSpace: colorSpace
+ components: rgba
+ count: 4];
+ return nscolor.CGColor;
+}
+
#define CGCOLOR(nscolor) nscolor.CGColor
+
#else
-#define CGCOLOR(nscolor) (0 ? (CGColorRef) nscolor : NULL)
-#define CGPathCreateWithRoundedRect(w, x, y, z) NULL
-#endif
-/*
- * If we try to draw a rounded rectangle with too large of a radius
- * CoreGraphics will raise a fatal exception. This macro returns if
- * the width or height is less than twice the radius. Presumably this
- * only happens when a widget has not yet been configured and has size
- * 1x1.
- */
+#define CGCOLOR(nscolor) nil
+#define CGColorFromRGBA(rgba) nil
+#define CGColorFromGray(gray) nil
+#define CGPathCreateWithRoundedRect(w, x, y, z) nil
-#define CHECK_RADIUS(radius, bounds) \
- if (radius > bounds.size.width / 2 || radius > bounds.size.height / 2) { \
- return; \
- }
+#endif
/*----------------------------------------------------------------------
* +++ Utilities.
*/
-/*
+/*----------------------------------------------------------------------
* BoxToRect --
- * Convert a Ttk_Box in Tk coordinates relative to the given Drawable
- * to a native Rect relative to the containing port.
+ *
+ * Convert a Ttk_Box in Tk coordinates relative to the given Drawable to a
+ * native CGRect relative to the containing NSView. (The coordinate system
+ * is the one used by CGContextRef, which has origin at the upper left
+ * corner, and y increasing downward.)
*/
static inline CGRect BoxToRect(
@@ -175,60 +199,85 @@ static inline CGRect BoxToRect(
return rect;
}
-/*
- * Table mapping Tk states to Appearance manager ThemeStates
+/*----------------------------------------------------------------------
+ * LookupGrayPalette
+ *
+ * Retrieves the palette of grayscale colors needed to draw a particular
+ * type of button, in a particular state, in light or dark mode.
+ *
*/
-static Ttk_StateTable ThemeStateTable[] = {
- {kThemeStateActive, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
- {kThemeStateUnavailable, TTK_STATE_DISABLED, 0},
- {kThemeStatePressed, TTK_STATE_PRESSED, 0},
- {kThemeStateInactive, TTK_STATE_BACKGROUND, 0},
- {kThemeStateActive, 0, 0}
-
- /* Others: Not sure what these are supposed to mean. Up/Down have
- * something to do with "little arrow" increment controls... Dunno what
- * a "Rollover" is.
- * NEM: Rollover is TTK_STATE_ACTIVE... but we don't handle that yet, by
- * the looks of things
- *
- * {kThemeStateRollover, 0, 0},
- * {kThemeStateUnavailableInactive, 0, 0}
- * {kThemeStatePressedUp, 0, 0},
- * {kThemeStatePressedDown, 0, 0}
- */
-};
+static GrayPalette LookupGrayPalette(
+ ButtonDesign *design,
+ unsigned int state,
+ int isDark)
+{
+ PaletteStateTable *entry = design->palettes;
+ while ((state & entry->onBits) != entry->onBits ||
+ (~state & entry->offBits) != entry->offBits)
+ {
+ ++entry;
+ }
+ return isDark ? entry->dark : entry->light;
+}
/*----------------------------------------------------------------------
* NormalizeButtonBounds --
*
- * Apple's Human Interface Guidelines only allow three specific heights
- * for most buttons: Regular, small and mini. We always use the regular
- * size. However, Ttk may provide an arbitrary bounding rectangle. We
- * always draw the button centered vertically on the rectangle, and
- * having the same width as the rectangle. This function returns the
- * actual bounding rectangle that will be used in drawing the button.
+ * This function returns the actual bounding rectangle that will be used
+ * in drawing the button.
+ *
+ * Apple only allows three specific heights for most buttons: regular,
+ * small and mini. We always use the regular size. However, Ttk may
+ * provide a bounding rectangle with arbitrary height. We draw the Mac
+ * button centered vertically in the Ttk rectangle, with the same width as
+ * the rectangle. (But we take care to produce an integer y coordinate,
+ * to avoid unexpected anti-aliasing.)
+ *
+ * In addition, the button types which are not known to HIToolbox need some
+ * adjustments to their bounds.
*
- * The BevelButton is allowed to have arbitrary size, and also has
- * external padding. This is handled separately here.
*/
static CGRect NormalizeButtonBounds(
- SInt32 heightMetric,
- CGRect bounds)
+ ThemeButtonParams *params,
+ CGRect bounds,
+ int isDark)
{
SInt32 height;
- if (heightMetric != (SInt32) NoThemeMetric) {
- ChkErr(GetThemeMetric, heightMetric, &height);
- bounds.origin.y += (bounds.size.height - height) / 2;
+ if (params->heightMetric != NoThemeMetric) {
+ ChkErr(GetThemeMetric, params->heightMetric, &height);
+ height += 2;
+ bounds.origin.y += round(1 + (bounds.size.height - height) / 2);
bounds.size.height = height;
}
+ switch (params->kind) {
+ case TkRoundedRectButton:
+ bounds.size.height -= 1;
+ break;
+ case TkInlineButton:
+ bounds.size.height -= 4;
+ bounds.origin.y += 1;
+ break;
+ case TkRecessedButton:
+ bounds.size.height -= 2;
+ break;
+ case kThemeRoundButtonHelp:
+ if (isDark) {
+ bounds.size.height = bounds.size.width = 22;
+ } else {
+ bounds.size.height = bounds.size.width = 22;
+ }
+ break;
+ default:
+ break;
+ }
return bounds;
}
/*----------------------------------------------------------------------
- * +++ Backgrounds
+ * +++ Background Colors
*
* Support for contrasting background colors when GroupBoxes or Tabbed
* panes are nested inside each other. Early versions of macOS used ridged
@@ -243,31 +292,31 @@ static CGRect NormalizeButtonBounds(
* support Dark Mode anyway.
*/
-static const CGFloat WINDOWBACKGROUND[4] = {
- 235.0 / 255, 235.0 / 255, 235.0 / 255, 1.0
-};
-static const CGFloat WHITERGBA[4] = {1.0, 1.0, 1.0, 1.0};
-static const CGFloat BLACKRGBA[4] = {0.0, 0.0, 0.0, 1.0};
+RGBACOLOR windowBackground[4] = RGBA256(235.0, 235.0, 235.0, 1.0);
/*----------------------------------------------------------------------
* GetBackgroundColor --
*
* Fills the array rgba with the color coordinates for a background color.
- * Start with the background color of a window's geometry container, or
- * the standard ttk window background if there is no container. If the
+ * Start with the background color of a window's container, or the
+ * standard ttk window background if there is no container. If the
* contrast parameter is nonzero, modify this color to be darker, for the
* aqua appearance, or lighter for the DarkAqua appearance. This is
- * primarily used by the Fill and Background elements.
+ * primarily used by the Fill and Background elements. The save parameter
+ * is normally YES, so the contrasting color is saved in the private
+ * data of the widget. This behavior can be disabled in special cases,
+ * such as when drawing notebook tabs in macOS 11.
*/
-static void GetBackgroundColor(
+static void GetBackgroundColorRGBA(
TCL_UNUSED(CGContextRef),
Tk_Window tkwin,
int contrast,
+ Bool save,
CGFloat *rgba)
{
- TkWindow *winPtr = (TkWindow *)tkwin;
- TkWindow *containerPtr = (TkWindow *)TkGetContainer(tkwin);
+ TkWindow *winPtr = (TkWindow *) tkwin;
+ TkWindow *containerPtr = (TkWindow *) TkGetContainer(tkwin);
while (containerPtr && containerPtr->privatePtr) {
if (containerPtr->privatePtr->flags & TTK_HAS_CONTRASTING_BG) {
@@ -287,23 +336,24 @@ static void GetBackgroundColor(
[windowColor getComponents: rgba];
} else {
for (int i = 0; i < 4; i++) {
- rgba[i] = WINDOWBACKGROUND[i];
+ rgba[i] = windowBackground[i];
}
}
}
+
if (contrast) {
int isDark = (rgba[0] + rgba[1] + rgba[2] < 1.5);
if (isDark) {
for (int i = 0; i < 3; i++) {
- rgba[i] += 8.0 / 255.0;
+ rgba[i] += Ttk_ContrastDelta*contrast / 255.0;
}
} else {
for (int i = 0; i < 3; i++) {
- rgba[i] -= 8.0 / 255.0;
+ rgba[i] -= Ttk_ContrastDelta*contrast / 255.0;
}
}
- if (winPtr->privatePtr) {
+ if (save && winPtr->privatePtr) {
winPtr->privatePtr->flags |= TTK_HAS_CONTRASTING_BG;
for (int i = 0; i < 4; i++) {
winPtr->privatePtr->fillRGBA[i] = rgba[i];
@@ -312,12 +362,313 @@ static void GetBackgroundColor(
}
}
+static CGColorRef GetBackgroundCGColor(
+ CGContextRef context,
+ Tk_Window tkwin,
+ int contrast,
+ Bool save)
+{
+ CGFloat rgba[4];
+ GetBackgroundColorRGBA(context, tkwin, contrast, save, rgba);
+ return CGColorFromRGBA(rgba);
+}
+
+/*----------------------------------------------------------------------
+ * +++ Buttons
+ */
+
+/*----------------------------------------------------------------------
+ * FillRoundedRectangle --
+ *
+ * Fill a rounded rectangle with a specified solid color.
+ */
+
+static void FillRoundedRectangle(
+ CGContextRef context,
+ CGRect bounds,
+ CGFloat radius,
+ CGColorRef color)
+{
+ CGPathRef path;
+ CHECK_RADIUS(radius, bounds)
+
+ path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
+ if (!path) {
+ return;
+ }
+ CGContextSetFillColorWithColor(context, color);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextFillPath(context);
+ CFRelease(path);
+}
+
+/*----------------------------------------------------------------------
+ * FillBorder --
+ *
+ * Draw a 1-pixel border around a rounded rectangle using a 3-step
+ * gradient of shades of gray.
+ */
+
+static void FillBorder(
+ CGContextRef context,
+ CGRect bounds,
+ GrayPalette palette,
+ CGFloat radius)
+{
+ if (bounds.size.width < 2) {
+ return;
+ }
+ NSColorSpace *sRGB = [NSColorSpace sRGBColorSpace];
+ CGPoint end = CGPointMake(bounds.origin.x, bounds.origin.y + bounds.size.height);
+ CGFloat corner = (radius > 0 ? radius : 2.0) / bounds.size.height;
+ CGFloat locations[4] = {0.0, corner, 1.0 - corner, 1.0};
+ CGPathRef path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
+ CGFloat colors[16];
+ colors[0] = colors[1] = colors[2] = palette.top / 255.0;
+ colors[4] = colors[5] = colors[6] = palette.side / 255.0;
+ colors[8] = colors[9] = colors[10] = palette.side / 255.0;
+ colors[12] = colors[13] = colors[14] = palette.bottom / 255.0;
+ colors[3] = colors[7] = colors[11] = colors[15] = 1.0;
+ CGGradientRef gradient = CGGradientCreateWithColorComponents(
+ sRGB.CGColorSpace, colors, locations, 4);
+ if (!gradient) {
+ return;
+ }
+ CGContextSaveGState(context);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextClip(context);
+ CGContextDrawLinearGradient(context, gradient, bounds.origin, end, 0.0);
+ CGContextRestoreGState(context);
+ CFRelease(path);
+ CFRelease(gradient);
+}
+
+/*----------------------------------------------------------------------
+ * DrawFocusRing --
+ *
+ * Draw a 4-pixel wide rounded focus ring enclosing a rounded
+ * rectangle, using the current system accent color.
+ */
+
+static void DrawFocusRing(
+ CGContextRef context,
+ CGRect bounds,
+ ButtonDesign *design)
+{
+ CGColorRef highlightColor;
+ CGFloat highlight[4] = {1.0, 1.0, 1.0, 0.2};
+ CGColorRef focusColor;
+
+ focusColor = CGCOLOR([controlAccentColor() colorWithAlphaComponent:0.6]);
+ FillRoundedRectangle(context, bounds, design->radius, focusColor);
+ bounds = CGRectInset(bounds, 3, 3);
+ highlightColor = CGColorFromRGBA(highlight);
+ CGContextSetFillColorWithColor(context, highlightColor);
+ CGContextFillRect(context, bounds);
+}
+
+/*----------------------------------------------------------------------
+ * DrawGrayButton --
+ *
+ * Draw a button in normal gray colors.
+ *
+ * Aqua buttons are normally drawn in a grayscale color. The buttons,
+ * which are shaped as rounded rectangles have a 1-pixel border which is
+ * drawn in a 3-step gradient and a solid gray face.
+ *
+ * Note that this will produce a round button if length = width =
+ * 2*radius.
+ */
+
+static void DrawGrayButton(
+ CGContextRef context,
+ CGRect bounds,
+ ButtonDesign *design,
+ unsigned int state,
+ Tk_Window tkwin)
+{
+ int isDark = TkMacOSXInDarkMode(tkwin);
+ GrayPalette palette = LookupGrayPalette(design, state, isDark);
+ GrayColor faceGray = {.grayscale = 0.0, .alpha = 1.0};
+ CGFloat radius = 2 * design->radius <= bounds.size.height ?
+ design->radius : bounds.size.height / 2;
+ if (palette.top <= 255.0) {
+ FillBorder(context, bounds, palette, radius);
+ }
+ if (palette.face <= 255.0) {
+ faceGray.grayscale = palette.face / 255.0;
+ } else {
+
+ /*
+ * Color values > 255 are "transparent" which really means that we
+ * fill with the background color.
+ */
+
+ CGFloat rgba[4], gray;
+ GetBackgroundColorRGBA(context, tkwin, 0, NO, rgba);
+ gray = (rgba[0] + rgba[1] + rgba[2]) / 3.0;
+ faceGray.grayscale = gray;
+ }
+ FillRoundedRectangle(context, CGRectInset(bounds, 1, 1), radius - 1,
+ CGColorFromGray(faceGray));
+}
+
+/*----------------------------------------------------------------------
+ * DrawAccentedButton --
+ *
+ * The accent color is only used when drawing buttons in the active
+ * window. Push Buttons and segmented Arrow Buttons are drawn in color
+ * when in the pressed state. Selected Check Buttons, Radio Buttons and
+ * notebook Tabs are also drawn in color. The color is based on the
+ * user's current choice for the controlAccentColor, but is actually a
+ * linear gradient with a 1-pixel darker line at the top and otherwise
+ * changing from lighter at the top to darker at the bottom. This
+ * function draws a colored rounded rectangular button.
+ */
+
+static void DrawAccentedButton(
+ CGContextRef context,
+ CGRect bounds,
+ ButtonDesign *design,
+ int state,
+ int isDark)
+{
+ NSColorSpace *sRGB = [NSColorSpace sRGBColorSpace];
+ CGColorRef faceColor = CGCOLOR(controlAccentColor());
+ CGFloat radius = design->radius;
+ CGPathRef path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
+ // This gradient should only be used for PushButtons and Tabs, and it needs
+ // to be lighter at the top.
+ static CGFloat components[12] = {1.0, 1.0, 1.0, 0.05,
+ 1.0, 1.0, 1.0, 0.2,
+ 1.0, 1.0, 1.0, 0.0};
+ CGFloat locations[3] = {0.0, 0.05, 1.0};
+ CGGradientRef gradient = CGGradientCreateWithColorComponents(
+ sRGB.CGColorSpace, components, locations, 3);
+ CGPoint end;
+ if (bounds.size.height > 2*radius) {
+ bounds.size.height -= 1;
+ }
+ end = CGPointMake(bounds.origin.x, bounds.origin.y + bounds.size.height);
+ CGContextSaveGState(context);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextClip(context);
+ FillRoundedRectangle(context, bounds, radius, faceColor);
+ CGContextDrawLinearGradient(context, gradient, bounds.origin, end, 0.0);
+ if (state & TTK_STATE_PRESSED &&
+ state & TTK_STATE_ALTERNATE) {
+ CGColorRef color = isDark ?
+ CGColorFromGray(darkPressedDefaultButton) :
+ CGColorFromGray(pressedDefaultButton);
+ FillRoundedRectangle(context, bounds, radius, color);
+ }
+ CGContextRestoreGState(context);
+ CFRelease(path);
+ CFRelease(gradient);
+}
/*----------------------------------------------------------------------
- * +++ Single Arrow Images --
+ * DrawAccentedSegment --
*
- * Used in ListHeaders and Comboboxes as well as disclosure triangles in
- * macOS 11.
+ * Draw the colored ends of widgets like popup buttons and combo buttons.
+ */
+
+static void DrawAccentedSegment(
+ CGContextRef context,
+ CGRect bounds,
+ ButtonDesign *design,
+ unsigned int state,
+ Tk_Window tkwin)
+{
+ /*
+ * Clip to the bounds and then draw an accented button which is extended so
+ * that the rounded corners on the left will be clipped off. This assumes
+ * that the bounds include room for the focus ring.
+ */
+ int isDark = TkMacOSXInDarkMode(tkwin);
+ GrayColor sepGray = isDark ? darkComboSeparator : lightComboSeparator;
+ CGColorRef sepColor = CGColorFromGray(sepGray);
+ CGRect clip = bounds;
+ clip.size.height += 10;
+ bounds.origin.x -= 10;
+ bounds.size.width += 10;
+ CGPoint separator[2] = {
+ CGPointMake(clip.origin.x - 1, bounds.origin.y + 5),
+ CGPointMake(clip.origin.x - 1,
+ bounds.origin.y + bounds.size.height - 3)};
+ CGContextSaveGState(context);
+ CGContextSetStrokeColorWithColor(context, sepColor);
+ CGContextSetShouldAntialias(context, false);
+ CGContextSetLineWidth(context, 0.5);
+ CGContextAddLines(context, separator, 2);
+ CGContextStrokePath(context);
+ CGContextSetShouldAntialias(context, true);
+ if (state & TTK_STATE_FOCUS) {
+ CGRect focusClip = clip;
+ clip.size.width += 4;
+ CGContextClipToRect(context, focusClip);
+ bounds = CGRectInset(bounds, 0, 1);
+ DrawFocusRing(context, bounds, design);
+ }
+ bounds = CGRectInset(bounds, 4, 4);
+ if (state & TTK_STATE_BACKGROUND) {
+ bounds.size.height += 2;
+ } else {
+ bounds.size.height += 1;
+ }
+ CGContextClipToRect(context, clip);
+ if ((state & TTK_STATE_BACKGROUND) || (state & TTK_STATE_DISABLED)) {
+ DrawGrayButton(context, bounds, design, state, tkwin);
+ } else {
+ DrawAccentedButton(context, bounds, design, state | TTK_STATE_ALTERNATE,
+ isDark);
+ }
+ CGContextRestoreGState(context);
+}
+
+/*----------------------------------------------------------------------
+ * +++ Entry boxes
+ */
+
+static void DrawEntry(
+ CGContextRef context,
+ CGRect bounds,
+ ButtonDesign *design,
+ int state,
+ Tk_Window tkwin)
+{
+ int isDark = TkMacOSXInDarkMode(tkwin);
+ GrayPalette palette = LookupGrayPalette(design, state, isDark);
+ CGColorRef backgroundColor;
+ CGFloat bgRGBA[4];
+ if (isDark) {
+ GetBackgroundColorRGBA(context, tkwin, 0, NO, bgRGBA);
+
+ /*
+ * Lighten the entry background to provide contrast.
+ */
+
+ for (int i = 0; i < 3; i++) {
+ bgRGBA[i] += 8.0 / 255.0;
+ }
+ backgroundColor = CGColorFromRGBA(bgRGBA);
+ } else {
+ backgroundColor = CG_WHITE;
+ }
+ if (state & TTK_STATE_FOCUS) {
+ DrawFocusRing(context, bounds, design);
+ } else {
+ FillBorder(context, CGRectInset(bounds,3,3), palette, design->radius);
+ }
+ bounds = CGRectInset(bounds, 4, 4);
+ FillRoundedRectangle(context, bounds, design->radius, backgroundColor);
+}
+
+/*----------------------------------------------------------------------
+ * +++ Chevrons, CheckMarks, etc. --
*/
static void DrawDownArrow(
@@ -325,14 +676,23 @@ static void DrawDownArrow(
CGRect bounds,
CGFloat inset,
CGFloat size,
- const CGFloat *rgba)
+ int state)
{
+ CGColorRef strokeColor;
CGFloat x, y;
- CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
+
+ if (state & TTK_STATE_DISABLED) {
+ strokeColor = CGCOLOR([NSColor disabledControlTextColor]);
+ } else if (state & TTK_STATE_IS_ACCENTED) {
+ strokeColor = CG_WHITE;
+ } else {
+ strokeColor = CGCOLOR([NSColor controlTextColor]);
+ }
+ CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextSetLineWidth(context, 1.5);
x = bounds.origin.x + inset;
- y = bounds.origin.y + trunc(bounds.size.height / 2);
+ y = bounds.origin.y + trunc(bounds.size.height / 2) + 1;
CGContextBeginPath(context);
CGPoint arrow[3] = {
{x, y - size / 4}, {x + size / 2, y + size / 4},
@@ -342,16 +702,28 @@ static void DrawDownArrow(
CGContextStrokePath(context);
}
+/*----------------------------------------------------------------------
+ * DrawUpArrow --
+ *
+ * Draws a single upward pointing arrow for ListHeaders and Disclosure Buttons.
+ */
+
static void DrawUpArrow(
CGContextRef context,
CGRect bounds,
CGFloat inset,
CGFloat size,
- const CGFloat *rgba)
+ int state)
{
+ NSColor *strokeColor;
CGFloat x, y;
- CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
+ if (state & TTK_STATE_DISABLED) {
+ strokeColor = [NSColor disabledControlTextColor];
+ } else {
+ strokeColor = [NSColor controlTextColor];
+ }
+ CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
CGContextSetLineWidth(context, 1.5);
x = bounds.origin.x + inset;
y = bounds.origin.y + trunc(bounds.size.height / 2);
@@ -364,28 +736,60 @@ static void DrawUpArrow(
CGContextStrokePath(context);
}
-static void DrawClosedDisclosure(
+/*----------------------------------------------------------------------
+ * DrawUpDownArrows --
+ *
+ * Draws the double arrows used in menu buttons and spin buttons.
+ */
+
+static void DrawUpDownArrows(
CGContextRef context,
CGRect bounds,
CGFloat inset,
CGFloat size,
- CGFloat *rgba)
+ CGFloat gap,
+ int state,
+ ThemeDrawState drawState)
{
CGFloat x, y;
-
- CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
+ NSColor *topStrokeColor, *bottomStrokeColor;
+ if (drawState == BOTH_ARROWS && !(state & TTK_STATE_BACKGROUND)) {
+ topStrokeColor = bottomStrokeColor = [NSColor whiteColor];
+ } else if (drawState == kThemeStatePressedDown) {
+ topStrokeColor = [NSColor controlTextColor];
+ bottomStrokeColor = [NSColor whiteColor];
+ } else if (drawState == kThemeStatePressedUp) {
+ topStrokeColor = [NSColor whiteColor];
+ bottomStrokeColor = [NSColor controlTextColor];
+ } else if (state & TTK_STATE_DISABLED) {
+ topStrokeColor = bottomStrokeColor = [NSColor disabledControlTextColor];
+ } else {
+ topStrokeColor = bottomStrokeColor = [NSColor controlTextColor];
+ }
CGContextSetLineWidth(context, 1.5);
x = bounds.origin.x + inset;
y = bounds.origin.y + trunc(bounds.size.height / 2);
CGContextBeginPath(context);
- CGPoint arrow[3] = {
- {x, y - size / 4 - 1}, {x + size / 2, y}, {x, y + size / 4 + 1}
- };
- CGContextAddLines(context, arrow, 3);
+ CGPoint bottomArrow[3] =
+ {{x, y + gap}, {x + size / 2, y + gap + size / 2}, {x + size, y + gap}};
+ CGContextAddLines(context, bottomArrow, 3);
+ CGContextSetStrokeColorWithColor(context, CGCOLOR(bottomStrokeColor));
+ CGContextStrokePath(context);
+ CGContextBeginPath(context);
+ CGPoint topArrow[3] =
+ {{x, y - gap}, {x + size / 2, y - gap - size / 2}, {x + size, y - gap}};
+ CGContextAddLines(context, topArrow, 3);
+ CGContextSetStrokeColorWithColor(context, CGCOLOR(topStrokeColor));
CGContextStrokePath(context);
}
-static void DrawOpenDisclosure(
+/*----------------------------------------------------------------------
+ * DrawClosedDisclosure --
+ *
+ * Draws a disclosure chevron in the Big Sur style, for Treeviews.
+ */
+
+static void DrawClosedDisclosure(
CGContextRef context,
CGRect bounds,
CGFloat inset,
@@ -400,24 +804,24 @@ static void DrawOpenDisclosure(
y = bounds.origin.y + trunc(bounds.size.height / 2);
CGContextBeginPath(context);
CGPoint arrow[3] = {
- {x, y - size / 4}, {x + size / 2, y + size / 2}, {x + size, y - size / 4}
+ {x, y - size / 4 - 1}, {x + size / 2, y}, {x, y + size / 4 + 1}
};
CGContextAddLines(context, arrow, 3);
CGContextStrokePath(context);
}
/*----------------------------------------------------------------------
- * +++ Double Arrow Buttons --
+ * DrawOpenDisclosure --
*
- * Used in MenuButtons and SpinButtons.
+ * Draws an open disclosure chevron in the Big Sur style, for Treeviews.
*/
-static void DrawUpDownArrows(
+static void DrawOpenDisclosure(
CGContextRef context,
CGRect bounds,
CGFloat inset,
CGFloat size,
- const CGFloat *rgba)
+ CGFloat *rgba)
{
CGFloat x, y;
@@ -426,642 +830,670 @@ static void DrawUpDownArrows(
x = bounds.origin.x + inset;
y = bounds.origin.y + trunc(bounds.size.height / 2);
CGContextBeginPath(context);
- CGPoint bottomArrow[3] =
- {{x, y + 2}, {x + size / 2, y + 2 + size / 2}, {x + size, y + 2}};
- CGContextAddLines(context, bottomArrow, 3);
- CGPoint topArrow[3] =
- {{x, y - 2}, {x + size / 2, y - 2 - size / 2}, {x + size, y - 2}};
- CGContextAddLines(context, topArrow, 3);
+ CGPoint arrow[3] = {
+ {x, y - size / 4}, {x + size / 2, y + size / 2}, {x + size, y - size / 4}
+ };
+ CGContextAddLines(context, arrow, 3);
CGContextStrokePath(context);
}
-
/*----------------------------------------------------------------------
- * +++ FillButtonBackground --
+ * IndicatorColor --
*
- * Fills a rounded rectangle with a transparent black gradient.
- * This is a no-op if building on 10.8 or older.
+ * Returns a CGColorRef of the appropriate shade for a check button or
+ * radio button in a given state.
*/
-static void FillButtonBackground(
- CGContextRef context,
- CGRect bounds,
- CGFloat radius)
+static CGColorRef IndicatorColor(
+ int state,
+ int isDark)
{
- CHECK_RADIUS(radius, bounds)
-
- CGPathRef path;
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- CGGradientRef backgroundGradient = CGGradientCreateWithColorComponents(
- deviceRGB.CGColorSpace, darkBackgroundGradient, NULL, 2);
- CGPoint backgroundEnd = {
- bounds.origin.x,
- bounds.origin.y + bounds.size.height
- };
- CGContextBeginPath(context);
- path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
- CGContextAddPath(context, path);
- CGContextClip(context);
- CGContextDrawLinearGradient(context, backgroundGradient,
- bounds.origin, backgroundEnd, 0);
- CFRelease(path);
- CFRelease(backgroundGradient);
+ if (state & TTK_STATE_DISABLED) {
+ return isDark ?
+ CGColorFromGray(darkDisabledIndicator) :
+ CGColorFromGray(lightDisabledIndicator);
+ } else if ((state & TTK_STATE_SELECTED || state & TTK_STATE_ALTERNATE) &&
+ !(state & TTK_STATE_BACKGROUND)) {
+ return CG_WHITE;
+ } else {
+ return CGCOLOR([NSColor controlTextColor]);
+ }
}
/*----------------------------------------------------------------------
- * +++ HighlightButtonBorder --
+ * DrawCheckIndicator --
*
- * Accent the top border of a rounded rectangle with a transparent
- * white gradient.
+ * Draws the checkmark or horizontal bar in a check box.
*/
-static void HighlightButtonBorder(
+static void DrawCheckIndicator(
CGContextRef context,
- CGRect bounds)
-{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- CGPoint topEnd = {bounds.origin.x, bounds.origin.y + 3};
- CGGradientRef topGradient = CGGradientCreateWithColorComponents(
- deviceRGB.CGColorSpace, darkTopGradient, NULL, 2);
-
- CGContextSaveGState(context);
- CGContextBeginPath(context);
- CGContextAddArc(context, bounds.origin.x + 4, bounds.origin.y + 4,
- 4, PI, 3 * PI / 2, 0);
- CGContextAddArc(context, bounds.origin.x + bounds.size.width - 4,
- bounds.origin.y + 4, 4, 3 * PI / 2, 0, 0);
- CGContextReplacePathWithStrokedPath(context);
- CGContextClip(context);
- CGContextDrawLinearGradient(context, topGradient, bounds.origin, topEnd,
- 0.0);
- CGContextRestoreGState(context);
- CFRelease(topGradient);
-}
-
-/*----------------------------------------------------------------------
- * DrawGroupBox --
- *
- * This is a standalone drawing procedure which draws the contrasting
- * rounded rectangular box for LabelFrames and Notebook panes used in
- * more recent versions of macOS.
- */
-
-static void DrawGroupBox(
CGRect bounds,
- CGContextRef context,
- Tk_Window tkwin)
+ int state,
+ int isDark)
{
- CHECK_RADIUS(4, bounds)
-
- CGPathRef path;
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *borderColor, *bgColor;
- static CGFloat border[4] = {1.0, 1.0, 1.0, 0.25};
- CGFloat fill[4];
-
- GetBackgroundColor(context, tkwin, 1, fill);
- bgColor = [NSColor colorWithColorSpace: deviceRGB components: fill
- count: 4];
- CGContextSetFillColorSpace(context, deviceRGB.CGColorSpace);
- CGContextSetFillColorWithColor(context, CGCOLOR(bgColor));
- path = CGPathCreateWithRoundedRect(bounds, 4, 4, NULL);
- CGContextClipToRect(context, bounds);
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGContextFillPath(context);
- borderColor = [NSColor colorWithColorSpace: deviceRGB components: border
- count: 4];
- CGContextSetFillColorWithColor(context, CGCOLOR(borderColor));
- [borderColor getComponents: fill];
- CGContextSetRGBFillColor(context, fill[0], fill[1], fill[2], fill[3]);
+ CGFloat x = bounds.origin.x, y = bounds.origin.y;
+ CGColorRef strokeColor = IndicatorColor(state, isDark);
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGContextReplacePathWithStrokedPath(context);
- CGContextFillPath(context);
- CFRelease(path);
+ CGContextSetStrokeColorWithColor(context, strokeColor);
+ if (state & TTK_STATE_SELECTED) {
+ CGContextSetLineWidth(context, 1.5);
+ CGContextBeginPath(context);
+ CGPoint check[3] = {{x + 3, y + 7}, {x + 6, y + 10}, {x + 10, y + 3}};
+ CGContextAddLines(context, check, 3);
+ CGContextStrokePath(context);
+ } else if (state & TTK_STATE_ALTERNATE) {
+ CGContextSetLineWidth(context, 2.0);
+ CGContextBeginPath(context);
+ CGPoint bar[2] = {{x + 3, y + 7}, {x + 11, y + 7}};
+ CGContextAddLines(context, bar, 2);
+ CGContextStrokePath(context);
+ }
}
/*----------------------------------------------------------------------
- * SolidFillRoundedRectangle --
+ * DrawRadioIndicator --
*
- * Fill a rounded rectangle with a specified solid color.
+ * Draws the dot in the middle of a selected radio button.
*/
-static void SolidFillRoundedRectangle(
+static void DrawRadioIndicator(
CGContextRef context,
CGRect bounds,
- CGFloat radius,
- NSColor *color)
+ int state,
+ int isDark)
{
- CGPathRef path;
+ CGFloat x = bounds.origin.x, y = bounds.origin.y;
+ CGColorRef fillColor = IndicatorColor(state, isDark);
- CHECK_RADIUS(radius, bounds)
- path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
- if (!path) {
- return;
+ CGContextSetFillColorWithColor(context, fillColor);
+ if (state & TTK_STATE_SELECTED) {
+ CGContextBeginPath(context);
+ CGRect dot = {{x + 5, y + 5}, {6, 6}};
+ CGContextAddEllipseInRect(context, dot);
+ CGContextFillPath(context);
+ } else if (state & TTK_STATE_ALTERNATE) {
+ CGRect bar = {{x + 4, y + 7}, {8, 2}};
+ CGContextFillRect(context, bar);
}
- CGContextSetFillColorWithColor(context, CGCOLOR(color));
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGContextFillPath(context);
- CFRelease(path);
}
-/*----------------------------------------------------------------------
- * +++ DrawListHeader --
- *
- * This is a standalone drawing procedure which draws column headers for
- * a Treeview in the Aqua appearance. The HITheme headers have not
- * matched the native ones since OSX 10.8. Note that the header image is
- * ignored, but we draw arrows according to the state.
- */
-
-static void DrawListHeader(
- CGRect bounds,
+static void
+DrawHelpSymbol(
CGContextRef context,
- Tk_Window tkwin,
+ CGRect bounds,
int state)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *strokeColor, *bgColor;
- static CGFloat borderRGBA[4] = {
- 200.0 / 255, 200.0 / 255, 200.0 / 255, 1.0
- };
- static CGFloat separatorRGBA[4] = {
- 220.0 / 255, 220.0 / 255, 220.0 / 255, 1.0
- };
- static CGFloat activeBgRGBA[4] = {
- 238.0 / 255, 238.0 / 255, 238.0 / 255, 1.0
- };
- static CGFloat inactiveBgRGBA[4] = {
- 246.0 / 255, 246.0 / 255, 246.0 / 255, 1.0
+ NSFont *font = [NSFont controlContentFontOfSize:15];
+ NSColor *foreground = state & TTK_STATE_DISABLED ?
+ [NSColor disabledControlTextColor] : [NSColor controlTextColor];
+ NSDictionary *attrs = @{
+ NSForegroundColorAttributeName : foreground,
+ NSFontAttributeName : font
};
-
- /*
- * Apple changes the background of a list header when the window is not
- * active. But Ttk does not indicate that in the state of a TreeHeader.
- * So we have to query the Apple window manager.
- */
-
- NSWindow *win = TkMacOSXGetNSWindowForDrawable(Tk_WindowId(tkwin));
- CGFloat *bgRGBA = [win isKeyWindow] ? activeBgRGBA : inactiveBgRGBA;
- CGFloat x = bounds.origin.x, y = bounds.origin.y;
- CGFloat w = bounds.size.width, h = bounds.size.height;
- CGPoint top[2] = {{x, y + 1}, {x + w, y + 1}};
- CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
- CGPoint separator[2] = {{x + w - 1, y + 3}, {x + w - 1, y + h - 3}};
-
- bgColor = [NSColor colorWithColorSpace: deviceRGB
- components: bgRGBA
- count: 4];
+ NSAttributedString *attributedString = [[NSAttributedString alloc]
+ initWithString:@"?"
+ attributes:attrs];
+ CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString(
+ (CFAttributedStringRef)attributedString);
+ CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(0, 1));
+ CGAffineTransform t = CGAffineTransformMake(
+ 1.0, 0.0, 0.0, -1.0, 0.0, bounds.size.height);
CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- CGContextSetFillColorSpace(context, deviceRGB.CGColorSpace);
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- CGContextBeginPath(context);
- CGContextSetFillColorWithColor(context, CGCOLOR(bgColor));
- CGContextAddRect(context, bounds);
- CGContextFillPath(context);
- strokeColor = [NSColor colorWithColorSpace: deviceRGB
- components: separatorRGBA
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
- CGContextAddLines(context, separator, 2);
- CGContextStrokePath(context);
- strokeColor = [NSColor colorWithColorSpace: deviceRGB
- components: borderRGBA
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
- CGContextAddLines(context, top, 2);
- CGContextStrokePath(context);
- CGContextAddLines(context, bottom, 2);
- CGContextStrokePath(context);
+ CGContextSetTextMatrix(context, t);
+ CGContextSetTextPosition(context,
+ bounds.origin.x + 6.5,
+ bounds.origin.y + bounds.size.height - 5);
+ CTLineDraw(line, context);
CGContextRestoreGState(context);
-
- if (state & TTK_TREEVIEW_STATE_SORTARROW) {
- CGRect arrowBounds = bounds;
- arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16;
- arrowBounds.size.width = 16;
- if (state & TTK_STATE_ALTERNATE) {
- DrawUpArrow(context, arrowBounds, 3, 8, BLACKRGBA);
- } else if (state & TTK_STATE_SELECTED) {
- DrawDownArrow(context, arrowBounds, 3, 8, BLACKRGBA);
- }
- }
+ CFRelease(line);
+ CFRelease(typesetter);
+ [attributedString release];
}
+
+
/*----------------------------------------------------------------------
- * +++ Drawing procedures for widgets in Apple's "Dark Mode" (10.14 and up).
- *
- * The HIToolbox does not support Dark Mode, and apparently never will,
- * so to make widgets look "native" we have to provide analogues of the
- * HITheme drawing functions to be used in DarkAqua. We continue to use
- * HITheme in Aqua, since it understands earlier versions of the OS.
- *
- * Drawing the dark widgets requires NSColors that were introduced in OSX
- * 10.14, so we make some of these functions be no-ops when building on
- * systems older than 10.14.
+ * +++ Progress bars.
*/
/*----------------------------------------------------------------------
- * GradientFillRoundedRectangle --
+ * DrawProgressBar --
*
- * Fill a rounded rectangle with a specified gradient.
+ * Draws a progress bar, with parameters supplied by a HIThemeTrackDrawInfo
+ * struct. Draws a rounded rectangular track overlayed by a colored
+ * rounded rectangular indicator. An indeterminate progress bar is
+ * animated.
*/
-static void GradientFillRoundedRectangle(
+static void DrawProgressBar(
CGContextRef context,
CGRect bounds,
- CGFloat radius,
- CGFloat *colors,
- int numColors)
+ HIThemeTrackDrawInfo info,
+ int state,
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- CGPathRef path;
- CHECK_RADIUS(radius, bounds)
-
- CGPoint end = {
- bounds.origin.x,
- bounds.origin.y + bounds.size.height
- };
- CGGradientRef gradient = CGGradientCreateWithColorComponents(
- deviceRGB.CGColorSpace, colors, NULL, numColors);
-
- path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGContextClip(context);
- CGContextDrawLinearGradient(context, gradient, bounds.origin, end, 0);
- CFRelease(path);
- CFRelease(gradient);
-}
-
-/*----------------------------------------------------------------------
- * +++ DrawDarkButton --
- *
- * This is a standalone drawing procedure which draws PushButtons and
- * PopupButtons in the Dark Mode style.
- */
+ CGRect colorBounds;
+ CGFloat rgba[4];
+ CGColorRef trackColor, highlightColor, fillColor;
+ NSColor *accent;
+ CGFloat ratio = (CGFloat) info.value / (CGFloat) (info.max - info.min);
-static void DrawDarkButton(
- CGRect bounds,
- ThemeButtonKind kind,
- Ttk_State state,
- CGContextRef context)
-{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *faceColor;
+ GetBackgroundColorRGBA(context, tkwin, 0, NO, rgba);
/*
- * To match the appearance of Apple's buttons we need to increase the
- * height by 1 pixel.
+ * Compute the bounds for the track and indicator. The track is 6 pixels
+ * wide in the center of the widget bounds.
*/
- bounds.size.height += 1;
-
- CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 5);
+ if (info.attributes & kThemeTrackHorizontal) {
+ bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 3);
+ colorBounds = bounds;
+ if (info.kind == kThemeIndeterminateBar) {
+ CGFloat width = 0.25*bounds.size.width;
+ CGFloat travel = 0.75*bounds.size.width;
+ CGFloat center = bounds.origin.x + (width / 2) + ratio*travel;
+ colorBounds.origin.x = center - width / 2;
+ colorBounds.size.width = width;
+ } else {
+ colorBounds.size.width = ratio*bounds.size.width;
+ }
+ if (colorBounds.size.width > 0 && colorBounds.size.width < 6) {
+ colorBounds.size.width = 6;
+ }
+ } else {
+ bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 1);
+ colorBounds = bounds;
+ if (info.kind == kThemeIndeterminateBar) {
+ CGFloat height = 0.25*bounds.size.height;
+ CGFloat travel = 0.75*bounds.size.height;
+ CGFloat center = bounds.origin.y + (height / 2) + ratio*travel;
+ colorBounds.origin.y = center - height / 2;
+ colorBounds.size.height = height;
+ } else {
+ colorBounds.size.height = ratio*(bounds.size.height);
+ }
+ if (colorBounds.size.height > 0 && colorBounds.size.height < 6) {
+ colorBounds.size.height = 6;
+ }
+ colorBounds.origin.y += bounds.size.height - colorBounds.size.height;
+ }
/*
- * Fill the button face with the appropriate color.
+ * Compute the colors for the track and indicator.
*/
- bounds = CGRectInset(bounds, 1, 1);
- if (kind == kThemePushButton && (state & TTK_STATE_PRESSED)) {
- if ([NSApp macOSVersion] < 120000) {
- GradientFillRoundedRectangle(context, bounds, 4,
- pressedPushButtonGradient, 2);
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkPressedButtonFace
- count: 4];
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
+ if (TkMacOSXInDarkMode(tkwin)) {
+ for(int i=0; i < 3; i++) {
+ rgba[i] += 30.0 / 255.0;
}
- } else if (kind == kThemePushButton &&
- (state & TTK_STATE_ALTERNATE) &&
- !(state & TTK_STATE_BACKGROUND)) {
- GradientFillRoundedRectangle(context, bounds, 4,
- darkSelectedGradient, 2);
+ trackColor = CGColorFromRGBA(rgba);
+ for(int i=0; i < 3; i++) {
+ rgba[i] -= 5.0 / 255.0;
+ }
+ highlightColor = CGColorFromRGBA(rgba);
+ FillRoundedRectangle(context, bounds, 3, trackColor);
} else {
- if (state & TTK_STATE_DISABLED) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkDisabledButtonFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkButtonFace
- count: 4];
+ for(int i=0; i < 3; i++) {
+ rgba[i] -= 14.0 / 255.0;
}
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
+ trackColor = CGColorFromRGBA(rgba);
+ for(int i=0; i < 3; i++) {
+ rgba[i] += 3.0 / 255.0;
+ }
+ highlightColor = CGColorFromRGBA(rgba);
+ bounds.size.height -= 1;
+ bounds = CGRectInset(bounds, 0, -1);
+ }
+ if (state & TTK_STATE_BACKGROUND) {
+ accent = [NSColor colorWithRed:0.72 green:0.72 blue:0.72 alpha:0.72];
+ } else {
+ accent = controlAccentColor();
}
/*
- * If this is a popup, draw the arrow button.
+ * Draw the track, with highlighting around the edge.
*/
- if ((kind == kThemePopupButton) | (kind == kThemeComboBox)) {
- CGRect arrowBounds = bounds;
- arrowBounds.size.width = 16;
- arrowBounds.origin.x += bounds.size.width - 16;
+ FillRoundedRectangle(context, bounds, 3, trackColor);
+ bounds = CGRectInset(bounds, 0, 0.5);
+ FillRoundedRectangle(context, bounds, 2.5, highlightColor);
+ bounds = CGRectInset(bounds, 0.5, 0.5);
+ FillRoundedRectangle(context, bounds, 2, trackColor);
+ bounds = CGRectInset(bounds, -0.5, -1);
- /*
- * If the toplevel is front, paint the button blue.
- */
+ /*
+ * Draw the indicator. Make it slightly transparent around the
+ * edge so the highlightng shows through.
+ */
- if (!(state & TTK_STATE_BACKGROUND) &&
- !(state & TTK_STATE_DISABLED)) {
- GradientFillRoundedRectangle(context, arrowBounds, 4,
- darkSelectedGradient, 2);
- }
- if (kind == kThemePopupButton) {
- DrawUpDownArrows(context, arrowBounds, 3, 7, WHITERGBA);
- } else {
- DrawDownArrow(context, arrowBounds, 4, 8, WHITERGBA);
- }
+ if (info.kind == kThemeIndeterminateBar &&
+ (state & TTK_STATE_SELECTED) == 0) {
+ return;
}
- HighlightButtonBorder(context, bounds);
+ fillColor = CGCOLOR([accent colorWithAlphaComponent:0.9]);
+ FillRoundedRectangle(context, colorBounds, 3, fillColor);
+ colorBounds = CGRectInset(colorBounds, 1, 1);
+ fillColor = CGCOLOR([accent colorWithAlphaComponent:1.0]);
+ FillRoundedRectangle(context, colorBounds, 2.5, fillColor);
}
+
+/*----------------------------------------------------------------------
+ * +++ Sliders.
+ */
/*----------------------------------------------------------------------
- * +++ DrawDarkIncDecButton --
+ * DrawSlider --
+ *
+ * Draws a slider track and round thumb for a Ttk scale widget. The accent
+ * color is used on the left or top part of the track, so the fraction of
+ * the track which is colored is equal to (value - from) / (to - from).
*
- * This is a standalone drawing procedure which draws an IncDecButton
- * (as used in a Spinbox) in the Dark Mode style.
*/
-static void DrawDarkIncDecButton(
+static void DrawSlider(
+ CGContextRef context,
CGRect bounds,
- ThemeDrawState drawState,
- Ttk_State state,
- CGContextRef context)
+ HIThemeTrackDrawInfo info,
+ int state,
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *faceColor;
-
- bounds = CGRectInset(bounds, 0, -1);
- CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 6);
+ CGColorRef trackColor;
+ CGRect clipBounds, trackBounds, thumbBounds;
+ CGPoint thumbPoint;
+ CGFloat position;
+ CGColorRef accentColor;
+ Bool fromIsSmaller = info.reserved;
+ double from = info.min, to = fabs((double) info.max), value = info.value;
/*
- * Fill the button face with the appropriate color.
+ * info.min, info.max and info.value are integers. When this is called
+ * we will have arranged that min = 0 and max is a large positive integer.
*/
- bounds = CGRectInset(bounds, 1, 1);
- if (state & TTK_STATE_DISABLED) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkDisabledButtonFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkButtonFace
- count: 4];
- }
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
-
- /*
- * If pressed, paint the appropriate half blue.
- */
+ double fraction = (from < to) ? (value - from) / (to - from) : 0.5;
+ int isDark = TkMacOSXInDarkMode(tkwin);
- if (state & TTK_STATE_PRESSED) {
- CGRect clip = bounds;
- clip.size.height /= 2;
- CGContextSaveGState(context);
- if (drawState == kThemeStatePressedDown) {
- clip.origin.y += clip.size.height;
+ if (info.attributes & kThemeTrackHorizontal) {
+ trackBounds = CGRectInset(bounds, 0, bounds.size.height / 2 - 3);
+ trackBounds.size.height = 3;
+ position = 8 + fraction * (trackBounds.size.width - 16);
+ clipBounds = trackBounds;
+ if (fromIsSmaller) {
+ clipBounds.size.width = position;
+ } else {
+ clipBounds.origin.x += position;
+ clipBounds.size.width -= position;
}
- CGContextClipToRect(context, clip);
- GradientFillRoundedRectangle(context, bounds, 5,
- darkSelectedGradient, 2);
- CGContextRestoreGState(context);
+ thumbPoint = CGPointMake(trackBounds.origin.x + position,
+ trackBounds.origin.y + 1);
+ } else {
+ trackBounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 0);
+ trackBounds.size.width = 3;
+ position = 8 + fraction * (trackBounds.size.height - 16);
+ clipBounds = trackBounds;
+ if (fromIsSmaller) {
+ clipBounds.size.height = position;
+ } else {
+ clipBounds.origin.y += position;
+ clipBounds.size.height -= position;
+ }
+ thumbPoint = CGPointMake(trackBounds.origin.x + 1,
+ trackBounds.origin.y + position);
}
- DrawUpDownArrows(context, bounds, 3, 5, WHITERGBA);
- HighlightButtonBorder(context, bounds);
+ trackColor = isDark ? CGColorFromGray(darkTrack):
+ CGColorFromGray(lightTrack);
+ thumbBounds = CGRectMake(thumbPoint.x - 8, thumbPoint.y - 8, 17, 17);
+ CGContextSaveGState(context);
+ FillRoundedRectangle(context, trackBounds, 1.5, trackColor);
+ CGContextClipToRect(context, clipBounds);
+ if (state & (TTK_STATE_BACKGROUND | TTK_STATE_DISABLED)) {
+ accentColor = isDark ? CGColorFromGray(darkInactiveTrack) :
+ CGColorFromGray(lightInactiveTrack);
+ } else {
+ accentColor = CGCOLOR(controlAccentColor());
+ }
+ FillRoundedRectangle(context, trackBounds, 1.5, accentColor);
+ CGContextRestoreGState(context);
+ DrawGrayButton(context, thumbBounds, &sliderDesign, state, tkwin);
}
/*----------------------------------------------------------------------
- * +++ DrawDarkBevelButton --
+ * +++ Drawing procedures for native widgets.
*
- * This is a standalone drawing procedure which draws RoundedBevelButtons
- * in the Dark Mode style.
+ * The HIToolbox does not support Dark Mode, and apparently never will.
+ * It also draws some widgets in discontinued older styles even when used
+ * on new OS releases. So to make widgets look "native" we have to provide
+ * analogues of the HIToolbox drawing functions to be used on newer systems.
+ * We continue to use NIToolbox for older versions of the OS.
+ *
+ * Drawing the dark widgets requires NSColors that were introduced in OSX
+ * 10.14, so we make some of these functions be no-ops when building on
+ * systems older than 10.14.
*/
-static void DrawDarkBevelButton(
+/*----------------------------------------------------------------------
+ * DrawButton --
+ *
+ * This is a standalone drawing procedure which draws most types of macOS
+ * buttons for newer OS releases. The button style is specified in the
+ * "kind" field of a HIThemeButtonDrawInfo struct, although some of the
+ * identifiers are not recognized by HIToolbox.
+ */
+
+static void DrawButton(
CGRect bounds,
+ HIThemeButtonDrawInfo info,
Ttk_State state,
- CGContextRef context)
+ CGContextRef context,
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *faceColor;
+ ThemeButtonKind kind = info.kind;
+ ThemeDrawState drawState = info.state;
+ CGRect arrowBounds = bounds = CGRectInset(bounds, 1, 1);
+ int hasIndicator, isDark = TkMacOSXInDarkMode(tkwin);
- CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 5);
+ switch (kind) {
+ case TkRoundedRectButton:
+ DrawGrayButton(context, bounds, &roundedrectDesign, state, tkwin);
+ break;
+ case TkInlineButton:
+ DrawGrayButton(context, bounds, &inlineDesign, state, tkwin);
+ break;
+ case TkRecessedButton:
+ DrawGrayButton(context, bounds, &recessedDesign, state, tkwin);
+ break;
+ case kThemeRoundedBevelButton:
+ DrawGrayButton(context, bounds, &bevelDesign, state, tkwin);
+ break;
+ case kThemePushButton:
- /*
- * Fill the button face with the appropriate color.
- */
+ /*
+ * The TTK_STATE_ALTERNATE bit means -default active. Apple only
+ * indicates the default state (which means that the key equivalent is
+ * "\n") for Push Buttons.
+ */
- bounds = CGRectInset(bounds, 1, 1);
- if (state & TTK_STATE_PRESSED) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkPressedBevelFace
- count: 4];
- } else if ((state & TTK_STATE_DISABLED) ||
- (state & TTK_STATE_ALTERNATE)) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkDisabledButtonFace
- count: 4];
- } else if (state & TTK_STATE_SELECTED) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkSelectedBevelFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkButtonFace
- count: 4];
+ if ((state & TTK_STATE_PRESSED || state & TTK_STATE_ALTERNATE) &&
+ !(state & TTK_STATE_BACKGROUND)) {
+ DrawAccentedButton(context, bounds, &pushbuttonDesign, state, isDark);
+ } else {
+ DrawGrayButton(context, bounds, &pushbuttonDesign, state, tkwin);
+ }
+ break;
+ case kThemeRoundButtonHelp:
+ DrawGrayButton(context, bounds, &helpDesign, state, tkwin);
+ DrawHelpSymbol(context, bounds, state);
+ break;
+ case kThemePopupButton:
+ drawState = 0;
+ DrawGrayButton(context, bounds, &popupDesign, state, tkwin);
+ arrowBounds.size.width = 17;
+ arrowBounds.origin.x += bounds.size.width - 17;
+ if (!(state & TTK_STATE_BACKGROUND) &&
+ !(state & TTK_STATE_DISABLED)) {
+ CGRect popupBounds = arrowBounds;
+
+ /*
+ * Allow room for nonexistent focus ring.
+ */
+
+ popupBounds.size.width += 4;
+ popupBounds.origin.y -= 4;
+ popupBounds.size.height += 8;
+ DrawAccentedSegment(context, popupBounds, &popupDesign, state, tkwin);
+ drawState = BOTH_ARROWS;
+ }
+ arrowBounds.origin.x += 2;
+ DrawUpDownArrows(context, arrowBounds, 3, 7, 2, state, drawState);
+ break;
+ case kThemeComboBox:
+ if (state & TTK_STATE_DISABLED) {
+ // Need to add the disabled case to entryDesign.
+ DrawEntry(context, bounds, &entryDesign, state, tkwin);
+ } else {
+ DrawEntry(context, bounds, &entryDesign, state, tkwin);
+ }
+ arrowBounds.size.width = 17;
+ if (state & TTK_STATE_BACKGROUND) {
+ arrowBounds.origin.x += bounds.size.width - 20;
+ arrowBounds.size.width += 4;
+ arrowBounds.origin.y -= 1;
+ } else {
+ arrowBounds.origin.y -= 1;
+ arrowBounds.origin.x += bounds.size.width - 20;
+ arrowBounds.size.width += 4;
+ arrowBounds.size.height += 2;
+ }
+ DrawAccentedSegment(context, arrowBounds, &comboDesign, state, tkwin);
+ if (!(state & TTK_STATE_BACKGROUND)) {
+ state |= TTK_STATE_IS_ACCENTED;
+ }
+ DrawDownArrow(context, arrowBounds, 6, 6, state);
+ break;
+ case kThemeCheckBox:
+ bounds = CGRectOffset(CGRectMake(0, bounds.size.height / 2 - 8, 16, 16),
+ bounds.origin.x, bounds.origin.y);
+ bounds = CGRectInset(bounds, 1, 1);
+ hasIndicator = state & TTK_STATE_SELECTED || state & TTK_STATE_ALTERNATE;
+ if (hasIndicator &&
+ !(state & TTK_STATE_BACKGROUND) &&
+ !(state & TTK_STATE_DISABLED)) {
+ DrawAccentedButton(context, bounds, &checkDesign, 0, isDark);
+ } else {
+ DrawGrayButton(context, bounds, &checkDesign, state, tkwin);
+ }
+ if (hasIndicator) {
+ DrawCheckIndicator(context, bounds, state, isDark);
+ }
+ break;
+ case kThemeRadioButton:
+ bounds = CGRectOffset(CGRectMake(0, bounds.size.height / 2 - 9, 18, 18),
+ bounds.origin.x, bounds.origin.y);
+ bounds = CGRectInset(bounds, 1, 1);
+ hasIndicator = state & TTK_STATE_SELECTED || state & TTK_STATE_ALTERNATE;
+ if (hasIndicator &&
+ !(state & TTK_STATE_BACKGROUND) &&
+ !(state & TTK_STATE_DISABLED)) {
+ DrawAccentedButton(context, bounds, &radioDesign, 0, isDark);
+ } else {
+ DrawGrayButton(context, bounds, &radioDesign, state, tkwin);
+ }
+ if (hasIndicator) {
+ DrawRadioIndicator(context, bounds, state, isDark);
+ }
+ break;
+ case kThemeArrowButton:
+ DrawGrayButton(context, bounds, &pushbuttonDesign, state, tkwin);
+ arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 17;
+ arrowBounds.size.width = 16;
+ arrowBounds.origin.y -= 1;
+ if (state & TTK_STATE_SELECTED) {
+ DrawUpArrow(context, arrowBounds, 5, 6, state);
+ } else {
+ DrawDownArrow(context, arrowBounds, 5, 6, state);
+ }
+ break;
+ case kThemeIncDecButton:
+ DrawGrayButton(context, bounds, &incdecDesign, state, tkwin);
+ if (state & TTK_STATE_PRESSED) {
+ CGRect clip;
+ if (drawState == kThemeStatePressedDown) {
+ clip = bounds;
+ clip.size.height /= 2;
+ clip.origin.y += clip.size.height;
+ bounds.size.height += 1;
+ clip.size.height += 1;
+ } else {
+ clip = bounds;
+ clip.size.height /= 2;
+ }
+ CGContextSaveGState(context);
+ CGContextClipToRect(context, clip);
+ DrawAccentedButton(context, bounds, &incdecDesign, 0, isDark);
+ CGContextRestoreGState(context);
+ }
+ CGFloat inset = (bounds.size.width - 5) / 2;
+ DrawUpDownArrows(context, bounds, inset, 5, 3, state, drawState);
+ break;
+ default:
+ break;
}
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
- HighlightButtonBorder(context, bounds);
}
-
+
/*----------------------------------------------------------------------
- * +++ DrawDarkCheckBox --
+ * DrawGroupBox --
*
- * This is a standalone drawing procedure which draws Checkboxes in the
- * Dark Mode style.
+ * This is a standalone drawing procedure which draws the contrasting rounded
+ * rectangular box for LabelFrames and Notebook panes used in more recent
+ * versions of macOS. Normally the contrast is set to one, since the nesting
+ * level of the Group Box is higher by 1 compared to its container. But we
+ * allow higher contrast for special cases, notably notebook tabs in macOS 11.
+ * The save parameter is passed to GetBackgroundColor and should probably be
+ * NO in such special cases.
*/
-static void DrawDarkCheckBox(
+static void DrawGroupBox(
CGRect bounds,
- Ttk_State state,
- CGContextRef context)
+ CGContextRef context,
+ Tk_Window tkwin,
+ int contrast,
+ Bool save)
{
- CGRect checkbounds = {{0, bounds.size.height / 2 - 8}, {16, 16}};
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *stroke;
- CGFloat x, y;
+ CHECK_RADIUS(5, bounds)
- bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y);
- x = bounds.origin.x;
- y = bounds.origin.y;
+ CGPathRef path;
+ CGColorRef backgroundColor, borderColor;
+ backgroundColor = GetBackgroundCGColor(context, tkwin, contrast, save);
+ borderColor = CGColorFromGray(boxBorder);
+ CGContextSetFillColorWithColor(context, backgroundColor);
+ path = CGPathCreateWithRoundedRect(bounds, 5, 5, NULL);
CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 4);
- bounds = CGRectInset(bounds, 1, 1);
- if (!(state & TTK_STATE_BACKGROUND) &&
- !(state & TTK_STATE_DISABLED) &&
- ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE))) {
- GradientFillRoundedRectangle(context, bounds, 3,
- darkSelectedGradient, 2);
- } else {
- GradientFillRoundedRectangle(context, bounds, 3,
- darkInactiveGradient, 2);
- }
- HighlightButtonBorder(context, bounds);
- if ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE)) {
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- if (state & TTK_STATE_DISABLED) {
- stroke = [NSColor disabledControlTextColor];
- } else {
- stroke = [NSColor controlTextColor];
- }
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- }
- if (state & TTK_STATE_SELECTED) {
- CGContextSetLineWidth(context, 1.5);
- CGContextBeginPath(context);
- CGPoint check[3] = {{x + 4, y + 8}, {x + 7, y + 11}, {x + 11, y + 4}};
- CGContextAddLines(context, check, 3);
- CGContextStrokePath(context);
- } else if (state & TTK_STATE_ALTERNATE) {
- CGContextSetLineWidth(context, 2.0);
- CGContextBeginPath(context);
- CGPoint bar[2] = {{x + 4, y + 8}, {x + 12, y + 8}};
- CGContextAddLines(context, bar, 2);
- CGContextStrokePath(context);
- }
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextFillPath(context);
+ CGContextSetFillColorWithColor(context, borderColor);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextReplacePathWithStrokedPath(context);
+ CGContextFillPath(context);
+ CFRelease(path);
}
-
+
/*----------------------------------------------------------------------
- * +++ DrawDarkRadioButton --
+ * DrawListHeader --
*
- * This is a standalone drawing procedure which draws RadioButtons
- * in the Dark Mode style.
+ * This is a standalone drawing procedure which draws column headers for a
+ * Treeview in the Aqua appearance. (The HIToolbox headers have not matched the
+ * native ones since OSX 10.8) Note that the header image is ignored, but we
+ * draw arrows according to the state.
*/
-static void DrawDarkRadioButton(
+static void DrawListHeader(
CGRect bounds,
- Ttk_State state,
- CGContextRef context)
+ CGContextRef context,
+ Tk_Window tkwin,
+ int state)
{
- CGRect checkbounds = {{0, bounds.size.height / 2 - 9}, {18, 18}};
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *fill;
- CGFloat x, y;
+ int isDark = TkMacOSXInDarkMode(tkwin);
+ CGFloat x = bounds.origin.x, y = bounds.origin.y;
+ CGFloat w = bounds.size.width, h = bounds.size.height;
+ CGPoint top[2] = {{x, y + 1}, {x + w, y + 1}};
+ CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
+ CGPoint separator[2] = {{x + w - 1, y + 3}, {x + w - 1, y + h - 3}};
+ CGColorRef strokeColor, backgroundColor;
- bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y);
- x = bounds.origin.x;
- y = bounds.origin.y;
+ /*
+ * Apple changes the background color of a list header when the window is
+ * not active. But Ttk does not indicate that in the state of a
+ * TreeHeader. So we have to query the Apple window manager.
+ */
- CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 9);
- bounds = CGRectInset(bounds, 1, 1);
- if (!(state & TTK_STATE_BACKGROUND) &&
- !(state & TTK_STATE_DISABLED) &&
- ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE))) {
- GradientFillRoundedRectangle(context, bounds, 8,
- darkSelectedGradient, 2);
- } else {
- GradientFillRoundedRectangle(context, bounds, 8,
- darkInactiveGradient, 2);
- }
- HighlightButtonBorder(context, bounds);
- if ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE)) {
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- if (state & TTK_STATE_DISABLED) {
- fill = [NSColor disabledControlTextColor];
- } else {
- fill = [NSColor controlTextColor];
- }
- CGContextSetFillColorWithColor(context, CGCOLOR(fill));
+ NSWindow *win = TkMacOSXGetNSWindowForDrawable(Tk_WindowId(tkwin));
+ if (!isDark) {
+ GrayColor bgGray = [win isKeyWindow] ?
+ listheaderActiveBG : listheaderInactiveBG;
+ backgroundColor = CGColorFromGray(bgGray);
}
- if (state & TTK_STATE_SELECTED) {
+
+ CGContextSaveGState(context);
+ CGContextSetShouldAntialias(context, false);
+ if (!isDark) {
CGContextBeginPath(context);
- CGRect dot = {{x + 6, y + 6}, {6, 6}};
- CGContextAddEllipseInRect(context, dot);
+ CGContextSetFillColorWithColor(context, backgroundColor);
+ CGContextAddRect(context, bounds);
CGContextFillPath(context);
- } else if (state & TTK_STATE_ALTERNATE) {
- CGRect bar = {{x + 5, y + 8}, {8, 2}};
- CGContextFillRect(context, bar);
}
-}
+ strokeColor = isDark ?
+ CGColorFromGray(darkListheaderBorder) :
+ CGColorFromGray(listheaderSeparator);
+ CGContextSetStrokeColorWithColor(context, strokeColor);
+ CGContextAddLines(context, separator, 2);
+ CGContextStrokePath(context);
+ strokeColor = isDark ?
+ CGColorFromGray(darkListheaderBorder) :
+ CGColorFromGray(lightListheaderBorder);
+ CGContextSetStrokeColorWithColor(context, strokeColor);
+ CGContextAddLines(context, top, 2);
+ CGContextStrokePath(context);
+ CGContextAddLines(context, bottom, 2);
+ CGContextStrokePath(context);
+ CGContextRestoreGState(context);
+ if (state & TTK_TREEVIEW_STATE_SORTARROW) {
+ CGRect arrowBounds = bounds;
+ arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16;
+ arrowBounds.size.width = 16;
+ if (state & TTK_STATE_ALTERNATE) {
+ DrawUpArrow(context, arrowBounds, 3, 8, state);
+ } else if (state & TTK_STATE_SELECTED) {
+ DrawDownArrow(context, arrowBounds, 3, 8, state);
+ }
+ }
+}
+
/*----------------------------------------------------------------------
- * +++ DrawDarkTab --
+ * DrawTab --
*
- * This is a standalone drawing procedure which draws Tabbed Pane
- * Tabs in the Dark Mode style.
+ * This is a standalone drawing procedure which draws Tabbed Pane Tabs for the
+ * notebook widget.
*/
-static void DrawDarkTab(
+static void
+DrawTab(
CGRect bounds,
Ttk_State state,
- CGContextRef context)
+ CGContextRef context,
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *faceColor, *stroke;
CGRect originalBounds = bounds;
- int OSVersion = [NSApp macOSVersion];
-
- CGContextSetLineWidth(context, 1.0);
- CGContextClipToRect(context, bounds);
+ CGColorRef strokeColor;
/*
* Extend the bounds to one or both sides so the rounded part will be
- * clipped off.
+ * clipped off if the right of the left tab, the left of the right tab,
+ * and both sides of the middle tabs.
*/
- if (OSVersion < 110000 || !(state & TTK_STATE_SELECTED)) {
- if (!(state & TTK_STATE_FIRST_TAB)) {
- bounds.origin.x -= 10;
- bounds.size.width += 10;
- }
- if (!(state & TTK_STATE_LAST_TAB)) {
- bounds.size.width += 10;
- }
+ CGContextClipToRect(context, bounds);
+ if (!(state & TTK_STATE_FIRST_TAB)) {
+ bounds.origin.x -= 10;
+ bounds.size.width += 10;
+ }
+ if (!(state & TTK_STATE_LAST_TAB)) {
+ bounds.size.width += 10;
}
/*
* Fill the tab face with the appropriate color or gradient. Use a solid
- * color if the tab is not selected, otherwise use a blue or gray
- * gradient.
+ * color if the tab is not selected, otherwise use the accent color with
+ * highlights
*/
- bounds = CGRectInset(bounds, 1, 1);
if (!(state & TTK_STATE_SELECTED)) {
- if (state & TTK_STATE_DISABLED) {
- if (OSVersion < 110000) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkDisabledButtonFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkTab
- count: 4];
- }
- } else {
- if (OSVersion < 110000) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkButtonFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkTab
- count: 4];
- }
- }
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
+ DrawGrayButton(context, bounds, &tabDesign, state, tkwin);
/*
* Draw a separator line on the left side of the tab if it
@@ -1070,11 +1502,8 @@ static void DrawDarkTab(
if (!(state & TTK_STATE_FIRST_TAB)) {
CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkTabSeparator
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
+ strokeColor = CGColorFromGray(darkTabSeparator);
+ CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextBeginPath(context);
CGContextMoveToPoint(context, originalBounds.origin.x,
originalBounds.origin.y + 1);
@@ -1086,299 +1515,118 @@ static void DrawDarkTab(
} else {
/*
- * This is the selected tab. If it is first, cover up the separator
- * line drawn by the second one. (The selected tab is always drawn
- * last.)
+ * This is the selected tab; paint it with the current accent color.
+ * If it is first, cover up the separator line drawn by the second one.
+ * (The selected tab is always drawn last.)
*/
if ((state & TTK_STATE_FIRST_TAB) && !(state & TTK_STATE_LAST_TAB)) {
bounds.size.width += 1;
}
if (!(state & TTK_STATE_BACKGROUND)) {
- if (OSVersion < 110000) {
- GradientFillRoundedRectangle(context, bounds, 4,
- darkSelectedGradient, 2);
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkSelectedTab
- count: 4];
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
- }
+ DrawAccentedButton(context, bounds, &tabDesign, 0, 0);
} else {
- if (OSVersion < 110000) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkInactiveSelectedTab
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkSelectedTab
- count: 4];
- }
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
+ DrawGrayButton(context, bounds, &tabDesign, state, tkwin);
}
- HighlightButtonBorder(context, bounds);
}
}
-/*----------------------------------------------------------------------
- * +++ DrawDarkSeparator --
- *
- * This is a standalone drawing procedure which draws a separator widget
- * in Dark Mode.
- */
-
-static void DrawDarkSeparator(
- CGRect bounds,
- CGContextRef context,
- TCL_UNUSED(Tk_Window))
-{
- static CGFloat fill[4] = {1.0, 1.0, 1.0, 0.3};
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *fillColor = [NSColor colorWithColorSpace: deviceRGB
- components: fill
- count:4];
-
- CGContextSetFillColorWithColor(context, CGCOLOR(fillColor));
- CGContextFillRect(context, bounds);
-}
-
-/*----------------------------------------------------------------------
- * +++ DrawDarkFocusRing --
- *
- * This is a standalone drawing procedure which draws a focus ring around
- * an Entry widget in Dark Mode.
- */
-
-static void DrawDarkFocusRing(
- CGRect bounds,
- CGContextRef context)
-{
- CGRect insetBounds = CGRectInset(bounds, -3, -3);
- CHECK_RADIUS(4, insetBounds)
-
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *strokeColor;
- NSColor *fillColor = [NSColor colorWithColorSpace:deviceRGB
- components:darkFocusRing
- count:4];
- CGFloat x = bounds.origin.x, y = bounds.origin.y;
- CGFloat w = bounds.size.width, h = bounds.size.height;
- CGPoint topPart[4] = {
- {x, y + h}, {x, y + 1}, {x + w - 1, y + 1}, {x + w - 1, y + h}
- };
- CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
-
- CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- CGContextBeginPath(context);
- strokeColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkFocusRingTop
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
- CGContextAddLines(context, topPart, 4);
- CGContextStrokePath(context);
- strokeColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkFocusRingBottom
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
- CGContextAddLines(context, bottom, 2);
- CGContextStrokePath(context);
- CGContextSetShouldAntialias(context, true);
- CGContextSetFillColorWithColor(context, CGCOLOR(fillColor));
- CGPathRef path = CGPathCreateWithRoundedRect(insetBounds, 4, 4, NULL);
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGContextAddRect(context, bounds);
- CGContextEOFillPath(context);
- CGContextRestoreGState(context);
-}
-/*----------------------------------------------------------------------
- * +++ DrawDarkFrame --
- *
- * This is a standalone drawing procedure which draws various
- * types of borders in Dark Mode.
- */
-
-static void DrawDarkFrame(
+static void
+DrawTab11(
CGRect bounds,
+ Ttk_State state,
CGContextRef context,
- HIThemeFrameKind kind)
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *stroke;
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- CGFloat x = bounds.origin.x, y = bounds.origin.y;
- CGFloat w = bounds.size.width, h = bounds.size.height;
- CGPoint topPart[4] = {
- {x, y + h - 1}, {x, y + 1}, {x + w, y + 1}, {x + w, y + h - 1}
- };
- CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
- CGPoint accent[2] = {{x, y + 1}, {x + w, y + 1}};
+ if (state & TTK_STATE_SELECTED) {
+ DrawGrayButton(context, bounds, &pushbuttonDesign, state, tkwin);
+ } else {
+ CGRect clipRect = bounds;
+ /*
+ * Draw a segment of a Group Box as a background for non-selected tabs.
+ * Clip the Group Box so that the segments fit together to form a long
+ * rounded rectangle behind the entire tab bar.
+ */
- switch (kind) {
- case kHIThemeFrameTextFieldSquare:
+ if (!(state & TTK_STATE_FIRST_TAB)) {
+ clipRect.origin.x -= 5;
+ bounds.origin.x -= 5;
+ bounds.size.width += 5;
+ }
+ if (!(state & TTK_STATE_LAST_TAB)) {
+ clipRect.size.width += 5;
+ bounds.size.width += 5;
+ }
CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- CGContextBeginPath(context);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkFrameTop
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- CGContextAddLines(context, topPart, 4);
- CGContextStrokePath(context);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkFrameBottom
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- CGContextAddLines(context, bottom, 2);
- CGContextStrokePath(context);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkFrameAccent
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- CGContextAddLines(context, accent, 2);
- CGContextStrokePath(context);
+ CGContextClipToRect(context, clipRect);
+ DrawGroupBox(bounds, context, tkwin, 3, NO);
CGContextRestoreGState(context);
- break;
- default:
- break;
}
}
/*----------------------------------------------------------------------
- * +++ DrawListHeader --
+ * DrawDarkSeparator --
*
- * This is a standalone drawing procedure which draws column
- * headers for a Treeview in the Dark Mode.
+ * This is a standalone drawing procedure which draws a separator widget
+ * in Dark Mode. HIToolbox is used in light mode.
*/
-static void DrawDarkListHeader(
+static void DrawDarkSeparator(
CGRect bounds,
CGContextRef context,
- TCL_UNUSED(Tk_Window),
- int state)
+ TCL_UNUSED(Tk_Window))
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *stroke;
-
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- CGFloat x = bounds.origin.x, y = bounds.origin.y;
- CGFloat w = bounds.size.width, h = bounds.size.height;
+ CGColorRef sepColor = CGColorFromGray(darkSeparator);
- CGPoint top[2] = {{x, y + 1}, {x + w, y + 1}};
- CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
- CGPoint separator[2] = {{x + w - 1, y + 3}, {x + w - 1, y + h - 3}};
- CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkFrameBottom
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- CGContextBeginPath(context);
- CGContextAddLines(context, top, 2);
- CGContextStrokePath(context);
- CGContextAddLines(context, bottom, 2);
- CGContextStrokePath(context);
- CGContextAddLines(context, separator, 2);
- CGContextStrokePath(context);
- CGContextRestoreGState(context);
-
- if (state & TTK_TREEVIEW_STATE_SORTARROW) {
- CGRect arrowBounds = bounds;
-
- arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16;
- arrowBounds.size.width = 16;
- if (state & TTK_STATE_ALTERNATE) {
- DrawUpArrow(context, arrowBounds, 3, 8, WHITERGBA);
- } else if (state & TTK_STATE_SELECTED) {
- DrawDownArrow(context, arrowBounds, 3, 8, WHITERGBA);
- }
- }
+ CGContextSetFillColorWithColor(context, sepColor);
+ CGContextFillRect(context, bounds);
}
/*----------------------------------------------------------------------
- * +++ Button element: Used for elements drawn with DrawThemeButton.
- */
-
-/*
- * When Ttk draws the various types of buttons, a pointer to one of these
- * is passed as the clientData.
- */
-
-typedef struct {
- ThemeButtonKind kind;
- ThemeMetric heightMetric;
-} ThemeButtonParams;
-static ThemeButtonParams
- PushButtonParams = {kThemePushButton, kThemeMetricPushButtonHeight},
- CheckBoxParams = {kThemeCheckBox, kThemeMetricCheckBoxHeight},
- RadioButtonParams = {kThemeRadioButton, kThemeMetricRadioButtonHeight},
- BevelButtonParams = {kThemeRoundedBevelButton, NoThemeMetric},
- PopupButtonParams = {kThemePopupButton, kThemeMetricPopupButtonHeight},
- DisclosureParams = {
- kThemeDisclosureButton, kThemeMetricDisclosureTriangleHeight
-},
- ListHeaderParams =
-{kThemeListHeaderButton, kThemeMetricListHeaderHeight};
-static Ttk_StateTable ButtonValueTable[] = {
- {kThemeButtonOff, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
- {kThemeButtonMixed, TTK_STATE_ALTERNATE, 0},
- {kThemeButtonOn, TTK_STATE_SELECTED, 0},
- {kThemeButtonOff, 0, 0}
-
- /*
- * Others: kThemeDisclosureRight, kThemeDisclosureDown,
- * kThemeDisclosureLeft
- */
-
-};
-static Ttk_StateTable ButtonAdornmentTable[] = {
- {kThemeAdornmentNone, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
- {kThemeAdornmentDefault | kThemeAdornmentFocus,
- TTK_STATE_ALTERNATE | TTK_STATE_FOCUS, 0},
- {kThemeAdornmentFocus, TTK_STATE_FOCUS, 0},
- {kThemeAdornmentDefault, TTK_STATE_ALTERNATE, 0},
- {kThemeAdornmentNone, 0, 0}
-};
-
-/*----------------------------------------------------------------------
- * +++ computeButtonDrawInfo --
+ * +++ DrawGradientButton --
*
- * Fill in an appearance manager HIThemeButtonDrawInfo record.
+ * This is a standalone drawing procedure which draws a
+ * a Gradient Button.
*/
-static inline HIThemeButtonDrawInfo computeButtonDrawInfo(
- ThemeButtonParams *params,
- Ttk_State state,
- TCL_UNUSED(Tk_Window))
+static void DrawGradientBorder(
+ CGRect bounds,
+ CGContextRef context,
+ Tk_Window tkwin,
+ Ttk_State state)
{
+ CGColorRef faceColor, borderColor;
+ GrayColor faceGray, borderGray;
+ CGRect inside = CGRectInset(bounds, 1, 1);
- /*
- * See ButtonElementDraw for the explanation of why we always draw
- * PushButtons in the active state.
- */
-
- SInt32 HIThemeState;
-
- HIThemeState = Ttk_StateTableLookup(ThemeStateTable, state);
- switch (params->kind) {
- case kThemePushButton:
- HIThemeState &= ~kThemeStateInactive;
- HIThemeState |= kThemeStateActive;
- break;
- default:
- break;
+ if (TkMacOSXInDarkMode(tkwin)) {
+ if (state & TTK_STATE_DISABLED) {
+ faceGray = darkGradientDisabled;
+ borderGray = darkGradientBorderDisabled;
+ } else {
+ faceGray = state & TTK_STATE_PRESSED ?
+ darkGradientPressed : darkGradientNormal;
+ borderGray = darkGradientBorder;
+ }
+ } else {
+ if (state & TTK_STATE_DISABLED) {
+ faceGray = lightGradientDisabled;
+ borderGray = lightGradientBorderDisabled;
+ } else {
+ faceGray = state & TTK_STATE_PRESSED ?
+ lightGradientPressed : lightGradientNormal;
+ borderGray = lightGradientBorder;
+ }
}
-
- const HIThemeButtonDrawInfo info = {
- .version = 0,
- .state = HIThemeState,
- .kind = params ? params->kind : 0,
- .value = Ttk_StateTableLookup(ButtonValueTable, state),
- .adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state),
- };
- return info;
+ faceColor = CGColorFromGray(faceGray);
+ borderColor = CGColorFromGray(borderGray);
+ CGContextSetFillColorWithColor(context, faceColor);
+ CGContextFillRect(context, inside);
+ CGContextSetFillColorWithColor(context, borderColor);
+ CGContextAddRect(context, bounds);
+ CGContextAddRect(context, inside);
+ CGContextEOFillPath(context);
}
/*----------------------------------------------------------------------
@@ -1387,16 +1635,13 @@ static inline HIThemeButtonDrawInfo computeButtonDrawInfo(
static void ButtonElementMinSize(
void *clientData,
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
int *minWidth,
- int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ int *minHeight)
{
- ThemeButtonParams *params = (ThemeButtonParams *)clientData;
+ ThemeButtonParams *params = clientData;
if (params->heightMetric != NoThemeMetric) {
- ChkErr(GetThemeMetric, params->heightMetric, (SInt *) minHeight);
+ ChkErr(GetThemeMetric, params->heightMetric, minHeight);
/*
* The theme height does not include the 1-pixel border around
@@ -1407,33 +1652,54 @@ static void ButtonElementMinSize(
*minHeight += 2;
/*
- * The minwidth must be 0 to force the generic ttk code to compute the
+ * For buttons with labels the minwidth must be 0 to force the
* correct text layout. For example, a non-zero value will cause the
* text to be left justified, no matter what -anchor setting is used in
* the style.
*/
- *minWidth = 0;
+ if (params->widthMetric != NoThemeMetric) {
+ ChkErr(GetThemeMetric, params->widthMetric, minWidth);
+ *minWidth += 2;
+ *minHeight += 2;
+ } else {
+ *minWidth = 0;
+ }
}
}
static void ButtonElementSize(
void *clientData,
- void *elementRecord,
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
int *minWidth,
int *minHeight,
Ttk_Padding *paddingPtr)
{
ThemeButtonParams *params = (ThemeButtonParams *)clientData;
- const HIThemeButtonDrawInfo info =
- computeButtonDrawInfo(params, 0, tkwin);
+ HIThemeButtonDrawInfo info =
+ ComputeButtonDrawInfo(params, 0, tkwin);
static const CGRect scratchBounds = {{0, 0}, {100, 100}};
CGRect contentBounds, backgroundBounds;
int verticalPad;
- ButtonElementMinSize(clientData, elementRecord, tkwin,
- minWidth, minHeight, paddingPtr);
+ ButtonElementMinSize(clientData, minWidth, minHeight);
+ switch (info.kind) {
+ case TkGradientButton:
+ *paddingPtr = Ttk_MakePadding(1, 1, 1, 1);
+ /* Fall through. */
+ case kThemeArrowButton:
+ case kThemeRoundButtonHelp:
+ return;
+ /* Buttons which are sized like PushButtons but unknown to HITheme. */
+ case TkRoundedRectButton:
+ case TkRecessedButton:
+ case TkInlineButton:
+ info.kind = kThemePushButton;
+ break;
+ default:
+ break;
+ }
/*
* Given a hypothetical bounding rectangle for a button, HIToolbox will
@@ -1442,7 +1708,7 @@ static void ButtonElementSize(
* enough to contain the image of the button in any state, which might
* include highlight borders, shadows, etc. The content rectangle is not
* centered vertically within the background rectangle, presumably because
- * shadows only appear on the bottom. Nonetheless, when HITools is asked
+ * shadows only appear on the bottom. Nonetheless, when HIToolbox is asked
* to draw a button with a certain bounding rectangle it draws the button
* centered within the rectangle.
*
@@ -1461,11 +1727,15 @@ static void ButtonElementSize(
CGRectGetMaxX(backgroundBounds) - CGRectGetMaxX(contentBounds);
verticalPad = backgroundBounds.size.height - contentBounds.size.height;
paddingPtr->top = paddingPtr->bottom = verticalPad / 2;
+ if (info.kind == kThemePopupButton) {
+ paddingPtr->top += 1;
+ paddingPtr->bottom -= 1;
+ }
}
static void ButtonElementDraw(
void *clientData,
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -1473,71 +1743,97 @@ static void ButtonElementDraw(
{
ThemeButtonParams *params = (ThemeButtonParams *)clientData;
CGRect bounds = BoxToRect(d, b);
- HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, state, tkwin);
+ HIThemeButtonDrawInfo info = ComputeButtonDrawInfo(params, state, tkwin);
+ int isDark = TkMacOSXInDarkMode(tkwin);
- bounds = NormalizeButtonBounds(params->heightMetric, bounds);
+ switch (info.kind) {
- BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- switch (info.kind) {
- case kThemePushButton:
- case kThemePopupButton:
- DrawDarkButton(bounds, info.kind, state, dc.context);
- break;
- case kThemeCheckBox:
- DrawDarkCheckBox(bounds, state, dc.context);
- break;
- case kThemeRadioButton:
- DrawDarkRadioButton(bounds, state, dc.context);
- break;
- case kThemeRoundedBevelButton:
- DrawDarkBevelButton(bounds, state, dc.context);
- break;
- default:
- ChkErr(HIThemeDrawButton, &bounds, &info, dc.context,
- HIOrientation, NULL);
- }
- } else if (info.kind == kThemePushButton &&
- (state & TTK_STATE_PRESSED)) {
- bounds.size.height += 2;
- if ([NSApp macOSVersion] > 100800) {
- GradientFillRoundedRectangle(dc.context, bounds, 4,
- pressedPushButtonGradient, 2);
- }
- } else {
+ /*
+ * A Gradient Button should have an image and no text. The size is set to
+ * that of the image. All we need to do is draw a 1-pixel border.
+ */
- /*
- * Apple's PushButton and PopupButton do not change their fill color
- * when the window is inactive. However, except in 10.7 (Lion), the
- * color of the arrow button on a PopupButton does change. For some
- * reason HITheme fills inactive buttons with a transparent color that
- * allows the window background to show through, leading to
- * inconsistent behavior. We work around this by filling behind an
- * inactive PopupButton with a text background color before asking
- * HIToolbox to draw it. For PushButtons, we simply draw them in the
- * active state.
- */
+ case TkGradientButton:
+ BEGIN_DRAWING(d)
+ DrawGradientBorder(bounds, dc.context, tkwin, state);
+ END_DRAWING
+ return;
+ /*
+ * Buttons with no height restrictions are ready to draw.
+ */
- if (info.kind == kThemePopupButton &&
- (state & TTK_STATE_BACKGROUND)) {
- CGRect innerBounds = CGRectInset(bounds, 1, 1);
- NSColor *whiteRGBA = [NSColor whiteColor];
- SolidFillRoundedRectangle(dc.context, innerBounds, 4, whiteRGBA);
- }
+ case kThemeArrowButton:
+ case kThemeCheckBox:
+ case kThemeRadioButton:
+ break;
- /*
- * A BevelButton with mixed value is drawn borderless, which does make
- * much sense for us.
- */
+ /*
+ * Other buttons have a maximum height. We have to deal with that.
+ */
- if (info.kind == kThemeRoundedBevelButton &&
- info.value == kThemeButtonMixed) {
- info.value = kThemeButtonOff;
- info.state = kThemeStateInactive;
- }
- ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
- NULL);
+ default:
+ bounds = NormalizeButtonBounds(params, bounds, isDark);
+ break;
+ }
+
+ /* We do our own drawing on new systems.*/
+
+ if ([NSApp macOSVersion] > 100800) {
+ BEGIN_DRAWING(d)
+ DrawButton(bounds, info, state, dc.context, tkwin);
+ END_DRAWING
+ return;
+ }
+
+ /*
+ * If execution reaches here it means we should use HIToolbox to draw the
+ * button. Buttons that HIToolbox doesn't know are rendered as
+ * PushButtons.
+ */
+
+ switch (info.kind) {
+ case TkRoundedRectButton:
+ case TkRecessedButton:
+ info.kind = kThemePushButton;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Apple's PushButton and PopupButton do not change their fill color
+ * when the window is inactive. However, except in 10.7 (Lion), the
+ * color of the arrow button on a PopupButton does change. For some
+ * reason HITheme fills inactive buttons with a transparent color that
+ * allows the window background to show through, leading to
+ * inconsistent behavior. We work around this by filling behind an
+ * inactive PopupButton with a text background color before asking
+ * HIToolbox to draw it. For PushButtons, we simply draw them in the
+ * active state.
+ */
+
+ BEGIN_DRAWING(d)
+ if (info.kind == kThemePopupButton &&
+ (state & TTK_STATE_BACKGROUND)) {
+ CGRect innerBounds = CGRectInset(bounds, 1, 1);
+ FillRoundedRectangle(dc.context, innerBounds, 4, CG_WHITE);
+ }
+
+ /*
+ * A BevelButton with mixed value is drawn borderless, which does make
+ * much sense for us.
+ */
+
+ if (info.kind == kThemeRoundedBevelButton &&
+ info.value == kThemeButtonMixed) {
+ info.value = kThemeButtonOff;
+ info.state = kThemeStateInactive;
}
+ if (info.kind == kThemePushButton) {
+ bounds.origin.y -= 2;
+ }
+ ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
+ NULL);
END_DRAWING
}
@@ -1617,41 +1913,40 @@ static Ttk_StateTable TabPositionTable[] = {
*/
static void TabElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- int *minHeight,
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
- GetThemeMetric(kThemeMetricLargeTabHeight, (SInt32 *) minHeight);
- *paddingPtr = Ttk_MakePadding(0, 0, 0, 2);
-
+ *paddingPtr = Ttk_MakePadding(0, -2, 0, 1);
}
static void TabElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
Ttk_State state)
{
CGRect bounds = BoxToRect(d, b);
- HIThemeTabDrawInfo info = {
- .version = 1,
- .style = Ttk_StateTableLookup(TabStyleTable, state),
- .direction = kThemeTabNorth,
- .size = kHIThemeTabSizeNormal,
- .adornment = Ttk_StateTableLookup(TabAdornmentTable, state),
- .kind = kHIThemeTabKindNormal,
- .position = Ttk_StateTableLookup(TabPositionTable, state),
- };
-
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- DrawDarkTab(bounds, state, dc.context);
+ if ([NSApp macOSVersion] >= 110000) {
+ DrawTab11(bounds, state, dc.context, tkwin);
+ } else if ([NSApp macOSVersion] > 100800) {
+ DrawTab(bounds, state, dc.context, tkwin);
} else {
+ HIThemeTabDrawInfo info = {
+ .version = 1,
+ .style = Ttk_StateTableLookup(TabStyleTable, state),
+ .direction = kThemeTabNorth,
+ .size = kHIThemeTabSizeNormal,
+ .adornment = Ttk_StateTableLookup(TabAdornmentTable, state),
+ .kind = kHIThemeTabKindNormal,
+ .position = Ttk_StateTableLookup(TabPositionTable, state),
+ };
ChkErr(HIThemeDrawTab, &bounds, &info, dc.context, HIOrientation,
NULL);
}
@@ -1671,19 +1966,19 @@ static Ttk_ElementSpec TabElementSpec = {
*/
static void PaneElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- TCL_UNUSED(int *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
*paddingPtr = Ttk_MakePadding(9, 5, 9, 9);
}
static void PaneElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -1695,7 +1990,7 @@ static void PaneElementDraw(
bounds.size.height += kThemeMetricTabFrameOverlap;
BEGIN_DRAWING(d)
if ([NSApp macOSVersion] > 100800) {
- DrawGroupBox(bounds, dc.context, tkwin);
+ DrawGroupBox(bounds, dc.context, tkwin, 1, YES);
} else {
HIThemeTabPaneDrawInfo info = {
.version = 1,
@@ -1727,24 +2022,22 @@ static Ttk_ElementSpec PaneElementSpec = {
* DrawThemePrimaryGroup reference: "The primary group box frame is drawn
* inside the specified rectangle and is a maximum of 2 pixels thick."
*
- * "Maximum of 2 pixels thick" is apparently a lie; looks more like 4 to me
- * with shading.
*/
static void GroupElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- TCL_UNUSED(int *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
- *paddingPtr = Ttk_UniformPadding(4);
+ *paddingPtr = Ttk_MakePadding(0, 0, 0, 0);
}
static void GroupElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -1754,7 +2047,7 @@ static void GroupElementDraw(
BEGIN_DRAWING(d)
if ([NSApp macOSVersion] > 100800) {
- DrawGroupBox(bounds, dc.context, tkwin);
+ DrawGroupBox(bounds, dc.context, tkwin, 1, YES);
} else {
const HIThemeGroupBoxDrawInfo info = {
.version = 0,
@@ -1797,18 +2090,18 @@ static Ttk_ElementOptionSpec EntryElementOptions[] = {
};
static void EntryElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- TCL_UNUSED(int *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
- *paddingPtr = Ttk_MakePadding(7, 5, 7, 6);
+ *paddingPtr = entryElementPadding;
}
static void EntryElementDraw(
- TCL_UNUSED(void *),
+ void *clientData,
void *elementRecord,
Tk_Window tkwin,
Drawable d,
@@ -1816,40 +2109,31 @@ static void EntryElementDraw(
Ttk_State state)
{
EntryElement *e = (EntryElement *)elementRecord;
- Ttk_Box inner = Ttk_PadBox(b, Ttk_UniformPadding(3));
- CGRect bounds = BoxToRect(d, inner);
- NSColor *background;
+ ThemeFrameParams *params = clientData;
+ HIThemeFrameKind kind = params ? params->kind :
+ kHIThemeFrameTextFieldSquare;
+ CGRect bounds = BoxToRect(d, b);
+ CGColorRef background;
Tk_3DBorder backgroundPtr = NULL;
static const char *defaultBG = ENTRY_DEFAULT_BACKGROUND;
- if (TkMacOSXInDarkMode(tkwin)) {
+ if ([NSApp macOSVersion] > 100800) {
BEGIN_DRAWING(d)
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- CGFloat fill[4];
- GetBackgroundColor(dc.context, tkwin, 1, fill);
-
- /*
- * Lighten the background to provide contrast.
- */
-
- for (int i = 0; i < 3; i++) {
- fill[i] += 9.0 / 255.0;
+ switch(kind) {
+ case kHIThemeFrameTextFieldRound:
+ DrawEntry(dc.context, bounds, &searchDesign, state, tkwin);
+ break;
+ case kHIThemeFrameTextFieldSquare:
+ DrawEntry(dc.context, bounds, &entryDesign, state, tkwin);
+ break;
+ default:
+ return;
}
- background = [NSColor colorWithColorSpace: deviceRGB
- components: fill
- count: 4];
- CGContextSetFillColorWithColor(dc.context, CGCOLOR(background));
- CGContextFillRect(dc.context, bounds);
- if (state & TTK_STATE_FOCUS) {
- DrawDarkFocusRing(bounds, dc.context);
- } else {
- DrawDarkFrame(bounds, dc.context, kHIThemeFrameTextFieldSquare);
- }
END_DRAWING
} else {
const HIThemeFrameDrawInfo info = {
.version = 0,
- .kind = kHIThemeFrameTextFieldSquare,
+ .kind = params->kind,
.state = Ttk_StateTableLookup(ThemeStateTable, state),
.isFocused = state & TTK_STATE_FOCUS,
};
@@ -1872,13 +2156,13 @@ static void EntryElementDraw(
if (backgroundPtr != NULL) {
XFillRectangle(Tk_Display(tkwin), d,
Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC),
- inner.x, inner.y, inner.width, inner.height);
+ b.x, b.y, b.width, b.height);
}
BEGIN_DRAWING(d)
if (backgroundPtr == NULL) {
if ([NSApp macOSVersion] > 100800) {
- background = [NSColor textBackgroundColor];
- CGContextSetFillColorWithColor(dc.context, CGCOLOR(background));
+ background = CGCOLOR([NSColor textBackgroundColor]);
+ CGContextSetFillColorWithColor(dc.context, background);
} else {
CGContextSetRGBFillColor(dc.context, 1.0, 1.0, 1.0, 1.0);
}
@@ -1917,29 +2201,25 @@ static Ttk_ElementSpec EntryElementSpec = {
* 1 pixel to account for the fact that the button is not centered.
*/
-static Ttk_Padding ComboboxPadding = {4, 4, 20, 4};
-static Ttk_Padding DarkComboboxPadding = {6, 6, 22, 6};
+// OS dependent ???
+static Ttk_Padding ComboboxPadding = {7, 5, 24, 5};
static void ComboboxElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- Tk_Window tkwin,
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
Ttk_Padding *paddingPtr)
{
*minWidth = 24;
- *minHeight = 23;
- if (TkMacOSXInDarkMode(tkwin)) {
- *paddingPtr = DarkComboboxPadding;
- } else {
- *paddingPtr = ComboboxPadding;
- }
+ *minHeight = 0;
+ *paddingPtr = ComboboxPadding;
}
static void ComboboxElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -1955,23 +2235,13 @@ static void ComboboxElementDraw(
};
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- bounds = CGRectInset(bounds, 3, 3);
- if (state & TTK_STATE_FOCUS) {
- DrawDarkFocusRing(bounds, dc.context);
- }
- DrawDarkButton(bounds, info.kind, state, dc.context);
+ if ([NSApp macOSVersion] > 100800) {
+ bounds = CGRectInset(bounds, -1, -1);
+ DrawButton(bounds, info, state, dc.context, tkwin);
} else {
- if ([NSApp macOSVersion] > 100800) {
- if ((state & TTK_STATE_BACKGROUND) &&
- !(state & TTK_STATE_DISABLED)) {
- NSColor *background = [NSColor textBackgroundColor];
- CGRect innerBounds = CGRectInset(bounds, 1, 4);
- bounds.origin.y += 1;
- SolidFillRoundedRectangle(dc.context, innerBounds, 4, background);
- }
- }
- ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL);
+ bounds.origin.y += 1;
+ ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
+ NULL);
}
END_DRAWING
}
@@ -2005,27 +2275,42 @@ static Ttk_ElementSpec ComboboxElementSpec = {
* that order. So the up button must be listed first in the layout.
*/
-static Ttk_Padding SpinbuttonMargins = {0, 0, 2, 0};
+static Ttk_Padding SpinbuttonMargins = {2, 0, 0, 0};
-static void SpinButtonUpElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+static void SpinButtonReBounds(
+ Tk_Window tkwin,
+ CGRect *bounds)
+{
+ if (TkMacOSXInDarkMode(tkwin)) {
+ bounds->origin.x -= 2;
+ bounds->origin.y += 1;
+ bounds->size.height -= 0.5;
+ } else {
+ bounds->origin.x -= 3;
+ bounds->origin.y += 1;
+ bounds->size.width += 1;
+ }
+}
+
+static void SpinButtonElementSize(
+ TCL_UNUSED(void *), /* clientdata */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* PaddingPtr */
{
SInt32 s;
ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s);
*minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins);
ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s);
- *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2;
+ *minHeight = 2 + (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2;
}
static void SpinButtonUpElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -2034,6 +2319,7 @@ static void SpinButtonUpElementDraw(
CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins));
int infoState;
+ SpinButtonReBounds(tkwin, &bounds);
bounds.size.height *= 2;
if (state & TTK_STATE_PRESSED) {
infoState = kThemeStatePressedUp;
@@ -2048,8 +2334,8 @@ static void SpinButtonUpElementDraw(
.adornment = kThemeAdornmentNone,
};
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- DrawDarkIncDecButton(bounds, infoState, state, dc.context);
+ if ([NSApp macOSVersion] > 100800) {
+ DrawButton(bounds, info, state, dc.context, tkwin);
} else {
ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
NULL);
@@ -2061,28 +2347,13 @@ static Ttk_ElementSpec SpinButtonUpElementSpec = {
TK_STYLE_VERSION_2,
sizeof(NullElement),
TtkNullElementOptions,
- SpinButtonUpElementSize,
+ SpinButtonElementSize,
SpinButtonUpElementDraw
};
-static void SpinButtonDownElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- int *minWidth,
- int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
-{
- SInt32 s;
-
- ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s);
- *minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins);
- ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s);
- *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2;
-}
static void SpinButtonDownElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -2091,8 +2362,9 @@ static void SpinButtonDownElementDraw(
CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins));
int infoState = 0;
+ SpinButtonReBounds(tkwin, &bounds);
bounds.origin.y -= bounds.size.height;
- bounds.size.height *= 2;
+ bounds.size.height += bounds.size.height;
if (state & TTK_STATE_PRESSED) {
infoState = kThemeStatePressedDown;
} else {
@@ -2107,8 +2379,8 @@ static void SpinButtonDownElementDraw(
};
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- DrawDarkIncDecButton(bounds, infoState, state, dc.context);
+ if ([NSApp macOSVersion] > 100800) {
+ DrawButton(bounds, info, state, dc.context, tkwin);
} else {
ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
NULL);
@@ -2120,7 +2392,7 @@ static Ttk_ElementSpec SpinButtonDownElementSpec = {
TK_STYLE_VERSION_2,
sizeof(NullElement),
TtkNullElementOptions,
- SpinButtonDownElementSize,
+ SpinButtonElementSize,
SpinButtonDownElementDraw
};
@@ -2167,11 +2439,11 @@ static Ttk_ElementOptionSpec TrackElementOptions[] = {
};
static void TrackElementSize(
void *clientData,
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
TrackElementData *data = (TrackElementData *)clientData;
SInt32 size = 24; /* reasonable default ... */
@@ -2191,33 +2463,28 @@ static void TrackElementDraw(
TrackElementData *data = (TrackElementData *)clientData;
TrackElement *elem = (TrackElement *)elementRecord;
Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
- double from = 0, to = 100, value = 0, factor;
- CGRect bounds;
+ double from = 0, to = 100, value = 0, fraction, max;
+ CGRect bounds = BoxToRect(d, b);
TtkGetOrientFromObj(NULL, elem->orientObj, &orientation);
Tcl_GetDoubleFromObj(NULL, elem->fromObj, &from);
Tcl_GetDoubleFromObj(NULL, elem->toObj, &to);
Tcl_GetDoubleFromObj(NULL, elem->valueObj, &value);
- factor = RangeToFactor(to);
-
- /*
- * HIThemeTrackDrawInfo uses 2-byte alignment; assigning to a separate
- * bounds variable avoids UBSan (-fsanitize=alignment) complaints.
- */
- bounds = BoxToRect(d, b);
+ fraction = (value - from) / (to - from);
+ max = RangeToFactor(fabs(to - from));
HIThemeTrackDrawInfo info = {
.version = 0,
.kind = data->kind,
.bounds = bounds,
- .min = from * factor,
- .max = to * factor,
- .value = value * factor,
+ .min = 0,
+ .max = max,
+ .value = fraction * max,
.attributes = kThemeTrackShowThumb |
(orientation == TTK_ORIENT_HORIZONTAL ?
kThemeTrackHorizontal : 0),
.enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state),
- .trackInfo.progress.phase = 0,
+ .trackInfo.progress.phase = 0
};
if (info.kind == kThemeSlider) {
@@ -2230,20 +2497,19 @@ static void TrackElementDraw(
}
}
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- bounds = BoxToRect(d, b);
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *trackColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkTrack
- count: 4];
- if (orientation == TTK_ORIENT_HORIZONTAL) {
- bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 2);
- } else {
- bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 2);
- }
- SolidFillRoundedRectangle(dc.context, bounds, 2, trackColor);
+ if (([NSApp macOSVersion] > 100800) && !(state & TTK_STATE_ALTERNATE)) {
+
+ /*
+ * We use the reserved field to indicate whether "from" is less than
+ * "to". It should be 0 if passing the info to HIThemeDrawInfo, but
+ * we aren't doing that.
+ */
+
+ info.reserved = (from < to);
+ DrawSlider(dc.context, bounds, info, state, tkwin);
+ } else {
+ ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
}
- ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
END_DRAWING
}
@@ -2265,12 +2531,12 @@ static Ttk_ElementSpec TrackElementSpec = {
*/
static void SliderElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
*minWidth = *minHeight = 24;
}
@@ -2313,12 +2579,12 @@ static Ttk_ElementOptionSpec PbarElementOptions[] = {
{NULL, TK_OPTION_BOOLEAN, 0, NULL}
};
static void PbarElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
SInt32 size = 24; /* @@@ Check HIG for correct default */
@@ -2327,7 +2593,7 @@ static void PbarElementSize(
}
static void PbarElementDraw(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
Tk_Window tkwin,
Drawable d,
@@ -2336,70 +2602,50 @@ static void PbarElementDraw(
{
PbarElement *pbar = (PbarElement *)elementRecord;
Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
-
- /*
- * Using 1000 as the maximum should give better than 1 pixel
- * resolution for most progress bars.
- */
-
- int kind, phase = 0, ivalue, imaximum = 1000;
- CGRect bounds;
+ int phase;
+ double value = 0, maximum = 100, factor;
+ CGRect bounds = BoxToRect(d, b);
+ int isIndeterminate = !strcmp("indeterminate",
+ Tcl_GetString(pbar->modeObj));
TtkGetOrientFromObj(NULL, pbar->orientObj, &orientation);
- kind = !strcmp("indeterminate", Tcl_GetString(pbar->modeObj)) ?
- kThemeIndeterminateBar : kThemeProgressBar;
- if (kind == kThemeIndeterminateBar) {
- Tcl_GetIntFromObj(NULL, pbar->phaseObj, &phase);
+ Tcl_GetDoubleFromObj(NULL, pbar->valueObj, &value);
+ Tcl_GetDoubleFromObj(NULL, pbar->maximumObj, &maximum);
+ Tcl_GetIntFromObj(NULL, pbar->phaseObj, &phase);
+
+ if (isIndeterminate) {
/*
- * On macOS 11 the fraction of an indeterminate progress bar which is
- * traversed by the oscillating thumb is value / maximum. The phase
- * determines the position of the moving thumb in that range and is
- * apparently expected to vary between 0 and 120. On earlier systems
- * it is unclear how the phase is used in generating the animation.
+ * When an indeterminate progress bar is animated the phase is
+ * (currently) always 0 and the value increases from min to max
+ * and then decreases back to min. We scale the value by 3 to
+ * speed the animation up a bit.
*/
- ivalue = imaximum;
- } else {
- double value, maximum;
- Tcl_GetDoubleFromObj(NULL, pbar->valueObj, &value);
- Tcl_GetDoubleFromObj(NULL, pbar->maximumObj, &maximum);
- ivalue = (value / maximum)*1000;
+ double remainder = fmod(3*value, 2*maximum);
+ value = remainder > maximum ? 2*maximum - remainder : remainder;
}
-
- /*
- * HIThemeTrackDrawInfo uses 2-byte alignment; assigning to a separate
- * bounds variable avoids UBSan (-fsanitize=alignment) complaints.
- */
-
- bounds = BoxToRect(d, b);
+ factor = RangeToFactor(maximum);
HIThemeTrackDrawInfo info = {
.version = 0,
- .kind = kind,
+ .kind = isIndeterminate? kThemeIndeterminateBar : kThemeProgressBar,
.bounds = bounds,
.min = 0,
- .max = imaximum,
- .value = ivalue,
+ .max = maximum * factor,
+ .value = value * factor,
.attributes = kThemeTrackShowThumb |
- (orientation == TTK_ORIENT_HORIZONTAL ? kThemeTrackHorizontal : 0),
+ (orientation == TTK_ORIENT_HORIZONTAL ?
+ kThemeTrackHorizontal : 0),
.enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state),
- .trackInfo.progress.phase = phase
+ .trackInfo.progress.phase = phase,
};
+
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- bounds = BoxToRect(d, b);
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *trackColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkTrack
- count: 4];
- if (orientation == TTK_ORIENT_HORIZONTAL) {
- bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 3);
- } else {
- bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 1);
- }
- SolidFillRoundedRectangle(dc.context, bounds, 3, trackColor);
+ if ([NSApp macOSVersion] > 100800) {
+ DrawProgressBar(dc.context, bounds, info, state, tkwin);
+ } else {
+ ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
}
- ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
END_DRAWING
}
@@ -2426,9 +2672,9 @@ static Ttk_ElementOptionSpec ScrollbarElementOptions[] = {
{NULL, TK_OPTION_BOOLEAN, 0, NULL}
};
static void TroughElementSize(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
Ttk_Padding *paddingPtr)
@@ -2452,34 +2698,18 @@ static void TroughElementSize(
}
}
-static CGFloat lightTrough[4] = {250.0 / 255, 250.0 / 255, 250.0 / 255, 1.0};
-static CGFloat darkTrough[4] = {45.0 / 255, 46.0 / 255, 49.0 / 255, 1.0};
-static CGFloat lightInactiveThumb[4] = {
- 200.0 / 255, 200.0 / 255, 200.0 / 255, 1.0
-};
-static CGFloat lightActiveThumb[4] = {
- 133.0 / 255, 133.0 / 255, 133.0 / 255, 1.0
-};
-static CGFloat darkInactiveThumb[4] = {
- 116.0 / 255, 117.0 / 255, 118.0 / 255, 1.0
-};
-static CGFloat darkActiveThumb[4] = {
- 158.0 / 255, 158.0 / 255, 159.0 / 255, 1.0
-};
static void TroughElementDraw(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
- TCL_UNUSED(Ttk_State))
+ TCL_UNUSED(Ttk_State)) /* state */
{
ScrollbarElement *scrollbar = (ScrollbarElement *)elementRecord;
Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
CGRect bounds = BoxToRect(d, b);
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *troughColor;
- CGFloat *rgba = TkMacOSXInDarkMode(tkwin) ? darkTrough : lightTrough;
+ GrayColor bgGray;
TtkGetOrientFromObj(NULL, scrollbar->orientObj, &orientation);
if (orientation == TTK_ORIENT_HORIZONTAL) {
@@ -2487,12 +2717,10 @@ static void TroughElementDraw(
} else {
bounds = CGRectInset(bounds, 1, 0);
}
- troughColor = [NSColor colorWithColorSpace: deviceRGB
- components: rgba
- count: 4];
BEGIN_DRAWING(d)
if ([NSApp macOSVersion] > 100800) {
- CGContextSetFillColorWithColor(dc.context, CGCOLOR(troughColor));
+ bgGray = TkMacOSXInDarkMode(tkwin) ? darkTrough : lightTrough;
+ CGContextSetFillColorWithColor(dc.context, CGColorFromGray(bgGray));
} else {
ChkErr(HIThemeSetFill, kThemeBrushDocumentWindowBackground, NULL,
dc.context, HIOrientation);
@@ -2509,12 +2737,12 @@ static Ttk_ElementSpec TroughElementSpec = {
TroughElementDraw
};
static void ThumbElementSize(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
ScrollbarElement *scrollbar = (ScrollbarElement *)elementRecord;
Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
@@ -2530,7 +2758,7 @@ static void ThumbElementSize(
}
static void ThumbElementDraw(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
Tk_Window tkwin,
Drawable d,
@@ -2549,16 +2777,20 @@ static void ThumbElementDraw(
* determine the thumb geometry from the input values of min, max, value
* and viewSize is undocumented. A seemingly natural algorithm is
* implemented below. This code uses that algorithm for older OS versions,
- * because using HITools also handles drawing the buttons and 3D thumb used
+ * because using HIToolbox also handles drawing the buttons and 3D thumb used
* on those systems. For newer systems the cleanest approach is to just
* draw the thumb directly.
*/
if ([NSApp macOSVersion] > 100800) {
CGRect thumbBounds = BoxToRect(d, b);
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *thumbColor;
- CGFloat *rgba;
+ CGColorRef thumbColor;
+ GrayColor bgGray;
+
+ /*
+ * Apple does not draw the thumb when scrolling is not possible.
+ */
+
if ((orientation == TTK_ORIENT_HORIZONTAL &&
thumbBounds.size.width >= Tk_Width(tkwin) - 8) ||
(orientation == TTK_ORIENT_VERTICAL &&
@@ -2568,15 +2800,13 @@ static void ThumbElementDraw(
int isDark = TkMacOSXInDarkMode(tkwin);
if ((state & TTK_STATE_PRESSED) ||
(state & TTK_STATE_HOVER)) {
- rgba = isDark ? darkActiveThumb : lightActiveThumb;
+ bgGray = isDark ? darkActiveThumb : lightActiveThumb;
} else {
- rgba = isDark ? darkInactiveThumb : lightInactiveThumb;
+ bgGray = isDark ? darkInactiveThumb : lightInactiveThumb;
}
- thumbColor = [NSColor colorWithColorSpace: deviceRGB
- components: rgba
- count: 4];
+ thumbColor = CGColorFromGray(bgGray);
BEGIN_DRAWING(d)
- SolidFillRoundedRectangle(dc.context, thumbBounds, 4, thumbColor);
+ FillRoundedRectangle(dc.context, thumbBounds, 4, thumbColor);
END_DRAWING
} else {
double thumbSize, trackSize, visibleSize, factor, fraction;
@@ -2645,12 +2875,12 @@ static Ttk_ElementSpec ThumbElementSpec = {
ThumbElementDraw
};
static void ArrowElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
if ([NSApp macOSVersion] < 100800) {
*minHeight = *minWidth = 14;
@@ -2676,19 +2906,19 @@ static Ttk_ElementSpec ArrowElementSpec = {
*/
static void SeparatorElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
*minWidth = *minHeight = 1;
}
static void SeparatorElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -2728,12 +2958,12 @@ static const ThemeGrowDirection sizegripGrowDirection
= kThemeGrowRight | kThemeGrowDown;
static void SizegripElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
HIThemeGrowBoxDrawInfo info = {
.version = 0,
@@ -2750,9 +2980,9 @@ static void SizegripElementSize(
}
static void SizegripElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
Drawable d,
Ttk_Box b,
unsigned int state)
@@ -2827,8 +3057,8 @@ static Ttk_ElementSpec SizegripElementSpec = {
*/
static void FillElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -2837,15 +3067,10 @@ static void FillElementDraw(
CGRect bounds = BoxToRect(d, b);
if ([NSApp macOSVersion] > 100800) {
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *bgColor;
- CGFloat fill[4];
+ CGColorRef bgColor;
BEGIN_DRAWING(d)
- GetBackgroundColor(dc.context, tkwin, 0, fill);
- bgColor = [NSColor colorWithColorSpace: deviceRGB components: fill
- count: 4];
- CGContextSetFillColorSpace(dc.context, deviceRGB.CGColorSpace);
- CGContextSetFillColorWithColor(dc.context, CGCOLOR(bgColor));
+ bgColor = GetBackgroundCGColor(dc.context, tkwin, NO, 0);
+ CGContextSetFillColorWithColor(dc.context, bgColor);
CGContextFillRect(dc.context, bounds);
END_DRAWING
} else {
@@ -2854,7 +3079,6 @@ static void FillElementDraw(
: kThemeBrushModelessDialogBackgroundActive;
BEGIN_DRAWING(d)
ChkErr(HIThemeSetFill, brush, NULL, dc.context, HIOrientation);
- //QDSetPatternOrigin(PatternOrigin(tkwin, d));
CGContextFillRect(dc.context, bounds);
END_DRAWING
}
@@ -2902,8 +3126,8 @@ static Ttk_ElementSpec BackgroundElementSpec = {
*/
static void ToolbarBackgroundElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
TCL_UNUSED(Ttk_Box),
@@ -2945,7 +3169,7 @@ static Ttk_ElementOptionSpec FieldElementOptions[] = {
};
static void FieldElementDraw(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
Tk_Window tkwin,
Drawable d,
@@ -2972,7 +3196,7 @@ static Ttk_ElementSpec FieldElementSpec = {
/*----------------------------------------------------------------------
* +++ Treeview headers --
*
- * On systems older than 10.9 The header is a kThemeListHeaderButton drawn
+ * On systems older than 10.9 the header is a kThemeListHeaderButton drawn
* by HIToolbox. On newer systems those buttons do not match the Apple
* buttons, so we draw them from scratch.
*/
@@ -2995,11 +3219,11 @@ static Ttk_StateTable TreeHeaderAdornmentTable[] = {
};
static void TreeAreaElementSize (
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- TCL_UNUSED(int *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
@@ -3009,7 +3233,7 @@ static void TreeAreaElementSize (
*/
if ([NSApp macOSVersion] > 100800) {
- paddingPtr->top = 4;
+ *paddingPtr = Ttk_MakePadding(0, 4, 0, 0);
}
}
@@ -3038,7 +3262,7 @@ static void TreeHeaderElementSize(
static void TreeHeaderElementDraw(
void *clientData,
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -3063,11 +3287,7 @@ static void TreeHeaderElementDraw(
*/
bounds.origin.y -= 4;
- if (TkMacOSXInDarkMode(tkwin)) {
- DrawDarkListHeader(bounds, dc.context, tkwin, state);
- } else {
- DrawListHeader(bounds, dc.context, tkwin, state);
- }
+ DrawListHeader(bounds, dc.context, tkwin, state);
} else {
ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
NULL);
@@ -3094,12 +3314,12 @@ static Ttk_StateTable DisclosureValueTable[] = {
{kThemeDisclosureRight, 0, 0},
};
static void DisclosureElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
SInt32 s;
@@ -3110,8 +3330,8 @@ static void DisclosureElementSize(
}
static void DisclosureElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -3191,6 +3411,44 @@ TTK_LAYOUT("TCombobox",
TTK_GROUP("Combobox.padding", TTK_FILL_BOTH,
TTK_NODE("Combobox.textarea", TTK_FILL_BOTH))))
+/* Image Button - no button */
+TTK_LAYOUT("ImageButton",
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH)))
+
+/* Inline Button */
+TTK_LAYOUT("InlineButton",
+ TTK_GROUP("InlineButton.button", TTK_FILL_BOTH,
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH))))
+
+/* Rounded Rect Button -- transparent face */
+TTK_LAYOUT("RoundedRectButton",
+ TTK_GROUP("RoundedRectButton.button", TTK_FILL_BOTH,
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH))))
+
+/* Gradient Button */
+TTK_LAYOUT("GradientButton",
+ TTK_GROUP("GradientButton.button", TTK_FILL_BOTH,
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH))))
+
+/* Recessed Button - text only radio button */
+
+TTK_LAYOUT("RecessedButton",
+ TTK_GROUP("RecessedButton.button", TTK_FILL_BOTH,
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH))))
+
+/* DisclosureButton (not a triangle) -- No label, no border*/
+TTK_LAYOUT("DisclosureButton",
+ TTK_NODE("DisclosureButton.button", TTK_FILL_BOTH))
+
+/* HelpButton -- No label, no border*/
+TTK_LAYOUT("HelpButton",
+ TTK_NODE("HelpButton.button", TTK_FILL_BOTH))
+
/* Notebook tabs -- no focus ring */
TTK_LAYOUT("Tab",
TTK_GROUP("Notebook.tab", TTK_FILL_BOTH,
@@ -3205,6 +3463,17 @@ TTK_LAYOUT("TSpinbox",
TTK_GROUP("Spinbox.field", TTK_FILL_X,
TTK_NODE("Spinbox.textarea", TTK_FILL_X)))
+TTK_LAYOUT("TEntry",
+ TTK_GROUP("Entry.field", TTK_FILL_BOTH|TTK_BORDER,
+ TTK_GROUP("Entry.padding", TTK_FILL_BOTH,
+ TTK_NODE("Entry.textarea", TTK_FILL_BOTH))))
+
+/* Searchbox */
+TTK_LAYOUT("Searchbox",
+ TTK_GROUP("Searchbox.field", TTK_FILL_BOTH|TTK_BORDER,
+ TTK_GROUP("Entry.padding", TTK_FILL_BOTH,
+ TTK_NODE("Entry.textarea", TTK_FILL_BOTH))))
+
/* Progress bars -- track only */
TTK_LAYOUT("TProgressbar",
TTK_NODE("Progressbar.track", TTK_FILL_BOTH))
@@ -3248,6 +3517,34 @@ TTK_END_LAYOUT_TABLE
* +++ Initialization --
*/
+/*----------------------------------------------------------------------
+ * +++ Ttk_MacOSXInit --
+ *
+ * Initialize variables which depend on [NSApp macOSVersion]. Called from
+ * [NSApp applicationDidFinishLaunching].
+ */
+
+MODULE_SCOPE
+void Ttk_MacOSXInit(
+ void)
+{
+ if ([NSApp macOSVersion] < 101400) {
+ entryElementPadding = Ttk_MakePadding(7, 6, 7, 5);
+ } else {
+ entryElementPadding = Ttk_MakePadding(7, 5, 7, 6);
+ }
+ if ([NSApp macOSVersion] < 110000) {
+ Ttk_ContrastDelta = 8.0;
+ } else {
+
+ /*
+ * The subtle contrast became event more subtle in 11.0.
+ */
+
+ Ttk_ContrastDelta = 5.0;
+ }
+}
+
static int AquaTheme_Init(
Tcl_Interp *interp)
{
@@ -3270,14 +3567,26 @@ static int AquaTheme_Init(
Ttk_RegisterElementSpec(themePtr, "Button.button",
&ButtonElementSpec, &PushButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "InlineButton.button",
+ &ButtonElementSpec, &InlineButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "RoundedRectButton.button",
+ &ButtonElementSpec, &RoundedRectButtonParams);
Ttk_RegisterElementSpec(themePtr, "Checkbutton.button",
&ButtonElementSpec, &CheckBoxParams);
Ttk_RegisterElementSpec(themePtr, "Radiobutton.button",
&ButtonElementSpec, &RadioButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "RecessedButton.button",
+ &ButtonElementSpec, &RecessedButtonParams);
Ttk_RegisterElementSpec(themePtr, "Toolbutton.border",
&ButtonElementSpec, &BevelButtonParams);
Ttk_RegisterElementSpec(themePtr, "Menubutton.button",
&ButtonElementSpec, &PopupButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "DisclosureButton.button",
+ &ButtonElementSpec, &DisclosureButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "HelpButton.button",
+ &ButtonElementSpec, &HelpButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "GradientButton.button",
+ &ButtonElementSpec, &GradientButtonParams);
Ttk_RegisterElementSpec(themePtr, "Spinbox.uparrow",
&SpinButtonUpElementSpec, 0);
Ttk_RegisterElementSpec(themePtr, "Spinbox.downarrow",
@@ -3296,8 +3605,12 @@ static int AquaTheme_Init(
Ttk_RegisterElementSpec(themePtr, "Labelframe.border", &GroupElementSpec,
0);
- Ttk_RegisterElementSpec(themePtr, "Entry.field", &EntryElementSpec, 0);
- Ttk_RegisterElementSpec(themePtr, "Spinbox.field", &EntryElementSpec, 0);
+ Ttk_RegisterElementSpec(themePtr, "Entry.field", &EntryElementSpec,
+ &EntryFieldParams);
+ Ttk_RegisterElementSpec(themePtr, "Searchbox.field", &EntryElementSpec,
+ &SearchboxFieldParams);
+ Ttk_RegisterElementSpec(themePtr, "Spinbox.field", &EntryElementSpec,
+ &EntryFieldParams);
Ttk_RegisterElementSpec(themePtr, "separator", &SeparatorElementSpec, 0);
Ttk_RegisterElementSpec(themePtr, "hseparator", &SeparatorElementSpec, 0);
diff --git a/macosx/ttkMacOSXTheme.h b/macosx/ttkMacOSXTheme.h
new file mode 100644
index 0000000..ce732eb
--- /dev/null
+++ b/macosx/ttkMacOSXTheme.h
@@ -0,0 +1,570 @@
+/*
+ * ttkMacOSXTheme.h --
+ *
+ * Static data and macros used in ttkMacOSXTheme.c
+ *
+ * Copyright 2019 Marc Culler
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+/*
+ * Macros for handling drawing contexts.
+ */
+
+#define BEGIN_DRAWING(d) { \
+ TkMacOSXDrawingContext dc; \
+ if (!TkMacOSXSetupDrawingContext((d), NULL, &dc)) { \
+ return; \
+ } \
+
+#define END_DRAWING \
+ TkMacOSXRestoreDrawingContext(&dc);}
+
+
+#define HIOrientation kHIThemeOrientationNormal
+#define NoThemeMetric 0xFFFFFFFF
+
+/*
+ * A scale factor used to map a range of non-negative doubles into a large
+ * range of non-negative 32-bit integers without losing too much information.
+ * (Use 1073741824 == 2^30 so we don't get surprised by roundoff.)
+ */
+
+#define RangeToFactor(max) ((double) (1073741824.0) / (max < 1.0 ? 1.0 : max))
+
+/*
+ * Meanings of Ttk states represented by User1 and User2.
+ */
+
+#define TTK_STATE_FIRST_TAB TTK_STATE_USER1
+#define TTK_STATE_LAST_TAB TTK_STATE_USER2
+#define TTK_STATE_IS_ACCENTED TTK_STATE_USER2
+#define TTK_TREEVIEW_STATE_SORTARROW TTK_STATE_USER1
+
+/*
+ * Colors and gradients used when drawing buttons.
+ */
+
+typedef struct GrayColor {
+ CGFloat grayscale;
+ CGFloat alpha;
+} GrayColor;
+
+#define RGBACOLOR static CGFloat
+#define RGBA256(r, g, b, a) {r / 255.0, g / 255.0, b / 255.0, a}
+#define GRAYCOLOR static GrayColor
+#define GRAY256(grayscale) {grayscale / 255.0, 1.0}
+
+/*
+ * Opaque Grays used for Gradient Buttons, Scrollbars and List Headers
+ */
+
+GRAYCOLOR darkDisabledIndicator = GRAY256(122.0);
+GRAYCOLOR lightDisabledIndicator = GRAY256(152.0);
+
+GRAYCOLOR darkGradientNormal = GRAY256(95.0);
+GRAYCOLOR darkGradientPressed = GRAY256(118.0);
+GRAYCOLOR darkGradientDisabled = GRAY256(82.0);
+GRAYCOLOR darkGradientBorder = GRAY256(118.0);
+GRAYCOLOR darkGradientBorderDisabled = GRAY256(94.0);
+GRAYCOLOR lightGradientNormal = GRAY256(244.0);
+GRAYCOLOR lightGradientPressed = GRAY256(175.0);
+GRAYCOLOR lightGradientDisabled = GRAY256(235.0);
+GRAYCOLOR lightGradientBorder = GRAY256(165.0);
+GRAYCOLOR lightGradientBorderDisabled = GRAY256(204.0);
+
+GRAYCOLOR lightTrough = GRAY256(250.0);
+GRAYCOLOR darkTrough = GRAY256(47.0);
+GRAYCOLOR lightInactiveThumb = GRAY256(200.0);
+GRAYCOLOR lightActiveThumb = GRAY256(133.0);
+GRAYCOLOR darkInactiveThumb = GRAY256(117.0);
+GRAYCOLOR darkActiveThumb = GRAY256(158.0);
+
+GRAYCOLOR lightListheaderBorder = GRAY256(200.0);
+GRAYCOLOR listheaderSeparator = GRAY256(220.0);
+GRAYCOLOR listheaderActiveBG = GRAY256(238.0);
+GRAYCOLOR listheaderInactiveBG = GRAY256(246.0);
+
+GRAYCOLOR lightComboSeparator = GRAY256(236.0);
+GRAYCOLOR darkComboSeparator = GRAY256(66.0);
+
+GRAYCOLOR darkTrack = GRAY256(84.0);
+GRAYCOLOR darkInactiveTrack = GRAY256(107.0);
+GRAYCOLOR lightTrack = GRAY256(177.0);
+GRAYCOLOR lightInactiveTrack = GRAY256(139.0);
+
+/*
+ * Transparent Grays
+ */
+
+GRAYCOLOR boxBorder = {1.0, 0.20};
+GRAYCOLOR darkSeparator = {1.0, 0.3};
+GRAYCOLOR darkTabSeparator = {0.0, 0.25};
+GRAYCOLOR darkListheaderBorder = {1.0, 0.125};
+GRAYCOLOR pressedDefaultButton = {0.0, 0.1};
+GRAYCOLOR darkPressedDefaultButton = {1.0, 0.1};
+
+#define CG_WHITE CGColorGetConstantColor(kCGColorWhite)
+
+
+/*
+ * Structures which comprise a database of corner radii and state-dependent
+ * colors used when drawing various types of buttons or entry widgets.
+ */
+
+typedef struct GrayPalette {
+ CGFloat face;
+ CGFloat top;
+ CGFloat side;
+ CGFloat bottom;
+} GrayPalette;
+
+typedef struct PaletteStateTable {
+ GrayPalette light; /* Light palette to use if this entry matches */
+ GrayPalette dark; /* dark palette to use if this entry matches */
+ unsigned int onBits; /* Bits which must be set */
+ unsigned int offBits; /* Bits which must be cleared */
+} PaletteStateTable;
+
+typedef struct ButtonDesign {
+ CGFloat radius;
+ PaletteStateTable palettes[];
+} ButtonDesign;
+
+/*
+ * Declaration of the lookup function.
+ */
+
+static GrayPalette LookupGrayPalette(ButtonDesign *design, unsigned int state,
+ int isDark);
+
+/*
+ * The data.
+ */
+
+static ButtonDesign pushbuttonDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 213.0, .side = 210.0, .bottom = 200.0},
+ .dark = {.face = 94.0, .top = 98.0, .side = 94.0, .bottom = 58.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0},
+ {
+ .light = {.face = 205.0, .top = 215.0, .side = 211.0, .bottom = 173.0},
+ .dark = {.face = 140.0, .top = 150.0, .side = 140.0, .bottom = 42.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 198.0, .side = 192.0, .bottom = 173.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign helpDesign = {
+ .radius = 11,
+ .palettes = {
+ {
+ .light = {.face = 241.0, .top = 218.0, .side = 217.0, .bottom = 206.0},
+ .dark = {.face = 83.0, .top = 89.0, .side = 82.0, .bottom = 82.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0},
+ {
+ .light = {.face = 240.0, .top = 208.0, .side = 205.0, .bottom = 188.0},
+ .dark = {.face = 129.0, .top = 121.0, .side = 106.0, .bottom = 106.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 208.0, .side = 205.0, .bottom = 188.0},
+ .dark = {.face = 107.0, .top = 121.0, .side = 106.0, .bottom = 106.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign inlineDesign = {
+ .radius = 8.0,
+ .palettes = {
+ {
+ .light = {.face = 170.0, .top = 170.0, .side = 170.0, .bottom = 170.0},
+ .dark = {.face = 106.0, .top = 106.0, .side = 106.0, .bottom = 106.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0},
+ {
+ .light = {.face = 34.0, .top = 34.0, .side = 34.0, .bottom = 34.0},
+ .dark = {.face = 225.0, .top = 225.0, .side = 225.0, .bottom = 225.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 113.0, .top = 113.0, .side = 113.0, .bottom = 113.0},
+ .dark = {.face = 165.0, .top = 165.0, .side = 165.0, .bottom = 165.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign roundedrectDesign = {
+ .radius = 3.0,
+ .palettes = {
+ {
+ .light = {.face = 256.0, .top = 192.0, .side = 192.0, .bottom = 192.0},
+ .dark = {.face = 256.0, .top = 165.0, .side = 163.0, .bottom = 42.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 204.0, .top = 158.0, .side = 158.0, .bottom = 158.0},
+ .dark = {.face = 85.0, .top = 115.0, .side = 115.0, .bottom = 115.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 205.0, .top = 215.0, .side = 211.0, .bottom = 173.0},
+ .dark = {.face = 140.0, .top = 150.0, .side = 140.0, .bottom = 42.0},
+ .onBits = TTK_STATE_ALTERNATE, .offBits = TTK_STATE_BACKGROUND
+ },
+
+ /*
+ * Gray values > 255 are replaced by the background color.
+ */
+
+ {
+ .light = {.face = 256.0, .top = 158.0, .side = 158.0, .bottom = 158.0},
+ .dark = {.face = 256.0, .top = 115.0, .side = 115.0, .bottom = 115.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign popupDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 213.0, .side = 210.0, .bottom = 200.0},
+ .dark = {.face = 94.0, .top = 98.0, .side = 94.0, .bottom = 58.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 198.0, .side = 192.0, .bottom = 173.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign checkDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 192.0, .side = 199.0, .bottom = 199.0},
+ .dark = {.face = 80.0, .top = 90.0, .side = 80.0, .bottom = 49.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 165.0, .side = 184.0, .bottom = 184.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign radioDesign = {
+ .radius = 8.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 189.0, .side = 198.0, .bottom = 199.0},
+ .dark = {.face = 80.0, .top = 84.0, .side = 88.0, .bottom = 60.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 165.0, .side = 184.0, .bottom = 184.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign recessedDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 117.0, .top = 117.0, .side = 117.0, .bottom = 117.0},
+ .dark = {.face = 129.0, .top = 129.0, .side = 129.0, .bottom = 129.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 182.0, .top = 182.0, .side = 182.0, .bottom = 182.0},
+ .dark = {.face = 105.0, .top = 105.0, .side = 105.0, .bottom = 105.0},
+ .onBits = TTK_STATE_ACTIVE, .offBits = TTK_STATE_SELECTED
+ },
+ {
+ .light = {.face = 145.0, .top = 145.0, .side = 145.0, .bottom = 145.0},
+ .dark = {.face = 166.0, .top = 166.0, .side = 166.0, .bottom = 166.0},
+ .onBits = TTK_STATE_SELECTED, .offBits = 0
+ },
+ {
+ .light = {.face = 256.0, .top = 256.0, .side = 256.0, .bottom = 256.0},
+ .dark = {.face = 256.0, .top = 256.0, .side = 256.0, .bottom = 256.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign incdecDesign = {
+ .radius = 5.0,
+ .palettes = {
+ {
+ .light = {.face = 246.0, .top = 236.0, .side = 227.0, .bottom = 213.0},
+ .dark = {.face = 80.0, .top = 90.0, .side = 80.0, .bottom = 49.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 198.0, .side = 192.0, .bottom = 173.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign bevelDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 213.0, .side = 210.0, .bottom = 200.0},
+ .dark = {.face = 94.0, .top = 98.0, .side = 94.0, .bottom = 58.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 205.0, .top = 215.0, .side = 211.0, .bottom = 173.0},
+ .dark = {.face = 140.0, .top = 150.0, .side = 140.0, .bottom = 42.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 228.0, .top = 215.0, .side = 211.0, .bottom = 173.0},
+ .dark = {.face = 163.0, .top = 150.0, .side = 140.0, .bottom = 42.0},
+ .onBits = TTK_STATE_SELECTED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 198.0, .side = 192.0, .bottom = 173.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign tabDesign = {
+ .radius = 4.0,
+ .palettes = {
+
+ /*
+ * Apple does not have such a thing as a disabled tab. If it is
+ * disabled, it should be removed. But we provide one based on the
+ * disabled button.
+ */
+
+ {
+ .light = {.face = 229.0, .top = 213.0, .side = 242.0, .bottom = 200.0},
+ .dark = {.face = 163.0, .top = 90.0, .side = 80.0, .bottom = 49.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 229.0, .top = 205.0, .side = 211.0, .bottom = 183.0},
+ .dark = {.face = 163.0, .top = 165.0, .side = 163.0, .bottom = 42.0},
+ .onBits = TTK_STATE_SELECTED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 215.0, .side = 211.0, .bottom = 183.0},
+ .dark = {.face = 108.0, .top = 129.0, .side = 108.0, .bottom = 47.0},
+ .onBits = 0, .offBits = 0
+ },
+ }
+};
+
+static ButtonDesign entryDesign = {
+ .radius = 0.0,
+ .palettes = {
+ {
+ .light = {.face = 256.0, .top = 198.0, .side = 198.0, .bottom = 198.0},
+ .dark = {.face = 256.0, .top = 66.0, .side = 66.0, .bottom = 84.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign searchDesign = {
+ .radius = 3.5,
+ .palettes = {
+ {
+ .light = {.face = 256.0, .top = 198.0, .side = 198.0, .bottom = 198.0},
+ .dark = {.face = 256.0, .top = 66.0, .side = 66.0, .bottom = 84.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign comboDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 256.0, .top = 190.0, .side = 190.0, .bottom = 190.0},
+ .dark = {.face = 256.0, .top = 66.0, .side = 66.0, .bottom = 90.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static ButtonDesign sliderDesign = {
+ .radius = 8.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 189.0, .side = 198.0, .bottom = 199.0},
+ .dark = {.face = 80.0, .top = 84.0, .side = 88.0, .bottom = 60.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 165.0, .side = 184.0, .bottom = 184.0},
+ .dark = {.face = 205.0, .top = 205.0, .side = 205.0, .bottom = 198.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+
+/*
+ * Table mapping Tk states to Appearance manager ThemeStates
+ */
+
+static Ttk_StateTable ThemeStateTable[] = {
+ {kThemeStateActive, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
+ {kThemeStateUnavailable, TTK_STATE_DISABLED, 0},
+ {kThemeStatePressed, TTK_STATE_PRESSED, 0},
+ {kThemeStateInactive, TTK_STATE_BACKGROUND, 0},
+ {kThemeStateUnavailableInactive, TTK_STATE_DISABLED | TTK_STATE_BACKGROUND, 0},
+ {kThemeStateActive, 0, 0}
+
+ /* Others:
+ * The kThemeStatePressedUp and kThemeStatePressedDown bits indicate
+ * which of the two segments of an IncDec button is being pressed.
+ * We don't use these. kThemeStateRollover roughly corresponds to
+ * TTK_STATE_ACTIVE, but does not do what we want with the help button.
+ *
+ * {kThemeStatePressedUp, 0, 0},
+ * {kThemeStatePressedDown, 0, 0}
+ * {kThemeStateRollover, TTK_STATE_ACTIVE, 0},
+ */
+};
+
+/*
+ * Translation between Ttk and HIToolbox.
+ */
+
+static Ttk_StateTable ButtonValueTable[] = {
+ {kThemeButtonOff, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
+ {kThemeButtonMixed, TTK_STATE_ALTERNATE, 0},
+ {kThemeButtonOn, TTK_STATE_SELECTED, 0},
+ {kThemeButtonOff, 0, 0}
+};
+
+static Ttk_StateTable ButtonAdornmentTable[] = {
+ {kThemeAdornmentNone, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
+ {kThemeAdornmentDefault | kThemeAdornmentFocus,
+ TTK_STATE_ALTERNATE | TTK_STATE_FOCUS, 0},
+ {kThemeAdornmentFocus, TTK_STATE_FOCUS, 0},
+ {kThemeAdornmentDefault, TTK_STATE_ALTERNATE, 0},
+ {kThemeAdornmentNone, 0, 0}
+};
+
+/*
+ * Our enums for button styles not known to HIToolbox.
+ */
+
+#define TkGradientButton 0x8001
+#define TkRoundedRectButton 0x8002
+#define TkRecessedButton 0x8003
+#define TkInlineButton 0x8004
+/*
+ * The struct passed as clientData when drawing Ttk buttons.
+ */
+
+typedef struct {
+ ThemeButtonKind kind;
+ ThemeMetric heightMetric;
+ ThemeMetric widthMetric;
+} ThemeButtonParams;
+
+static ThemeButtonParams
+ PushButtonParams = {kThemePushButton, kThemeMetricPushButtonHeight,
+ NoThemeMetric},
+ CheckBoxParams = {kThemeCheckBox, kThemeMetricCheckBoxHeight,
+ NoThemeMetric},
+ RadioButtonParams = {kThemeRadioButton, kThemeMetricRadioButtonHeight,
+ NoThemeMetric},
+ BevelButtonParams = {kThemeRoundedBevelButton, NoThemeMetric, NoThemeMetric},
+ PopupButtonParams = {kThemePopupButton, kThemeMetricPopupButtonHeight,
+ NoThemeMetric},
+ DisclosureParams = {kThemeDisclosureButton,
+ kThemeMetricDisclosureTriangleHeight,
+ kThemeMetricDisclosureTriangleWidth},
+ DisclosureButtonParams = {kThemeArrowButton,
+ kThemeMetricSmallDisclosureButtonHeight,
+ kThemeMetricSmallDisclosureButtonWidth},
+ HelpButtonParams = {kThemeRoundButtonHelp, kThemeMetricRoundButtonSize,
+ kThemeMetricRoundButtonSize},
+ ListHeaderParams = {kThemeListHeaderButton, kThemeMetricListHeaderHeight,
+ NoThemeMetric},
+ GradientButtonParams = {TkGradientButton, NoThemeMetric, NoThemeMetric},
+ RoundedRectButtonParams = {TkRoundedRectButton, kThemeMetricPushButtonHeight,
+ NoThemeMetric},
+ RecessedButtonParams = {TkRecessedButton, kThemeMetricPushButtonHeight,
+ NoThemeMetric},
+ InlineButtonParams = {TkInlineButton, kThemeMetricPushButtonHeight,
+ NoThemeMetric};
+
+ /*
+ * Others: kThemeDisclosureRight, kThemeDisclosureDown,
+ * kThemeDisclosureLeft
+ */
+
+/*
+ * The struct passed as clientData when drawing Ttk Entry widgets.
+ */
+
+typedef struct {
+ HIThemeFrameKind kind;
+ ThemeMetric heightMetric;
+ ThemeMetric widthMetric;
+} ThemeFrameParams;
+
+static ThemeFrameParams
+ EntryFieldParams = {kHIThemeFrameTextFieldSquare, NoThemeMetric, NoThemeMetric},
+ SearchboxFieldParams = {kHIThemeFrameTextFieldRound, NoThemeMetric, NoThemeMetric};
+
+/*
+ * If we try to draw a rounded rectangle with too large of a radius, the Core
+ * Graphics library will sometimes raise a fatal exception. This macro
+ * protects against this by returning if the width or height is less than
+ * twice the radius. Presumably this only happens when a widget has not yet
+ * been configured and has size 1x1, so there is nothing to draw anyway.
+ */
+
+#define CHECK_RADIUS(radius, bounds) \
+ if (radius > bounds.size.width / 2 || radius > bounds.size.height / 2) { \
+ return; \
+ }
+
+/*
+ * The spinbox widget needs to draw the two arrows in different colors when
+ * one half or the other is being pressed, but the menubutton always draws
+ * them in the same color. This constant is used to distinguish those two
+ * situations.
+ */
+
+#define BOTH_ARROWS 1 << 30
+
+/*
+ * Local Variables:
+ * mode: objc
+ * c-basic-offset: 4
+ * fill-column: 79
+ * coding: utf-8
+ * End:
+ */