diff options
author | culler <culler> | 2019-04-11 04:39:49 (GMT) |
---|---|---|
committer | culler <culler> | 2019-04-11 04:39:49 (GMT) |
commit | 71b7db434ab281ba494f9fb0235b6d3c88bf8530 (patch) | |
tree | ff7925b73b37f09061575bfa71d5c548b3c9e3af /macosx | |
parent | 7c1acea352995895a3152bfa54230a3b6e3091f6 (diff) | |
parent | c40319586abe475d1861ac8925b4fcef35f56c32 (diff) | |
download | tk-71b7db434ab281ba494f9fb0235b6d3c88bf8530.zip tk-71b7db434ab281ba494f9fb0235b6d3c88bf8530.tar.gz tk-71b7db434ab281ba494f9fb0235b6d3c88bf8530.tar.bz2 |
Support for Apple's Dark Mode and many improvements to the Aqua theme.
Diffstat (limited to 'macosx')
-rw-r--r-- | macosx/README | 48 | ||||
-rw-r--r-- | macosx/tkMacOSXButton.c | 12 | ||||
-rw-r--r-- | macosx/tkMacOSXColor.c | 597 | ||||
-rw-r--r-- | macosx/tkMacOSXDefault.h | 39 | ||||
-rw-r--r-- | macosx/tkMacOSXInt.h | 12 | ||||
-rw-r--r-- | macosx/tkMacOSXPort.h | 38 | ||||
-rw-r--r-- | macosx/tkMacOSXWm.c | 133 | ||||
-rw-r--r-- | macosx/ttkMacOSXTheme.c | 2014 |
8 files changed, 2398 insertions, 495 deletions
diff --git a/macosx/README b/macosx/README index adc709c..cca90c5 100644 --- a/macosx/README +++ b/macosx/README @@ -189,6 +189,16 @@ 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 @@ -275,8 +285,42 @@ 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. Building Tcl/Tk on macOS +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. + +5. Building Tcl/Tk on macOS ------------------------------ - At least macOS 10.3 is required to build Tcl and TkX11, and macOS 10.6 diff --git a/macosx/tkMacOSXButton.c b/macosx/tkMacOSXButton.c index 484dcf2..ad963e1 100644 --- a/macosx/tkMacOSXButton.c +++ b/macosx/tkMacOSXButton.c @@ -388,7 +388,7 @@ TkpComputeButtonGeometry( * 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; @@ -770,6 +770,16 @@ TkMacOSXDrawButton( 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); diff --git a/macosx/tkMacOSXColor.c b/macosx/tkMacOSXColor.c index 459ca47..5e983e3 100644 --- a/macosx/tkMacOSXColor.c +++ b/macosx/tkMacOSXColor.c @@ -17,11 +17,32 @@ #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; */ /* @@ -32,157 +53,171 @@ struct SystemColorMapEntry { #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. @@ -190,36 +225,28 @@ static const struct SystemColorMapEntry systemColorMap[] = { *---------------------------------------------------------------------- */ -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 @@ -231,59 +258,150 @@ GetThemeFromPixelCode( */ static OSStatus -GetThemeColor( +SetCGColorComponents( + struct SystemColorMapEntry entry, unsigned long pixel, - ThemeBrush brush, - ThemeTextColor textColor, - ThemeBackgroundKind background, CGColorRef *c) { OSStatus err = noErr; + NSColor *bgColor, *color; + CGFloat rgba[4] = {0, 0, 0, 1}; + static CGColorSpaceRef deviceRGBSpace = NULL; + if (!deviceRGBSpace) { + deviceRGBSpace = CGColorSpaceCreateDeviceRGB(); + } - 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; + 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 TRANSPARENT_PIXEL: - rgba[3] = 0.0; + 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; - static CGColorSpaceRef deviceRGBSpace = NULL; - if (!deviceRGBSpace) { - deviceRGBSpace = CGColorSpaceCreateDeviceRGB(); - } - *c = CGColorCreate(deviceRGBSpace, rgba ); + /* + * 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 @@ -299,14 +417,9 @@ TkSetMacColor( { 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); } @@ -321,7 +434,7 @@ TkSetMacColor( * Results: * None resp. retained CGColorRef for CopyCachedColor() * - * Side effects: + * Side effects:M * None. * *---------------------------------------------------------------------- @@ -484,48 +597,54 @@ TkMacOSXSetColorInContext( 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, + 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, brush, NULL, context, + err = ChkErr(HIThemeSetStroke, entry.value, NULL, context, kHIThemeOrientationNormal); } - } else if (textColor) { - err = ChkErr(HIThemeSetTextFill, textColor, NULL, context, + break; + case HIText: + err = ChkErr(HIThemeSetTextFill, entry.value, NULL, context, kHIThemeOrientationNormal); - } else if (background) { - CGRect rect = CGContextGetClipBoundingBox(context); - HIThemeBackgroundDrawInfo info = { 0, kThemeStateActive, - background }; - + 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 (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); } if (cgColor) { CGContextSetFillColorWithColor(context, cgColor); CGContextSetStrokeColorWithColor(context, cgColor); CGColorRelease(cgColor); } + if (err != noErr) { + TkMacOSXDbgMsg("Ignored unknown pixel value 0x%lx", pixel); + } } /* @@ -549,7 +668,7 @@ TkMacOSXSetColorInContext( 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; @@ -561,6 +680,7 @@ TkpGetColor( * 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; @@ -572,11 +692,8 @@ TkpGetColor( 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); diff --git a/macosx/tkMacOSXDefault.h b/macosx/tkMacOSXDefault.h index dd6be69..61fee2d 100644 --- a/macosx/tkMacOSXDefault.h +++ b/macosx/tkMacOSXDefault.h @@ -23,7 +23,9 @@ /* * 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. @@ -33,12 +35,13 @@ #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" @@ -54,7 +57,7 @@ #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" @@ -64,7 +67,7 @@ #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" @@ -178,12 +181,12 @@ #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" @@ -237,7 +240,7 @@ #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" @@ -247,14 +250,14 @@ */ #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 @@ -348,7 +351,7 @@ #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 @@ -379,7 +382,7 @@ #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 @@ -443,7 +446,7 @@ #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 @@ -505,14 +508,14 @@ #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" diff --git a/macosx/tkMacOSXInt.h b/macosx/tkMacOSXInt.h index d942286..22d7d2c 100644 --- a/macosx/tkMacOSXInt.h +++ b/macosx/tkMacOSXInt.h @@ -71,6 +71,7 @@ struct TkWindowPrivate { * 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; @@ -86,6 +87,7 @@ typedef struct TkWindowPrivate MacDrawable; #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 @@ -201,6 +203,7 @@ 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. @@ -209,3 +212,12 @@ MODULE_SCOPE Bool TkTestAppIsDrawing(void); #include "tkIntPlatDecls.h" #endif /* _TKMACINT */ + +/* + * Local Variables: + * mode: objc + * c-basic-offset: 4 + * fill-column: 79 + * coding: utf-8 + * End: + */ diff --git a/macosx/tkMacOSXPort.h b/macosx/tkMacOSXPort.h index bd48bd5..617abda 100644 --- a/macosx/tkMacOSXPort.h +++ b/macosx/tkMacOSXPort.h @@ -128,17 +128,6 @@ #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. */ @@ -159,19 +148,20 @@ */ #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 */ diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c index a1fbbde..2b4384a 100644 --- a/macosx/tkMacOSXWm.c +++ b/macosx/tkMacOSXWm.c @@ -10,7 +10,7 @@ * Copyright 2001-2009, Apple Inc. * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> * 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. @@ -311,6 +311,8 @@ 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); @@ -5498,10 +5500,10 @@ TkUnsupported1ObjCmd( 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; @@ -5529,16 +5531,18 @@ TkUnsupported1ObjCmd( 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)) { @@ -5546,9 +5550,27 @@ TkUnsupported1ObjCmd( 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; } /* @@ -5806,6 +5828,99 @@ WmWinTabbingId( /* *---------------------------------------------------------------------- * + * 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; +} + +/* + *---------------------------------------------------------------------- + * * TkpMakeMenuWindow -- * * Configure the window to be either a undecorated pull-down (or pop-up) diff --git a/macosx/ttkMacOSXTheme.c b/macosx/ttkMacOSXTheme.c index f9611c5..02dd7e4 100644 --- a/macosx/ttkMacOSXTheme.c +++ b/macosx/ttkMacOSXTheme.c @@ -8,6 +8,7 @@ * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> * Copyright 2008-2009, Apple Inc. * Copyright 2009 Kevin Walzer/WordTech Communications LLC. + * Copyright 2019 Marc Culler * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -32,17 +33,18 @@ #include "tkMacOSXPrivate.h" #include "ttk/ttkTheme.h" - +#include <math.h> /* - * 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)) @@ -50,14 +52,18 @@ #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) { @@ -93,16 +99,932 @@ static Ttk_StateTable ThemeStateTable[] = { */ }; +/* + * 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 /*---------------------------------------------------------------------- - * +++ Button element: Used for elements drawn with DrawThemeButton. + * +++ 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. */ /* - * Extra margins to account for drop shadow. + * 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 Ttk_Padding ButtonMargins = {2,2,2,2}; -#define NoThemeMetric 0xFFFFFFFF +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. + */ + +/* + * When Ttk draws the various types of buttons, a pointer to one of these + * is passed as the clientData. + */ typedef struct { ThemeButtonKind kind; @@ -110,13 +1032,13 @@ typedef struct { } 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 }, @@ -126,23 +1048,41 @@ static Ttk_StateTable ButtonValueTable[] = { }; 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), @@ -150,54 +1090,73 @@ static inline HIThemeButtonDrawInfo computeButtonDrawInfo( 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); + + /* + * 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; - ChkErr(GetThemeMetric, params->heightMetric, &height); - *heightPtr = height; + /* + * 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( @@ -205,11 +1164,64 @@ static void ButtonElementDraw( 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 } @@ -225,15 +1237,12 @@ static Ttk_ElementSpec ButtonElementSpec = { * +++ 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}, @@ -241,18 +1250,16 @@ static Ttk_StateTable TabStyleTable[] = { }; 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 }, }; @@ -296,9 +1303,9 @@ static Ttk_StateTable TabPositionTable[] = { 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); } @@ -317,9 +1324,16 @@ static void TabElementDraw( .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 } @@ -334,9 +1348,10 @@ static Ttk_ElementSpec TabElementSpec = { /* * 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); } @@ -345,7 +1360,15 @@ 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), @@ -354,12 +1377,12 @@ static void PaneElementDraw( .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 = { @@ -380,9 +1403,10 @@ static Ttk_ElementSpec PaneElementSpec = { * "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); } @@ -392,14 +1416,18 @@ static void GroupElementDraw( 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 } @@ -413,23 +1441,28 @@ static Ttk_ElementSpec GroupElementSpec = { /*---------------------------------------------------------------------- * +++ 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); } @@ -439,29 +1472,64 @@ static void EntryElementDraw( 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, - }; + NSColor *background; + Tk_3DBorder backgroundPtr = NULL; + static char *defaultBG = ENTRY_DEFAULT_BACKGROUND; - /* - * 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); + 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, + }; - BEGIN_DRAWING(d) - ChkErr(HIThemeDrawFrame, &bounds, &info, dc.context, HIOrientation); + /* + * 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 = { @@ -476,27 +1544,39 @@ static Ttk_ElementSpec EntryElementSpec = { * +++ 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), @@ -506,7 +1586,24 @@ static void ComboboxElementDraw( }; 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 } @@ -518,69 +1615,139 @@ static Ttk_ElementSpec ComboboxElementSpec = { 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 *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 SpinButtonUpElementDraw( 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; + bounds.size.height *= 2; + if (state & TTK_STATE_PRESSED) { + infoState = kThemeStatePressedUp; + } else { + infoState = Ttk_StateTableLookup(ThemeStateTable, state); + } 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 SpinButtonUpElementSpec = { TK_STYLE_VERSION_2, sizeof(NullElement), TtkNullElementOptions, - SpinButtonElementSize, - SpinButtonElementDraw + SpinButtonUpElementSize, + SpinButtonUpElementDraw }; +static void SpinButtonDownElementSize( + 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 SpinButtonDownElementDraw( + 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 = 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 = 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 SpinButtonDownElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + SpinButtonDownElementSize, + SpinButtonDownElementDraw +}; /*---------------------------------------------------------------------- * +++ DrawThemeTrack-based elements -- - * Progress bars and scales. (See also: <<NOTE-TRACKS>>) + * Progress bars and scales. (See also: <<NOTE-TRACKS>>) + */ + +/* + * 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, ?, ? }, */ }; @@ -612,13 +1779,13 @@ static Ttk_ElementOptionSpec TrackElementOptions[] = { 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( @@ -653,11 +1820,28 @@ static void TrackElementDraw( 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 } @@ -682,9 +1866,9 @@ static Ttk_ElementSpec TrackElementSpec = { */ 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 = { @@ -696,7 +1880,7 @@ static Ttk_ElementSpec SliderElementSpec = { }; /*---------------------------------------------------------------------- - * +++ 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 @@ -727,12 +1911,12 @@ static Ttk_ElementOptionSpec PbarElementOptions[] = { 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( @@ -765,6 +1949,21 @@ static void PbarElementDraw( }; 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 } @@ -778,18 +1977,248 @@ static Ttk_ElementSpec PbarElementSpec = { }; /*---------------------------------------------------------------------- + * +++ 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( @@ -804,7 +2233,15 @@ static void SeparatorElementDraw( }; 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 } @@ -819,12 +2256,13 @@ static Ttk_ElementSpec SeparatorElementSpec = { /*---------------------------------------------------------------------- * +++ 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, @@ -836,8 +2274,8 @@ static void SizegripElementSize( 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( @@ -870,38 +2308,81 @@ static Ttk_ElementSpec SizegripElementSpec = { /*---------------------------------------------------------------------- * +++ 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. + * 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. * - * 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. + * 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 = { @@ -923,16 +2404,17 @@ static Ttk_ElementSpec BackgroundElementSpec = { /*---------------------------------------------------------------------- * +++ 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. * * <URL: http://developer.apple.com/documentation/Carbon/Reference/ - * Appearance_Manager/appearance_manager/constant_7.html#/ - * /apple_ref/doc/uid/TP30000243/C005321> + * 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) @@ -956,11 +2438,45 @@ static Ttk_ElementSpec ToolbarBackgroundElementSpec = { }; /*---------------------------------------------------------------------- + * +++ 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}, @@ -977,6 +2493,41 @@ static Ttk_StateTable TreeHeaderAdornmentTable[] = { { 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) @@ -992,7 +2543,22 @@ static void TreeHeaderElementDraw( }; 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 } @@ -1000,13 +2566,14 @@ 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[] = { @@ -1016,14 +2583,14 @@ static Ttk_StateTable DisclosureValueTable[] = { 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( @@ -1031,10 +2598,12 @@ static void DisclosureElementDraw( 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, @@ -1084,9 +2653,9 @@ TTK_LAYOUT("TMenubutton", 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", @@ -1094,20 +2663,29 @@ TTK_LAYOUT("Tab", 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", @@ -1116,6 +2694,20 @@ TTK_LAYOUT("Item", 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 /*---------------------------------------------------------------------- @@ -1135,6 +2727,7 @@ static int AquaTheme_Init(Tcl_Interp *interp) */ 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); @@ -1145,11 +2738,13 @@ static int AquaTheme_Init(Tcl_Interp *interp) 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", @@ -1157,33 +2752,51 @@ static int AquaTheme_Init(Tcl_Interp *interp) 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, "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, "separator", &SeparatorElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "hseparator", &SeparatorElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "vseparator", &SeparatorElementSpec, 0); - Ttk_RegisterElementSpec(themePtr, "sizegrip",&SizegripElementSpec,0); + Ttk_RegisterElementSpec(themePtr, "sizegrip", &SizegripElementSpec, 0); /* * <<NOTE-TRACKS>> - * 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); @@ -1204,4 +2817,3 @@ int Ttk_MacOSXPlatformInit(Tcl_Interp *interp) * coding: utf-8 * End: */ - |