diff options
Diffstat (limited to 'macosx')
-rw-r--r-- | macosx/tkMacOSXConstants.h | 3 | ||||
-rw-r--r-- | macosx/tkMacOSXDraw.c | 2 | ||||
-rw-r--r-- | macosx/tkMacOSXImage.c | 623 | ||||
-rw-r--r-- | macosx/tkMacOSXInit.c | 118 | ||||
-rw-r--r-- | macosx/tkMacOSXMenu.c | 3 | ||||
-rw-r--r-- | macosx/tkMacOSXPrivate.h | 3 | ||||
-rw-r--r-- | macosx/tkMacOSXRegion.c | 12 | ||||
-rw-r--r-- | macosx/tkMacOSXSubwindows.c | 2 | ||||
-rw-r--r-- | macosx/ttkMacOSXTheme.c | 2957 | ||||
-rw-r--r-- | macosx/ttkMacOSXTheme.h | 570 |
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: + */ |