Index: doc/ttk_entry.n ================================================================== --- doc/ttk_entry.n +++ doc/ttk_entry.n @@ -470,20 +470,20 @@ \fBTEntry\fP styling options configurable with \fBttk::style\fP are: .PP \fB\-background\fP \fIcolor\fP .RS -When using the aqua theme (Mac OS X), changes the \fB\-fieldbackground\fP. +For backwards compatibility, when using the aqua theme (for macOS), this +option behaves as an alias for the \fB\-fieldbackground\fP provided that no +value is specified for \fB\-fieldbackground\fP. Otherwise it is ignored. .RE \fB\-bordercolor\fP \fIcolor\fP .br \fB\-darkcolor\fP \fIcolor\fP .br \fB\-fieldbackground\fP \fIcolor\fP .RS -Does not work with the aqua theme (Mac OS X). -.br Some themes use a graphical background and their field background colors cannot be changed. .RE \fB\-foreground\fP \fIcolor\fP .br \fB\-insertwidth\fP \fIamount\fP Index: doc/ttk_spinbox.n ================================================================== --- doc/ttk_spinbox.n +++ doc/ttk_spinbox.n @@ -93,20 +93,20 @@ .br \fB\-arrowsize\fP \fIamount\fP .br \fB\-background\fP \fIcolor\fP .RS -When using the aqua theme (Mac OS X), changes the \fB\-fieldbackground\fP. +For backwards compatibility, when using the aqua theme (for macOS), this +option behaves as an alias for the \fB\-fieldbackground\fP provided that no +value is specified for \fB\-fieldbackground\fP. Otherwise it is ignored. .RE \fB\-bordercolor\fP \fIcolor\fP .br \fB\-darkcolor\fP \fIcolor\fP .br \fB\-fieldbackground\fP \fIcolor\fP -.RS -Does not work with the aqua theme (Mac OS X). -.RE +.br \fB\-foreground\fP \fIcolor\fP .br \fB\-lightcolor\fP \fIcolor\fP .br \fB\-padding\fP \fIpadding\fP Index: generic/tk.h ================================================================== --- generic/tk.h +++ generic/tk.h @@ -674,11 +674,11 @@ unsigned long serial; /* # of last request processed by server. */ Bool send_event; /* True if this came from a SendEvent * request. */ Display *display; /* Display the event was read from. */ Window event; /* Window on which event was requested. */ - Window root; /* Root window that the event occured on. */ + Window root; /* Root window that the event occurred on. */ Window subwindow; /* Child window. */ Time time; /* Milliseconds. */ int x, y; /* Pointer x, y coordinates in event * window. */ int x_root, y_root; /* Coordinates relative to root. */ @@ -811,14 +811,15 @@ int internalBorderRight; int internalBorderTop; int internalBorderBottom; int minReqWidth; int minReqHeight; - char *dummy20; /* geometryMaster */ #ifdef TK_USE_INPUT_METHODS - int dummy21; + int dummy20; #endif /* TK_USE_INPUT_METHODS */ + char *dummy21; /* geomMgrName */ + Tk_Window dummy22; /* maintainerPtr */ } Tk_FakeWin; /* * Flag values for TkWindow (and Tk_FakeWin) structures are: * Index: generic/tkGeometry.c ================================================================== --- generic/tkGeometry.c +++ generic/tkGeometry.c @@ -325,27 +325,27 @@ * set. */ const char *master) /* The master identity. */ { register TkWindow *winPtr = (TkWindow *) tkwin; - if (winPtr->geometryMaster != NULL && - strcmp(winPtr->geometryMaster, master) == 0) { + if (winPtr->geomMgrName != NULL && + strcmp(winPtr->geomMgrName, master) == 0) { return TCL_OK; } - if (winPtr->geometryMaster != NULL) { + if (winPtr->geomMgrName != NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot use geometry manager %s inside %s which already" " has slaves managed by %s", - master, Tk_PathName(tkwin), winPtr->geometryMaster)); + master, Tk_PathName(tkwin), winPtr->geomMgrName)); Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "FIGHT", NULL); } return TCL_ERROR; } - winPtr->geometryMaster = ckalloc(strlen(master) + 1); - strcpy(winPtr->geometryMaster, master); + winPtr->geomMgrName = ckalloc(strlen(master) + 1); + strcpy(winPtr->geomMgrName, master); return TCL_OK; } /* *---------------------------------------------------------------------- @@ -370,18 +370,18 @@ * cleared. */ const char *master) /* The master identity. */ { register TkWindow *winPtr = (TkWindow *) tkwin; - if (winPtr->geometryMaster != NULL && - strcmp(winPtr->geometryMaster, master) != 0) { + if (winPtr->geomMgrName != NULL && + strcmp(winPtr->geomMgrName, master) != 0) { Tcl_Panic("Trying to free %s from geometry manager %s", - winPtr->geometryMaster, master); + winPtr->geomMgrName, master); } - if (winPtr->geometryMaster != NULL) { - ckfree(winPtr->geometryMaster); - winPtr->geometryMaster = NULL; + if (winPtr->geomMgrName != NULL) { + ckfree(winPtr->geomMgrName); + winPtr->geomMgrName = NULL; } } /* *---------------------------------------------------------------------- @@ -422,10 +422,12 @@ MaintainMaster *masterPtr; register MaintainSlave *slavePtr; int isNew, map; Tk_Window ancestor, parent; TkDisplay *dispPtr = ((TkWindow *) master)->dispPtr; + + ((TkWindow *)slave)->maintainerPtr = (TkWindow *)master; if (master == Tk_Parent(slave)) { /* * If the slave is a direct descendant of the master, don't bother * setting up the extra infrastructure for management, just make a @@ -567,10 +569,12 @@ Tcl_HashEntry *hPtr; MaintainMaster *masterPtr; register MaintainSlave *slavePtr, *prevPtr; Tk_Window ancestor; TkDisplay *dispPtr = ((TkWindow *) slave)->dispPtr; + + ((TkWindow *)slave)->maintainerPtr = NULL; if (master == Tk_Parent(slave)) { /* * If the slave is a direct descendant of the master, * Tk_MaintainGeometry will not have set up any of the extra Index: generic/tkInt.h ================================================================== --- generic/tkInt.h +++ generic/tkInt.h @@ -321,10 +321,13 @@ /* Hash table that maps from a master's * Tk_Window token to a list of slaves managed * by that master. */ int geomInit; +#define TkGetGeomMaster(tkwin) (((TkWindow *)tkwin)->maintainerPtr != NULL ? \ + ((TkWindow *)tkwin)->maintainerPtr : ((TkWindow *)tkwin)->parentPtr) + /* * Information used by tkGet.c only: */ Tcl_HashTable uidTable; /* Stores all Tk_Uid used in a thread. */ @@ -810,14 +813,18 @@ int internalBorderTop; int internalBorderBottom; int minReqWidth; /* Minimum requested width. */ int minReqHeight; /* Minimum requested height. */ - char *geometryMaster; #ifdef TK_USE_INPUT_METHODS int ximGeneration; /* Used to invalidate XIC */ #endif /* TK_USE_INPUT_METHODS */ + char *geomMgrName; /* Records the name of the geometry manager. */ + struct TkWindow *maintainerPtr; + /* The geometry master for this window. The + * value is NULL if the window has no master or + * if its master is its parent. */ } TkWindow; /* * Real definition of some events. Note that these events come from outside * but have internally generated pieces added to them. Index: generic/tkWindow.c ================================================================== --- generic/tkWindow.c +++ generic/tkWindow.c @@ -662,10 +662,12 @@ winPtr->numTags = 0; winPtr->optionLevel = -1; winPtr->selHandlerList = NULL; winPtr->geomMgrPtr = NULL; winPtr->geomData = NULL; + winPtr->geomMgrName = NULL; + winPtr->maintainerPtr = NULL; winPtr->reqWidth = winPtr->reqHeight = 1; winPtr->internalBorderLeft = 0; winPtr->wmInfoPtr = NULL; winPtr->classProcsPtr = NULL; winPtr->instanceData = NULL; @@ -673,11 +675,10 @@ winPtr->internalBorderRight = 0; winPtr->internalBorderTop = 0; winPtr->internalBorderBottom = 0; winPtr->minReqWidth = 0; winPtr->minReqHeight = 0; - winPtr->geometryMaster = NULL; return winPtr; } /* @@ -1458,13 +1459,13 @@ TkFreeBindingTags(winPtr); } TkOptionDeadWindow(winPtr); TkSelDeadWindow(winPtr); TkGrabDeadWindow(winPtr); - if (winPtr->geometryMaster != NULL) { - ckfree(winPtr->geometryMaster); - winPtr->geometryMaster = NULL; + if (winPtr->geomMgrName != NULL) { + ckfree(winPtr->geomMgrName); + winPtr->geomMgrName = NULL; } if (winPtr->mainPtr != NULL) { if (winPtr->pathName != NULL) { Tk_DeleteAllBindings(winPtr->mainPtr->bindingTable, winPtr->pathName); Index: generic/ttk/ttkTheme.h ================================================================== --- generic/ttk/ttkTheme.h +++ generic/ttk/ttkTheme.h @@ -27,13 +27,17 @@ /*------------------------------------------------------------------------ * +++ Defaults for element option specifications. */ #define DEFAULT_FONT "TkDefaultFont" +#ifdef MAC_OSX_TK +#define DEFAULT_BACKGROUND "systemTextBackgroundColor" +#define DEFAULT_FOREGROUND "systemTextColor" +#else #define DEFAULT_BACKGROUND "#d9d9d9" #define DEFAULT_FOREGROUND "black" - +#endif /*------------------------------------------------------------------------ * +++ Widget states. * Keep in sync with stateNames[] in tkstate.c. */ Index: library/demos/toolbar.tcl ================================================================== --- library/demos/toolbar.tcl +++ library/demos/toolbar.tcl @@ -29,11 +29,11 @@ set t [frame $w.toolbar] ;# Must be a frame! ttk::separator $w.sep ttk::frame $t.tearoff -cursor fleur ttk::separator $t.tearoff.to -orient vertical ttk::separator $t.tearoff.to2 -orient vertical -pack $t.tearoff.to -fill y -expand 1 -padx 2 -side left +pack $t.tearoff.to -fill y -expand 1 -padx 4 -side left pack $t.tearoff.to2 -fill y -expand 1 -side left ttk::frame $t.contents grid $t.tearoff $t.contents -sticky nsew grid columnconfigure $t $t.contents -weight 1 grid columnconfigure $t.contents 1000 -weight 1 @@ -77,11 +77,11 @@ ## Some content for the rest of the toplevel text $w.txt -width 40 -height 10 interp alias {} doInsert {} $w.txt insert end ;# Make bindings easy to write ## Arrange contents -grid $t.button $t.check $t.menu $t.combo -in $t.contents -padx 2 -sticky ns +grid $t.button $t.check $t.menu $t.combo -in $t.contents -padx 2 -pady 4 -sticky ns grid $t -sticky ew grid $w.sep -sticky ew grid $w.msg -sticky ew grid $w.txt -sticky nsew grid rowconfigure $w $w.txt -weight 1 Index: library/demos/tree.tcl ================================================================== --- library/demos/tree.tcl +++ library/demos/tree.tcl @@ -73,11 +73,11 @@ -yscroll "$w.vsb set" -xscroll "$w.hsb set" ttk::scrollbar $w.vsb -orient vertical -command "$w.tree yview" ttk::scrollbar $w.hsb -orient horizontal -command "$w.tree xview" $w.tree heading \#0 -text "Directory Structure" $w.tree heading size -text "File Size" -$w.tree column size -stretch 0 -width 70 +$w.tree column size -width 70 populateRoots $w.tree bind $w.tree <> {populateTree %W [%W focus]} ## Arrange the tree and its scrollbars in the toplevel lower [ttk::frame $w.dummy] Index: library/demos/ttkpane.tcl ================================================================== --- library/demos/ttkpane.tcl +++ library/demos/ttkpane.tcl @@ -102,11 +102,11 @@ pack $w.sb -side right -fill y -in $w.outer.inRight.bot pack $w.outer.inRight.bot.f -fill both -expand 1 pack $w.outer -fill both -expand 1 } else { text $w.txt -wrap word -yscroll "$w.sb set" -width 30 -borderwidth 0 - scrollbar $w.sb -orient vertical -command "$w.txt yview" + ttk::scrollbar $w.sb -orient vertical -command "$w.txt yview" pack $w.sb -side right -fill y -in $w.outer.inRight.bot pack $w.txt -fill both -expand 1 -in $w.outer.inRight.bot pack $w.outer -fill both -expand 1 -padx 10 -pady {6 10} } Index: library/ttk/aquaTheme.tcl ================================================================== --- library/ttk/aquaTheme.tcl +++ library/ttk/aquaTheme.tcl @@ -5,49 +5,124 @@ namespace eval ttk::theme::aqua { ttk::style theme settings aqua { ttk::style configure . \ -font TkDefaultFont \ - -background systemWindowBody \ - -foreground systemModelessDialogActiveText \ + -background systemTtkBackground \ + -foreground systemLabelColor \ -selectbackground systemHighlight \ - -selectforeground systemModelessDialogActiveText \ + -selectforeground systemLabelColor \ -selectborderwidth 0 \ -insertwidth 1 ttk::style map . \ - -foreground {disabled systemModelessDialogInactiveText - background systemModelessDialogInactiveText} \ - -selectbackground {background systemHighlightSecondary - !focus systemHighlightSecondary} \ - -selectforeground {background systemModelessDialogInactiveText - !focus systemDialogActiveText} + -foreground { + disabled systemLabelColor + background systemLabelColor} \ + -selectbackground { + background systemHighlight + !focus systemHighlightSecondary} \ + -selectforeground { + background systemLabelColor + !focus systemDialogActiveText} + + # Button + ttk::style configure TButton -anchor center -width -6\ + -foreground systemControlTextColor + ttk::style map TButton \ + -foreground { + disabled systemDisabledControlTextColor} + ttk::style map TCheckbutton \ + -foreground { + disabled systemDisabledControlTextColor} + ttk::style map TRadiobutton \ + -foreground { + disabled systemDisabledControlTextColor} + ttk::style configure TMenubutton -anchor center -padding {2 0 0 2} + ttk::style configure Toolbutton -anchor center + ttk::style map Toolbutton \ + -foreground { + disabled systemDisabledControlTextColor + } + + # Entry + ttk::style configure TEntry \ + -foreground systemTextColor \ + -background systemTextBackgroundColor \ + -selectforeground systemSelectedTextColor + ttk::style map TEntry \ + -foreground { + disabled systemDisabledControlTextColor} # Workaround for #1100117: # Actually, on Aqua we probably shouldn't stipple images in # disabled buttons even if it did work... ttk::style configure . -stipple {} - ttk::style configure TButton -anchor center -width -6 - ttk::style configure Toolbutton -padding 4 - + # Notebook ttk::style configure TNotebook -tabmargins {10 0} -tabposition n ttk::style configure TNotebook -padding {18 8 18 17} ttk::style configure TNotebook.Tab -padding {12 3 12 2} + ttk::style configure TNotebook.Tab -foreground white + ttk::style map TNotebook.Tab \ + -foreground { + {background !disabled !selected} systemControlTextColor + {background selected} systemTextBackgroundColor + disabled systemDisabledControlTextColor + !selected systemControlTextColor} # Combobox: - ttk::style configure TCombobox -postoffset {5 -2 -10 0} - - # Treeview: - ttk::style configure Heading -font TkHeadingFont - ttk::style configure Treeview -rowheight 18 -background White - ttk::style map Treeview \ - -background [list disabled systemDialogBackgroundInactive \ - {selected background} systemHighlightSecondary \ - selected systemHighlight] \ - -foreground [list disabled systemModelessDialogInactiveText \ - selected systemModelessDialogActiveText] + ttk::style configure TCombobox \ + -foreground systemTextColor \ + -background systemTransparent \ + -selectforeground systemSelectedTextColor \ + -selectbackground systemSelectedTextBackgroundColor + ttk::style map TCombobox \ + -foreground { + disabled systemDisabledControlTextColor + } \ + -selectforeground { + !active systemTextColor + } \ + -selectbackground { + !active systemTextBackgroundColor + !focus systemTextBackgroundColor + focus systemSelectedTextBackgroundColor + } + + # Spinbox + ttk::style configure TSpinbox \ + -foreground systemTextColor \ + -background systemTextBackgroundColor \ + -selectforeground systemSelectedTextColor \ + -selectbackground systemSelectedTextBackgroundColor + ttk::style map TSpinbox \ + -foreground { + disabled systemDisabledControlTextColor + } \ + -selectforeground { + !active systemTextColor + } \ + -selectbackground { + !active systemTextBackgroundColor + !focus systemTextBackgroundColor + focus systemSelectedTextBackgroundColor + } + + # Treeview: + ttk::style configure Heading \ + -font TkHeadingFont \ + -foreground systemTextColor \ + -background systemTtkBackground + ttk::style configure Treeview -rowheight 18 \ + -background systemTextBackgroundColor \ + -foreground systemTextColor \ + -fieldbackground systemTextBackgroundColor + ttk::style map Treeview \ + -background { + selected systemSelectedTextBackgroundColor + } # Enable animation for ttk::progressbar widget: ttk::style configure TProgressbar -period 100 -maxphase 255 # For Aqua, labelframe labels should appear outside the border, Index: library/ttk/scrollbar.tcl ================================================================== --- library/ttk/scrollbar.tcl +++ library/ttk/scrollbar.tcl @@ -1,27 +1,9 @@ # # Bindings for TScrollbar widget # -# Still don't have a working ttk::scrollbar under OSX - -# Swap in a [tk::scrollbar] on that platform, -# unless user specifies -class or -style. -# -if {[tk windowingsystem] eq "aqua"} { - rename ::ttk::scrollbar ::ttk::_scrollbar - proc ttk::scrollbar {w args} { - set constructor ::tk::scrollbar - foreach {option _} $args { - if {$option eq "-class" || $option eq "-style"} { - set constructor ::ttk::_scrollbar - break - } - } - return [$constructor $w {*}$args] - } -} - namespace eval ttk::scrollbar { variable State # State(xPress) -- # State(yPress) -- initial position of mouse at start of drag. # State(first) -- value of -first at start of drag. Index: macosx/README ================================================================== --- macosx/README +++ macosx/README @@ -186,10 +186,20 @@ - Another command available in the tk::unsupported::MacWindowStyle namespace is tk::unsupported::MacWindowStyle tabbingid window ?newId? which can be used to get or set the tabbingIdentifier for the NSWindow associated with a Tk Window. See section 3 for details. + +- A command tk::unsupported::MacWindowStyle appearance window ?newAappearance? +is available when Tk is built and run on macOS 10.14 (Mojave) or later. In +that case the Ttk widgets all support the "Dark Mode" appearance which was +introduced in 10.14. The command accepts the following values for the optional +newAppearance option: "aqua", "darkaqua", or "auto". If the appearance is set +to aqua or darkaqua then the window will be displayed with the corresponding +appearance independent of any preferences settings. If it is set to "auto" +the appearance will be determined by the preferences. This command can be +used to opt out of Dark Mode on a per-window basis. - If you want to use Remote Debugging with Xcode, you need to set the environment variable XCNOSTDIN to 1 in the Executable editor for Wish. That will cause us to force closing stdin & stdout. Otherwise, given how Xcode launches Wish remotely, they will be left open and then Wish & gdb will fight for stdin. @@ -273,12 +283,46 @@ unique tabbingIdentifier. This is independent of any preferences setting. To ensure that we maintain consistency, changing the tabbingIdentifier of a window which is already displayed as a tab will also cause it to become a separate window. +4. Ttk, Dark Mode and semantic colors +--------------------------------------- + +With the release of OSX 10.14 (Mojave), Apple introduced the DarkAqua +appearance. Part of the implementation of the Dark Mode was to make some of +the named NSColors have dynamic values. Apple calls these "semantic colors" +because the name does not specify a specific color, but rather refers to the +context in which the color should be used. Tk now provides the following +semantic colors as system colors: systemTextColor, systemTextBackgroundColor, +systemSelectedTextColor, systemSelectedTextBackgroundColor, +systemControlTextColor, systemDisabledControlTextColor, and systemLabelColor. +All of these except systemLabelColor, which was introduced in 10.10, were +present in OSX 10.0. The change in 10.14 was that the RGB color value of these +colors became dynamic, meaning that the color value can change when the +application appearance changes. In particular, when a user selects Dark Mode +in the system preferences these colors change appearance. For example +systemTextColor is dark in Aqua and light in DarkAqua. + +The default background and foreground colors of most of the Tk widgets +have been set to semantic colors, which means that the widgets will change +appearance, and remain usable, when Dark Mode is selected in the system +preferences. However, to get a close match to the native Dark Mode style it +is recommended to use Ttk widgets when possible. + +Apple's tab view and GroupBox objects delimit their content by +displaying it within a rounded rectangle with a background color that +contrasts with the background of the containing object. This means +that the background color of a Ttk widget depends on how deeply it is +nested inside of other widgets that use contrasting backgrounds. To +support this, there are 8 contrasting system colors named +systemTtkBackground, systemTtkBackground1, ... , systemTtkBackground7. +The systemTtkBackground color is the standard background for a dialog +window and the others match the contrasting background colors for +backgrounds which are nested to the corresponding depth. -4. Building Tcl/Tk on macOS +5. Building Tcl/Tk on macOS ------------------------------ - At least macOS 10.3 is required to build Tcl and TkX11, and macOS 10.6 is required to build TkAqua. The XCode application provides everything needed to build Tk, but it is not necessary to install the full XCode. Index: macosx/tkMacOSXButton.c ================================================================== --- macosx/tkMacOSXButton.c +++ macosx/tkMacOSXButton.c @@ -386,11 +386,11 @@ * are not underestimating the size by requesting the content * size of a Pushbutton whose overall size is our content size * expanded by the standard padding. */ - tmpRect = CGRectMake(0, 0, width + 2*HI_PADX, height + 2*HI_PADY); + tmpRect = CGRectMake(0, 0, width + 2*HI_PADX, height + 2*HI_PADY); HIThemeGetButtonContentBounds(&tmpRect, &mbPtr->drawinfo, &contBounds); if (height < contBounds.size.height) { height = contBounds.size.height; } if (width < contBounds.size.width) { @@ -768,10 +768,20 @@ hiinfo.animation.time.current = CFAbsoluteTimeGetCurrent(); if (hiinfo.animation.time.start == 0) { hiinfo.animation.time.start = hiinfo.animation.time.current; } + /* + * To avoid buttons with white text on a white background, we always + * set the state to inactive in Dark Mode. It isn't perfect but + * it is usable. Using a ttk::button would be a better choice, + * however. + */ + + if (TkMacOSXInDarkMode(butPtr->tkwin)) { + hiinfo.state = kThemeStateInactive; + } HIThemeDrawButton(&cntrRect, &hiinfo, dc.context, kHIThemeOrientationNormal, &contHIRec); TkMacOSXRestoreDrawingContext(&dc); ButtonContentDrawCB(&contHIRec, mbPtr->btnkind, &mbPtr->drawinfo, Index: macosx/tkMacOSXColor.c ================================================================== --- macosx/tkMacOSXColor.c +++ macosx/tkMacOSXColor.c @@ -15,15 +15,36 @@ */ #include "tkMacOSXPrivate.h" #include "tkColor.h" +/* + * The colorType specifies how the color value should be interpreted. For the + * unique rgbColor entry, the RGB values are generated from the pixel value of + * an XColor. The ttkBackground and semantic types are dynamic, meaning + * that they change when dark mode is enabled on OSX 10.13 and later. + */ + +enum colorType { + clearColor, /* There should be only one of these. */ + rgbColor, /* There should be only one of these. */ + appearance, /* There should be only one of these. */ + HIBrush, /* The value is a HITheme brush color table index. */ + HIText, /* The value is a HITheme text color table index. */ + HIBackground, /* The value is a HITheme background color table index. */ + ttkBackground, /* The value can be used as a parameter.*/ + semantic, /* The value can be used as a parameter.*/ +}; + +/* + + */ + struct SystemColorMapEntry { const char *name; - ThemeBrush brush; - ThemeTextColor textColor; - ThemeBackgroundKind background; + enum colorType type; + long value; }; /* unsigned char pixelCode; */ /* * Array of system color definitions: the array index is required to equal the * color's (pixelCode - MIN_PIXELCODE), i.e. the array order needs to be kept @@ -30,198 +51,204 @@ * in sync with the public pixel code values in tkMacOSXPort.h ! */ #define MIN_PIXELCODE 30 static const struct SystemColorMapEntry systemColorMap[] = { - { "Transparent", 0, 0, 0 }, /* 30: TRANSPARENT_PIXEL */ - { "Highlight", kThemeBrushPrimaryHighlightColor, 0, 0 }, /* 31: HIGHLIGHT_PIXEL */ - { "HighlightSecondary", kThemeBrushSecondaryHighlightColor, 0, 0 }, /* 32: HIGHLIGHT_SECONDARY_PIXEL */ - { "HighlightText", kThemeBrushBlack, 0, 0 }, /* 33: HIGHLIGHT_TEXT_PIXEL */ - { "HighlightAlternate", kThemeBrushAlternatePrimaryHighlightColor, 0, 0 }, /* 34: HIGHLIGHT_ALTERNATE_PIXEL */ - { "ButtonText", 0, kThemeTextColorPushButtonActive, 0 }, /* 35: CONTROL_TEXT_PIXEL */ - { "PrimaryHighlightColor", kThemeBrushPrimaryHighlightColor, 0, 0 }, /* 36 */ - { "ButtonFace", kThemeBrushButtonFaceActive, 0, 0 }, /* 37: CONTROL_BODY_PIXEL */ - { "SecondaryHighlightColor", kThemeBrushSecondaryHighlightColor, 0, 0 }, /* 38 */ - { "ButtonFrame", kThemeBrushButtonFrameActive, 0, 0 }, /* 39: CONTROL_FRAME_PIXEL */ - { "AlternatePrimaryHighlightColor", kThemeBrushAlternatePrimaryHighlightColor, 0, 0 }, /* 40 */ - { "WindowBody", kThemeBrushDocumentWindowBackground, 0, 0 }, /* 41: WINDOW_BODY_PIXEL */ - { "SheetBackground", kThemeBrushSheetBackground, 0, 0 }, /* 42 */ - { "MenuActive", kThemeBrushMenuBackgroundSelected, 0, 0 }, /* 43: MENU_ACTIVE_PIXEL */ - { "Black", kThemeBrushBlack, 0, 0 }, /* 44 */ - { "MenuActiveText", 0, kThemeTextColorMenuItemSelected, 0 }, /* 45: MENU_ACTIVE_TEXT_PIXEL */ - { "White", kThemeBrushWhite, 0, 0 }, /* 46 */ - { "Menu", kThemeBrushMenuBackground, 0, 0 }, /* 47: MENU_BACKGROUND_PIXEL */ - { "DialogBackgroundActive", kThemeBrushDialogBackgroundActive, 0, 0 }, /* 48 */ - { "MenuDisabled", 0, kThemeTextColorMenuItemDisabled, 0 }, /* 49: MENU_DISABLED_PIXEL */ - { "DialogBackgroundInactive", kThemeBrushDialogBackgroundInactive, 0, 0 }, /* 50 */ - { "MenuText", 0, kThemeTextColorMenuItemActive, 0 }, /* 51: MENU_TEXT_PIXEL */ - { "AppearanceColor", 0, 0, 0 }, /* 52: APPEARANCE_PIXEL */ - { "AlertBackgroundActive", kThemeBrushAlertBackgroundActive, 0, 0 }, /* 53 */ - { "AlertBackgroundInactive", kThemeBrushAlertBackgroundInactive, 0, 0 }, /* 54 */ - { "ModelessDialogBackgroundActive", kThemeBrushModelessDialogBackgroundActive, 0, 0 }, /* 55 */ - { "ModelessDialogBackgroundInactive", kThemeBrushModelessDialogBackgroundInactive, 0, 0 }, /* 56 */ - { "UtilityWindowBackgroundActive", kThemeBrushUtilityWindowBackgroundActive, 0, 0 }, /* 57 */ - { "UtilityWindowBackgroundInactive", kThemeBrushUtilityWindowBackgroundInactive, 0, 0 }, /* 58 */ - { "ListViewSortColumnBackground", kThemeBrushListViewSortColumnBackground, 0, 0 }, /* 59 */ - { "ListViewBackground", kThemeBrushListViewBackground, 0, 0 }, /* 60 */ - { "IconLabelBackground", kThemeBrushIconLabelBackground, 0, 0 }, /* 61 */ - { "ListViewSeparator", kThemeBrushListViewSeparator, 0, 0 }, /* 62 */ - { "ChasingArrows", kThemeBrushChasingArrows, 0, 0 }, /* 63 */ - { "DragHilite", kThemeBrushDragHilite, 0, 0 }, /* 64 */ - { "DocumentWindowBackground", kThemeBrushDocumentWindowBackground, 0, 0 }, /* 65 */ - { "FinderWindowBackground", kThemeBrushFinderWindowBackground, 0, 0 }, /* 66 */ - { "ScrollBarDelimiterActive", kThemeBrushScrollBarDelimiterActive, 0, 0 }, /* 67 */ - { "ScrollBarDelimiterInactive", kThemeBrushScrollBarDelimiterInactive, 0, 0 }, /* 68 */ - { "FocusHighlight", kThemeBrushFocusHighlight, 0, 0 }, /* 69 */ - { "PopupArrowActive", kThemeBrushPopupArrowActive, 0, 0 }, /* 70 */ - { "PopupArrowPressed", kThemeBrushPopupArrowPressed, 0, 0 }, /* 71 */ - { "PopupArrowInactive", kThemeBrushPopupArrowInactive, 0, 0 }, /* 72 */ - { "AppleGuideCoachmark", kThemeBrushAppleGuideCoachmark, 0, 0 }, /* 73 */ - { "IconLabelBackgroundSelected", kThemeBrushIconLabelBackgroundSelected, 0, 0 }, /* 74 */ - { "StaticAreaFill", kThemeBrushStaticAreaFill, 0, 0 }, /* 75 */ - { "ActiveAreaFill", kThemeBrushActiveAreaFill, 0, 0 }, /* 76 */ - { "ButtonFrameActive", kThemeBrushButtonFrameActive, 0, 0 }, /* 77 */ - { "ButtonFrameInactive", kThemeBrushButtonFrameInactive, 0, 0 }, /* 78 */ - { "ButtonFaceActive", kThemeBrushButtonFaceActive, 0, 0 }, /* 79 */ - { "ButtonFaceInactive", kThemeBrushButtonFaceInactive, 0, 0 }, /* 80 */ - { "ButtonFacePressed", kThemeBrushButtonFacePressed, 0, 0 }, /* 81 */ - { "ButtonActiveDarkShadow", kThemeBrushButtonActiveDarkShadow, 0, 0 }, /* 82 */ - { "ButtonActiveDarkHighlight", kThemeBrushButtonActiveDarkHighlight, 0, 0 }, /* 83 */ - { "ButtonActiveLightShadow", kThemeBrushButtonActiveLightShadow, 0, 0 }, /* 84 */ - { "ButtonActiveLightHighlight", kThemeBrushButtonActiveLightHighlight, 0, 0 }, /* 85 */ - { "ButtonInactiveDarkShadow", kThemeBrushButtonInactiveDarkShadow, 0, 0 }, /* 86 */ - { "ButtonInactiveDarkHighlight", kThemeBrushButtonInactiveDarkHighlight, 0, 0 }, /* 87 */ - { "ButtonInactiveLightShadow", kThemeBrushButtonInactiveLightShadow, 0, 0 }, /* 88 */ - { "ButtonInactiveLightHighlight", kThemeBrushButtonInactiveLightHighlight, 0, 0 }, /* 89 */ - { "ButtonPressedDarkShadow", kThemeBrushButtonPressedDarkShadow, 0, 0 }, /* 90 */ - { "ButtonPressedDarkHighlight", kThemeBrushButtonPressedDarkHighlight, 0, 0 }, /* 91 */ - { "ButtonPressedLightShadow", kThemeBrushButtonPressedLightShadow, 0, 0 }, /* 92 */ - { "ButtonPressedLightHighlight", kThemeBrushButtonPressedLightHighlight, 0, 0 }, /* 93 */ - { "BevelActiveLight", kThemeBrushBevelActiveLight, 0, 0 }, /* 94 */ - { "BevelActiveDark", kThemeBrushBevelActiveDark, 0, 0 }, /* 95 */ - { "BevelInactiveLight", kThemeBrushBevelInactiveLight, 0, 0 }, /* 96 */ - { "BevelInactiveDark", kThemeBrushBevelInactiveDark, 0, 0 }, /* 97 */ - { "NotificationWindowBackground", kThemeBrushNotificationWindowBackground, 0, 0 }, /* 98 */ - { "MovableModalBackground", kThemeBrushMovableModalBackground, 0, 0 }, /* 99 */ - { "SheetBackgroundOpaque", kThemeBrushSheetBackgroundOpaque, 0, 0 }, /* 100 */ - { "DrawerBackground", kThemeBrushDrawerBackground, 0, 0 }, /* 101 */ - { "ToolbarBackground", kThemeBrushToolbarBackground, 0, 0 }, /* 102 */ - { "SheetBackgroundTransparent", kThemeBrushSheetBackgroundTransparent, 0, 0 }, /* 103 */ - { "MenuBackground", kThemeBrushMenuBackground, 0, 0 }, /* 104 */ - { "Pixel", 0, 0, 0 }, /* 105: PIXEL_MAGIC */ - { "MenuBackgroundSelected", kThemeBrushMenuBackgroundSelected, 0, 0 }, /* 106 */ - { "ListViewOddRowBackground", kThemeBrushListViewOddRowBackground, 0, 0 }, /* 107 */ - { "ListViewEvenRowBackground", kThemeBrushListViewEvenRowBackground, 0, 0 }, /* 108 */ - { "ListViewColumnDivider", kThemeBrushListViewColumnDivider, 0, 0 }, /* 109 */ - { "BlackText", 0, kThemeTextColorBlack, 0 }, /* 110 */ - { "DialogActiveText", 0, kThemeTextColorDialogActive, 0 }, /* 111 */ - { "DialogInactiveText", 0, kThemeTextColorDialogInactive, 0 }, /* 112 */ - { "AlertActiveText", 0, kThemeTextColorAlertActive, 0 }, /* 113 */ - { "AlertInactiveText", 0, kThemeTextColorAlertInactive, 0 }, /* 114 */ - { "ModelessDialogActiveText", 0, kThemeTextColorModelessDialogActive, 0 }, /* 115 */ - { "ModelessDialogInactiveText", 0, kThemeTextColorModelessDialogInactive, 0 }, /* 116 */ - { "WindowHeaderActiveText", 0, kThemeTextColorWindowHeaderActive, 0 }, /* 117 */ - { "WindowHeaderInactiveText", 0, kThemeTextColorWindowHeaderInactive, 0 }, /* 118 */ - { "PlacardActiveText", 0, kThemeTextColorPlacardActive, 0 }, /* 119 */ - { "PlacardInactiveText", 0, kThemeTextColorPlacardInactive, 0 }, /* 120 */ - { "PlacardPressedText", 0, kThemeTextColorPlacardPressed, 0 }, /* 121 */ - { "PushButtonActiveText", 0, kThemeTextColorPushButtonActive, 0 }, /* 122 */ - { "PushButtonInactiveText", 0, kThemeTextColorPushButtonInactive, 0 }, /* 123 */ - { "PushButtonPressedText", 0, kThemeTextColorPushButtonPressed, 0 }, /* 124 */ - { "BevelButtonActiveText", 0, kThemeTextColorBevelButtonActive, 0 }, /* 125 */ - { "BevelButtonInactiveText", 0, kThemeTextColorBevelButtonInactive, 0 }, /* 126 */ - { "BevelButtonPressedText", 0, kThemeTextColorBevelButtonPressed, 0 }, /* 127 */ - { "PopupButtonActiveText", 0, kThemeTextColorPopupButtonActive, 0 }, /* 128 */ - { "PopupButtonInactiveText", 0, kThemeTextColorPopupButtonInactive, 0 }, /* 129 */ - { "PopupButtonPressedText", 0, kThemeTextColorPopupButtonPressed, 0 }, /* 130 */ - { "IconLabelText", 0, kThemeTextColorIconLabel, 0 }, /* 131 */ - { "ListViewText", 0, kThemeTextColorListView, 0 }, /* 132 */ - { "DocumentWindowTitleActiveText", 0, kThemeTextColorDocumentWindowTitleActive, 0 }, /* 133 */ - { "DocumentWindowTitleInactiveText", 0, kThemeTextColorDocumentWindowTitleInactive, 0 }, /* 134 */ - { "MovableModalWindowTitleActiveText", 0, kThemeTextColorMovableModalWindowTitleActive, 0 }, /* 135 */ - { "MovableModalWindowTitleInactiveText",0, kThemeTextColorMovableModalWindowTitleInactive, 0 }, /* 136 */ - { "UtilityWindowTitleActiveText", 0, kThemeTextColorUtilityWindowTitleActive, 0 }, /* 137 */ - { "UtilityWindowTitleInactiveText", 0, kThemeTextColorUtilityWindowTitleInactive, 0 }, /* 138 */ - { "PopupWindowTitleActiveText", 0, kThemeTextColorPopupWindowTitleActive, 0 }, /* 139 */ - { "PopupWindowTitleInactiveText", 0, kThemeTextColorPopupWindowTitleInactive, 0 }, /* 140 */ - { "RootMenuActiveText", 0, kThemeTextColorRootMenuActive, 0 }, /* 141 */ - { "RootMenuSelectedText", 0, kThemeTextColorRootMenuSelected, 0 }, /* 142 */ - { "RootMenuDisabledText", 0, kThemeTextColorRootMenuDisabled, 0 }, /* 143 */ - { "MenuItemActiveText", 0, kThemeTextColorMenuItemActive, 0 }, /* 144 */ - { "MenuItemSelectedText", 0, kThemeTextColorMenuItemSelected, 0 }, /* 145 */ - { "MenuItemDisabledText", 0, kThemeTextColorMenuItemDisabled, 0 }, /* 146 */ - { "PopupLabelActiveText", 0, kThemeTextColorPopupLabelActive, 0 }, /* 147 */ - { "PopupLabelInactiveText", 0, kThemeTextColorPopupLabelInactive, 0 }, /* 148 */ - { "TabFrontActiveText", 0, kThemeTextColorTabFrontActive, 0 }, /* 149 */ - { "TabNonFrontActiveText", 0, kThemeTextColorTabNonFrontActive, 0 }, /* 150 */ - { "TabNonFrontPressedText", 0, kThemeTextColorTabNonFrontPressed, 0 }, /* 151 */ - { "TabFrontInactiveText", 0, kThemeTextColorTabFrontInactive, 0 }, /* 152 */ - { "TabNonFrontInactiveText", 0, kThemeTextColorTabNonFrontInactive, 0 }, /* 153 */ - { "IconLabelSelectedText", 0, kThemeTextColorIconLabelSelected, 0 }, /* 154 */ - { "BevelButtonStickyActiveText", 0, kThemeTextColorBevelButtonStickyActive, 0 }, /* 155 */ - { "BevelButtonStickyInactiveText", 0, kThemeTextColorBevelButtonStickyInactive, 0 }, /* 156 */ - { "NotificationText", 0, kThemeTextColorNotification, 0 }, /* 157 */ - { "SystemDetailText", 0, kThemeTextColorSystemDetail, 0 }, /* 158 */ - { "WhiteText", 0, kThemeTextColorWhite, 0 }, /* 159 */ - { "TabPaneBackground", 0, 0, kThemeBackgroundTabPane }, /* 160 */ - { "PlacardBackground", 0, 0, kThemeBackgroundPlacard }, /* 161 */ - { "WindowHeaderBackground", 0, 0, kThemeBackgroundWindowHeader }, /* 162 */ - { "ListViewWindowHeaderBackground", 0, 0, kThemeBackgroundListViewWindowHeader }, /* 163 */ - { "SecondaryGroupBoxBackground", 0, 0, kThemeBackgroundSecondaryGroupBox }, /* 164 */ - { "MetalBackground", 0, 0, kThemeBackgroundMetal }, /* 165 */ - { NULL, 0, 0, 0 } + { "Transparent", clearColor, 0 }, /* 30: TRANSPARENT_PIXEL */ + { "Highlight", HIBrush, kThemeBrushPrimaryHighlightColor }, /* 31 */ + { "HighlightSecondary", HIBrush, kThemeBrushSecondaryHighlightColor }, /* 32 */ + { "HighlightText", HIBrush, kThemeBrushBlack }, /* 33 */ + { "HighlightAlternate", HIBrush, kThemeBrushAlternatePrimaryHighlightColor }, /* 34 */ + { "ButtonText", HIText, kThemeTextColorPushButtonActive }, /* 35 */ + { "PrimaryHighlightColor", HIBrush, kThemeBrushPrimaryHighlightColor }, /* 36 */ + { "ButtonFace", HIBrush, kThemeBrushButtonFaceActive }, /* 37 */ + { "SecondaryHighlightColor", HIBrush, kThemeBrushSecondaryHighlightColor }, /* 38 */ + { "ButtonFrame", HIBrush, kThemeBrushButtonFrameActive }, /* 39 */ + { "AlternatePrimaryHighlightColor", HIBrush, kThemeBrushAlternatePrimaryHighlightColor }, /* 40 */ + { "WindowBody", HIBrush, kThemeBrushDocumentWindowBackground }, /* 41 */ + { "SheetBackground", HIBrush, kThemeBrushSheetBackground }, /* 42 */ + { "MenuActive", HIBrush, kThemeBrushMenuBackgroundSelected }, /* 43 */ + { "Black", HIBrush, kThemeBrushBlack }, /* 44 */ + { "MenuActiveText", HIText, kThemeTextColorMenuItemSelected }, /* 45 */ + { "White", HIBrush, kThemeBrushWhite }, /* 46 */ + { "Menu", HIBrush, kThemeBrushMenuBackground }, /* 47 */ + { "DialogBackgroundActive", HIBrush, kThemeBrushDialogBackgroundActive }, /* 48 */ + { "MenuDisabled", HIText, kThemeTextColorMenuItemDisabled }, /* 49 */ + { "DialogBackgroundInactive", HIBrush, kThemeBrushDialogBackgroundInactive }, /* 50 */ + { "MenuText", HIText, kThemeTextColorMenuItemActive }, /* 51 */ + { "AppearanceColor", appearance, 0 }, /* 52: APPEARANCE_PIXEL */ + { "AlertBackgroundActive", HIBrush, kThemeBrushAlertBackgroundActive }, /* 53 */ + { "AlertBackgroundInactive", HIBrush, kThemeBrushAlertBackgroundInactive }, /* 54 */ + { "ModelessDialogBackgroundActive", HIBrush, kThemeBrushModelessDialogBackgroundActive }, /* 55 */ + { "ModelessDialogBackgroundInactive", HIBrush, kThemeBrushModelessDialogBackgroundInactive }, /* 56 */ + { "UtilityWindowBackgroundActive", HIBrush, kThemeBrushUtilityWindowBackgroundActive }, /* 57 */ + { "UtilityWindowBackgroundInactive", HIBrush, kThemeBrushUtilityWindowBackgroundInactive }, /* 58 */ + { "ListViewSortColumnBackground", HIBrush, kThemeBrushListViewSortColumnBackground }, /* 59 */ + { "ListViewBackground", HIBrush, kThemeBrushListViewBackground }, /* 60 */ + { "IconLabelBackground", HIBrush, kThemeBrushIconLabelBackground }, /* 61 */ + { "ListViewSeparator", HIBrush, kThemeBrushListViewSeparator }, /* 62 */ + { "ChasingArrows", HIBrush, kThemeBrushChasingArrows }, /* 63 */ + { "DragHilite", HIBrush, kThemeBrushDragHilite }, /* 64 */ + { "DocumentWindowBackground", HIBrush, kThemeBrushDocumentWindowBackground }, /* 65 */ + { "FinderWindowBackground", HIBrush, kThemeBrushFinderWindowBackground }, /* 66 */ + { "ScrollBarDelimiterActive", HIBrush, kThemeBrushScrollBarDelimiterActive }, /* 67 */ + { "ScrollBarDelimiterInactive", HIBrush, kThemeBrushScrollBarDelimiterInactive }, /* 68 */ + { "FocusHighlight", HIBrush, kThemeBrushFocusHighlight }, /* 69 */ + { "PopupArrowActive", HIBrush, kThemeBrushPopupArrowActive }, /* 70 */ + { "PopupArrowPressed", HIBrush, kThemeBrushPopupArrowPressed }, /* 71 */ + { "PopupArrowInactive", HIBrush, kThemeBrushPopupArrowInactive }, /* 72 */ + { "AppleGuideCoachmark", HIBrush, kThemeBrushAppleGuideCoachmark }, /* 73 */ + { "IconLabelBackgroundSelected", HIBrush, kThemeBrushIconLabelBackgroundSelected }, /* 74 */ + { "StaticAreaFill", HIBrush, kThemeBrushStaticAreaFill }, /* 75 */ + { "ActiveAreaFill", HIBrush, kThemeBrushActiveAreaFill }, /* 76 */ + { "ButtonFrameActive", HIBrush, kThemeBrushButtonFrameActive }, /* 77 */ + { "ButtonFrameInactive", HIBrush, kThemeBrushButtonFrameInactive }, /* 78 */ + { "ButtonFaceActive", HIBrush, kThemeBrushButtonFaceActive }, /* 79 */ + { "ButtonFaceInactive", HIBrush, kThemeBrushButtonFaceInactive }, /* 80 */ + { "ButtonFacePressed", HIBrush, kThemeBrushButtonFacePressed }, /* 81 */ + { "ButtonActiveDarkShadow", HIBrush, kThemeBrushButtonActiveDarkShadow }, /* 82 */ + { "ButtonActiveDarkHighlight", HIBrush, kThemeBrushButtonActiveDarkHighlight }, /* 83 */ + { "ButtonActiveLightShadow", HIBrush, kThemeBrushButtonActiveLightShadow }, /* 84 */ + { "ButtonActiveLightHighlight", HIBrush, kThemeBrushButtonActiveLightHighlight }, /* 85 */ + { "ButtonInactiveDarkShadow", HIBrush, kThemeBrushButtonInactiveDarkShadow }, /* 86 */ + { "ButtonInactiveDarkHighlight", HIBrush, kThemeBrushButtonInactiveDarkHighlight }, /* 87 */ + { "ButtonInactiveLightShadow", HIBrush, kThemeBrushButtonInactiveLightShadow }, /* 88 */ + { "ButtonInactiveLightHighlight", HIBrush, kThemeBrushButtonInactiveLightHighlight }, /* 89 */ + { "ButtonPressedDarkShadow", HIBrush, kThemeBrushButtonPressedDarkShadow }, /* 90 */ + { "ButtonPressedDarkHighlight", HIBrush, kThemeBrushButtonPressedDarkHighlight }, /* 91 */ + { "ButtonPressedLightShadow", HIBrush, kThemeBrushButtonPressedLightShadow }, /* 92 */ + { "ButtonPressedLightHighlight", HIBrush, kThemeBrushButtonPressedLightHighlight }, /* 93 */ + { "BevelActiveLight", HIBrush, kThemeBrushBevelActiveLight }, /* 94 */ + { "BevelActiveDark", HIBrush, kThemeBrushBevelActiveDark }, /* 95 */ + { "BevelInactiveLight", HIBrush, kThemeBrushBevelInactiveLight }, /* 96 */ + { "BevelInactiveDark", HIBrush, kThemeBrushBevelInactiveDark }, /* 97 */ + { "NotificationWindowBackground", HIBrush, kThemeBrushNotificationWindowBackground }, /* 98 */ + { "MovableModalBackground", HIBrush, kThemeBrushMovableModalBackground }, /* 99 */ + { "SheetBackgroundOpaque", HIBrush, kThemeBrushSheetBackgroundOpaque }, /* 100 */ + { "DrawerBackground", HIBrush, kThemeBrushDrawerBackground }, /* 101 */ + { "ToolbarBackground", HIBrush, kThemeBrushToolbarBackground }, /* 102 */ + { "SheetBackgroundTransparent", HIBrush, kThemeBrushSheetBackgroundTransparent }, /* 103 */ + { "MenuBackground", HIBrush, kThemeBrushMenuBackground }, /* 104 */ + { "Pixel", rgbColor, 0 }, /* 105: PIXEL_MAGIC */ + { "MenuBackgroundSelected", HIBrush, kThemeBrushMenuBackgroundSelected }, /* 106 */ + { "ListViewOddRowBackground", HIBrush, kThemeBrushListViewOddRowBackground }, /* 107 */ + { "ListViewEvenRowBackground", HIBrush, kThemeBrushListViewEvenRowBackground }, /* 108 */ + { "ListViewColumnDivider", HIBrush, kThemeBrushListViewColumnDivider }, /* 109 */ + { "BlackText", HIText, kThemeTextColorBlack }, /* 110 */ + { "DialogActiveText", HIText, kThemeTextColorDialogActive }, /* 111 */ + { "DialogInactiveText", HIText, kThemeTextColorDialogInactive }, /* 112 */ + { "AlertActiveText", HIText, kThemeTextColorAlertActive }, /* 113 */ + { "AlertInactiveText", HIText, kThemeTextColorAlertInactive }, /* 114 */ + { "ModelessDialogActiveText", HIText, kThemeTextColorModelessDialogActive }, /* 115 */ + { "ModelessDialogInactiveText", HIText, kThemeTextColorModelessDialogInactive }, /* 116 */ + { "WindowHeaderActiveText", HIText, kThemeTextColorWindowHeaderActive }, /* 117 */ + { "WindowHeaderInactiveText", HIText, kThemeTextColorWindowHeaderInactive }, /* 118 */ + { "PlacardActiveText", HIText, kThemeTextColorPlacardActive }, /* 119 */ + { "PlacardInactiveText", HIText, kThemeTextColorPlacardInactive }, /* 120 */ + { "PlacardPressedText", HIText, kThemeTextColorPlacardPressed }, /* 121 */ + { "PushButtonActiveText", HIText, kThemeTextColorPushButtonActive }, /* 122 */ + { "PushButtonInactiveText", HIText, kThemeTextColorPushButtonInactive }, /* 123 */ + { "PushButtonPressedText", HIText, kThemeTextColorPushButtonPressed }, /* 124 */ + { "BevelButtonActiveText", HIText, kThemeTextColorBevelButtonActive }, /* 125 */ + { "BevelButtonInactiveText", HIText, kThemeTextColorBevelButtonInactive }, /* 126 */ + { "BevelButtonPressedText", HIText, kThemeTextColorBevelButtonPressed }, /* 127 */ + { "PopupButtonActiveText", HIText, kThemeTextColorPopupButtonActive }, /* 128 */ + { "PopupButtonInactiveText", HIText, kThemeTextColorPopupButtonInactive }, /* 129 */ + { "PopupButtonPressedText", HIText, kThemeTextColorPopupButtonPressed }, /* 130 */ + { "IconLabelText", HIText, kThemeTextColorIconLabel }, /* 131 */ + { "ListViewText", HIText, kThemeTextColorListView }, /* 132 */ + { "DocumentWindowTitleActiveText", HIText, kThemeTextColorDocumentWindowTitleActive }, /* 133 */ + { "DocumentWindowTitleInactiveText", HIText, kThemeTextColorDocumentWindowTitleInactive }, /* 134 */ + { "MovableModalWindowTitleActiveText", HIText, kThemeTextColorMovableModalWindowTitleActive }, /* 135 */ + { "MovableModalWindowTitleInactiveText",HIText, kThemeTextColorMovableModalWindowTitleInactive }, /* 136 */ + { "UtilityWindowTitleActiveText", HIText, kThemeTextColorUtilityWindowTitleActive }, /* 137 */ + { "UtilityWindowTitleInactiveText", HIText, kThemeTextColorUtilityWindowTitleInactive }, /* 138 */ + { "PopupWindowTitleActiveText", HIText, kThemeTextColorPopupWindowTitleActive }, /* 139 */ + { "PopupWindowTitleInactiveText", HIText, kThemeTextColorPopupWindowTitleInactive }, /* 140 */ + { "RootMenuActiveText", HIText, kThemeTextColorRootMenuActive }, /* 141 */ + { "RootMenuSelectedText", HIText, kThemeTextColorRootMenuSelected }, /* 142 */ + { "RootMenuDisabledText", HIText, kThemeTextColorRootMenuDisabled }, /* 143 */ + { "MenuItemActiveText", HIText, kThemeTextColorMenuItemActive }, /* 144 */ + { "MenuItemSelectedText", HIText, kThemeTextColorMenuItemSelected }, /* 145 */ + { "MenuItemDisabledText", HIText, kThemeTextColorMenuItemDisabled }, /* 146 */ + { "PopupLabelActiveText", HIText, kThemeTextColorPopupLabelActive }, /* 147 */ + { "PopupLabelInactiveText", HIText, kThemeTextColorPopupLabelInactive }, /* 148 */ + { "TabFrontActiveText", HIText, kThemeTextColorTabFrontActive }, /* 149 */ + { "TabNonFrontActiveText", HIText, kThemeTextColorTabNonFrontActive }, /* 150 */ + { "TabNonFrontPressedText", HIText, kThemeTextColorTabNonFrontPressed }, /* 151 */ + { "TabFrontInactiveText", HIText, kThemeTextColorTabFrontInactive }, /* 152 */ + { "TabNonFrontInactiveText", HIText, kThemeTextColorTabNonFrontInactive }, /* 153 */ + { "IconLabelSelectedText", HIText, kThemeTextColorIconLabelSelected }, /* 154 */ + { "BevelButtonStickyActiveText", HIText, kThemeTextColorBevelButtonStickyActive }, /* 155 */ + { "BevelButtonStickyInactiveText", HIText, kThemeTextColorBevelButtonStickyInactive }, /* 156 */ + { "NotificationText", HIText, kThemeTextColorNotification }, /* 157 */ + { "SystemDetailText", HIText, kThemeTextColorSystemDetail }, /* 158 */ + { "WhiteText", HIText, kThemeTextColorWhite }, /* 159 */ + { "TabPaneBackground", HIBackground, kThemeBackgroundTabPane }, /* 160 */ + { "PlacardBackground", HIBackground, kThemeBackgroundPlacard }, /* 161 */ + { "WindowHeaderBackground", HIBackground, kThemeBackgroundWindowHeader }, /* 162 */ + { "ListViewWindowHeaderBackground", HIBackground, kThemeBackgroundListViewWindowHeader }, /* 163 */ + { "SecondaryGroupBoxBackground", HIBackground, kThemeBackgroundSecondaryGroupBox }, /* 164 */ + { "MetalBackground", HIBackground, kThemeBackgroundMetal }, /* 165 */ + { "TtkBackground", ttkBackground, 0 }, /* 166 */ + { "TtkBackground1", ttkBackground, 1 }, /* 167 */ + { "TtkBackground2", ttkBackground, 2 }, /* 168 */ + { "TtkBackground3", ttkBackground, 3 }, /* 169 */ + { "TtkBackground4", ttkBackground, 4 }, /* 170 */ + { "TtkBackground5", ttkBackground, 5 }, /* 171 */ + { "TtkBackground6", ttkBackground, 6 }, /* 172 */ + { "TtkBackground7", ttkBackground, 7 }, /* 173 */ + { "TextColor", semantic, 0 }, /* 174 */ + { "SelectedTextColor", semantic, 1 }, /* 175 */ + { "LabelColor", semantic, 2 }, /* 176 */ + { "ControlTextColor", semantic, 3 }, /* 177 */ + { "DisabledControlTextColor", semantic, 4 }, /* 178 */ + { "TextBackgroundColor", semantic, 5 }, /* 179 */ + { "SelectedTextBackgroundColor", semantic, 6 }, /* 180 */ + { NULL, 0, 0 } }; -#define MAX_PIXELCODE 165 +#define FIRST_SEMANTIC_COLOR 166 +#define MAX_PIXELCODE 180 /* *---------------------------------------------------------------------- * - * GetThemeFromPixelCode -- + * GetEntryFromPixelCode -- * - * When given a pixel code corresponding to a theme system color, - * set one of brush, textColor or background to the corresponding - * Appearance Mgr theme constant. + * Extract a SystemColorMapEntry from the table. * * Results: - * Returns false if not a real pixel, true otherwise. + * Returns false if the code is out of bounds. * * Side effects: * None. * *---------------------------------------------------------------------- */ -static int -GetThemeFromPixelCode( +static bool +GetEntryFromPixelCode( unsigned char code, - ThemeBrush *brush, - ThemeTextColor *textColor, - ThemeBackgroundKind *background) + struct SystemColorMapEntry *entry) { if (code >= MIN_PIXELCODE && code <= MAX_PIXELCODE) { - *brush = systemColorMap[code - MIN_PIXELCODE].brush; - *textColor = systemColorMap[code - MIN_PIXELCODE].textColor; - *background = systemColorMap[code - MIN_PIXELCODE].background; + *entry = systemColorMap[code - MIN_PIXELCODE]; + return true; } else { - *brush = 0; - *textColor = 0; - *background = 0; - } - if (!*brush && !*textColor && !*background && code != PIXEL_MAGIC && - code != TRANSPARENT_PIXEL) { return false; - } else { - return true; } } /* *---------------------------------------------------------------------- * - * GetThemeColor -- + * SetCGColorComponents -- * - * Get RGB color for a given system color or pixel value. + * Set the components of a CGColorRef from an XColor pixel value and a + * system color map entry. The pixel value is only used in the case where + * the color is of type rgbColor. In that case the normalized XColor RGB + * values are copied into the CGColorRef. * * Results: * OSStatus * * Side effects: @@ -229,63 +256,154 @@ * *---------------------------------------------------------------------- */ static OSStatus -GetThemeColor( +SetCGColorComponents( + struct SystemColorMapEntry entry, unsigned long pixel, - ThemeBrush brush, - ThemeTextColor textColor, - ThemeBackgroundKind background, CGColorRef *c) { OSStatus err = noErr; - - if (brush) { - err = ChkErr(HIThemeBrushCreateCGColor, brush, c); - /*} else if (textColor) { - err = ChkErr(GetThemeTextColor, textColor, 32, true, c);*/ - } else { - CGFloat rgba[4] = {0, 0, 0, 1}; - - switch ((pixel >> 24) & 0xff) { - case PIXEL_MAGIC: { - unsigned short red, green, blue; - red = (pixel >> 16) & 0xff; - green = (pixel >> 8) & 0xff; - blue = (pixel ) & 0xff; - red |= red << 8; - green |= green << 8; - blue |= blue << 8; - rgba[0] = red / 65535.0; - rgba[1] = green / 65535.0; - rgba[2] = blue / 65535.0; - break; - } - case TRANSPARENT_PIXEL: - rgba[3] = 0.0; - break; - } - - static CGColorSpaceRef deviceRGBSpace = NULL; - if (!deviceRGBSpace) { - deviceRGBSpace = CGColorSpaceCreateDeviceRGB(); - } - *c = CGColorCreate(deviceRGBSpace, rgba ); - } + NSColor *bgColor, *color; + CGFloat rgba[4] = {0, 0, 0, 1}; + static CGColorSpaceRef deviceRGBSpace = NULL; + if (!deviceRGBSpace) { + deviceRGBSpace = CGColorSpaceCreateDeviceRGB(); + } + + switch (entry.type) { + case HIBrush: + err = ChkErr(HIThemeBrushCreateCGColor, entry.value, c); + return err; + case rgbColor: + rgba[0] = ((pixel >> 16) & 0xff) / 255.0; + rgba[1] = ((pixel >> 8) & 0xff) / 255.0; + rgba[2] = ((pixel ) & 0xff) / 255.0; + break; + case ttkBackground: + bgColor = [[NSColor windowBackgroundColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; + [bgColor getComponents: rgba]; + for (int i=0; i<3; i++) { + rgba[i] -= entry.value*(8.0/255.0); + } + break; + case semantic: + switch (entry.value) { + case 0: + color = [[NSColor textColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; + break; + case 1: + color = [[NSColor selectedTextColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; + break; + case 2: +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101000 + color = [[NSColor labelColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; +#else + color = [[NSColor textColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; +#endif + break; + case 3: + color = [[NSColor controlTextColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; + break; + case 4: + color = [[NSColor disabledControlTextColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; + break; + case 5: + color = [[NSColor textBackgroundColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; + break; + case 6: + color = [[NSColor selectedTextBackgroundColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; + break; + default: + if ([NSApp macMinorVersion] < 10) { + color = [[NSColor textColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; + } else { +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 + color = [[NSColor labelColor] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; +#endif + break; + } + } + [color getComponents: rgba]; + break; + case clearColor: + rgba[3] = 0.0; + break; + + /* + * There are no HITheme functions which convert Text or background colors + * to CGColors. (GetThemeTextColor has been removed, and it was never + * possible with backgrounds.) If we get one of these we return black. + */ + + case HIText: + case HIBackground: + default: + break; + } + *c = CGColorCreate(deviceRGBSpace, rgba ); return err; } /* *---------------------------------------------------------------------- * + * TkMacOSXInDarkMode -- + * + * Tests whether the given window's NSView has a DarkAqua Appearance. + * + * Results: + * Returns true if the NSView is in DarkMode, false if not. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +MODULE_SCOPE Bool +TkMacOSXInDarkMode(Tk_Window tkwin) { +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101300 + return false; +#else + static NSAppearanceName darkAqua = @"NSAppearanceNameDarkAqua"; + TkWindow *winPtr = (TkWindow*)tkwin; + NSView *view = TkMacOSXDrawableView(winPtr->privatePtr); + if (view && [view.effectiveAppearance.name isEqualToString:darkAqua]) { + return True; + } else { + return false; + } +#endif +} + + +/* + *---------------------------------------------------------------------- + * * TkSetMacColor -- * - * Creates a CGColorRef from a X style pixel value. + * Sets the components of a CGColorRef from an XColor pixel value. + * The high order byte of the pixel value is used as an index into + * the system color table, and then SetCGColorComponents is called + * with the table entry and the pixel value. * * Results: - * Returns false if not a real pixel, true otherwise. + * Returns false if the high order byte is not a valid index, true + * otherwise. * * Side effects: * The variable macColor is set to a new CGColorRef, the caller is * responsible for releasing it! * @@ -297,18 +415,13 @@ unsigned long pixel, /* Pixel value to convert. */ void *macColor) /* CGColorRef to modify. */ { CGColorRef *color = (CGColorRef*)macColor; OSStatus err = -1; - ThemeBrush brush; - ThemeTextColor textColor; - ThemeBackgroundKind background; - - if (GetThemeFromPixelCode((pixel >> 24) & 0xff, &brush, &textColor, - &background)) { - err = ChkErr(GetThemeColor, pixel, brush, textColor, background, - color); + struct SystemColorMapEntry entry; + if (GetEntryFromPixelCode((pixel >> 24) & 0xff, &entry)) { + err = ChkErr(SetCGColorComponents, entry, pixel, color); } return (err == noErr); } /* @@ -319,11 +432,11 @@ * Maintain a per-GC cache of previously converted CGColorRefs * * Results: * None resp. retained CGColorRef for CopyCachedColor() * - * Side effects: + * Side effects:M * None. * *---------------------------------------------------------------------- */ @@ -482,52 +595,58 @@ TkMacOSXSetColorInContext( GC gc, unsigned long pixel, CGContextRef context) { - OSStatus err = -1; - CGColorRef cgColor = CopyCachedColor(gc, pixel); - ThemeBrush brush; - ThemeTextColor textColor; - ThemeBackgroundKind background; - - if (!cgColor && GetThemeFromPixelCode((pixel >> 24) & 0xff, &brush, - &textColor, &background)) { - if (brush) { - err = ChkErr(HIThemeSetFill, brush, NULL, context, - kHIThemeOrientationNormal); - if (err == noErr) { - err = ChkErr(HIThemeSetStroke, brush, NULL, context, - kHIThemeOrientationNormal); - } - } else if (textColor) { - err = ChkErr(HIThemeSetTextFill, textColor, NULL, context, - kHIThemeOrientationNormal); - } else if (background) { - CGRect rect = CGContextGetClipBoundingBox(context); - HIThemeBackgroundDrawInfo info = { 0, kThemeStateActive, - background }; - - err = ChkErr(HIThemeApplyBackground, &rect, &info, - context, kHIThemeOrientationNormal); - } - if (err == noErr) { - return; - } - err = ChkErr(GetThemeColor, pixel, brush, textColor, background, - &cgColor); - if (err == noErr) { - SetCachedColor(gc, pixel, cgColor); - } - } else if (!cgColor) { - TkMacOSXDbgMsg("Ignored unknown pixel value 0x%lx", pixel); + OSStatus err = noErr; + CGColorRef cgColor = nil; + struct SystemColorMapEntry entry; + CGRect rect; + int code = (pixel >> 24) & 0xff; + HIThemeBackgroundDrawInfo info = { 0, kThemeStateActive, entry.value }; + static CGColorSpaceRef deviceRGBSpace = NULL; + if (!deviceRGBSpace) { + deviceRGBSpace = CGColorSpaceCreateDeviceRGB(); + } + if (code < FIRST_SEMANTIC_COLOR) { + cgColor = CopyCachedColor(gc, pixel); + } + if (!cgColor && GetEntryFromPixelCode(code, &entry)) { + switch (entry.type) { + case HIBrush: + err = ChkErr(HIThemeSetFill, entry.value, NULL, context, + kHIThemeOrientationNormal); + if (err == noErr) { + err = ChkErr(HIThemeSetStroke, entry.value, NULL, context, + kHIThemeOrientationNormal); + } + break; + case HIText: + err = ChkErr(HIThemeSetTextFill, entry.value, NULL, context, + kHIThemeOrientationNormal); + break; + case HIBackground: + rect = CGContextGetClipBoundingBox(context); + err = ChkErr(HIThemeApplyBackground, &rect, &info, + context, kHIThemeOrientationNormal); + break; + default: + err = ChkErr(SetCGColorComponents, entry, pixel, &cgColor); + if (err == noErr) { + SetCachedColor(gc, pixel, cgColor); + } + break; + } } if (cgColor) { CGContextSetFillColorWithColor(context, cgColor); CGContextSetStrokeColorWithColor(context, cgColor); CGColorRelease(cgColor); } + if (err != noErr) { + TkMacOSXDbgMsg("Ignored unknown pixel value 0x%lx", pixel); + } } /* *---------------------------------------------------------------------- * @@ -547,11 +666,11 @@ */ TkColor * TkpGetColor( Tk_Window tkwin, /* Window in which color will be used. */ - Tk_Uid name) /* Name of color to allocated (in form + Tk_Uid name) /* Name of color to be allocated (in form * suitable for passing to XParseColor). */ { Display *display = tkwin != None ? Tk_Display(tkwin) : NULL; Colormap colormap = tkwin!= None ? Tk_Colormap(tkwin) : None; TkColor *tkColPtr; @@ -559,10 +678,11 @@ /* * Check to see if this is a system color. Otherwise, XParseColor * will do all the work. */ + if (strncasecmp(name, "system", 6) == 0) { Tcl_Obj *strPtr = Tcl_NewStringObj(name+6, -1); int idx, result; result = Tcl_GetIndexFromObjStruct(NULL, strPtr, systemColorMap, @@ -570,15 +690,12 @@ Tcl_DecrRefCount(strPtr); if (result == TCL_OK) { OSStatus err; CGColorRef c; unsigned char pixelCode = idx + MIN_PIXELCODE; - ThemeBrush brush = systemColorMap[idx].brush; - ThemeTextColor textColor = systemColorMap[idx].textColor; - ThemeBackgroundKind background = systemColorMap[idx].background; - - err = ChkErr(GetThemeColor, 0, brush, textColor, background, &c); + struct SystemColorMapEntry entry = systemColorMap[idx]; + err = ChkErr(SetCGColorComponents, entry, 0, &c); if (err == noErr) { const size_t n = CGColorGetNumberOfComponents(c); const CGFloat *rgba = CGColorGetComponents(c); switch (n) { Index: macosx/tkMacOSXDefault.h ================================================================== --- macosx/tkMacOSXDefault.h +++ macosx/tkMacOSXDefault.h @@ -21,26 +21,29 @@ //#endif /* * The definitions below provide symbolic names for the default colors. * NORMAL_BG - Normal background color. + * NORMAL_FG - Normal foreground color. * ACTIVE_BG - Background color when widget is active. + * ACTIVE_FG - Foreground color when widget is active. * SELECT_BG - Background color for selected text. * SELECT_FG - Foreground color for selected text. * TROUGH - Background color for troughs in scales and scrollbars. * INDICATOR - Color for indicator when button is selected. * DISABLED - Foreground color when widget is disabled. */ #define BLACK "Black" #define WHITE "White" -#define NORMAL_BG "systemWindowBody" -#define ACTIVE_BG "systemButtonFacePressed" -#define ACTIVE_FG "systemPushButtonPressedText" -#define SELECT_BG "systemHighlight" -#define SELECT_FG NULL -#define INACTIVE_SELECT_BG "systemHighlightSecondary" +#define NORMAL_BG "systemTextBackgroundColor" +#define NORMAL_FG "systemTextColor" +#define ACTIVE_BG "systemTextBackgroundColor" +#define ACTIVE_FG "systemTextColor" +#define SELECT_BG "systemSelectedTextBackgroundColor" +#define SELECT_FG "systemSelectedTextColor" +#define INACTIVE_SELECT_BG "systemSelectedTextBackgroundColor" #define TROUGH "#c3c3c3" #define INDICATOR "#b03060" #define DISABLED "#a3a3a3" /* @@ -52,21 +55,21 @@ #define DEF_BUTTON_ACTIVE_BG_MONO BLACK #define DEF_BUTTON_ACTIVE_FG_COLOR ACTIVE_FG #define DEF_CHKRAD_ACTIVE_FG_COLOR DEF_BUTTON_ACTIVE_FG_COLOR #define DEF_BUTTON_ACTIVE_FG_MONO WHITE /* #define DEF_BUTTON_BG_COLOR "systemButtonFace"*/ -#define DEF_BUTTON_BG_COLOR WHITE +#define DEF_BUTTON_BG_COLOR NORMAL_BG #define DEF_BUTTON_BG_MONO WHITE #define DEF_BUTTON_BITMAP "" #define DEF_BUTTON_BORDER_WIDTH "2" #define DEF_BUTTON_CURSOR "" #define DEF_BUTTON_COMMAND "" #define DEF_BUTTON_COMPOUND "none" #define DEF_BUTTON_DEFAULT "disabled" #define DEF_BUTTON_DISABLED_FG_COLOR DISABLED #define DEF_BUTTON_DISABLED_FG_MONO "" -#define DEF_BUTTON_FG "systemButtonText" +#define DEF_BUTTON_FG NORMAL_FG #define DEF_CHKRAD_FG DEF_BUTTON_FG #define DEF_BUTTON_FONT "TkDefaultFont" #define DEF_BUTTON_HEIGHT "0" #define DEF_BUTTON_HIGHLIGHT_BG_COLOR DEF_BUTTON_BG_COLOR #define DEF_BUTTON_HIGHLIGHT_BG_MONO DEF_BUTTON_BG_MONO @@ -176,16 +179,16 @@ #define DEF_ENTRY_DISABLED_BG_COLOR NORMAL_BG #define DEF_ENTRY_DISABLED_BG_MONO WHITE #define DEF_ENTRY_DISABLED_FG DISABLED #define DEF_ENTRY_EXPORT_SELECTION "1" #define DEF_ENTRY_FONT "TkTextFont" -#define DEF_ENTRY_FG BLACK +#define DEF_ENTRY_FG NORMAL_FG #define DEF_ENTRY_HIGHLIGHT_BG NORMAL_BG #define DEF_ENTRY_HIGHLIGHT BLACK /* #define DEF_ENTRY_HIGHLIGHT_WIDTH "3" */ #define DEF_ENTRY_HIGHLIGHT_WIDTH "3" -#define DEF_ENTRY_INSERT_BG BLACK +#define DEF_ENTRY_INSERT_BG NORMAL_FG #define DEF_ENTRY_INSERT_BD_COLOR "0" #define DEF_ENTRY_INSERT_BD_MONO "0" #define DEF_ENTRY_INSERT_OFF_TIME "300" #define DEF_ENTRY_INSERT_ON_TIME "600" /* #define DEF_ENTRY_INSERT_WIDTH "2" */ @@ -235,28 +238,28 @@ */ #define DEF_LABELFRAME_BORDER_WIDTH "2" #define DEF_LABELFRAME_CLASS "Labelframe" #define DEF_LABELFRAME_RELIEF "groove" -#define DEF_LABELFRAME_FG "systemButtonText" +#define DEF_LABELFRAME_FG NORMAL_FG #define DEF_LABELFRAME_FONT "TkDefaultFont" #define DEF_LABELFRAME_TEXT "" #define DEF_LABELFRAME_LABELANCHOR "nw" /* * Defaults for listboxes: */ #define DEF_LISTBOX_ACTIVE_STYLE "dotbox" -#define DEF_LISTBOX_BG_COLOR WHITE +#define DEF_LISTBOX_BG_COLOR NORMAL_BG #define DEF_LISTBOX_BG_MONO WHITE #define DEF_LISTBOX_BORDER_WIDTH "1" #define DEF_LISTBOX_CURSOR "" #define DEF_LISTBOX_DISABLED_FG DISABLED #define DEF_LISTBOX_EXPORT_SELECTION "1" #define DEF_LISTBOX_FONT "TkTextFont" -#define DEF_LISTBOX_FG BLACK +#define DEF_LISTBOX_FG NORMAL_FG #define DEF_LISTBOX_HEIGHT "10" #define DEF_LISTBOX_HIGHLIGHT_BG NORMAL_BG #define DEF_LISTBOX_HIGHLIGHT BLACK #define DEF_LISTBOX_HIGHLIGHT_WIDTH "0" #define DEF_LISTBOX_JUSTIFY "left" @@ -346,11 +349,11 @@ #define DEF_MENUBUTTON_CURSOR "" #define DEF_MENUBUTTON_DIRECTION "below" #define DEF_MENUBUTTON_DISABLED_FG_COLOR DISABLED #define DEF_MENUBUTTON_DISABLED_FG_MONO "" #define DEF_MENUBUTTON_FONT "TkDefaultFont" -#define DEF_MENUBUTTON_FG BLACK +#define DEF_MENUBUTTON_FG NORMAL_FG #define DEF_MENUBUTTON_HEIGHT "0" #define DEF_MENUBUTTON_HIGHLIGHT_BG_COLOR DEF_MENUBUTTON_BG_COLOR #define DEF_MENUBUTTON_HIGHLIGHT_BG_MONO DEF_MENUBUTTON_BG_MONO #define DEF_MENUBUTTON_HIGHLIGHT BLACK #define DEF_MENUBUTTON_HIGHLIGHT_WIDTH "0" @@ -377,11 +380,11 @@ #define DEF_MESSAGE_ASPECT "150" #define DEF_MESSAGE_BG_COLOR NORMAL_BG #define DEF_MESSAGE_BG_MONO WHITE #define DEF_MESSAGE_BORDER_WIDTH "1" #define DEF_MESSAGE_CURSOR "" -#define DEF_MESSAGE_FG BLACK +#define DEF_MESSAGE_FG NORMAL_FG #define DEF_MESSAGE_FONT "TkDefaultFont" #define DEF_MESSAGE_HIGHLIGHT_BG NORMAL_BG #define DEF_MESSAGE_HIGHLIGHT BLACK #define DEF_MESSAGE_HIGHLIGHT_WIDTH "0" #define DEF_MESSAGE_JUSTIFY "left" @@ -441,11 +444,11 @@ #define DEF_SCALE_BORDER_WIDTH "1" #define DEF_SCALE_COMMAND "" #define DEF_SCALE_CURSOR "" #define DEF_SCALE_DIGITS "0" #define DEF_SCALE_FONT "TkDefaultFont" -#define DEF_SCALE_FG_COLOR BLACK +#define DEF_SCALE_FG_COLOR NORMAL_FG #define DEF_SCALE_FG_MONO BLACK #define DEF_SCALE_FROM "0" #define DEF_SCALE_HIGHLIGHT_BG_COLOR DEF_SCALE_BG_COLOR #define DEF_SCALE_HIGHLIGHT_BG_MONO DEF_SCALE_BG_MONO #define DEF_SCALE_HIGHLIGHT BLACK @@ -503,18 +506,18 @@ #define DEF_TEXT_BG_COLOR NORMAL_BG #define DEF_TEXT_BG_MONO WHITE #define DEF_TEXT_BLOCK_CURSOR "0" #define DEF_TEXT_BORDER_WIDTH "0" #define DEF_TEXT_CURSOR "xterm" -#define DEF_TEXT_FG BLACK +#define DEF_TEXT_FG NORMAL_FG #define DEF_TEXT_EXPORT_SELECTION "1" #define DEF_TEXT_FONT "TkFixedFont" #define DEF_TEXT_HEIGHT "24" #define DEF_TEXT_HIGHLIGHT_BG NORMAL_BG #define DEF_TEXT_HIGHLIGHT BLACK #define DEF_TEXT_HIGHLIGHT_WIDTH "3" -#define DEF_TEXT_INSERT_BG BLACK +#define DEF_TEXT_INSERT_BG NORMAL_FG #define DEF_TEXT_INSERT_BD_COLOR "0" #define DEF_TEXT_INSERT_BD_MONO "0" #define DEF_TEXT_INSERT_OFF_TIME "300" #define DEF_TEXT_INSERT_ON_TIME "600" #define DEF_TEXT_INSERT_UNFOCUSSED "none" Index: macosx/tkMacOSXInt.h ================================================================== --- macosx/tkMacOSXInt.h +++ macosx/tkMacOSXInt.h @@ -69,10 +69,11 @@ HIShapeRef drawRgn; /* Clipped drawing region */ int referenceCount; /* Don't delete toplevel until children are * gone. */ struct TkWindowPrivate *toplevel; /* Pointer to the toplevel datastruct. */ + CGFloat fillRGBA[4]; /* Background used by the ttk FillElement */ int flags; /* Various state see defines below. */ }; typedef struct TkWindowPrivate MacDrawable; /* @@ -84,10 +85,11 @@ #define TK_HOST_EXISTS 0x04 #define TK_DRAWN_UNDER_MENU 0x08 #define TK_IS_PIXMAP 0x10 #define TK_IS_BW_PIXMAP 0x20 #define TK_DO_NOT_DRAW 0x40 +#define TTK_HAS_CONTRASTING_BG 0x80 /* * I am reserving TK_EMBEDDED = 0x100 in the MacDrawable flags * This is defined in tk.h. We need to duplicate the TK_EMBEDDED flag in the * TkWindow structure for the window, but in the MacWin. This way we can @@ -199,13 +201,23 @@ MODULE_SCOPE void TkpReleaseRegion(TkRegion r); MODULE_SCOPE void TkpShiftButton(NSButton *button, NSPoint delta); MODULE_SCOPE Bool TkpAppIsDrawing(void); MODULE_SCOPE void TkpDisplayWindow(Tk_Window tkwin); MODULE_SCOPE Bool TkTestAppIsDrawing(void); +MODULE_SCOPE Bool TkMacOSXInDarkMode(Tk_Window tkwin); /* * Include the stubbed internal platform-specific API. */ #include "tkIntPlatDecls.h" #endif /* _TKMACINT */ + +/* + * Local Variables: + * mode: objc + * c-basic-offset: 4 + * fill-column: 79 + * coding: utf-8 + * End: + */ Index: macosx/tkMacOSXPort.h ================================================================== --- macosx/tkMacOSXPort.h +++ macosx/tkMacOSXPort.h @@ -125,21 +125,10 @@ #define TkpCmapStressed(tkwin,colormap) (0) #define TkpFreeColor(tkColPtr) #define TkSetPixmapColormap(p,c) {} #define TkpSync(display) -/* - * The following macro returns the pixel value that corresponds to the - * RGB values in the given XColor structure. - */ - -#define PIXEL_MAGIC ((unsigned char) 0x69) -#define TkpGetPixel(p) ((((((PIXEL_MAGIC << 8) \ - | (((p)->red >> 8) & 0xff)) << 8) \ - | (((p)->green >> 8) & 0xff)) << 8) \ - | (((p)->blue >> 8) & 0xff)) - /* * This macro stores a representation of the window handle in a string. */ #define TkpPrintWindowId(buf,w) \ @@ -157,21 +146,22 @@ * NOTE: values must be kept in sync with indices into the * systemColorMap array in tkMacOSXColor.c ! */ #define TRANSPARENT_PIXEL 30 -#define HIGHLIGHT_PIXEL 31 -#define HIGHLIGHT_SECONDARY_PIXEL 32 -#define HIGHLIGHT_TEXT_PIXEL 33 -#define HIGHLIGHT_ALTERNATE_PIXEL 34 -#define CONTROL_TEXT_PIXEL 35 -#define CONTROL_BODY_PIXEL 37 -#define CONTROL_FRAME_PIXEL 39 -#define WINDOW_BODY_PIXEL 41 -#define MENU_ACTIVE_PIXEL 43 -#define MENU_ACTIVE_TEXT_PIXEL 45 -#define MENU_BACKGROUND_PIXEL 47 -#define MENU_DISABLED_PIXEL 49 -#define MENU_TEXT_PIXEL 51 #define APPEARANCE_PIXEL 52 +#define PIXEL_MAGIC ((unsigned char) 0x69) + +/* + * The following macro returns the pixel value that corresponds to the + * 16-bit RGB values in the given XColor structure. + * The format is: (PIXEL_MAGIC <<< 24) | (R << 16) | (G << 8) | B + * where each of R, G and B is the high order byte of a 16-bit component. + */ + +#define TkpGetPixel(p) ((((((PIXEL_MAGIC << 8) \ + | (((p)->red >> 8) & 0xff)) << 8) \ + | (((p)->green >> 8) & 0xff)) << 8) \ + | (((p)->blue >> 8) & 0xff)) + #endif /* _TKMACPORT */ Index: macosx/tkMacOSXWm.c ================================================================== --- macosx/tkMacOSXWm.c +++ macosx/tkMacOSXWm.c @@ -8,11 +8,11 @@ * * Copyright (c) 1994-1997 Sun Microsystems, Inc. * Copyright 2001-2009, Apple Inc. * Copyright (c) 2006-2009 Daniel A. Steffen * Copyright (c) 2010 Kevin Walzer/WordTech Communications LLC. - * Copyright (c) 2017-2018 Marc Culler. + * Copyright (c) 2017-2019 Marc Culler. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ @@ -308,10 +308,12 @@ Tcl_Obj *const objv[]); static void WmUpdateGeom(WmInfo *wmPtr, TkWindow *winPtr); static int WmWinStyle(Tcl_Interp *interp, TkWindow *winPtr, int objc, Tcl_Obj *const objv[]); static int WmWinTabbingId(Tcl_Interp *interp, TkWindow *winPtr, + int objc, Tcl_Obj *const objv[]); +static int WmWinAppearance(Tcl_Interp *interp, TkWindow *winPtr, int objc, Tcl_Obj *const objv[]); static void ApplyWindowAttributeFlagChanges(TkWindow *winPtr, NSWindow *macWindow, UInt64 oldAttributes, int oldFlags, int create, int initial); static void ApplyMasterOverrideChanges(TkWindow *winPtr, @@ -5507,14 +5509,14 @@ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const subcmds[] = { - "style", "tabbingid", NULL + "style", "tabbingid", "appearance", NULL }; enum SubCmds { - TKMWS_STYLE, TKMWS_TABID + TKMWS_STYLE, TKMWS_TABID, TKMWS_APPEARANCE }; Tk_Window tkwin = clientData; TkWindow *winPtr; int index; @@ -5538,30 +5540,50 @@ if (Tcl_GetIndexFromObjStruct(interp, objv[1], subcmds, sizeof(char *), "option", 0, &index) != TCL_OK) { return TCL_ERROR; } - if (((enum SubCmds) index) == TKMWS_STYLE) { + switch((enum SubCmds) index) { + case TKMWS_STYLE: if ((objc < 3) || (objc > 5)) { Tcl_WrongNumArgs(interp, 2, objv, "window ?class attributes?"); return TCL_ERROR; } return WmWinStyle(interp, winPtr, objc, objv); - } else if (((enum SubCmds) index) == TKMWS_TABID) { + case TKMWS_TABID: if ([NSApp macMinorVersion] < 12) { - Tcl_AddErrorInfo(interp, - "\n (TabbingIdentifiers only exist on OSX 10.12 or later)"); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "Tabbing identifiers did not exist until OSX 10.12.", -1)); + Tcl_SetErrorCode(interp, "TK", "WINDOWSTYLE", "TABBINGID", NULL); return TCL_ERROR; } if ((objc < 3) || (objc > 4)) { Tcl_WrongNumArgs(interp, 2, objv, "tabbingid window ?newid?"); return TCL_ERROR; } return WmWinTabbingId(interp, winPtr, objc, objv); + case TKMWS_APPEARANCE: + if ([NSApp macMinorVersion] < 9) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "Window appearances did not exist until OSX 10.9.", -1)); + Tcl_SetErrorCode(interp, "TK", "WINDOWSTYLE", "APPEARANCE", NULL); + return TCL_ERROR; + } + if ((objc < 3) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "appearance window ?appearancename?"); + return TCL_ERROR; + } + if (objc == 4 && [NSApp macMinorVersion] < 14) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "Window appearances cannot be changed before OSX 10.14.", -1)); + Tcl_SetErrorCode(interp, "TK", "WINDOWSTYLE", "APPEARANCE", NULL); + return TCL_ERROR; + } + return WmWinAppearance(interp, winPtr, objc, objv); + default: + return TCL_ERROR; } - /* won't be reached */ - return TCL_ERROR; } /* *---------------------------------------------------------------------- * @@ -5808,10 +5830,103 @@ if ([idString compare:newIdString] != NSOrderedSame && [win tab]) { [win moveTabToNewWindow:nil]; } return TCL_OK; } +#endif + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * WmWinAppearance -- + * + * This procedure is invoked to process the + * "::tk::unsupported::MacWindowStyle appearance" subcommand. The command + * allows you to get or set the appearance for the NSWindow + * associated with a Tk Window. The syntax is: + * tk::unsupported::MacWindowStyle tabbingid window ?newAppearance? + * Allowed appearance names are "aqua", "darkaqua", and "auto". + * + * Results: + * Returns the appearance setting of the window prior to calling this + * function. + * + * Side effects: + * The underlying NSWindow's appearance property is set to the specified + * value if the optional newAppearance argument is supplied. Otherwise + * the window's appearance property is not changed. If the appearance is + * set to aqua or darkaqua then the window will use the associated + * NSAppearance even if the user has selected a different appearance with + * the system preferences. If it is set to auto then the appearance + * property is set to nil, meaning that the preferences will determine the + * appearance. + * + * + *---------------------------------------------------------------------- + */ + +static int +WmWinAppearance( + Tcl_Interp *interp, /* Current interpreter. */ + TkWindow *winPtr, /* Window to be manipulated. */ + int objc, /* Number of arguments. */ + Tcl_Obj * const objv[]) /* Argument objects. */ +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1090 + static const char *const appearanceStrings[] = { + "aqua", "darkaqua", "auto", NULL + }; + enum appearances { + APPEARANCE_AQUA, APPEARANCE_DARKAQUA, APPEARANCE_AUTO + }; + Tcl_Obj *result = NULL; + NSAppearanceName appearance; + const char *resultString; + NSWindow *win = TkMacOSXDrawableWindow(winPtr->window); + if (win) { + appearance = win.appearance.name; + if (appearance == nil) { + resultString = appearanceStrings[APPEARANCE_AUTO]; + } else if (appearance == NSAppearanceNameAqua) { + resultString = appearanceStrings[APPEARANCE_AQUA]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + } else if (appearance == NSAppearanceNameDarkAqua) { + resultString = appearanceStrings[APPEARANCE_DARKAQUA]; +#endif + } else { + resultString = "unrecognized"; + } + result = Tcl_NewStringObj(resultString, strlen(resultString)); + } + if (result == NULL) { + Tcl_Panic("Failed to read appearance name."); + } + if (objc == 4) { + int index; + if (Tcl_GetIndexFromObjStruct(interp, objv[3], appearanceStrings, + sizeof(char *), "appearancename", 0, &index) != TCL_OK) { + return TCL_ERROR; + } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + switch ((enum appearances) index) { + case APPEARANCE_AQUA: + win.appearance = [NSAppearance appearanceNamed: + NSAppearanceNameAqua]; + break; + case APPEARANCE_DARKAQUA: + win.appearance = [NSAppearance appearanceNamed: + NSAppearanceNameDarkAqua]; + break; + default: + win.appearance = nil; + } +#endif + } + Tcl_SetObjResult(interp, result); + return TCL_OK; #endif return TCL_ERROR; } /* Index: macosx/ttkMacOSXTheme.c ================================================================== --- macosx/ttkMacOSXTheme.c +++ macosx/ttkMacOSXTheme.c @@ -6,10 +6,11 @@ * Copyright (c) 2004 Joe English * Copyright (c) 2005 Neil Madden * Copyright (c) 2006-2009 Daniel A. Steffen * Copyright 2008-2009, Apple Inc. * Copyright 2009 Kevin Walzer/WordTech Communications LLC. + * 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. * * See also: @@ -30,36 +31,41 @@ * accounts for this. */ #include "tkMacOSXPrivate.h" #include "ttk/ttkTheme.h" - +#include /* - * Use this version in the core: + * Macros for handling drawing contexts. */ #define BEGIN_DRAWING(d) { \ - TkMacOSXDrawingContext dc; \ - if (!TkMacOSXSetupDrawingContext((d), NULL, 1, &dc)) {return;} + TkMacOSXDrawingContext dc; \ + if (!TkMacOSXSetupDrawingContext((d), NULL, 1, &dc)) {return;} #define END_DRAWING \ - TkMacOSXRestoreDrawingContext(&dc); } + TkMacOSXRestoreDrawingContext(&dc); } #define HIOrientation kHIThemeOrientationNormal +#define NoThemeMetric 0xFFFFFFFF #ifdef __LP64__ #define RangeToFactor(maximum) (((double) (INT_MAX >> 1)) / (maximum)) #else #define RangeToFactor(maximum) (((double) (LONG_MAX >> 1)) / (maximum)) #endif /* __LP64__ */ +#define TTK_STATE_FIRST_TAB TTK_STATE_USER1 +#define TTK_STATE_LAST_TAB TTK_STATE_USER2 +#define TTK_TREEVIEW_STATE_SORTARROW TTK_STATE_USER1 + /*---------------------------------------------------------------------- * +++ 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 Rect relative to the containing port. */ static inline CGRect BoxToRect(Drawable d, Ttk_Box b) { MacDrawable *md = (MacDrawable*)d; CGRect rect; @@ -91,127 +97,1133 @@ {kThemeStatePressedUp, 0, 0}, {kThemeStatePressedDown, 0, 0} */ }; +/* + * 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. + * + * 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) +{ + SInt32 height; + if (heightMetric != NoThemeMetric) { + ChkErr(GetThemeMetric, heightMetric, &height); + bounds.origin.y += (bounds.size.height - height)/2; + bounds.size.height = height; + } + return bounds; +} + +#if MAC_OS_X_VERSION_MIN_REQUIRED > 1080 +/*---------------------------------------------------------------------- + * +++ Support for contrasting background colors when GroupBoxes + * or Tabbed panes are nested inside each other. Early versions + * of macOS used ridged borders, so do not need contrasting backgrounds. + */ + +/* + * For systems older than 10.14, [NSColor windowBackGroundColor] generates + * garbage when called from this function. In 10.14 it works correctly, + * and must be used in order to have a background color which responds + * to Dark Mode. So we use this hard-wired RGBA color on the older systems + * which don't support Dark Mode anyway. + */ + +static CGFloat windowBackground[4] = {235.0/255, 235.0/255, 235.0/255, 1.0}; +static CGFloat whiteRGBA[4] = {1.0, 1.0, 1.0, 1.0}; +static CGFloat blackRGBA[4] = {0.0, 0.0, 0.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 master, or the + * standard ttk window background if not. 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. + */ + +static void GetBackgroundColor( + CGContextRef context, + Tk_Window tkwin, + int contrast, + CGFloat *rgba) +{ + TkWindow *winPtr = (TkWindow *)tkwin; + TkWindow *masterPtr = (TkWindow *)TkGetGeomMaster(tkwin); + while (masterPtr != NULL) { + if (masterPtr->privatePtr->flags & TTK_HAS_CONTRASTING_BG) { + break; + } + masterPtr = (TkWindow *)TkGetGeomMaster(masterPtr); + } + if (masterPtr) { + for (int i = 0; i < 4; i++) { + rgba[i] = masterPtr->privatePtr->fillRGBA[i]; + } + } else { + if ([NSApp macMinorVersion] > 13) { + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *windowColor = [[NSColor windowBackgroundColor] + colorUsingColorSpace: deviceRGB]; + [windowColor getComponents: rgba]; + } else { + for (int i = 0; i < 4; 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; + } + } else { + for (int i=0; i<3; i++) { + rgba[i] -= 8.0/255.0; + } + } + winPtr->privatePtr->flags |= TTK_HAS_CONTRASTING_BG; + for (int i = 0; i < 4; i++) { + winPtr->privatePtr->fillRGBA[i] = rgba[i]; + } + } +} + +/* + * DrawGroupBox -- + * + * This is a standalone drawing procedure which draws the contrasting + * rounded rectangular box for LabelFrames and Notebook panes. + */ + +static void DrawGroupBox( + CGRect bounds, + CGContextRef context, + Tk_Window tkwin) +{ + 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, bgColor.CGColor); + 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, borderColor.CGColor); + [borderColor getComponents: fill]; + CGContextSetRGBFillColor(context, fill[0], fill[1], fill[2], fill[3]); + + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextReplacePathWithStrokedPath(context); + CGContextFillPath(context); + CFRelease(path); +} + +/* SolidFillRoundedRectangle -- + * + * Fill a rounded rectangle with a specified solid color. + */ + +static void SolidFillRoundedRectangle( + CGContextRef context, + CGRect bounds, + CGFloat radius, + NSColor *color) +{ + CGPathRef path; + CGContextSetFillColorWithColor(context, color.CGColor); + path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL); + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextFillPath(context); + CFRelease(path); +} + + +/* GradientFillRoundedRectangle -- + * + * Fill a rounded rectangle with a specified gradient. + */ + +static void GradientFillRoundedRectangle( + CGContextRef context, + CGRect bounds, + CGFloat radius, + CGFloat* colors, + int numColors) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + CGPathRef path; + 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); +} + + +static void DrawUpDownArrows( + CGContextRef context, + CGRect bounds, + CGFloat inset, + CGFloat size, + CGFloat *rgba) +{ + CGFloat x, y; + CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]); + CGContextSetLineWidth(context, 1.5); + 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); + CGContextStrokePath(context); +} + +static void DrawDownArrow( + CGContextRef context, + CGRect bounds, + CGFloat inset, + CGFloat size, + CGFloat *rgba) +{ + CGFloat x, y; + CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]); + 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}, {x+size/2, y+size/4}, {x+size, y-size/4}}; + CGContextAddLines(context, arrow, 3); + CGContextStrokePath(context); +} + +static void DrawUpArrow( + CGContextRef context, + CGRect bounds, + CGFloat inset, + CGFloat size, + CGFloat *rgba) +{ + CGFloat x, y; + CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]); + 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}, {x+size/2, y-size/4}, {x+size, y+size/4}}; + CGContextAddLines(context, arrow, 3); + CGContextStrokePath(context); +} + +/* + * 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, + CGContextRef context, + Tk_Window tkwin, + 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}; + + /* + * 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 = TkMacOSXDrawableWindow(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]; + CGContextSaveGState(context); + CGContextSetShouldAntialias(context, false); + CGContextSetFillColorSpace(context, deviceRGB.CGColorSpace); + CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace); + CGContextBeginPath(context); + CGContextSetFillColorWithColor(context, bgColor.CGColor); + CGContextAddRect(context, bounds); + CGContextFillPath(context); + strokeColor = [NSColor colorWithColorSpace: deviceRGB + components: separatorRGBA + count: 4]; + CGContextSetStrokeColorWithColor(context, strokeColor.CGColor); + CGContextAddLines(context, separator, 2); + CGContextStrokePath(context); + strokeColor = [NSColor colorWithColorSpace: deviceRGB + components: borderRGBA + count: 4]; + CGContextSetStrokeColorWithColor(context, strokeColor.CGColor); + 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, blackRGBA); + } else if (state & TTK_STATE_SELECTED) { + DrawDownArrow(context, arrowBounds, 3, 8, blackRGBA); + } + } +} + +#endif /* MAC_OS_X_VERSION_MIN_REQUIRED > 1080 */ + +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101300 +/*---------------------------------------------------------------------- + * +++ 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. + */ + +/* + * Colors and gradients used in Dark Mode. + */ + +static CGFloat darkButtonFace[4] = {112.0/255, 113.0/255, 115.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 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}; + +/* FillButtonBackground -- + * + * Fills a rounded rectangle with a transparent black gradient. + */ + +static void FillButtonBackground( + CGContextRef context, + CGRect bounds, + CGFloat radius) +{ + 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); +} + +/* HighlightButtonBorder -- + * + * Accent the top border of a rounded rectangle with a transparent + * white gradient. + */ + +static void HighlightButtonBorder( + 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); +} + +/* + * DrawDarkButton -- + * + * This is a standalone drawing procedure which draws PushButtons and + * PopupButtons in the Dark Mode style. + */ + +static void DrawDarkButton( + CGRect bounds, + ThemeButtonKind kind, + Ttk_State state, + CGContextRef context) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *faceColor; + + /* + * To match the appearance of Apple's buttons we need to increase the + * height by 1 pixel. + */ + + bounds.size.height += 1; + + CGContextClipToRect(context, bounds); + FillButtonBackground(context, bounds, 5); + + /* + * Fill the button face with the appropriate color. + */ + + bounds = CGRectInset(bounds, 1, 1); + if (kind == kThemePushButton && (state & TTK_STATE_PRESSED)) { + GradientFillRoundedRectangle(context, bounds, 4, + darkSelectedGradient, 2); + } else { + 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 this is a popup, draw the arrow button. + */ + + if (kind == kThemePopupButton | kind == kThemeComboBox) { + CGRect arrowBounds = bounds; + arrowBounds.size.width = 16; + arrowBounds.origin.x += bounds.size.width - 16; + + /* + * If the toplevel is front, paint the button blue. + */ + + 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); + } + } + + HighlightButtonBorder(context, bounds); +} + +/* + * DrawDarkIncDecButton -- + * + * This is a standalone drawing procedure which draws an IncDecButton + * (as used in a Spinbox) in the Dark Mode style. + */ + +static void DrawDarkIncDecButton( + CGRect bounds, + ThemeDrawState drawState, + Ttk_State state, + CGContextRef context) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *faceColor; + + bounds = CGRectInset(bounds, 0, -1); + CGContextClipToRect(context, bounds); + FillButtonBackground(context, bounds, 6); + + /* + * Fill the button face with the appropriate color. + */ + + 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. + */ + + if (state & TTK_STATE_PRESSED) { + CGRect clip = bounds; + clip.size.height /= 2; + CGContextSaveGState(context); + if (drawState == kThemeStatePressedDown) { + clip.origin.y += clip.size.height; + } + CGContextClipToRect(context, clip); + GradientFillRoundedRectangle(context, bounds, 5, + darkSelectedGradient, 2); + CGContextRestoreGState(context); + } + DrawUpDownArrows(context, bounds, 3, 5, whiteRGBA); + HighlightButtonBorder(context, bounds); +} + +/* + * DrawDarkBevelButton -- + * + * This is a standalone drawing procedure which draws + * RoundedBevelButtons in the Dark Mode style. + */ + +static void DrawDarkBevelButton( + CGRect bounds, + Ttk_State state, + CGContextRef context) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *faceColor; + CGContextClipToRect(context, bounds); + FillButtonBackground(context, bounds, 5); + + /* + * Fill the button face with the appropriate color. + */ + + 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]; + } + SolidFillRoundedRectangle(context, bounds, 4, faceColor); + HighlightButtonBorder(context, bounds); +} + +/* + * DrawDarkCheckBox -- + * + * This is a standalone drawing procedure which draws Checkboxes + * in the Dark Mode style. + */ + +static void DrawDarkCheckBox( + CGRect bounds, + Ttk_State state, + CGContextRef context) +{ + CGRect checkbounds = {{0, bounds.size.height/2 - 8},{16, 16}}; + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *stroke; + CGFloat x, y; + bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y); + x = bounds.origin.x; + y = bounds.origin.y; + + 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, stroke.CGColor); + } + 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); + } +} + +/* + * DrawDarkRadioButton -- + * + * This is a standalone drawing procedure which draws RadioButtons + * in the Dark Mode style. + */ + +static void DrawDarkRadioButton( + CGRect bounds, + Ttk_State state, + CGContextRef context) +{ + CGRect checkbounds = {{0, bounds.size.height/2 - 9},{18, 18}}; + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *fill; + CGFloat x, y; + bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y); + x = bounds.origin.x; + y = bounds.origin.y; + + 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, fill.CGColor); + } + if (state & TTK_STATE_SELECTED) { + CGContextBeginPath(context); + CGRect dot = {{x + 6, y + 6}, {6, 6}}; + CGContextAddEllipseInRect(context, dot); + CGContextFillPath(context); + } else if (state & TTK_STATE_ALTERNATE) { + CGRect bar = {{x + 5, y + 8}, {8, 2}}; + CGContextFillRect(context, bar); + } +} + +/* + * DrawDarkTab -- + * + * This is a standalone drawing procedure which draws Tabbed Pane + * Tabs in the Dark Mode style. + */ + +static void DrawDarkTab( + CGRect bounds, + Ttk_State state, + CGContextRef context) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *faceColor, *stroke; + CGRect originalBounds= bounds; + + CGContextSetLineWidth(context, 1.0); + CGContextClipToRect(context, bounds); + + /* + * Extend the bounds to one or both sides so the rounded part will be + * clipped off. + */ + + 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. + */ + + bounds = CGRectInset(bounds, 1, 1); + if (!(state & TTK_STATE_SELECTED)) { + 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); + + /* + * Draw a separator line on the left side of the tab if it + * not first. + */ + + if (!(state & TTK_STATE_FIRST_TAB)) { + CGContextSaveGState(context); + CGContextSetShouldAntialias(context, false); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkTabSeparator + count: 4]; + CGContextSetStrokeColorWithColor(context, stroke.CGColor); + CGContextBeginPath(context); + CGContextMoveToPoint(context, originalBounds.origin.x, + originalBounds.origin.y + 1); + CGContextAddLineToPoint(context, originalBounds.origin.x, + originalBounds.origin.y + originalBounds.size.height - 1); + CGContextStrokePath(context); + CGContextRestoreGState(context); + } + } else { + + /* + * This is the selected tab; paint it blue. 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)) { + GradientFillRoundedRectangle(context, bounds, 4, + darkSelectedGradient, 2); + } else { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkInactiveSelectedTab + count: 4]; + SolidFillRoundedRectangle(context, bounds, 4, faceColor); + } + 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, + Tk_Window tkwin) +{ + 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, fillColor.CGColor); + CGContextFillRect(context, bounds); +} + +/* + * DrawDarkFrame -- + * + * This is a standalone drawing procedure which draws various + * types of borders in Dark Mode. + */ + +static void DrawDarkFrame( + CGRect bounds, + CGContextRef context, + HIThemeFrameKind kind) +{ + 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}, {x + w, y}, {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}}; + switch(kind) { + case kHIThemeFrameTextFieldSquare: + CGContextSaveGState(context); + CGContextSetShouldAntialias(context, false); + CGContextBeginPath(context); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkFrameTop + count: 4]; + CGContextSetStrokeColorWithColor(context, stroke.CGColor); + CGContextAddLines(context, topPart, 4); + CGContextStrokePath(context); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkFrameBottom + count: 4]; + CGContextSetStrokeColorWithColor(context, stroke.CGColor); + CGContextAddLines(context, bottom, 2); + CGContextStrokePath(context); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkFrameAccent + count: 4]; + CGContextSetStrokeColorWithColor(context, stroke.CGColor); + CGContextAddLines(context, accent, 2); + CGContextStrokePath(context); + CGContextRestoreGState(context); + break; + default: + break; + } +} + +/* + * DrawListHeader -- + * + * This is a standalone drawing procedure which draws column + * headers for a Treeview in the Dark Mode. + */ + +static void DrawDarkListHeader( + CGRect bounds, + CGContextRef context, + Tk_Window tkwin, + int state) +{ + 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 top[2] = {{x, y}, {x + w, y}}; + CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}}; + CGPoint separator[2] = {{x + w, y + 3}, {x + w, y + h - 3}}; + + CGContextSaveGState(context); + CGContextSetShouldAntialias(context, false); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkFrameBottom + count: 4]; + CGContextSetStrokeColorWithColor(context, stroke.CGColor); + 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); + } + } +} + +#endif /* MAC_OS_X_VERSION_MIN_REQUIRED >101300 */ + /*---------------------------------------------------------------------- * +++ Button element: Used for elements drawn with DrawThemeButton. */ /* - * Extra margins to account for drop shadow. + * When Ttk draws the various types of buttons, a pointer to one of these + * is passed as the clientData. */ -static Ttk_Padding ButtonMargins = {2,2,2,2}; - -#define NoThemeMetric 0xFFFFFFFF typedef struct { ThemeButtonKind kind; ThemeMetric heightMetric; } ThemeButtonParams; static ThemeButtonParams - PushButtonParams = { kThemePushButton, kThemeMetricPushButtonHeight }, - CheckBoxParams = { kThemeCheckBox, kThemeMetricCheckBoxHeight }, + PushButtonParams = { kThemePushButton, kThemeMetricPushButtonHeight }, + CheckBoxParams = { kThemeCheckBox, kThemeMetricCheckBoxHeight }, RadioButtonParams = { kThemeRadioButton, kThemeMetricRadioButtonHeight }, - BevelButtonParams = { kThemeBevelButton, NoThemeMetric }, + BevelButtonParams = { kThemeRoundedBevelButton, NoThemeMetric }, PopupButtonParams = { kThemePopupButton, kThemeMetricPopupButtonHeight }, - DisclosureParams = { kThemeDisclosureButton, kThemeMetricDisclosureTriangleHeight }, - ListHeaderParams = { kThemeListHeaderButton, kThemeMetricListHeaderHeight }; + DisclosureParams = { kThemeDisclosureButton, kThemeMetricDisclosureTriangleHeight }, + ListHeaderParams = { kThemeListHeaderButton, kThemeMetricListHeaderHeight }; static Ttk_StateTable ButtonValueTable[] = { { kThemeButtonMixed, TTK_STATE_ALTERNATE, 0 }, { kThemeButtonOn, TTK_STATE_SELECTED, 0 }, { kThemeButtonOff, 0, 0 } /* Others: kThemeDisclosureRight, kThemeDisclosureDown, kThemeDisclosureLeft */ }; static Ttk_StateTable ButtonAdornmentTable[] = { - { kThemeAdornmentDefault| kThemeAdornmentFocus, - TTK_STATE_ALTERNATE| TTK_STATE_FOCUS, 0 }, + { kThemeAdornmentDefault | kThemeAdornmentFocus, + TTK_STATE_ALTERNATE | TTK_STATE_FOCUS, 0 }, { kThemeAdornmentDefault, TTK_STATE_ALTERNATE, 0 }, + { kThemeAdornmentNone, TTK_STATE_ALTERNATE, 0 }, { kThemeAdornmentFocus, TTK_STATE_FOCUS, 0 }, - { kThemeAdornmentNone, 0, 0 } -}; + { kThemeAdornmentNone, 0, 0 }}; /* * computeButtonDrawInfo -- - * Fill in an appearance manager HIThemeButtonDrawInfo record. + * Fill in an appearance manager HIThemeButtonDrawInfo record. */ + static inline HIThemeButtonDrawInfo computeButtonDrawInfo( - ThemeButtonParams *params, Ttk_State state) + ThemeButtonParams *params, + Ttk_State state, + Tk_Window tkwin) { + /* + * See ButtonElementDraw for the explanation of why we always draw + * PushButtons in the active state. + */ + + SInt32 HIThemeState; + switch (params->kind) { + case kThemePushButton: + HIThemeState = kThemeStateActive; + break; + default: + HIThemeState = Ttk_StateTableLookup(ThemeStateTable, state); + break; + } + const HIThemeButtonDrawInfo info = { .version = 0, - .state = Ttk_StateTableLookup(ThemeStateTable, state), + .state = HIThemeState, .kind = params ? params->kind : 0, .value = Ttk_StateTableLookup(ButtonValueTable, state), .adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state), }; return info; } -static void ButtonElementSizeNoPadding( +static void ButtonElementMinSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { ThemeButtonParams *params = clientData; if (params->heightMetric != NoThemeMetric) { - SInt32 height; + ChkErr(GetThemeMetric, params->heightMetric, minHeight); - ChkErr(GetThemeMetric, params->heightMetric, &height); - *heightPtr = height; + /* + * The theme height does not include the 1-pixel border around + * the button, although it does include the 1-pixel shadow at + * the bottom. + */ + + *minHeight += 2; + + /* + * The minwidth must be 0 to force the generic ttk code to compute + * 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; } } static void ButtonElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { ThemeButtonParams *params = clientData; - const HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, 0); + const HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, 0, tkwin); static const CGRect scratchBounds = {{0, 0}, {100, 100}}; - CGRect contentBounds; + CGRect contentBounds, backgroundBounds; + int verticalPad; - ButtonElementSizeNoPadding( - clientData, elementRecord, tkwin, - widthPtr, heightPtr, paddingPtr); + ButtonElementMinSize(clientData, elementRecord, tkwin, + minWidth, minHeight, paddingPtr); /* - * To compute internal padding, query the appearance manager - * for the content bounds of a dummy rectangle, then use - * the difference as the padding. + * Given a hypothetical bounding rectangle for a button, HIToolbox will + * compute a bounding rectangle for the button contents and a bounding + * rectangle for the button background. The background bounds are large + * 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 + * to draw a button with a certain bounding rectangle it draws the button + * centered within the rectangle. + * + * To compute the effective padding around a button we request the + * content and bounding rectangles for a 100x100 button and use the + * padding between those. However, we symmetrize the padding on the + * top and bottom, because that is how the button will be drawn. */ + ChkErr(HIThemeGetButtonContentBounds, &scratchBounds, &info, &contentBounds); - - paddingPtr->left = CGRectGetMinX(contentBounds); - paddingPtr->top = CGRectGetMinY(contentBounds); - paddingPtr->right = CGRectGetMaxX(scratchBounds) - CGRectGetMaxX(contentBounds) + 1; - paddingPtr->bottom = CGRectGetMaxY(scratchBounds) - CGRectGetMaxY(contentBounds); - - /* - * Now add a little extra padding to account for drop shadows. - * @@@ SHOULD: call GetThemeButtonBackgroundBounds() instead. - */ - - *paddingPtr = Ttk_AddPadding(*paddingPtr, ButtonMargins); - *widthPtr += Ttk_PaddingWidth(ButtonMargins); - *heightPtr += Ttk_PaddingHeight(ButtonMargins); + ChkErr(HIThemeGetButtonBackgroundBounds, + &scratchBounds, &info, &backgroundBounds); + paddingPtr->left = contentBounds.origin.x - backgroundBounds.origin.x; + paddingPtr->right = (CGRectGetMaxX(backgroundBounds) - + CGRectGetMaxX(contentBounds)); + verticalPad = backgroundBounds.size.height - contentBounds.size.height; + paddingPtr->top = paddingPtr->bottom = verticalPad / 2; } static void ButtonElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { ThemeButtonParams *params = clientData; - CGRect bounds = BoxToRect(d, Ttk_PadBox(b, ButtonMargins)); - const HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, state); - + CGRect bounds = BoxToRect(d, b); + HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, state, tkwin); + bounds = NormalizeButtonBounds(params->heightMetric, bounds); + BEGIN_DRAWING(d) - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + if (TkMacOSXInDarkMode(tkwin)) { +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101300 + 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); + } +#endif + } else { + /* + * 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. + */ + +#if MAC_OS_X_VERSION_MIN_REQUIRED > 1080 + if (info.kind == kThemePopupButton && (state & TTK_STATE_BACKGROUND)) { + CGRect innerBounds = CGRectInset(bounds, 1, 1); + NSColor *whiteRGBA = [NSColor whiteColor]; + SolidFillRoundedRectangle(dc.context, innerBounds, 4, whiteRGBA); + } +#endif + + /* + * 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; + } + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + } END_DRAWING } static Ttk_ElementSpec ButtonElementSpec = { TK_STYLE_VERSION_2, @@ -223,38 +1235,33 @@ /*---------------------------------------------------------------------- * +++ Notebook elements. */ - /* Tab position logic, c.f. ttkNotebook.c TabState() */ -#define TTK_STATE_NOTEBOOK_FIRST TTK_STATE_USER1 -#define TTK_STATE_NOTEBOOK_LAST TTK_STATE_USER2 static Ttk_StateTable TabStyleTable[] = { - { kThemeTabFrontInactive, TTK_STATE_SELECTED|TTK_STATE_BACKGROUND}, + { kThemeTabFrontInactive, TTK_STATE_SELECTED | TTK_STATE_BACKGROUND}, { kThemeTabNonFrontInactive, TTK_STATE_BACKGROUND}, - { kThemeTabFrontUnavailable, TTK_STATE_DISABLED|TTK_STATE_SELECTED}, + { kThemeTabFrontUnavailable, TTK_STATE_DISABLED | TTK_STATE_SELECTED}, { kThemeTabNonFrontUnavailable, TTK_STATE_DISABLED}, { kThemeTabFront, TTK_STATE_SELECTED}, { kThemeTabNonFrontPressed, TTK_STATE_PRESSED}, { kThemeTabNonFront, 0} }; static Ttk_StateTable TabAdornmentTable[] = { - { kHIThemeTabAdornmentNone, - TTK_STATE_NOTEBOOK_FIRST|TTK_STATE_NOTEBOOK_LAST}, - {kHIThemeTabAdornmentTrailingSeparator, TTK_STATE_NOTEBOOK_FIRST}, - {kHIThemeTabAdornmentNone, TTK_STATE_NOTEBOOK_LAST}, - {kHIThemeTabAdornmentTrailingSeparator, 0 }, + { kHIThemeTabAdornmentNone, TTK_STATE_FIRST_TAB | TTK_STATE_LAST_TAB}, + { kHIThemeTabAdornmentTrailingSeparator, TTK_STATE_FIRST_TAB}, + { kHIThemeTabAdornmentNone, TTK_STATE_LAST_TAB}, + { kHIThemeTabAdornmentTrailingSeparator, 0 }, }; static Ttk_StateTable TabPositionTable[] = { - { kHIThemeTabPositionOnly, - TTK_STATE_NOTEBOOK_FIRST|TTK_STATE_NOTEBOOK_LAST}, - { kHIThemeTabPositionFirst, TTK_STATE_NOTEBOOK_FIRST}, - { kHIThemeTabPositionLast, TTK_STATE_NOTEBOOK_LAST}, + { kHIThemeTabPositionOnly, TTK_STATE_FIRST_TAB | TTK_STATE_LAST_TAB}, + { kHIThemeTabPositionFirst, TTK_STATE_FIRST_TAB}, + { kHIThemeTabPositionLast, TTK_STATE_LAST_TAB}, { kHIThemeTabPositionMiddle, 0 }, }; /* * Apple XHIG Tab View Specifications: @@ -294,13 +1301,13 @@ * TP30000359-TPXREF116> */ static void TabElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { - GetThemeMetric(kThemeMetricLargeTabHeight, (SInt32 *)heightPtr); + GetThemeMetric(kThemeMetricLargeTabHeight, (SInt32 *)minHeight); *paddingPtr = Ttk_MakePadding(0, 0, 0, 2); } static void TabElementDraw( @@ -315,13 +1322,20 @@ .size = kHIThemeTabSizeNormal, .adornment = Ttk_StateTableLookup(TabAdornmentTable, state), .kind = kHIThemeTabKindNormal, .position = Ttk_StateTableLookup(TabPositionTable, state), }; - BEGIN_DRAWING(d) +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101300 + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkTab(bounds, state, dc.context); + } else { + ChkErr(HIThemeDrawTab, &bounds, &info, dc.context, HIOrientation, NULL); + } +#else ChkErr(HIThemeDrawTab, &bounds, &info, dc.context, HIOrientation, NULL); +#endif END_DRAWING } static Ttk_ElementSpec TabElementSpec = { TK_STYLE_VERSION_2, @@ -332,36 +1346,45 @@ }; /* * Notebook panes: */ + static void PaneElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { *paddingPtr = Ttk_MakePadding(9, 5, 9, 9); } static void PaneElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { + TkWindow *winPtr = (TkWindow *)tkwin; + MacDrawable *macWin = winPtr->privatePtr; CGRect bounds = BoxToRect(d, b); + bounds.origin.y -= kThemeMetricTabFrameOverlap; + bounds.size.height += kThemeMetricTabFrameOverlap; + BEGIN_DRAWING(d) +#if MAC_OS_X_VERSION_MIN_REQUIRED > 10800 + DrawGroupBox(bounds, dc.context, tkwin); +#else HIThemeTabPaneDrawInfo info = { .version = 1, .state = Ttk_StateTableLookup(ThemeStateTable, state), .direction = kThemeTabNorth, .size = kHIThemeTabSizeNormal, .kind = kHIThemeTabKindNormal, .adornment = kHIThemeTabPaneAdornmentNormal, }; - bounds.origin.y -= kThemeMetricTabFrameOverlap; bounds.size.height += kThemeMetricTabFrameOverlap; - BEGIN_DRAWING(d) - ChkErr(HIThemeDrawTabPane, &bounds, &info, dc.context, HIOrientation); + ChkErr(HIThemeDrawTabPane, &bounds, &info, dc.context, HIOrieRectntation); +#endif END_DRAWING + [TkMacOSXDrawableView(macWin) setNeedsDisplay:YES]; } static Ttk_ElementSpec PaneElementSpec = { TK_STYLE_VERSION_2, sizeof(NullElement), @@ -378,30 +1401,35 @@ * 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( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { *paddingPtr = Ttk_UniformPadding(4); } static void GroupElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { CGRect bounds = BoxToRect(d, b); + + BEGIN_DRAWING(d) +#if MAC_OS_X_VERSION_MIN_REQUIRED > 10800 + DrawGroupBox(bounds, dc.context, tkwin); +#else const HIThemeGroupBoxDrawInfo info = { .version = 0, .state = Ttk_StateTableLookup(ThemeStateTable, state), .kind = kHIThemeGroupBoxKindPrimaryOpaque, }; - - BEGIN_DRAWING(d) ChkErr(HIThemeDrawGroupBox, &bounds, &info, dc.context, HIOrientation); +#endif END_DRAWING } static Ttk_ElementSpec GroupElementSpec = { TK_STYLE_VERSION_2, @@ -411,59 +1439,99 @@ GroupElementDraw }; /*---------------------------------------------------------------------- * +++ Entry element -- - * 3 pixels padding for focus rectangle - * 2 pixels padding for EditTextFrame + * 3 pixels padding for focus rectangle + * 2 pixels padding for EditTextFrame */ typedef struct { Tcl_Obj *backgroundObj; + Tcl_Obj *fieldbackgroundObj; } EntryElement; + +#define ENTRY_DEFAULT_BACKGROUND "systemTextBackgroundColor" static Ttk_ElementOptionSpec EntryElementOptions[] = { { "-background", TK_OPTION_BORDER, - Tk_Offset(EntryElement,backgroundObj), "white" }, + Tk_Offset(EntryElement,backgroundObj), ENTRY_DEFAULT_BACKGROUND }, + { "-fieldbackground", TK_OPTION_BORDER, + Tk_Offset(EntryElement,fieldbackgroundObj), ENTRY_DEFAULT_BACKGROUND }, {0} }; static void EntryElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { *paddingPtr = Ttk_UniformPadding(5); } static void EntryElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { EntryElement *e = elementRecord; - Tk_3DBorder backgroundPtr = Tk_Get3DBorderFromObj(tkwin,e->backgroundObj); Ttk_Box inner = Ttk_PadBox(b, Ttk_UniformPadding(3)); CGRect bounds = BoxToRect(d, inner); - const HIThemeFrameDrawInfo info = { - .version = 0, - .kind = kHIThemeFrameTextFieldSquare, - .state = Ttk_StateTableLookup(ThemeStateTable, state), - .isFocused = state & TTK_STATE_FOCUS, - }; - - /* - * Erase w/background color: - */ - XFillRectangle(Tk_Display(tkwin), d, - Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC), - inner.x,inner.y, inner.width, inner.height); - - BEGIN_DRAWING(d) - ChkErr(HIThemeDrawFrame, &bounds, &info, dc.context, HIOrientation); + NSColor *background; + Tk_3DBorder backgroundPtr = NULL; + static char *defaultBG = ENTRY_DEFAULT_BACKGROUND; + + if (TkMacOSXInDarkMode(tkwin)) { +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101300 + BEGIN_DRAWING(d) + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + CGFloat fill[4]; + GetBackgroundColor(dc.context, tkwin, 1, fill); + background = [NSColor colorWithColorSpace: deviceRGB + components: fill + count: 4]; + CGContextSetFillColorWithColor(dc.context, background.CGColor); + CGContextFillRect(dc.context, bounds); + DrawDarkFrame(bounds, dc.context, kHIThemeFrameTextFieldSquare); + END_DRAWING +#endif + } else { + const HIThemeFrameDrawInfo info = { + .version = 0, + .kind = kHIThemeFrameTextFieldSquare, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .isFocused = state & TTK_STATE_FOCUS, + }; + + /* + * Earlier versions of the Aqua theme ignored the -fieldbackground + * option and used the -background as if it were -fieldbackground. + * Here we are enabling -fieldbackground. For backwards compatibility, + * if -fieldbackground is set to the default color and -background + * is set to a different color then we use -background as -fieldbackground. + */ + + if (0 != strcmp(Tcl_GetString(e->fieldbackgroundObj), defaultBG)) { + backgroundPtr = Tk_Get3DBorderFromObj(tkwin,e->fieldbackgroundObj); + } else if (0 != strcmp(Tcl_GetString(e->backgroundObj), defaultBG)) { + backgroundPtr = Tk_Get3DBorderFromObj(tkwin,e->backgroundObj); + } + if (backgroundPtr != NULL) { + XFillRectangle(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC), + inner.x, inner.y, inner.width, inner.height); + } + BEGIN_DRAWING(d) + if (backgroundPtr == NULL) { + background = [NSColor textBackgroundColor]; + CGContextSetFillColorWithColor(dc.context, background.CGColor); + CGContextFillRect(dc.context, bounds); + } + ChkErr(HIThemeDrawFrame, &bounds, &info, dc.context, HIOrientation); + END_DRAWING + } /*if (state & TTK_STATE_FOCUS) { ChkErr(DrawThemeFocusRect, &bounds, 1); }*/ - END_DRAWING } static Ttk_ElementSpec EntryElementSpec = { TK_STYLE_VERSION_2, sizeof(EntryElement), @@ -474,41 +1542,70 @@ /*---------------------------------------------------------------------- * +++ Combobox: * * NOTES: - * kThemeMetricComboBoxLargeDisclosureWidth -> 17 - * Padding and margins guesstimated by trial-and-error. + * The HIToolbox has incomplete and inconsistent support for ComboBoxes. + * There is no constant available to get the height of a ComboBox with + * GetThemeMetric. In fact, ComboBoxes are the same (fixed) height as + * PopupButtons and PushButtons, but they have no shadow at the bottom. + * As a result, they are drawn 1 pixel above the center of the bounds + * rectangle rather than being centered like the other buttons. One + * can request background bounds for a ComboBox, and it is reported with + * height 23, while the actual button face, including its 1-pixel border + * has height 21. Attempting to request the content bounds returns a + * 0 x 0 rectangle. Measurement indicates that the arrow button has + * width 18. + * + * With no help available from HIToolbox, we have to use hard-wired + * constants for the padding. We shift the bounding rectangle downward + * by 1 pixel to account for the fact that the button is not centered. */ -static Ttk_Padding ComboboxPadding = { 2, 3, 17, 1 }; -static Ttk_Padding ComboboxMargins = { 3, 3, 4, 4 }; +static Ttk_Padding ComboboxPadding = {4, 2, 20, 2 }; static void ComboboxElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { - *widthPtr = 0; - *heightPtr = 0; - *paddingPtr = Ttk_AddPadding(ComboboxMargins, ComboboxPadding); + *minWidth = 24; + *minHeight = 23; + *paddingPtr = ComboboxPadding; } static void ComboboxElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { - CGRect bounds = BoxToRect(d, Ttk_PadBox(b, ComboboxMargins)); + CGRect bounds = BoxToRect(d, b); const HIThemeButtonDrawInfo info = { .version = 0, .state = Ttk_StateTableLookup(ThemeStateTable, state), .kind = kThemeComboBox, .value = Ttk_StateTableLookup(ButtonValueTable, state), .adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state), }; BEGIN_DRAWING(d) - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + bounds.origin.y += 1; + if (TkMacOSXInDarkMode(tkwin)) { +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101300 + bounds.size.height += 1; + DrawDarkButton(bounds, info.kind, state, dc.context); +#endif + } else + { +#if MAC_OS_X_VERSION_MIN_REQUIRED > 1080 + if ((state & TTK_STATE_BACKGROUND) && + !(state & TTK_STATE_DISABLED)) { + NSColor *background = [NSColor textBackgroundColor]; + CGRect innerBounds = CGRectInset(bounds, 1, 2); + SolidFillRoundedRectangle(dc.context, innerBounds, 4, background); + } +#endif + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + } END_DRAWING } static Ttk_ElementSpec ComboboxElementSpec = { TK_STYLE_VERSION_2, @@ -516,73 +1613,143 @@ TtkNullElementOptions, ComboboxElementSize, ComboboxElementDraw }; - - - - /*---------------------------------------------------------------------- * +++ Spinbuttons. * - * From Apple HIG, part III, section "Controls", "The Stepper Control": - * there should be 2 pixels of space between the stepper control - * (AKA IncDecButton, AKA "little arrows") and the text field it modifies. + * From Apple HIG, part III, section "Controls", "The Stepper Control": + * there should be 2 pixels of space between the stepper control (AKA + * IncDecButton, AKA "little arrows") and the text field it modifies. + * + * Ttk expects the up and down arrows to be distinct elements but HIToolbox + * draws them as one widget with two different pressed states. We work + * around this by defining them as separate elements in the layout, but + * making each one have a drawing method which also draws the other one. + * The down button does no drawing when not pressed, and when pressed draws + * the entire IncDecButton in its "pressed down" state. The up button draws + * the entire IncDecButton when not pressed and when pressed draws the + * IncDecButton in its "pressed up" state. NOTE: This means that when the + * down button is pressed the IncDecButton will be drawn twice, first + * in unpressed state by the up arrow and then in "pressed down" state by + * the down button. The drawing must be done in that order. So the up + * button must be listed first in the layout. */ -static Ttk_Padding SpinbuttonMargins = {2,0,2,0}; -static void SpinButtonElementSize( +static Ttk_Padding SpinbuttonMargins = {0, 0, 2, 0}; +static void SpinButtonUpElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) +{ + SInt32 s; + + ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s); + *minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins); + ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s); + *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2; +} + +static void SpinButtonUpElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins)); + int infoState; + bounds.size.height *= 2; + if (state & TTK_STATE_PRESSED) { + infoState = kThemeStatePressedUp; + } else { + infoState = Ttk_StateTableLookup(ThemeStateTable, state); + } + const HIThemeButtonDrawInfo info = { + .version = 0, + .state = infoState, + .kind = kThemeIncDecButton, + .value = Ttk_StateTableLookup(ButtonValueTable, state), + .adornment = kThemeAdornmentNone, + }; + BEGIN_DRAWING(d) + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkIncDecButton(bounds, infoState, state, dc.context); + } else { + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + } + END_DRAWING +} + +static Ttk_ElementSpec SpinButtonUpElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + SpinButtonUpElementSize, + SpinButtonUpElementDraw +}; + +static void SpinButtonDownElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { SInt32 s; ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s); - *widthPtr = s + Ttk_PaddingWidth(SpinbuttonMargins); + *minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins); ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s); - *heightPtr = s + Ttk_PaddingHeight(SpinbuttonMargins); + *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2; } -static void SpinButtonElementDraw( +static void SpinButtonDownElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins)); - /* @@@ can't currently distinguish PressedUp (== Pressed) from PressedDown; - * ignore this bit for now [see #2219588] - */ + int infoState = 0; + bounds.origin.y -= bounds.size.height; + bounds.size.height *= 2; + if (state & TTK_STATE_PRESSED) { + infoState = kThemeStatePressedDown; + } else { + return; + } const HIThemeButtonDrawInfo info = { .version = 0, - .state = Ttk_StateTableLookup(ThemeStateTable, state & ~TTK_STATE_PRESSED), + .state = infoState, .kind = kThemeIncDecButton, .value = Ttk_StateTableLookup(ButtonValueTable, state), .adornment = kThemeAdornmentNone, }; BEGIN_DRAWING(d) - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkIncDecButton(bounds, infoState, state, dc.context); + } else { + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + } END_DRAWING } -static Ttk_ElementSpec SpinButtonElementSpec = { +static Ttk_ElementSpec SpinButtonDownElementSpec = { TK_STYLE_VERSION_2, sizeof(NullElement), TtkNullElementOptions, - SpinButtonElementSize, - SpinButtonElementDraw + SpinButtonDownElementSize, + SpinButtonDownElementDraw }; - /*---------------------------------------------------------------------- * +++ DrawThemeTrack-based elements -- - * Progress bars and scales. (See also: <>) + * Progress bars and scales. (See also: <>) + */ + +/* + * Apple does not change the appearance of a slider when the window + * becomes inactive. So we shouldn't either. */ static Ttk_StateTable ThemeTrackEnableTable[] = { { kThemeTrackDisabled, TTK_STATE_DISABLED, 0 }, - { kThemeTrackInactive, TTK_STATE_BACKGROUND, 0 }, + { kThemeTrackActive, TTK_STATE_BACKGROUND, 0 }, { kThemeTrackActive, 0, 0 } /* { kThemeTrackNothingToScroll, ?, ? }, */ }; typedef struct { /* TrackElement client data */ @@ -610,17 +1777,17 @@ {0,0,0} }; static void TrackElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { TrackElementData *data = clientData; SInt32 size = 24; /* reasonable default ... */ ChkErr(GetThemeMetric, data->thicknessMetric, &size); - *widthPtr = *heightPtr = size; + *minWidth = *minHeight = size; } static void TrackElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) @@ -651,15 +1818,32 @@ }; if (info.kind == kThemeSlider) { info.trackInfo.slider.pressState = state & TTK_STATE_PRESSED ? kThemeThumbPressed : 0; - info.trackInfo.slider.thumbDir = kThemeThumbPlain; + if (state & TTK_STATE_ALTERNATE) { + info.trackInfo.slider.thumbDir = kThemeThumbDownward; + } else { + info.trackInfo.slider.thumbDir = kThemeThumbPlain; + } } - - BEGIN_DRAWING(d) +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101300 + if (TkMacOSXInDarkMode(tkwin)) { + CGRect 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); + } +#endif ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation); END_DRAWING } static Ttk_ElementSpec TrackElementSpec = { @@ -680,13 +1864,13 @@ * figure out how to get the Appearance Manager to tell me the * slider size. */ static void SliderElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { - *widthPtr = *heightPtr = 24; + *minWidth = *minHeight = 24; } static Ttk_ElementSpec SliderElementSpec = { TK_STYLE_VERSION_2, sizeof(NullElement), @@ -694,11 +1878,11 @@ SliderElementSize, TtkNullElementDraw }; /*---------------------------------------------------------------------- - * +++ Progress bar element (new): + * +++ Progress bar element: * * @@@ NOTE: According to an older revision of the Aqua reference docs, * @@@ the 'phase' field is between 0 and 4. Newer revisions say * @@@ that it can be any UInt8 value. */ @@ -725,16 +1909,16 @@ {0,0,0,0} }; static void PbarElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { SInt32 size = 24; /* @@@ Check HIG for correct default */ ChkErr(GetThemeMetric, kThemeMetricLargeProgressBarThickness, &size); - *widthPtr = *heightPtr = size; + *minWidth = *minHeight = size; } static void PbarElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) @@ -763,10 +1947,25 @@ .enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state), .trackInfo.progress.phase = phase, }; BEGIN_DRAWING(d) +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101300 + if (TkMacOSXInDarkMode(tkwin)) { + CGRect 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); + } +#endif ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation); END_DRAWING } static Ttk_ElementSpec PbarElementSpec = { @@ -774,24 +1973,254 @@ sizeof(PbarElement), PbarElementOptions, PbarElementSize, PbarElementDraw }; + +/*---------------------------------------------------------------------- + * +++ Scrollbar element + */ + +typedef struct +{ + Tcl_Obj *orientObj; +} ScrollbarElement; + +static Ttk_ElementOptionSpec ScrollbarElementOptions[] = { + { "-orient", TK_OPTION_STRING, + Tk_Offset(ScrollbarElement,orientObj), "horizontal" }, + {0,0,0,0} +}; + +static void TroughElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) +{ + ScrollbarElement *scrollbar = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL; + SInt32 thickness = 15; + + Ttk_GetOrientFromObj(NULL, scrollbar->orientObj, &orientation); + ChkErr(GetThemeMetric, kThemeMetricScrollBarWidth, &thickness); + if (orientation == TTK_ORIENT_HORIZONTAL) { + *minHeight = thickness; + if ([NSApp macMinorVersion] > 7) { + *paddingPtr = Ttk_MakePadding(4, 4, 4, 3); + } + } else { + *minWidth = thickness; + if ([NSApp macMinorVersion] > 7) { + *paddingPtr = Ttk_MakePadding(4, 4, 3, 4); + } + } +} + +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( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + ScrollbarElement *scrollbar = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL; + CGRect bounds = BoxToRect(d, b); + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *troughColor; + CGFloat *rgba = TkMacOSXInDarkMode(tkwin) ? darkTrough : lightTrough; + Ttk_GetOrientFromObj(NULL, scrollbar->orientObj, &orientation); + if (orientation == TTK_ORIENT_HORIZONTAL) { + bounds = CGRectInset(bounds, 0, 1); + } else { + bounds = CGRectInset(bounds, 1, 0); + } + troughColor = [NSColor colorWithColorSpace: deviceRGB + components: rgba + count: 4]; + BEGIN_DRAWING(d) + if ([NSApp macMinorVersion] > 8) { +#if MAC_OS_X_VERSION_MIN_REQUIRED > 1080 + CGContextSetFillColorWithColor(dc.context, troughColor.CGColor); +#endif + } else { + ChkErr(HIThemeSetFill, kThemeBrushDocumentWindowBackground, NULL, + dc.context, HIOrientation); + } + CGContextFillRect(dc.context, bounds); + END_DRAWING +} + +static Ttk_ElementSpec TroughElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + TroughElementSize, + TroughElementDraw +}; + +static void ThumbElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) +{ + ScrollbarElement *scrollbar = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL; + + Ttk_GetOrientFromObj(NULL, scrollbar->orientObj, &orientation); + if (orientation == TTK_ORIENT_HORIZONTAL) { + *minHeight = 8; + } else { + *minWidth = 8; + } +} + +static void ThumbElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + ScrollbarElement *scrollbar = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL; + Ttk_GetOrientFromObj(NULL, scrollbar->orientObj, &orientation); + + /* + * In order to make ttk scrollbars work correctly it is necessary to be + * able to display the thumb element at the size and location which the ttk + * scrollbar widget requests. The algorithm that HIToolbox uses to + * determine the thumb geometry from the input values of min, max, value + * and viewSizeis, of course, undocumented. And this turns out to be a + * hard reverse engineering problem. A seemingly natural algorithm is + * implemented below, but it does not correctly compute the same thumb + * geometry as HITools (which also apparently does not agree with + * NSScrollbar). This code uses that algorithm for older OS versions, + * because using HITools also handles drawing the buttons and 3D thumb used + * on those systems. The incorrect geometry is annoying but not unusable. + * For newer systems the cleanest approach is to just draw the thumb + * directly. + */ + + if ([NSApp macMinorVersion] > 8) { +#if MAC_OS_X_VERSION_MIN_REQUIRED > 1080 + CGRect thumbBounds = BoxToRect(d, b); + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *thumbColor; + CGFloat *rgba; + if ((orientation == TTK_ORIENT_HORIZONTAL && + thumbBounds.size.width >= Tk_Width(tkwin) - 8) || + (orientation == TTK_ORIENT_VERTICAL && + thumbBounds.size.height >= Tk_Height(tkwin) - 8)) { + return; + } + int isDark = TkMacOSXInDarkMode(tkwin); + if ((state & TTK_STATE_PRESSED) || + (state & TTK_STATE_HOVER) ) { + rgba = isDark ? darkActiveThumb : lightActiveThumb; + } else { + rgba = isDark ? darkInactiveThumb : lightInactiveThumb; + } + thumbColor = [NSColor colorWithColorSpace: deviceRGB + components: rgba + count: 4]; + BEGIN_DRAWING(d) + SolidFillRoundedRectangle(dc.context, thumbBounds, 4, thumbColor); + END_DRAWING +#endif + } else { + double thumbSize, trackSize, visibleSize, viewSize; + MacDrawable *macWin = (MacDrawable *) Tk_WindowId(tkwin); + CGRect troughBounds = {{macWin->xOff, macWin->yOff}, + {Tk_Width(tkwin), Tk_Height(tkwin)}}; + + /* + * The info struct has integer fields, which will be converted to + * floats in the drawing routine. All of values provided in the info + * struct, namely min, max, value, and viewSize are only defined up to + * an arbitrary scale factor. To avoid roundoff error we scale so that + * the viewSize is a large float which is smaller than the largest int. + */ + + viewSize = RangeToFactor(100.0); + HIThemeTrackDrawInfo info = { + .version = 0, + .bounds = troughBounds, + .min = 0, + .attributes = kThemeTrackShowThumb | kThemeTrackThumbRgnIsNotGhost, + .enableState = kThemeTrackActive + }; + info.trackInfo.scrollbar.viewsize = viewSize*.8; + if (orientation == TTK_ORIENT_HORIZONTAL) { + trackSize = troughBounds.size.width; + thumbSize = b.width; + visibleSize = (thumbSize / trackSize) * viewSize; + info.max = viewSize - visibleSize; + info.value = info.max * (b.x / (trackSize - thumbSize)); + } else { + thumbSize = b.height; + trackSize = troughBounds.size.height; + visibleSize = (thumbSize / trackSize) * viewSize; + info.max = viewSize - visibleSize; + info.value = info.max * (b.y / (trackSize - thumbSize)); + } + if ((state & TTK_STATE_PRESSED) || + (state & TTK_STATE_HOVER) ) { + info.trackInfo.scrollbar.pressState = kThemeThumbPressed; + } else { + info.trackInfo.scrollbar.pressState = 0; + } + if (orientation == TTK_ORIENT_HORIZONTAL) { + info.attributes |= kThemeTrackHorizontal; + } else { + info.attributes &= ~kThemeTrackHorizontal; + } + BEGIN_DRAWING(d) + HIThemeDrawTrack (&info, 0, dc.context, kHIThemeOrientationNormal); + END_DRAWING + } +} + +static Ttk_ElementSpec ThumbElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + ThumbElementSize, + ThumbElementDraw +}; + +static void ArrowElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) +{ + if ([NSApp macMinorVersion] < 8) { + *minHeight = *minWidth = 14; + } else { + *minHeight = *minWidth = -1; + } +} + +static Ttk_ElementSpec ArrowElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + ArrowElementSize, + TtkNullElementDraw +}; /*---------------------------------------------------------------------- * +++ Separator element. * - * DrawThemeSeparator() guesses the orientation of the line from - * the width and height of the rectangle, so the same element can - * can be used for horizontal, vertical, and general separators. + * DrawThemeSeparator() guesses the orientation of the line from the width + * and height of the rectangle, so the same element can can be used for + * horizontal, vertical, and general separators. */ static void SeparatorElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { - *widthPtr = *heightPtr = 1; + *minWidth = *minHeight = 1; } static void SeparatorElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, unsigned int state) @@ -802,11 +2231,19 @@ /* Separator only supports kThemeStateActive, kThemeStateInactive */ .state = Ttk_StateTableLookup(ThemeStateTable, state & TTK_STATE_BACKGROUND), }; BEGIN_DRAWING(d) +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101300 + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkSeparator(bounds, dc.context, tkwin); + } else { + ChkErr(HIThemeDrawSeparator, &bounds, &info, dc.context, HIOrientation); + } +#else ChkErr(HIThemeDrawSeparator, &bounds, &info, dc.context, HIOrientation); +#endif END_DRAWING } static Ttk_ElementSpec SeparatorElementSpec = { TK_STYLE_VERSION_2, @@ -817,16 +2254,17 @@ }; /*---------------------------------------------------------------------- * +++ Size grip element. */ + static const ThemeGrowDirection sizegripGrowDirection = kThemeGrowRight|kThemeGrowDown; static void SizegripElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { HIThemeGrowBoxDrawInfo info = { .version = 0, .state = kThemeStateActive, .kind = kHIThemeGrowBoxKindNormal, @@ -834,12 +2272,12 @@ .size = kHIThemeGrowBoxSizeNormal, }; CGRect bounds = CGRectZero; ChkErr(HIThemeGetGrowBoxBounds, &bounds.origin, &info, &bounds); - *widthPtr = bounds.size.width; - *heightPtr = bounds.size.height; + *minWidth = bounds.size.width; + *minHeight = bounds.size.height; } static void SizegripElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, unsigned int state) @@ -868,42 +2306,85 @@ }; /*---------------------------------------------------------------------- * +++ Background and fill elements. * - * This isn't quite right: In Aqua, the correct background for - * a control depends on what kind of container it belongs to, - * and the type of the top-level window. - * - * Also: patterned backgrounds should be aligned with the coordinate - * system of the top-level window. If we're drawing into an - * off-screen graphics port this leads to alignment glitches. - */ + * Before drawing any ttk widget, its bounding rectangle is filled with a + * background color. This color must match the background color of the + * containing widget to avoid looking ugly. The need for care when doing + * this is exacerbated by the fact that ttk enforces its "native look" by + * not allowing user control of the background or highlight colors of ttk + * widgets. + * + * This job is made more complicated in recent versions of macOS by the fact + * that the Appkit GroupBox (used for ttk LabelFrames) and TabbedPane (used + * for the Notebook widget) both place their content inside a rectangle with + * rounded corners that has a color which contrasts with the dialog + * background color. Moreover, although the Apple human interface + * guidelines recommend against doing so, there are times when one wants to + * nest these widgets, for example having a GroupBox inside of a TabbedPane. + * To have the right contrast, each level of nesting requires a different + * color. + * + * Previous Tk releases used the HIThemeDrawGroupBox routine to draw + * GroupBoxes and TabbedPanes. This meant that the best that could be done + * was to set the GroupBox to be of kind kHIThemeGroupBoxKindPrimaryOpaque, + * and set its fill color to be the system background color. If widgets + * inside the box were drawn with the system background color the + * backgrounds would match. But this produces a GroupBox with no contrast, + * the only visual clue being a faint highlighting around the top of the + * GroupBox. Moreover, the TabbedPane does not have an Opaque version, so + * while it is drawn inside a contrasting rounded rectangle, the widgets + * inside the pane needed to be enclosed in a frame with the system + * background color. This added a visual artifact since the frame's + * background color does not match the Pane's background color. That code + * has now been replaced with the standalone drawing procedure + * macOSXDrawGroupBox, which draws a rounded rectangle with an appropriate + * contrasting background color. + * + * Patterned backgrounds, which are now obsolete, should be aligned with the + * coordinate system of the top-level window. Apparently failing to do this + * used to cause graphics anomalies when drawing into an off-screen graphics + * port. The code for handling this is currently commented out. + */ + static void FillElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { CGRect bounds = BoxToRect(d, b); +#if MAC_OS_X_VERSION_MIN_REQUIRED > 1080 + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *bgColor; + CGFloat fill[4]; + 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, bgColor.CGColor); + CGContextFillRect(dc.context, bounds); + END_DRAWING +#else ThemeBrush brush = (state & TTK_STATE_BACKGROUND) ? kThemeBrushModelessDialogBackgroundInactive : kThemeBrushModelessDialogBackgroundActive; - BEGIN_DRAWING(d) ChkErr(HIThemeSetFill, brush, NULL, dc.context, HIOrientation); //QDSetPatternOrigin(PatternOrigin(tkwin, d)); CGContextFillRect(dc.context, bounds); END_DRAWING +#endif } static void BackgroundElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, unsigned int state) { - FillElementDraw( - clientData, elementRecord, tkwin, - d, Ttk_WinBox(tkwin), state); + FillElementDraw(clientData, elementRecord, tkwin, d, Ttk_WinBox(tkwin), + state); } static Ttk_ElementSpec FillElementSpec = { TK_STYLE_VERSION_2, sizeof(NullElement), @@ -921,20 +2402,21 @@ }; /*---------------------------------------------------------------------- * +++ ToolbarBackground element -- toolbar style for frames. * - * This is very similar to the normal background element, but uses a - * different ThemeBrush in order to get the lighter pinstripe effect - * used in toolbars. We use SetThemeBackground() rather than - * ApplyThemeBackground() in order to get the right style. + * This is very similar to the normal background element, but uses a + * different ThemeBrush in order to get the lighter pinstripe effect + * used in toolbars. We use SetThemeBackground() rather than + * ApplyThemeBackground() in order to get the right style. * * + * Appearance_Manager/appearance_manager/constant_7.html#/ + * /apple_ref/doc/uid/TP30000243/C005321> * */ + static void ToolbarBackgroundElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { ThemeBrush brush = kThemeBrushToolbarBackground; @@ -952,17 +2434,51 @@ sizeof(NullElement), TtkNullElementOptions, TtkNullElementSize, ToolbarBackgroundElementDraw }; + +/*---------------------------------------------------------------------- + * +++ Field element: + * Used for the Treeview widget. This is like the BackgroundElement + * except that the fieldbackground color is configureable. + */ + +typedef struct { + Tcl_Obj *backgroundObj; +} FieldElement; + +static Ttk_ElementOptionSpec FieldElementOptions[] = { + { "-fieldbackground", TK_OPTION_BORDER, + Tk_Offset(FieldElement, backgroundObj), "white" }, + { NULL, 0, 0, NULL } +}; + +static void FieldElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + FieldElement *e = elementRecord; + Tk_3DBorder backgroundPtr = Tk_Get3DBorderFromObj(tkwin,e->backgroundObj); + XFillRectangle(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC), + b.x, b.y, b.width, b.height); +} + +static Ttk_ElementSpec FieldElementSpec = { + TK_STYLE_VERSION_2, + sizeof(FieldElement), + FieldElementOptions, + TtkNullElementSize, + FieldElementDraw +}; /*---------------------------------------------------------------------- * +++ Treeview header - * Redefine the header to use a kThemeListHeaderButton. + * Redefine the header to use a kThemeListHeaderButton. */ -#define TTK_TREEVIEW_STATE_SORTARROW TTK_STATE_USER1 static Ttk_StateTable TreeHeaderValueTable[] = { { kThemeButtonOn, TTK_STATE_ALTERNATE}, { kThemeButtonOn, TTK_STATE_SELECTED}, { kThemeButtonOff, 0} }; @@ -974,10 +2490,45 @@ { kThemeAdornmentHeaderButtonNoSortArrow, TTK_STATE_ALTERNATE}, { kThemeAdornmentHeaderButtonNoSortArrow, TTK_STATE_SELECTED}, { kThemeAdornmentFocus, TTK_STATE_FOCUS}, { kThemeAdornmentNone, 0} }; + +static void TreeAreaElementSize ( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) +{ + /* + * Padding is needed to get the heading text to align correctly, since the + * widget expects the heading to be the same height as a row. + */ + + if ([NSApp macMinorVersion] > 8) { + paddingPtr->top = 4; + } +} + +static Ttk_ElementSpec TreeAreaElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + TreeAreaElementSize, + TtkNullElementDraw +}; + +static void TreeHeaderElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) +{ + if ([NSApp macMinorVersion] > 8) { + *minHeight = 24; + + } else { + ButtonElementSize(clientData, elementRecord, tkwin, minWidth, + minHeight, paddingPtr); + } +} static void TreeHeaderElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { @@ -990,53 +2541,71 @@ .value = Ttk_StateTableLookup(TreeHeaderValueTable, state), .adornment = Ttk_StateTableLookup(TreeHeaderAdornmentTable, state), }; BEGIN_DRAWING(d) - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + if ([NSApp macMinorVersion] > 8) { + + /* + * Compensate for the padding added in TreeHeaderElementSize, so + * the larger heading will be drawn at the top of the widget. + */ + + bounds.origin.y -= 4; + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkListHeader(bounds, dc.context, tkwin, state); + } else { + DrawListHeader(bounds, dc.context, tkwin, state); + } + } else { + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + } END_DRAWING } static Ttk_ElementSpec TreeHeaderElementSpec = { TK_STYLE_VERSION_2, sizeof(NullElement), TtkNullElementOptions, - ButtonElementSizeNoPadding, + TreeHeaderElementSize, TreeHeaderElementDraw }; /* * Disclosure triangle: */ + #define TTK_TREEVIEW_STATE_OPEN TTK_STATE_USER1 #define TTK_TREEVIEW_STATE_LEAF TTK_STATE_USER2 static Ttk_StateTable DisclosureValueTable[] = { { kThemeDisclosureDown, TTK_TREEVIEW_STATE_OPEN, 0 }, { kThemeDisclosureRight, 0, 0 }, }; static void DisclosureElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + int *minWidth, int *minHeight, Ttk_Padding *paddingPtr) { SInt32 s; ChkErr(GetThemeMetric, kThemeMetricDisclosureTriangleWidth, &s); - *widthPtr = s; + *minWidth = s; ChkErr(GetThemeMetric, kThemeMetricDisclosureTriangleHeight, &s); - *heightPtr = s; + *minHeight = s; } static void DisclosureElementDraw( void *clientData, void *elementRecord, Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state) { if (!(state & TTK_TREEVIEW_STATE_LEAF)) { + int triangleState = TkMacOSXInDarkMode(tkwin) ? + kThemeStateInactive : kThemeStateActive; CGRect bounds = BoxToRect(d, b); const HIThemeButtonDrawInfo info = { .version = 0, - .state = Ttk_StateTableLookup(ThemeStateTable, state), + .state = triangleState, .kind = kThemeDisclosureTriangle, .value = Ttk_StateTableLookup(DisclosureValueTable, state), .adornment = kThemeAdornmentDrawIndicatorOnly, }; @@ -1082,41 +2651,64 @@ TTK_GROUP("Menubutton.button", TTK_FILL_BOTH, TTK_GROUP("Menubutton.padding", TTK_FILL_BOTH, TTK_NODE("Menubutton.label", TTK_PACK_LEFT)))) TTK_LAYOUT("TCombobox", - TTK_GROUP("Combobox.button", TTK_PACK_TOP|TTK_FILL_X, + TTK_GROUP("Combobox.button", TTK_FILL_BOTH, TTK_GROUP("Combobox.padding", TTK_FILL_BOTH, - TTK_NODE("Combobox.textarea", TTK_FILL_X)))) + TTK_NODE("Combobox.textarea", TTK_FILL_BOTH)))) /* Notebook tabs -- no focus ring */ TTK_LAYOUT("Tab", TTK_GROUP("Notebook.tab", TTK_FILL_BOTH, TTK_GROUP("Notebook.padding", TTK_EXPAND|TTK_FILL_BOTH, TTK_NODE("Notebook.label", TTK_EXPAND|TTK_FILL_BOTH)))) -/* Progress bars -- track only */ -TTK_LAYOUT("TSpinbox", - TTK_NODE("Spinbox.spinbutton", TTK_PACK_RIGHT|TTK_STICK_E) - TTK_GROUP("Spinbox.field", TTK_EXPAND|TTK_FILL_X, - TTK_NODE("Spinbox.textarea", TTK_EXPAND|TTK_FILL_X))) +/* Spinbox -- buttons 2px to the right of the field. */ + TTK_LAYOUT("TSpinbox", + TTK_GROUP("Spinbox.buttons", TTK_PACK_RIGHT, + TTK_NODE("Spinbox.uparrow", TTK_PACK_TOP|TTK_STICK_E) + TTK_NODE("Spinbox.downarrow", TTK_PACK_BOTTOM|TTK_STICK_E)) + TTK_GROUP("Spinbox.field", TTK_EXPAND|TTK_FILL_X, + TTK_NODE("Spinbox.textarea", TTK_EXPAND|TTK_FILL_X))) +/* Progress bars -- track only */ TTK_LAYOUT("TProgressbar", TTK_NODE("Progressbar.track", TTK_EXPAND|TTK_FILL_BOTH)) +/* Treeview -- no border. */ +TTK_LAYOUT("Treeview", + TTK_GROUP("Treeview.field", TTK_FILL_BOTH, + TTK_GROUP("Treeview.padding", TTK_FILL_BOTH, + TTK_NODE("Treeview.treearea", TTK_FILL_BOTH)))) + /* Tree heading -- no border, fixed height */ TTK_LAYOUT("Heading", - TTK_NODE("Treeheading.cell", TTK_FILL_X) + TTK_NODE("Treeheading.cell", TTK_FILL_BOTH) TTK_NODE("Treeheading.image", TTK_PACK_RIGHT) - TTK_NODE("Treeheading.text", 0)) + TTK_NODE("Treeheading.text", TTK_PACK_TOP)) /* Tree items -- omit focus ring */ TTK_LAYOUT("Item", TTK_GROUP("Treeitem.padding", TTK_FILL_BOTH, TTK_NODE("Treeitem.indicator", TTK_PACK_LEFT) TTK_NODE("Treeitem.image", TTK_PACK_LEFT) TTK_NODE("Treeitem.text", TTK_PACK_LEFT))) + +/* Scrollbar Layout -- Buttons at the bottom (Snow Leopard and Lion only) */ + +TTK_LAYOUT("Vertical.TScrollbar", + TTK_GROUP("Vertical.Scrollbar.trough", TTK_FILL_Y, + TTK_NODE("Vertical.Scrollbar.thumb", TTK_PACK_TOP|TTK_EXPAND|TTK_FILL_BOTH) + TTK_NODE("Vertical.Scrollbar.downarrow", TTK_PACK_BOTTOM) + TTK_NODE("Vertical.Scrollbar.uparrow", TTK_PACK_BOTTOM))) + +TTK_LAYOUT("Horizontal.TScrollbar", + TTK_GROUP("Horizontal.Scrollbar.trough", TTK_FILL_X, + TTK_NODE("Horizontal.Scrollbar.thumb", TTK_PACK_LEFT|TTK_EXPAND|TTK_FILL_BOTH) + TTK_NODE("Horizontal.Scrollbar.rightarrow", TTK_PACK_RIGHT) + TTK_NODE("Horizontal.Scrollbar.leftarrow", TTK_PACK_RIGHT))) TTK_END_LAYOUT_TABLE /*---------------------------------------------------------------------- * +++ Initialization. @@ -1133,10 +2725,11 @@ /* * Elements: */ Ttk_RegisterElementSpec(themePtr, "background", &BackgroundElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "fill", &FillElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "field", &FieldElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Toolbar.background", &ToolbarBackgroundElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Button.button", &ButtonElementSpec, &PushButtonParams); @@ -1143,49 +2736,69 @@ Ttk_RegisterElementSpec(themePtr, "Checkbutton.button", &ButtonElementSpec, &CheckBoxParams); Ttk_RegisterElementSpec(themePtr, "Radiobutton.button", &ButtonElementSpec, &RadioButtonParams); Ttk_RegisterElementSpec(themePtr, "Toolbutton.border", - &ButtonElementSpec, &BevelButtonParams); + &ButtonElementSpec, &BevelButtonParams); Ttk_RegisterElementSpec(themePtr, "Menubutton.button", &ButtonElementSpec, &PopupButtonParams); - Ttk_RegisterElementSpec(themePtr, "Spinbox.spinbutton", - &SpinButtonElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Spinbox.uparrow", + &SpinButtonUpElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Spinbox.downarrow", + &SpinButtonDownElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Combobox.button", &ComboboxElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Treeitem.indicator", &DisclosureElementSpec, &DisclosureParams); Ttk_RegisterElementSpec(themePtr, "Treeheading.cell", &TreeHeaderElementSpec, &ListHeaderParams); + Ttk_RegisterElementSpec(themePtr, "Treeview.treearea", &TreeAreaElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Notebook.tab", &TabElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Notebook.client", &PaneElementSpec, 0); - Ttk_RegisterElementSpec(themePtr, "Labelframe.border",&GroupElementSpec,0); - Ttk_RegisterElementSpec(themePtr, "Entry.field",&EntryElementSpec,0); - Ttk_RegisterElementSpec(themePtr, "Spinbox.field",&EntryElementSpec,0); - - Ttk_RegisterElementSpec(themePtr, "separator",&SeparatorElementSpec,0); - Ttk_RegisterElementSpec(themePtr, "hseparator",&SeparatorElementSpec,0); - Ttk_RegisterElementSpec(themePtr, "vseparator",&SeparatorElementSpec,0); - - Ttk_RegisterElementSpec(themePtr, "sizegrip",&SizegripElementSpec,0); + Ttk_RegisterElementSpec(themePtr, "Labelframe.border", &GroupElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Entry.field", &EntryElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Spinbox.field", &EntryElementSpec, 0); + + Ttk_RegisterElementSpec(themePtr, "separator", &SeparatorElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "hseparator", &SeparatorElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "vseparator", &SeparatorElementSpec, 0); + + Ttk_RegisterElementSpec(themePtr, "sizegrip", &SizegripElementSpec, 0); /* * <> - * The Progressbar widget adjusts the size of the pbar element. - * In the Aqua theme, the appearance manager computes the bar geometry; - * we do all the drawing in the ".track" element and leave the .pbar out. + * In some themes the Layouts for a progress bar has a trough element and a + * pbar element. But in our case the appearance manager draws both parts + * of the progress bar, so we just have a single element called ".track". */ - Ttk_RegisterElementSpec(themePtr,"Scale.trough", - &TrackElementSpec, &ScaleData); - Ttk_RegisterElementSpec(themePtr,"Scale.slider",&SliderElementSpec,0); + Ttk_RegisterElementSpec(themePtr,"Progressbar.track", &PbarElementSpec, 0); + Ttk_RegisterElementSpec(themePtr,"Scale.trough", &TrackElementSpec, + &ScaleData); + Ttk_RegisterElementSpec(themePtr,"Scale.slider", &SliderElementSpec, 0); + + Ttk_RegisterElementSpec(themePtr,"Vertical.Scrollbar.trough", &TroughElementSpec, 0); + Ttk_RegisterElementSpec(themePtr,"Vertical.Scrollbar.thumb", &ThumbElementSpec, 0); + Ttk_RegisterElementSpec(themePtr,"Horizontal.Scrollbar.trough", &TroughElementSpec, 0); + Ttk_RegisterElementSpec(themePtr,"Horizontal.Scrollbar.thumb", &ThumbElementSpec, 0); + + /* + * If we are not in Snow Leopard or Lion the arrows won't actually be displayed. + */ + + Ttk_RegisterElementSpec(themePtr,"Vertical.Scrollbar.uparrow", &ArrowElementSpec, 0); + Ttk_RegisterElementSpec(themePtr,"Vertical.Scrollbar.downarrow", &ArrowElementSpec, 0); + Ttk_RegisterElementSpec(themePtr,"Horizontal.Scrollbar.leftarrow", &ArrowElementSpec, 0); + Ttk_RegisterElementSpec(themePtr,"Horizontal.Scrollbar.rightarrow", &ArrowElementSpec, 0); + /* * Layouts: */ + Ttk_RegisterLayouts(themePtr, LayoutTable); Tcl_PkgProvide(interp, "ttk::theme::aqua", TTK_VERSION); return TCL_OK; } @@ -1202,6 +2815,5 @@ * c-basic-offset: 4 * fill-column: 79 * coding: utf-8 * End: */ - Index: tests/ttk/scrollbar.test ================================================================== --- tests/ttk/scrollbar.test +++ tests/ttk/scrollbar.test @@ -2,20 +2,30 @@ package require tcltest ; namespace import -force tcltest::* loadTestedCommands testConstraint coreScrollbar [expr {[tk windowingsystem] eq "aqua"}] -test scrollbar-swapout-1 "Use core scrollbars on OSX..." -constraints { - coreScrollbar +# Before 2019 the code in library/ttk/scrollbar.tcl would replace the +# constructor of ttk::scrollbar with the constructor of tk::scrollbar +# unless the -class or -style options were specified.. +# Now there is an implementation of ttk::scrollbar for macOS. The +# tests are left in place, though, except that scrollbar-swapout-1 +# test was changed to expect the class to be TScrollbar instead of +# Scrollbar. + +test scrollbar-swapout-1 "Don't use core scrollbars on OSX..." \ + -constraints { + coreScrollbar } -body { ttk::scrollbar .sb -command "yadda" list [winfo class .sb] [.sb cget -command] -} -result [list Scrollbar yadda] -cleanup { +} -result [list TScrollbar yadda] -cleanup { destroy .sb } -test scrollbar-swapout-2 "... unless -style is specified ..." -constraints { +test scrollbar-swapout-2 "... regardless of whether -style ..." \ +-constraints { coreScrollbar } -body { ttk::style layout Vertical.Custom.TScrollbar \ [ttk::style layout Vertical.TScrollbar] ; # See #1833339 ttk::scrollbar .sb -command "yadda" -style Custom.TScrollbar @@ -22,11 +32,11 @@ list [winfo class .sb] [.sb cget -command] [.sb cget -style] } -result [list TScrollbar yadda Custom.TScrollbar] -cleanup { destroy .sb } -test scrollbar-swapout-3 "... or -class." -constraints { +test scrollbar-swapout-3 "... or -class is specified." -constraints { coreScrollbar } -body { ttk::scrollbar .sb -command "yadda" -class Custom.TScrollbar list [winfo class .sb] [.sb cget -command] } -result [list Custom.TScrollbar yadda] -cleanup { @@ -42,17 +52,23 @@ .tsb get } -result [list 0.2 0.4] test scrollbar-1.2 "Set orientation" -body { .tsb configure -orient vertical - set w [winfo reqwidth .tsb] ; set h [winfo reqheight .tsb] + pack .tsb -side right -anchor e -expand 1 -fill y + wm geometry . 200x200 + update + set w [winfo width .tsb] ; set h [winfo height .tsb] expr {$h > $w} } -result 1 test scrollbar-1.3 "Change orientation" -body { .tsb configure -orient horizontal - set w [winfo reqwidth .tsb] ; set h [winfo reqheight .tsb] + pack .tsb -side bottom -anchor s -expand 1 -fill x + wm geometry . 200x200 + update + set w [winfo width .tsb] ; set h [winfo height .tsb] expr {$h < $w} } -result 1 # # Scale tests: