summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/image.n14
-rw-r--r--doc/nsimage.n129
-rw-r--r--doc/ttk_button.n7
-rw-r--r--library/demos/images/Tk_feather.pngbin0 -> 119158 bytes
-rw-r--r--library/demos/images/plowed_field.pngbin0 -> 8363308 bytes
-rw-r--r--library/demos/images/starry_night.pngbin0 -> 8015314 bytes
-rw-r--r--library/demos/mac_styles.tcl268
-rw-r--r--library/demos/widget9
-rw-r--r--library/ttk/aquaTheme.tcl57
-rw-r--r--library/ttk/combobox.tcl233
-rw-r--r--library/ttk/menubutton.tcl10
-rw-r--r--library/ttk/progress.tcl6
-rw-r--r--macosx/tkMacOSXConstants.h3
-rw-r--r--macosx/tkMacOSXDraw.c2
-rw-r--r--macosx/tkMacOSXImage.c716
-rw-r--r--macosx/tkMacOSXInit.c121
-rw-r--r--macosx/tkMacOSXMenu.c1
-rw-r--r--macosx/tkMacOSXPrivate.h4
-rw-r--r--macosx/tkMacOSXRegion.c12
-rw-r--r--macosx/tkMacOSXSubwindows.c2
-rw-r--r--macosx/ttkMacOSXTheme.c2962
-rw-r--r--macosx/ttkMacOSXTheme.h563
-rw-r--r--tests/ttk/combobox.test10
23 files changed, 3674 insertions, 1455 deletions
diff --git a/doc/image.n b/doc/image.n
index b620076..24b768a 100644
--- a/doc/image.n
+++ b/doc/image.n
@@ -93,8 +93,20 @@ See the \fBbitmap\fR manual entry for more information.
Displays a variety of full-color images, using dithering to
approximate colors on displays with limited color capabilities.
See the \fBphoto\fR manual entry for more information.
+.TP
+\fBnsimage\fR
+This type is only available in the Aqua platform. It is a full-color
+image which may be created from a named system image. It has options
+designed to facilitate the use of these images in buttons. An
+\fBnsimage\fR can be based on an a \fItemplate image\fR, which is an
+anti-aliased 2-color image format that automatically interchanges
+black and white in response to changes between the light and dark
+modes on macOS. An \fBnsimage\fR can also generate an alternate
+\fIpressed\fR variant which is lighter in dark mode and darker in
+light mode. These are for use in image buttons. See the \fBnsimage\fR
+manual entry for more information.
.SH "SEE ALSO"
-bitmap(n), options(n), photo(n)
+bitmap(n), options(n), photo(n), nsimage(n)
.SH KEYWORDS
height, image, types of images, width
'\" Local Variables:
diff --git a/doc/nsimage.n b/doc/nsimage.n
new file mode 100644
index 0000000..8b401d7
--- /dev/null
+++ b/doc/nsimage.n
@@ -0,0 +1,129 @@
+'\"
+'\" Copyright (c) 2022 Marc Culler
+'\"
+'\" See the file "license.terms" for information on usage and redistribution
+'\" of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+'\"
+.TH nsimage n 8.7 Tk "Tk Built-In Commands"
+.so man.macros
+.BS
+'\" Note: do not modify the .SH NAME line immediately below!
+.SH NAME
+nsimage \- A Tk image type for macOS based on the NSImage class.
+.BE
+.SH DESCRIPTION
+The nsimage is implemented as a Tk image type. The \fBimage\fR
+command is used to create, delete, and query all images, including
+images of type \fBnsimage\fR. The options that are available are
+specific to the nsimage type and are described below.
+.PP
+The command to create an \fBnsimage\fR:
+.TP
+\fBimage create nsimage \fR?\fIname\fR? ?\fIoption value ...\fR?
+.PP
+creates a new nsimage and a command with the same name and returns its
+name.
+.SH OPTIONS
+.PP
+Valid \fIoptions\fR are:
+.TP
+\fB\-source\fR
+.PP
+The value of the \fB\-source\fR option is a string describing an
+NSimage. There are several ways to interpret this string, and the
+interpretation is determined by the value of the \fB\-as\fR option.
+This option is required.
+.PP
+.TP
+\fB\-as\fR
+.PP
+There are four possible values for the \fB-as\fR option which specify
+how the source string should be interpreted. The allowed values and
+their meanings are:
+.IP
+\fBname\fR
+.IP
+The source should be interpreted as the name of a named NSImage
+provided by the system. This is the default if the -as option is not specified.
+.IP
+\fBfile\fR
+.IP
+The source should be interpreted as a path to an image
+file in one of the formats understood by the NSImage class.
+.IP
+\fBpath\fR
+.IP
+The source should be interpreted as a path to an arbitrary
+file. The type of the file will be examined and the resulting image
+will be the system icon for files of that type.
+.IP
+\fBfiletype\fR
+.IP
+The source is interpreted as a string identifying a
+particular file type. It may be a filename extension, an Apple Uniform Type
+Identifier or a 4-character OSType value as used in the HFS filesystem.
+.TP
+\fB\-width\fR
+.PP
+The value of the \fIwidth\fR option is an integer specifying the width
+in pixels of the nsimage. If the width is not specified it will be
+computed from the height so as to preserve the aspect ration. If
+neither width nor height are specified then the width and height of
+the underlying NSImage will be used.
+.TP
+\fB\-height\fR
+.PP
+The value of the \fIheight\fR option is an integer specifying the
+height in pixels of the nsimage. If the height is not specified it
+will be computed from the height so as to preserve the aspect ration.
+If neither width nor height are specified then the width and height of
+the underlying NSImage will be used.
+.TP
+\fB\-radius\fR
+.PP
+The value of the \fIradius\fR option is an integer. If non-zero the
+image will be clipped to a rounded rectangle with the same width and
+height as the image, but with circular arcs of the specified radius
+cutting off the corners of the rectangle.
+.TP
+\fB\-ring\fR
+.PP
+The value of the \fIring\fR option is an integer. If non-zero then it
+specifies the thickness of a focus ring which will be drawn around the
+image using the control accent color specified in the System
+Preferences. The image is resized to reduce its width and height by
+twice the thickness of the ring. Note that this may create a small
+amount of distortion. The aspect ration of a non-square image will
+change slightly.
+.TP
+\fB\-alpha\fR
+.PP
+The value of the \fIalpha\fR option should be a floating point number
+between 0.0 and 1.0. This alpha value will be applied to each pixel
+of the nsimage, producing a partially transparent image. The default value
+is 1,0, which makes the image opaque.
+.TP
+\fB\-pressed\fR
+.PP
+The \fIpressed\fR option takes a boolean value. If the value is true
+or 1 then the image will be algorithmically modified to become darker
+in light mode or lighter in dark mode. The default is false. For an
+image button, the primary image should use the value false while the
+pressed image should be the same image but with the \fIpressed\fR
+option set to true.
+.TP
+\fB\-template\fR
+.PP
+The \fItemplate\fR option takes a boolean value. If the value is true
+or 1 then the image will be marked as being a template image. This
+means that the system will algorithmically convert the image to a
+light colored image when in dark mode. For the algorithm to work
+correctly the image must consist only of black pixels with alpha
+values.
+.SH "SEE ALSO"
+image(n), options(n), photo(n)
+.SH KEYWORDS
+height, image, types of images, width
+'\" Local Variables:
+'\" mode: nroff
+'\" End:
diff --git a/doc/ttk_button.n b/doc/ttk_button.n
index 7afd052..896432f 100644
--- a/doc/ttk_button.n
+++ b/doc/ttk_button.n
@@ -59,6 +59,13 @@ Invokes the command associated with the button.
.PP
\fBTtk::button\fR widgets support the \fBToolbutton\fR style in all standard
themes, which is useful for creating widgets for toolbars.
+.PP
+In the Aqua theme there are several other styles which can be used to
+produce replicas of many of the different button types that are
+discussed in Apple's Human Interface Guidelines. These include
+\fBDisclosureButton\fR, \fBDisclosureTriangle\fR, \fBHelpButton\fR,
+\fBImageButton\fR, \fBInlineButton\fR, \fBGradientButton\fR,
+\fBRoundedRectButton\fR, and \fBRecessedButton\fR.
.SH "STYLING OPTIONS"
.PP
The class name for a \fBttk::button\fP is \fBTButton\fP.
diff --git a/library/demos/images/Tk_feather.png b/library/demos/images/Tk_feather.png
new file mode 100644
index 0000000..226042b
--- /dev/null
+++ b/library/demos/images/Tk_feather.png
Binary files differ
diff --git a/library/demos/images/plowed_field.png b/library/demos/images/plowed_field.png
new file mode 100644
index 0000000..2103b6b
--- /dev/null
+++ b/library/demos/images/plowed_field.png
Binary files differ
diff --git a/library/demos/images/starry_night.png b/library/demos/images/starry_night.png
new file mode 100644
index 0000000..f65e408
--- /dev/null
+++ b/library/demos/images/starry_night.png
Binary files differ
diff --git a/library/demos/mac_styles.tcl b/library/demos/mac_styles.tcl
new file mode 100644
index 0000000..df727df
--- /dev/null
+++ b/library/demos/mac_styles.tcl
@@ -0,0 +1,268 @@
+# mac_styles.tcl --
+#
+# This demonstration script creates a toplevel window containing a notebook
+# whose pages provide examples of the various mac-specific widgets that are
+# provided via special values for the -style option.
+
+if {![info exists widgetDemo]} {
+ error "This script should be run from the \"widget\" demo."
+}
+
+package require Tk
+
+set w .mac_styles
+catch {destroy $w}
+toplevel $w
+package require Tk
+wm title $w "Tk Aqua Widgets"
+wm iconname $w "mac_styles"
+positionWindow $w
+##
+# System images we use in our buttons
+
+set featherImg [file join $tk_demoDirectory images Tk_feather.png]
+set starryImg [file join $tk_demoDirectory images starry_night.png]
+set fieldImg [file join $tk_demoDirectory images plowed_field.png]
+image create nsimage action -source NSAction -width 48 -height 48
+image create nsimage bonjour -source NSBonjour -width 48 -height 48
+image create nsimage bonjour1 -source NSBonjour -width 48 -height 48 -pressed 1
+image create nsimage tkfeather -source $featherImg -as file -width 48 -height 48
+image create nsimage tkfeather1 -source $featherImg -as file -width 48 -height 48 -pressed 1
+image create nsimage starry -source $starryImg -as file -width 96 -radius 10
+image create nsimage starry1 -source $starryImg -as file -width 96 -radius 10 -pressed 1
+image create nsimage starry2 -source $starryImg -as file -width 96 -radius 10 -ring 3
+image create nsimage field -source $fieldImg -as file -width 96 -radius 10
+image create nsimage field1 -source $fieldImg -as file -width 96 -radius 10 -pressed 1
+image create nsimage field2 -source $fieldImg -as file -width 96 -radius 10 -ring 3
+image create nsimage add -source NSAddTemplate -width 11 -height 11
+image create nsimage remove -source NSRemoveTemplate -width 11 -height 11
+
+# Off state and variables for checkbuttons and radio buttons
+set off {!selected !alternate}
+variable $w.checkVar
+variable $w.radioVar
+variable $w.triangleVar
+variable $w.popupVar
+variable $w.stepVar
+variable $w.comboVar
+
+# Make a disclosure triangle change state when clicked.
+proc toggleTriangle {tri} {
+ $tri instate {user1} {
+ $tri state {!user1}
+ return
+ }
+ $tri instate {!user1} {
+ $tri state {user1}
+ }
+}
+
+proc popupButton {win varName firstValue args} {
+ upvar #0 $varName var
+ if {![info exists var]} {
+ set var $firstValue
+ }
+ ttk::menubutton $win -textvariable $varName -menu $win.menu -direction flush
+ menu $win.menu -tearoff 0
+ $win.menu add radiobutton -label $firstValue -variable $varName
+ foreach i $args {
+ $win.menu add radiobutton -label $i -variable $varName
+ }
+ return $win.menu
+}
+
+set mag [encoding convertfrom utf-8 "\xf0\x9f\x94\x8d"]
+
+proc searchFocusOut {e} {
+ global mag
+ if {[$e get] eq ""} {
+ $e configure -foreground gray60
+ $e insert 0 "Search"
+ }
+}
+
+proc searchFocusIn {e} {
+ if {[$e cget -foreground] == {gray60}} {
+ $e delete 0 end
+ $e configure -foreground black
+ }
+}
+
+## Make the notebook and set up Ctrl+Tab traversal
+ttk::notebook $w.notebook
+ttk::notebook::enableTraversal $w.notebook
+
+# Frames pane
+set framesFrame [ttk::frame $w.notebook.frames -padding {40 35 40 50}]
+$w.notebook add $framesFrame -text "Frames"
+pack [ttk::labelframe $framesFrame.darker -text Darker -padding {50 30 50 50}] \
+ -fill both -expand 1
+pack [ttk::label $framesFrame.darker.label -padding {0 0 0 6} \
+ -text "This Group Box is nested to depth 2"] \
+ -fill x
+pack [ttk::labelframe $framesFrame.darker.darker -text "Darker Still" -padding 24] \
+ -fill both -expand 1
+pack [ttk::label $framesFrame.darker.darker.label \
+ -text "This Group Box is nested to depth 3"] -fill x
+pack [button $framesFrame.darker.darker.tkbutton -text "Tk Button" -width 7 \
+ -highlightbackground systemWindowBackgroundColor3] -pady 10
+pack [ttk::button $framesFrame.darker.darker.ttkbutton -text "Ttk Button" \
+ -width 7 -padding {-4 0 -4 0}] \
+ -pady 3
+# Button pane
+set buttonFrame [ttk::frame $w.notebook.buttons -padding {100 20 0 20}]
+$w.notebook add $buttonFrame -text "Buttons"
+grid columnconfigure $buttonFrame 0 -minsize 100
+grid columnconfigure $buttonFrame 1 -minsize 100
+
+set plain [ttk::button $buttonFrame.plain -text Button -padding {-12 0}]
+popupButton $buttonFrame.options .popupVar "Item 1" "Item 2" "Item 3"
+set options $buttonFrame.options
+set check [ttk::checkbutton $buttonFrame.check -text Check -variable .checkVar]
+set radio [ttk::frame $buttonFrame.radio]
+pack [ttk::radiobutton $radio.r1 -text "Radio 1" -variable .radioVar -value 1] -pady 4
+pack [ttk::radiobutton $radio.r2 -text "Radio 2" -variable .radioVar -value 2] -pady 4
+set triangle [ttk::checkbutton $buttonFrame.triangle -style Item -variable TriangleVar]
+bind $triangle <Button-1> {toggleTriangle %W}
+set bonjour [ttk::button $buttonFrame.bonjour -style ImageButton -text Bonjour \
+ -image {bonjour pressed bonjour1}]
+set feather [ttk::button $buttonFrame.feather -style ImageButton -text Tk \
+ -image {tkfeather pressed tkfeather1}]
+set gradient [ttk::frame $buttonFrame.gradient]
+pack [ttk::button $buttonFrame.gradient.add -style GradientButton \
+ -image add -padding 7] -side left
+pack [ttk::button $buttonFrame.gradient.remove -style GradientButton \
+ -image remove -padding 7] -side left
+set disclosure [ttk::checkbutton $buttonFrame.disclosure -style DisclosureButton]
+set help [ttk::button $buttonFrame.help -style HelpButton];
+
+$check state $off
+$radio.r1 state $off
+$radio.r2 state $off
+
+grid [ttk::label $buttonFrame.plainLabel -text "Push Button:"]\
+ -row 0 -column 0 -padx 4 -sticky e
+grid $plain -pady 4 -row 0 -column 1 -sticky w
+grid [ttk::label $buttonFrame.optionsLabel -text "Pop-up Button:"]\
+ -row 1 -column 0 -padx 4 -sticky e
+grid $options -pady 4 -row 1 -column 1 -sticky w
+grid [ttk::label $buttonFrame.checkLabel -text "Check Button:"]\
+ -row 2 -column 0 -padx 4 -sticky e
+grid $check -pady 4 -row 2 -column 1 -sticky w
+grid [ttk::label $buttonFrame.radioLabel -text "Radio Buttons:"]\
+ -row 3 -column 0 -padx 4 -sticky e
+grid $radio -pady 4 -row 3 -column 1 -sticky w
+grid [ttk::label $buttonFrame.triangleLabel -text "Disclosure Triangle:"]\
+ -row 4 -column 0 -padx 4 -sticky e
+grid $triangle -pady 4 -row 4 -column 1 -sticky w
+grid [ttk::label $buttonFrame.disclosureLabel -text "Disclosure Button:"]\
+ -row 5 -column 0 -padx 4 -sticky e
+grid $disclosure -row 5 -column 1 -sticky w
+grid [ttk::label $buttonFrame.imageLabel -text "Image Buttons:"]\
+ -row 7 -column 0 -padx 4 -sticky e
+grid $bonjour -pady 4 -row 6 -rowspan 4 -column 1 -sticky w
+grid $feather -padx 10 -pady 4 -row 6 -rowspan 4 -column 2
+grid [ttk::label $buttonFrame.gradentLabel -text "Gradient Buttons:"]\
+-row 10 -column 0 -padx 4 -sticky e
+grid $gradient -pady 4 -row 10 -column 1 -sticky w
+grid [ttk::label $buttonFrame.helpLabel -text "Help Button:"]\
+-row 11 -column 0 -padx 4 -sticky e
+grid $help -row 11 -column 1 -sticky w
+
+#ttk::button .f.b1 -style Toolbutton -image action
+#pack $buttonFrame
+
+# Entries Frame
+set entryFrame [ttk::frame $w.notebook.entries -padding {0 30 80 0}]
+grid columnconfigure $entryFrame 0 -minsize 200
+$w.notebook add $entryFrame -text "Entries"
+
+set textfield [ttk::entry $entryFrame.text -width 17]
+set searchfield [ttk::entry $entryFrame.search -width 1]
+set combo [ttk::combobox $entryFrame.combo -width 1 -textvariable comboVar \
+ -values {"Item 1" "Item 2" "Item 3"}]
+set stepper [ttk::spinbox $entryFrame.stepper -width 1 -textvariable stepVar \
+ -from 99000 -to 101000 -increment 1]
+set stepVar 100000
+searchFocusOut $searchfield
+bind $searchfield <FocusIn> {searchFocusIn %W}
+bind $searchfield <FocusOut> {searchFocusOut %W}
+
+grid [ttk::label $entryFrame.l0 -text "Text Field"] -row 0 -column 0 -padx 20 -sticky e
+grid $textfield -sticky ew -row 0 -column 1 -pady 13
+grid [ttk::label $entryFrame.l1 -text "Search Field"] -row 1 -column 0 -padx 20 -sticky e
+grid $searchfield -sticky ew -row 1 -column 1 -pady 13
+grid [ttk::label $entryFrame.l2 -text "Combo Box"] -row 2 -column 0 -padx 20 -sticky e
+grid $combo -sticky ew -row 2 -column 1 -pady 13
+grid [ttk::label $entryFrame.l3 -text "Stepper"] -row 3 -column 0 -padx 20 -sticky e
+grid $stepper -sticky ew -row 3 -column 1 -pady 13
+
+#Scales Frame
+set scaleFrame [ttk::frame $w.notebook.scales -padding {0 40 0 80}]
+$w.notebook add $scaleFrame -text "Scales"
+
+variable topVar 50
+set topSlider [ttk::scale $scaleFrame.topSlider -from 0 -to 100 \
+ -length 280 -variable topVar]
+set topProgress [ttk::progressbar $scaleFrame.topProgress \
+ -maximum 100 -variable topVar]
+
+variable bottomVar 50
+set bottomSlider [ttk::scale $scaleFrame.bottomSlider -from 0 -to 100 \
+ -length 280 -variable bottomVar]
+$bottomSlider state alternate
+set bottomProgress [ttk::progressbar $scaleFrame.bottomProgress \
+ -maximum 100 -variable bottomVar]
+
+
+grid $topSlider -padx 80 -pady 12 -sticky ew -row 0 -column 0 -columnspan 2
+grid $topProgress -padx 120 -pady 15 -sticky ew -row 1 -column 0 -columnspan 2
+grid [ttk::frame $scaleFrame.spacer] -row 2 -column 0 -columnspan 2 -pady 32
+
+grid $bottomSlider -padx 80 -sticky new -row 3 -column 0 -columnspan 2
+grid [ttk::label $scaleFrame.low -text Low -padding {70 0 0 0}] \
+ -row 4 -column 0 -sticky sw
+grid [ttk::label $scaleFrame.high -text High -padding {0 0 70 0}] \
+ -row 4 -column 1 -sticky se
+grid $bottomProgress -padx 120 -pady 15 -sticky ew -row 5 -column 0 -columnspan 2
+
+#Appearance Frame
+set appearanceFrame [ttk::frame $w.notebook.appearance -padding {0 40 0 80}]
+grid [ttk::label $w.notebook.appearance.info -justify left -padding {0 20 0 40}\
+ -text "Use the image buttons below to view this demo in light or dark mode."] \
+ -row 0 -column 0 -columnspan 3
+set light [ttk::button $appearanceFrame.light -style ImageButton -text Light \
+ -image {field pressed field1 selected field2} \
+ -command "beLight $appearanceFrame $w"]
+grid columnconfigure $appearanceFrame 1 -minsize 10
+grid $light -row 1 -column 0 -sticky e
+set dark [ttk::button $appearanceFrame.dark -style ImageButton -text Dark \
+ -image {starry pressed starry1 selected starry2} \
+ -command "beDark $appearanceFrame $w"]
+grid $dark -row 1 -column 2 -sticky w
+if { [::tk::unsupported::MacWindowStyle isdark $w] } {
+ $dark state selected
+} else {
+ $light state selected
+}
+proc beLight {f w} {
+ ::tk::unsupported::MacWindowStyle appearance $w aqua
+ $f.dark state !selected
+ $f.light state selected
+ after 10 $f.light state !hover
+}
+
+proc beDark {f w} {
+ ::tk::unsupported::MacWindowStyle appearance $w darkaqua
+ $f.light state !selected
+ $f.dark state selected
+ after 10 $f.dark state !hover
+}
+$w.notebook add $appearanceFrame -text "Appearance"
+
+## See Code / Dismiss
+pack [addSeeDismiss $w.buttons $w] -side bottom -fill x
+
+## Notebook
+pack $w.notebook -side bottom -fill both -expand 1 -padx 16 -pady 16
+
diff --git a/library/demos/widget b/library/demos/widget
index 354ac3f..055dd60 100644
--- a/library/demos/widget
+++ b/library/demos/widget
@@ -329,7 +329,14 @@ addFormattedText {
@@demo image2 A simple user interface for viewing images
@@demo labelframe Labelled frames
@@demo ttkbut The simple Themed Tk widgets
-
+}
+if {[tk windowingsystem] eq "aqua"} {
+ addFormattedText {
+ @@new
+ @@demo mac_styles Special widgets for macOS
+ }
+}
+addFormattedText {
@@subtitle Listboxes and Trees
@@demo states The 50 states
@@demo colors Colors: change the color scheme for the application
diff --git a/library/ttk/aquaTheme.tcl b/library/ttk/aquaTheme.tcl
index 7328cf1..00bb686 100644
--- a/library/ttk/aquaTheme.tcl
+++ b/library/ttk/aquaTheme.tcl
@@ -26,15 +26,55 @@ namespace eval ttk::theme::aqua {
!focus systemSelectedTextColor}
# Button
- ttk::style configure TButton -anchor center -width -6 \
+ ttk::style configure TButton -anchor center \
-foreground systemControlTextColor
ttk::style map TButton \
-foreground {
pressed white
- {alternate !pressed !background} white}
+ {alternate !pressed !background} white
+ disabled systemDisabledControlTextColor}
+
+ # Menubutton
ttk::style configure TMenubutton -anchor center -padding {2 0 0 2}
+
+ # Toolbutton
ttk::style configure Toolbutton -anchor center
+ # Inline Button
+ ttk::style configure InlineButton -anchor center -font TkHeadingFont \
+ -foreground systemTextBackgroundColor
+ ttk::style map InlineButton \
+ -foreground {
+ disabled systemWindowBackgroundColor
+ }
+
+ # Image Button
+ ttk::style configure ImageButton -anchor center -width 1 \
+ -compound top
+ ttk::style map ImageButton \
+ -foreground {
+ pressed systemLabelColor
+ !pressed systemSecondaryLabelColor
+ }
+
+ # Recessed (radio) button
+ font create RecessedFont -family EmphasizedSystem -size 11 -weight bold
+ ttk::style configure RecessedButton \
+ -foreground systemControlTextColor
+ ttk::style map RecessedButton \
+ -foreground {
+ {disabled selected} systemWindowBackgroundColor3
+ {disabled !selected} systemDisabledControlTextColor
+ selected systemTextBackgroundColor
+ active white
+ pressed white
+ } \
+ -font {
+ selected RecessedFont
+ active RecessedFont
+ pressed RecessedFont
+ }
+
# For Entry, Combobox and Spinbox widgets the selected text background
# is the "Highlight color" selected in preferences when the widget
# has focus. It is a gray color when the widget does not have focus or
@@ -86,9 +126,10 @@ namespace eval ttk::theme::aqua {
ttk::style configure TNotebook.Tab -foreground systemControlTextColor
ttk::style map TNotebook.Tab \
-foreground {
- background systemControlTextColor
- disabled systemDisabledControlTextColor
- selected systemSelectedTabTextColor}
+ {background !selected} systemControlTextColor
+ {background selected} black
+ {!background selected} systemSelectedTabTextColor
+ disabled systemDisabledControlTextColor}
# Treeview:
ttk::style configure Heading \
@@ -113,7 +154,11 @@ namespace eval ttk::theme::aqua {
# (ref: Apple Human Interface Guidelines / Controls / Grouping Controls)
#
ttk::style configure TLabelframe \
- -labeloutside true -labelmargins {14 0 14 4}
+ -labeloutside true \
+ -labelmargins {14 0 14 2}
+
+ ttk::style configure TLabelframe.Label \
+ -font TkSmallCaptionFont
# TODO: panedwindow sashes should be 9 pixels (HIG:Controls:Split Views)
}
diff --git a/library/ttk/combobox.tcl b/library/ttk/combobox.tcl
index 8df4172..653102e 100644
--- a/library/ttk/combobox.tcl
+++ b/library/ttk/combobox.tcl
@@ -16,20 +16,17 @@
# window managers (even though the older ICCCM spec says
# it's meaningless).
#
-# On OSX: [wm transient] does utterly the wrong thing.
-# Instead, we use [MacWindowStyle "help" "noActivates hideOnSuspend"].
-# The "noActivates" attribute prevents the parent toplevel
-# from deactivating when the popdown is posted, and is also
-# necessary for "help" windows to receive mouse events.
-# "hideOnSuspend" makes the popdown disappear (resp. reappear)
-# when the parent toplevel is deactivated (resp. reactivated).
-# (see [#1814778]). Also set [wm resizable 0 0], to prevent
-# TkAqua from shrinking the scrollbar to make room for a grow box
-# that isn't there.
-#
-# In order to work around other platform quirks in TkAqua,
-# [grab] and [focus] are set in <Map> bindings instead of
-# immediately after deiconifying the window.
+# On OSX: The native combobox uses a popup menu to display the
+# combobox choices. So this implementation does that as well,
+# rather than construc a Tk listbox. Since the window manager
+# takes care of scrolling and making sure that the menu can be
+# displayed even when the button is close to the bottom of the
+# screen, this actually simplifies the implementation. The Post
+# and PopupWindow procs have separate implementations for Aqua
+# and other systems. The configuration of the menu is handled
+# by a different proc than the one which configures the listbox
+# on other platforms -- ConfigureAquaMenu instead of
+# ConfigureListbox.
#
namespace eval ttk::combobox {
@@ -76,6 +73,9 @@ switch -- [tk windowingsystem] {
# NB: *only* do this on Windows (see #1814778)
bind ComboboxListbox <FocusOut> { ttk::combobox::LBCancel %W }
}
+ aqua {
+ bind TCombobox <Destroy> { ttk::combobox::AquaCleanup %W }
+ }
}
### Combobox popdown window bindings.
@@ -98,9 +98,6 @@ switch -- [tk windowingsystem] {
x11 {
option add *TCombobox*Listbox.background white widgetDefault
}
- aqua {
- option add *TCombobox*Listbox.borderWidth 0 widgetDefault
- }
}
### Binding procedures.
@@ -264,34 +261,52 @@ proc ttk::combobox::UnmapPopdown {w} {
# Returns the popdown widget associated with a combobox,
# creating it if necessary.
#
-proc ttk::combobox::PopdownWindow {cb} {
- if {![winfo exists $cb.popdown]} {
- set poplevel [PopdownToplevel $cb.popdown]
- set popdown [ttk::frame $poplevel.f -style ComboboxPopdownFrame]
-
- ttk::scrollbar $popdown.sb \
- -orient vertical -command [list $popdown.l yview]
- listbox $popdown.l \
- -listvariable ttk::combobox::Values($cb) \
- -yscrollcommand [list $popdown.sb set] \
- -exportselection false \
- -selectmode browse \
- -activestyle none \
- ;
-
- bindtags $popdown.l \
- [list $popdown.l ComboboxListbox Listbox $popdown all]
-
- grid $popdown.l -row 0 -column 0 -padx {1 0} -pady 1 -sticky nsew
- grid $popdown.sb -row 0 -column 1 -padx {0 1} -pady 1 -sticky ns
- grid columnconfigure $popdown 0 -weight 1
- grid rowconfigure $popdown 0 -weight 1
-
- grid $popdown -sticky news -padx 0 -pady 0
- grid rowconfigure $poplevel 0 -weight 1
- grid columnconfigure $poplevel 0 -weight 1
+
+if {[tk windowingsystem] ne "aqua"} {
+ proc ttk::combobox::PopdownWindow {cb} {
+ if {![winfo exists $cb.popdown]} {
+ set poplevel [PopdownToplevel $cb.popdown]
+ set popdown [ttk::frame $poplevel.f -style ComboboxPopdownFrame]
+
+ ttk::scrollbar $popdown.sb \
+ -orient vertical -command [list $popdown.l yview]
+ listbox $popdown.l \
+ -listvariable ttk::combobox::Values($cb) \
+ -yscrollcommand [list $popdown.sb set] \
+ -exportselection false \
+ -selectmode browse \
+ -activestyle none \
+ ;
+
+ bindtags $popdown.l \
+ [list $popdown.l ComboboxListbox Listbox $popdown all]
+
+ grid $popdown.l -row 0 -column 0 -padx {1 0} -pady 1 -sticky nsew
+ grid $popdown.sb -row 0 -column 1 -padx {0 1} -pady 1 -sticky ns
+ grid columnconfigure $popdown 0 -weight 1
+ grid rowconfigure $popdown 0 -weight 1
+
+ grid $popdown -sticky news -padx 0 -pady 0
+ grid rowconfigure $poplevel 0 -weight 1
+ grid columnconfigure $poplevel 0 -weight 1
+ }
+ return $cb.popdown
+ }
+} else {
+ proc ttk::combobox::PopdownWindow {cb} {
+ if {![winfo exists $cb.popdown]} {
+ set poplevel [PopdownToplevel $cb.popdown]
+ # The menu should be (at least) the same length as the button.
+ # Since there is no direct way to control the width of a menu
+ # in Tk, we fake it by using an invisible image in a disabled
+ # menu item, adjusting the image size to make the menu be the
+ # correct width.
+ image create nsimage $cb.spacer -source NSStatusNone -as name \
+ -alpha 0
+ set menu [menu $cb.popdown.menu -tearoff 0]
+ }
+ return $cb.popdown
}
- return $cb.popdown
}
## PopdownToplevel -- Create toplevel window for the combobox popdown
@@ -314,10 +329,8 @@ proc ttk::combobox::PopdownToplevel {w} {
wm attributes $w -topmost 1
}
aqua {
- $w configure -relief solid -borderwidth 0
- tk::unsupported::MacWindowStyle style $w \
- help {noActivates hideOnSuspend}
- wm resizable $w 0 0
+ wm overrideredirect $w true
+ wm attributes $w -alpha 0
}
}
return $w
@@ -353,6 +366,39 @@ proc ttk::combobox::ConfigureListbox {cb} {
$popdown.l configure -height $height
}
+proc ttk::combobox::ConfigureAquaMenu {cb width} {
+ set popdown [PopdownWindow $cb]
+ set values [$cb cget -values]
+ set current [$cb current]
+ if {$current < 0} {
+ set current 0 ;# no current entry, highlight first one
+ }
+ $cb.popdown.menu delete 0 end
+ $cb.spacer configure -width [expr {$width - 40}] -height 1
+ set i 0
+ foreach item $values {
+ if {$i == 0} {
+ # Add spaces to the first item to make the menu as long as cb
+ set menufont [$cb cget -font]
+ set stretch $item
+ while {[font measure $menufont $stretch] < [expr {$width - 32}]} {
+ set stretch "$stretch "
+ }
+ $cb.popdown.menu add command -label "$stretch" \
+ -command "ttk::combobox::SelectEntry $cb $i"
+ } else {
+ $cb.popdown.menu add command -label "$item" \
+ -command "ttk::combobox::SelectEntry $cb $i"
+ }
+ incr i
+ }
+ if { $i == 0 } {
+ # There are no items. To make an empty menu appear add a dummy item
+ # containing a transparent image of the right width.
+ $cb.popdown.menu add command -label {} -image $cb.spacer -state disabled
+ }
+}
+
## PlacePopdown --
# Set popdown window geometry.
#
@@ -381,34 +427,75 @@ proc ttk::combobox::PlacePopdown {cb popdown} {
wm geometry $popdown ${w}x${H}+${x}+${Y}
}
+proc ttk::combobox::AquaPlacePopdown {cb popdown} {
+ set x [winfo rootx $cb]
+ set y [winfo rooty $cb]
+ set w [winfo width $cb]
+ set h [winfo height $cb]
+ set style [$cb cget -style]
+ set postoffset [ttk::style lookup $style -postoffset {} {0 0 0 0}]
+ foreach var {x y w h} delta $postoffset {
+ incr $var $delta
+ }
+ wm geometry $popdown ${w}x${h}+${x}+${y}
+ return [list $x $y $w $h]
+}
+
## Post $cb --
-# Pop down the associated listbox.
-#
-proc ttk::combobox::Post {cb} {
- # Don't do anything if disabled:
- #
- $cb instate disabled { return }
+# Pop down the associated listbox or menu.
+#
+if {[tk windowingsystem] ne "aqua"} {
+ proc ttk::combobox::Post {cb} {
+ # Don't do anything if disabled:
+ #
+ $cb instate disabled { return }
+
+ # ASSERT: ![$cb instate pressed]
+
+ # Run -postcommand callback:
+ #
+ uplevel #0 [$cb cget -postcommand]
+
+ set popdown [PopdownWindow $cb]
+ ConfigureListbox $cb
+ update idletasks ;# needed for geometry propagation.
+ PlacePopdown $cb $popdown
+ # See <<NOTE-WM-TRANSIENT>>
+ switch -- [tk windowingsystem] {
+ x11 - win32 { wm transient $popdown [winfo toplevel $cb] }
+ }
- # ASSERT: ![$cb instate pressed]
+ # Post the listbox:
+ #
+ wm attribute $popdown -topmost 1
+ wm deiconify $popdown
+ raise $popdown
+ }
+} else {
+ proc ttk::combobox::Post {cb} {
+ # Don't do anything if disabled:
+ #
+ $cb instate disabled { return }
- # Run -postcommand callback:
- #
- uplevel #0 [$cb cget -postcommand]
+ # ASSERT: ![$cb instate pressed]
- set popdown [PopdownWindow $cb]
- ConfigureListbox $cb
- update idletasks ;# needed for geometry propagation.
- PlacePopdown $cb $popdown
- # See <<NOTE-WM-TRANSIENT>>
- switch -- [tk windowingsystem] {
- x11 - win32 { wm transient $popdown [winfo toplevel $cb] }
- }
+ # Run -postcommand callback:
+ #
+ uplevel #0 [$cb cget -postcommand]
+
+ set popdown [PopdownWindow $cb]
+
+ # Configure the menu
- # Post the listbox:
- #
- wm attribute $popdown -topmost 1
- wm deiconify $popdown
- raise $popdown
+ foreach {x y width height} [AquaPlacePopdown $cb $popdown] { break }
+ ConfigureAquaMenu $cb [winfo width $cb]
+
+ # Post the menu. It will have a disclosure indicator if it is too
+ # close to the bottom of the screen, and it may be posted above the
+ # button if necessary to be visible.
+
+ $popdown.menu post [expr {$x + 2}] [expr {$y + $height + 2}]
+ }
}
## Unpost $cb --
@@ -452,4 +539,8 @@ proc ttk::combobox::LBCleanup {lb} {
unset Values([LBMain $lb])
}
+proc ttk::combobox::AquaCleanup {cb} {
+ catch {image delete $cb.spacer}
+}
+
#*EOF*
diff --git a/library/ttk/menubutton.tcl b/library/ttk/menubutton.tcl
index cfc2690..cd34690 100644
--- a/library/ttk/menubutton.tcl
+++ b/library/ttk/menubutton.tcl
@@ -57,7 +57,7 @@ if {[tk windowingsystem] eq "x11"} {
bind TMenubutton <Button-1> \
{ %W state pressed ; ttk::menubutton::Popdown %W }
bind TMenubutton <ButtonRelease-1> \
- { if {[winfo exists %W]} { %W state !pressed } }
+ { if {[winfo exists %W]} { %W state {!pressed}} }
}
# PostPosition --
@@ -77,6 +77,7 @@ if {[tk windowingsystem] eq "aqua"} {
set menuPad 5
set buttonPad 1
set bevelPad 4
+ set flushPad 4
set mh [winfo reqheight $menu]
set bh [expr {[winfo height $mb]} + $buttonPad]
set bbh [expr {[winfo height $mb]} + $bevelPad]
@@ -105,8 +106,11 @@ if {[tk windowingsystem] eq "aqua"} {
incr y $menuPad
incr x $bw
}
- default { # flush
- incr y $bbh
+ flush {
+ incr y $flushPad
+ incr x -$flushPad
+ }
+ default {
}
}
return [list $x $y $entry]
diff --git a/library/ttk/progress.tcl b/library/ttk/progress.tcl
index 1729e11..929f0e6 100644
--- a/library/ttk/progress.tcl
+++ b/library/ttk/progress.tcl
@@ -32,6 +32,9 @@ proc ttk::progressbar::start {pb {steptime 50} {stepsize 1}} {
if {![info exists Timers($pb)]} {
Autoincrement $pb $steptime $stepsize
}
+ if {[tk windowingsystem] eq "aqua"} {
+ $pb state selected
+ }
}
# ttk::progressbar::stop --
@@ -44,6 +47,9 @@ proc ttk::progressbar::stop {pb} {
unset Timers($pb)
}
$pb configure -value 0
+ if {[tk windowingsystem] eq "aqua"} {
+ $pb state !selected
+ }
}
diff --git a/macosx/tkMacOSXConstants.h b/macosx/tkMacOSXConstants.h
index 2341cb8..29bc391 100644
--- a/macosx/tkMacOSXConstants.h
+++ b/macosx/tkMacOSXConstants.h
@@ -79,6 +79,9 @@ typedef NSInteger NSModalResponse;
#define NSCursorUpdate NSEventTypeCursorUpdate
#define NSTexturedBackgroundWindowMask NSWindowStyleMaskTexturedBackground
#define NSCompositeCopy NSCompositingOperationCopy
+#define NSCompositeSourceOver NSCompositingOperationSourceOver
+#define NSCompositeSourceAtop NSCompositingOperationSourceAtop
+#define NSCompositeDestinationIn NSCompositingOperationDestinationIn
#define NSWarningAlertStyle NSAlertStyleWarning
#define NSInformationalAlertStyle NSAlertStyleInformational
#define NSCriticalAlertStyle NSAlertStyleCritical
diff --git a/macosx/tkMacOSXDraw.c b/macosx/tkMacOSXDraw.c
index 4779c9a..69c0dc5 100644
--- a/macosx/tkMacOSXDraw.c
+++ b/macosx/tkMacOSXDraw.c
@@ -133,7 +133,7 @@ TkMacOSXGetNSImageFromTkImage(
{
Pixmap pixmap;
NSImage *nsImage;
- if (width == 0 || height == 0) {
+ if (width <= 0 || height <= 0) {
return nsImage = [[NSImage alloc] initWithSize:NSMakeSize(0,0)];
}
pixmap = Tk_GetPixmap(display, None, width, height, 0);
diff --git a/macosx/tkMacOSXImage.c b/macosx/tkMacOSXImage.c
index 0b63148..00135c5 100644
--- a/macosx/tkMacOSXImage.c
+++ b/macosx/tkMacOSXImage.c
@@ -1,7 +1,8 @@
/*
* tkMacOSXImage.c --
*
- * The code in this file provides an interface for XImages,
+ * The code in this file provides an interface for XImages, and
+ * implements the nsimage image type.
*
* Copyright (c) 1995-1997 Sun Microsystems, Inc.
* Copyright (c) 2001-2009, Apple Inc.
@@ -14,6 +15,7 @@
#include "tkMacOSXPrivate.h"
#include "tkMacOSXConstants.h"
+#include "tkColor.h"
#include "xbytes.h"
static CGImageRef CreateCGImageFromPixmap(Drawable pixmap);
@@ -114,7 +116,7 @@ _XInitImageFuncPtrs(
static void ReleaseData(
void *info,
TCL_UNUSED(const void *), /* data */
- TCL_UNUSED(size_t)) /* size */
+ TCL_UNUSED(size_t)) /* size */
{
ckfree(info);
}
@@ -388,7 +390,7 @@ ImagePutPixel(
XImage *
XCreateImage(
Display* display,
- TCL_UNUSED(Visual*), /* visual */
+ TCL_UNUSED(Visual*), /* visual */
unsigned int depth,
int format,
int offset,
@@ -732,7 +734,7 @@ XGetImage(
int y,
unsigned int width,
unsigned int height,
- TCL_UNUSED(unsigned long), /* plane_mask */
+ TCL_UNUSED(unsigned long), /* plane_mask */
int format)
{
NSBitmapImageRep* bitmapRep = nil;
@@ -1011,6 +1013,712 @@ XCopyPlane(
}
}
+/* ---------------------------------------------------------------------------*/
+
+/*
+ * Implementation of a Tk image type which provide access to NSImages
+ * for use in buttons etc.
+ */
+
+/*
+ * Forward declarations.
+ */
+
+typedef struct TkMacOSXNSImageInstance TkMacOSXNSImageInstance;
+typedef struct TkMacOSXNSImageModel TkMacOSXNSImageModel;
+
+/*
+ * The following data structure represents a particular use of an nsimage
+ * in a widget.
+ */
+
+struct TkMacOSXNSImageInstance {
+ TkMacOSXNSImageModel *modelPtr; /* Pointer to the model for the image. */
+ NSImage *image; /* Pointer to an NSImage.*/
+ TkMacOSXNSImageInstance *nextPtr; /* First in the list of instances associated
+ * with this model. */
+};
+
+/*
+ * The following data structure represents the model for an nsimage:
+ */
+
+struct TkMacOSXNSImageModel {
+ Tk_ImageModel tkModel; /* Tk's token for image model. */
+ Tcl_Interp *interp; /* Interpreter for application. */
+ int width, height; /* Dimensions of the image. */
+ int radius; /* Radius for rounded corners. */
+ int ring; /* Thickness of the focus ring. */
+ double alpha; /* Transparency, between 0.0 and 1.0*/
+ bool pressed; /* Image is for use in a pressed button.*/
+ bool template; /* Image is for use as a template.*/
+ char *imageName ; /* Malloc'ed image name. */
+ char *source; /* Malloc'ed string describing the image. */
+ char *as; /* Malloc'ed interpretation of source */
+ int flags; /* Sundry flags, defined below. */
+ TkMacOSXNSImageInstance *instancePtr; /* Start of list of instances associated
+ * with this model. */
+ NSImage *image; /* The underlying NSImage object. */
+ NSImage *darkModeImage; /* A modified image to use in Dark Mode. */
+};
+
+/*
+ * Bit definitions for the flags field of a TkMacOSXNSImageModel.
+ * IMAGE_CHANGED: 1 means that the instances of this image need
+ * to be redisplayed.
+ */
+
+#define IMAGE_CHANGED 1
+
+/*
+ * The type record for nsimage images:
+ */
+
+static int TkMacOSXNSImageCreate(Tcl_Interp *interp,
+ const char *name, int argc, Tcl_Obj *const objv[],
+ const Tk_ImageType *typePtr, Tk_ImageModel model,
+ ClientData *clientDataPtr);
+static ClientData TkMacOSXNSImageGet(Tk_Window tkwin, ClientData clientData);
+static void TkMacOSXNSImageDisplay(ClientData clientData,
+ Display *display, Drawable drawable,
+ int imageX, int imageY, int width,
+ int height, int drawableX,
+ int drawableY);
+static void TkMacOSXNSImageFree(ClientData clientData, Display *display);
+static void TkMacOSXNSImageDelete(ClientData clientData);
+
+static Tk_ImageType TkMacOSXNSImageType = {
+ "nsimage", /* name of image type */
+ TkMacOSXNSImageCreate, /* createProc */
+ TkMacOSXNSImageGet, /* getProc */
+ TkMacOSXNSImageDisplay, /* displayProc */
+ TkMacOSXNSImageFree, /* freeProc */
+ TkMacOSXNSImageDelete, /* deleteProc */
+ NULL, /* postscriptPtr */
+ NULL, /* nextPtr */
+ NULL
+};
+
+/*
+ * Default values used for parsing configuration specifications:
+ */
+#define DEF_SOURCE ""
+#define DEF_AS "name"
+#define DEF_HEIGHT "0"
+#define DEF_WIDTH "0"
+#define DEF_RADIUS "0"
+#define DEF_RING "0"
+#define DEF_ALPHA "1.0"
+#define DEF_PRESSED "0"
+#define DEF_TEMPLATE "0"
+
+static const Tk_OptionSpec systemImageOptions[] = {
+ {TK_OPTION_STRING, "-source", NULL, NULL, DEF_SOURCE,
+ -1, Tk_Offset(TkMacOSXNSImageModel, source), 0, NULL, 0},
+ {TK_OPTION_STRING, "-as", NULL, NULL, DEF_AS,
+ -1, Tk_Offset(TkMacOSXNSImageModel, as), 0, NULL, 0},
+ {TK_OPTION_INT, "-width", NULL, NULL, DEF_WIDTH,
+ -1, Tk_Offset(TkMacOSXNSImageModel, width), 0, NULL, 0},
+ {TK_OPTION_INT, "-height", NULL, NULL, DEF_HEIGHT,
+ -1, Tk_Offset(TkMacOSXNSImageModel, height), 0, NULL, 0},
+ {TK_OPTION_INT, "-radius", NULL, NULL, DEF_RADIUS,
+ -1, Tk_Offset(TkMacOSXNSImageModel, radius), 0, NULL, 0},
+ {TK_OPTION_INT, "-ring", NULL, NULL, DEF_RING,
+ -1, Tk_Offset(TkMacOSXNSImageModel, ring), 0, NULL, 0},
+ {TK_OPTION_DOUBLE, "-alpha", NULL, NULL, DEF_ALPHA,
+ -1, Tk_Offset(TkMacOSXNSImageModel, alpha), 0, NULL, 0},
+ {TK_OPTION_BOOLEAN, "-pressed", NULL, NULL, DEF_PRESSED,
+ -1, Tk_Offset(TkMacOSXNSImageModel, pressed), 0, NULL, 0},
+ {TK_OPTION_BOOLEAN, "-template", NULL, NULL, DEF_TEMPLATE,
+ -1, Tk_Offset(TkMacOSXNSImageModel, pressed), 0, NULL, 0},
+ {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, -1, 0, NULL, 0}
+};
+
+/*
+ * The -as option specifies how the string provided in the -source
+ * option should be interpreted as a description of an NSImage.
+ * Below are the possible values and their meanings. (The last two
+ * provide the macOS icon for a particular file type.)
+ */
+
+static const char *sourceInterpretations[] = {
+ "name", /* A name for a named NSImage. */
+ "file", /* A path to an image file. */
+ "path", /* A path to a file whose type should be examined. */
+ "filetype", /* A file extension or 4-byte OSCode. */
+};
+
+enum {NAME_SOURCE, FILE_SOURCE, PATH_SOURCE, FILETYPE_SOURCE};
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TintImage --
+ *
+ * Modify an NSImage by blending it with a color. The transparent part of
+ * the image remains transparent. The opaque part of the image is painted
+ * with the color, using the specified alpha value for the transparency of
+ * the color.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The appearance of the NSImage changes.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void TintImage(
+ NSImage *image,
+ NSColor *color,
+ double alpha)
+{
+ NSSize size = [image size];
+ NSRect rect = {NSZeroPoint, size};
+ NSImage *mask = [[[NSImage alloc] initWithSize:size] retain];
+ [mask lockFocus];
+ [color set];
+ NSRectFillUsingOperation(rect, NSCompositeCopy);
+ [image drawInRect:rect
+ fromRect:rect
+ operation:NSCompositeDestinationIn
+ fraction:1.0];
+ [mask unlockFocus];
+ [image lockFocus];
+ [mask drawInRect:rect
+ fromRect:rect
+ operation:NSCompositeSourceOver
+ fraction:alpha];
+ [image unlockFocus];
+ [mask release];
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXNSImageConfigureModel --
+ *
+ * This function is called when an nsimage image is created or
+ * reconfigured. It processes configuration options and resets any
+ * instances of the image.
+ *
+ * Results:
+ * A standard Tcl return value. If TCL_ERROR is returned then an error
+ * message is left in the modelPtr->interp's result.
+ *
+ * Side effects:
+ * Existing instances of the image will be redisplayed to match the new
+ * configuration options.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TkMacOSXNSImageConfigureModel(
+ Tcl_Interp *interp, /* Interpreter to use for reporting errors. */
+ TkMacOSXNSImageModel *modelPtr, /* Pointer to data structure describing
+ * overall photo image to (re)configure. */
+ int objc, /* Number of entries in objv. */
+ Tcl_Obj *const objv[]) /* Pairs of configuration options for image. */
+{
+ Tk_OptionTable optionTable = Tk_CreateOptionTable(interp, systemImageOptions);
+ NSImage *newImage;
+ Tcl_Obj *objPtr;
+ static Tcl_Obj *asOption = NULL;
+ int sourceInterpretation;
+ NSString *source;
+ int oldWidth = modelPtr->width, oldHeight = modelPtr->height;
+
+ if (asOption == NULL) {
+ asOption = Tcl_NewStringObj("-as", -1);
+ Tcl_IncrRefCount(asOption);
+ }
+
+ modelPtr->width = 0;
+ modelPtr->height = 0;
+ if (Tk_SetOptions(interp, (char *) modelPtr, optionTable, objc, objv,
+ NULL, NULL, NULL) != TCL_OK){
+ goto errorExit;
+ }
+ if (modelPtr->width == 0 && modelPtr->height == 0) {
+ modelPtr->width = oldWidth;
+ modelPtr->height = oldHeight;
+ }
+
+ if (modelPtr->source == NULL || modelPtr->source[0] == '0') {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("-source is required.", -1));
+ Tcl_SetErrorCode(interp, "TK", "IMAGE", "SYSTEM", "BAD_VALUE", NULL);
+ goto errorExit;
+ }
+
+ objPtr = Tk_GetOptionValue(interp, (char *) modelPtr, optionTable,
+ asOption, NULL);
+ if (Tcl_GetIndexFromObj(interp, objPtr, sourceInterpretations, "option",
+ 0, &sourceInterpretation) != TCL_OK) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "Unknown interpretation for source in -as option. "
+ "Should be name, file, path, or filetype.", -1));
+ Tcl_SetErrorCode(interp, "TK", "IMAGE", "SYSTEM", "BAD_VALUE", NULL);
+ goto errorExit;
+ }
+
+ source = [[NSString alloc] initWithUTF8String: modelPtr->source];
+ switch (sourceInterpretation) {
+ case NAME_SOURCE:
+ newImage = [[NSImage imageNamed:source] copy];
+ break;
+ case FILE_SOURCE:
+ newImage = [[NSImage alloc] initWithContentsOfFile:source];
+ break;
+ case PATH_SOURCE:
+ newImage = [[NSWorkspace sharedWorkspace] iconForFile:source];
+ break;
+ case FILETYPE_SOURCE:
+ newImage = TkMacOSXIconForFileType(source);
+ break;
+ default:
+ newImage = NULL;
+ break;
+ }
+ [source release];
+ if (newImage) {
+ NSSize size = NSMakeSize(modelPtr->width - 2*modelPtr->ring,
+ modelPtr->height - 2*modelPtr->ring);
+ [modelPtr->image release];
+ [modelPtr->darkModeImage release];
+ newImage.size = size;
+ modelPtr->image = [newImage retain];
+ if (modelPtr->template) {
+ newImage.template = YES;
+ }
+ modelPtr->darkModeImage = [[newImage copy] retain];
+ if ([modelPtr->darkModeImage isTemplate]) {
+
+ /*
+ * For a template image the Dark Mode version should be white.
+ */
+
+ NSRect rect = {NSZeroPoint, size};
+ [modelPtr->darkModeImage lockFocus];
+ [[NSColor whiteColor] set];
+ NSRectFillUsingOperation(rect, NSCompositeSourceAtop);
+ [modelPtr->darkModeImage unlockFocus];
+ } else if (modelPtr->pressed) {
+
+ /*
+ * Non-template pressed images are darker in Light Mode and lighter
+ * in Dark Mode.
+ */
+
+ TintImage(modelPtr->image, [NSColor blackColor], 0.2);
+ TintImage(modelPtr->darkModeImage, [NSColor whiteColor], 0.5);
+ }
+ } else {
+ switch(sourceInterpretation) {
+ case NAME_SOURCE:
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("Unknown named NSImage.\n"
+ "Try omitting ImageName, "
+ "e.g. use NSCaution for NSImageNameCaution.", -1));
+ Tcl_SetErrorCode(interp, "TK", "IMAGE", "SYSTEM", "BAD_VALUE", NULL);
+ goto errorExit;
+ case FILE_SOURCE:
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "Failed to load image file.\n", -1));
+ Tcl_SetErrorCode(interp, "TK", "IMAGE", "SYSTEM", "BAD_VALUE", NULL);
+ goto errorExit;
+ default:
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "Unrecognized file type.\n"
+ "If using a filename extension, do not include the dot.\n", -1));
+ Tcl_SetErrorCode(interp, "TK", "IMAGE", "SYSTEM", "BAD_VALUE", NULL);
+ goto errorExit;
+ }
+ }
+
+ /*
+ * Set the width and height. If only one is specified, set the other one
+ * so as to preserve the aspect ratio. If neither is specified, match the
+ * size of the image.
+ */
+
+ if (modelPtr->width == 0 && modelPtr->height == 0) {
+ CGSize size = [modelPtr->image size];
+ modelPtr->width = (int) size.width;
+ modelPtr->height = (int) size.height;
+ } else {
+ CGSize size = [modelPtr->image size], newsize;
+ CGFloat aspect = size.width && size.height ?
+ size.height / size.width : 1;
+ if (modelPtr->width == 0) {
+ modelPtr->width = (int) ((CGFloat)(modelPtr->height) / aspect);
+ } else if (modelPtr->height == 0) {
+ modelPtr->height = (int) ((CGFloat)(modelPtr->width) * aspect);
+ }
+ newsize = NSMakeSize(modelPtr->width, modelPtr->height);
+ modelPtr->image.size = newsize;
+ modelPtr->darkModeImage.size = newsize;
+ }
+
+ /*
+ * Inform the generic image code that the image has (potentially) changed.
+ */
+
+ Tk_ImageChanged(modelPtr->tkModel, 0, 0, modelPtr->width,
+ modelPtr->height, modelPtr->width, modelPtr->height);
+ modelPtr->flags &= ~IMAGE_CHANGED;
+
+ return TCL_OK;
+
+ errorExit:
+ return TCL_ERROR;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXNSImageObjCmd --
+ *
+ * This function implements the configure and cget commands for an
+ * nsimage instance.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * The image may be reconfigured.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkMacOSXNSImageObjCmd(
+ ClientData clientData, /* Information about the image model. */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. */
+{
+ TkMacOSXNSImageModel *modelPtr = (TkMacOSXNSImageModel *)clientData;
+ Tk_OptionTable optionTable = Tk_CreateOptionTable(interp, systemImageOptions);
+ static const char *const options[] = {"cget", "configure", NULL};
+ enum {CGET, CONFIGURE};
+ Tcl_Obj *objPtr;
+ int index;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObjStruct(interp, objv[1], options,
+ sizeof(char *), "option", 0, &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ Tcl_Preserve(modelPtr);
+ switch (index) {
+ case CGET:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "option");
+ return TCL_ERROR;
+ }
+ objPtr = Tk_GetOptionValue(interp, (char *)modelPtr, optionTable,
+ objv[2], NULL);
+ if (objPtr == NULL) {
+ goto error;
+ }
+ Tcl_SetObjResult(interp, objPtr);
+ break;
+ case CONFIGURE:
+ if (objc == 2) {
+ objPtr = Tk_GetOptionInfo(interp, (char *)modelPtr, optionTable,
+ NULL, NULL);
+ if (objPtr == NULL) {
+ goto error;
+ }
+ Tcl_SetObjResult(interp, objPtr);
+ break;
+ } else if (objc == 3) {
+ objPtr = Tk_GetOptionInfo(interp, (char *)modelPtr, optionTable,
+ objv[2], NULL);
+ if (objPtr == NULL) {
+ goto error;
+ }
+ Tcl_SetObjResult(interp, objPtr);
+ break;
+ } else {
+ TkMacOSXNSImageConfigureModel(interp, modelPtr, objc - 2, objv + 2);
+ break;
+ }
+ default:
+ break;
+ }
+
+ Tcl_Release(modelPtr);
+ return TCL_OK;
+
+ error:
+ Tcl_Release(modelPtr);
+ return TCL_ERROR;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXNSImageCreate --
+ *
+ * Allocate and initialize an nsimage model.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * The data structure for a new image is allocated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TkMacOSXNSImageCreate(
+ Tcl_Interp *interp, /* Interpreter for application using image. */
+ const char *name, /* Name to use for image. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[], /* Argument strings for options (not
+ * including image name or type). */
+ TCL_UNUSED(const Tk_ImageType *), /* typePtr */
+ Tk_ImageModel model, /* Token for image, to be used in callbacks. */
+ ClientData *clientDataPtr) /* Store manager's token for image here; it
+ * will be returned in later callbacks. */
+{
+ TkMacOSXNSImageModel *modelPtr;
+ Tk_OptionTable optionTable = Tk_CreateOptionTable(interp, systemImageOptions);
+
+ modelPtr = (TkMacOSXNSImageModel *)ckalloc(sizeof(TkMacOSXNSImageModel));
+ modelPtr->tkModel = model;
+ modelPtr->interp = interp;
+ modelPtr->imageName = (char *)ckalloc(strlen(name) + 1);
+ strcpy(modelPtr->imageName, name);
+ modelPtr->flags = 0;
+ modelPtr->instancePtr = NULL;
+ modelPtr->image = NULL;
+ modelPtr->darkModeImage = NULL;
+ modelPtr->source = NULL;
+ modelPtr->as = NULL;
+
+ /*
+ * Process configuration options given in the image create command.
+ */
+
+ if (Tk_InitOptions(interp, (char *) modelPtr, optionTable, NULL) != TCL_OK
+ || TkMacOSXNSImageConfigureModel(interp, modelPtr, objc, objv) != TCL_OK) {
+ TkMacOSXNSImageDelete(modelPtr);
+ return TCL_ERROR;
+ }
+ Tcl_CreateObjCommand(interp, name, TkMacOSXNSImageObjCmd, modelPtr, NULL);
+ *clientDataPtr = modelPtr;
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXNSImageGet --
+ *
+ * Allocate and initialize an nsimage instance.
+ *
+ * Results:
+ * The return value is a token for the image instance, which is used in
+ * future callbacks to ImageDisplay and ImageFree.
+ *
+ * Side effects:
+ * A new new nsimage instance is created.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static ClientData
+TkMacOSXNSImageGet(
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ ClientData clientData) /* Pointer to TkMacOSXNSImageModel for image. */
+{
+ TkMacOSXNSImageModel *modelPtr = (TkMacOSXNSImageModel *) clientData;
+ TkMacOSXNSImageInstance *instPtr;
+
+ instPtr = (TkMacOSXNSImageInstance *)ckalloc(sizeof(TkMacOSXNSImageInstance));
+ instPtr->modelPtr = modelPtr;
+ return instPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXNSImageDisplay --
+ *
+ * Display or redisplay an nsimage in the given drawable.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The image gets drawn.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TkMacOSXNSImageDisplay(
+ ClientData clientData, /* Pointer to TkMacOSXNSImageInstance for image. */
+ TCL_UNUSED(Display *), /* display */
+ Drawable drawable, /* Where to draw or redraw image. */
+ int imageX, int imageY, /* Origin of area to redraw, relative to
+ * origin of image. */
+ int width, int height, /* Dimensions of area to redraw. */
+ int drawableX, int drawableY)
+ /* Coordinates in drawable corresponding to
+ * imageX and imageY. */
+{
+ MacDrawable *macWin = (MacDrawable *) drawable;
+ Tk_Window tkwin = (Tk_Window) macWin->winPtr;
+ TkMacOSXNSImageInstance *instPtr = (TkMacOSXNSImageInstance *) clientData;
+ TkMacOSXNSImageModel *modelPtr = instPtr->modelPtr;
+ TkMacOSXDrawingContext dc;
+ NSRect dstRect = NSMakeRect(macWin->xOff + drawableX,
+ macWin->yOff + drawableY, width, height);
+ NSRect srcRect = NSMakeRect(imageX, imageY, width, height);
+ NSImage *image = TkMacOSXInDarkMode(tkwin) ? modelPtr->darkModeImage :
+ modelPtr->image;
+ int ring = modelPtr->ring;
+ int radius = modelPtr->radius;
+
+ if (TkMacOSXSetupDrawingContext(drawable, NULL, &dc)) {
+ if (dc.context) {
+ CGRect clipRect = CGRectMake(
+ dstRect.origin.x - srcRect.origin.x + ring,
+ dstRect.origin.y - srcRect.origin.y + ring,
+ modelPtr->width - 2*ring,
+ modelPtr->height - 2*ring);
+ CGPathRef path = CGPathCreateWithRoundedRect(clipRect, radius, radius, NULL);
+ CGContextSaveGState(dc.context);
+ CGContextBeginPath(dc.context);
+ CGContextAddPath(dc.context, path);
+ CGContextClip(dc.context);
+ NSGraphicsContext *savedContext = NSGraphicsContext.currentContext;
+ NSGraphicsContext.currentContext = [NSGraphicsContext
+ graphicsContextWithCGContext:dc.context flipped:YES];
+ [image drawInRect:clipRect
+ fromRect:srcRect
+ operation:NSCompositeSourceOver
+ fraction:modelPtr->alpha
+ respectFlipped:YES
+ hints:nil];
+ CGContextRestoreGState(dc.context);
+
+ /*
+ * Draw the focus ring.
+ */
+
+ if (ring) {
+ CGRect ringRect = CGRectInset(clipRect, -ring, -ring);
+ CGPathRef ringPath = CGPathCreateWithRoundedRect(ringRect,
+ radius + ring, radius + ring, NULL);
+ CGContextSaveGState(dc.context);
+ CGContextAddPath(dc.context, path);
+ CGContextAddPath(dc.context, ringPath);
+ CGContextSetFillColorWithColor(dc.context,
+ controlAccentColor().CGColor);
+ CGContextEOFillPath(dc.context);
+ CGContextRestoreGState(dc.context);
+ CFRelease(ringPath);
+ }
+ CFRelease(path);
+ NSGraphicsContext.currentContext = savedContext;
+ }
+ TkMacOSXRestoreDrawingContext(&dc);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXNSImageFree --
+ *
+ * Deallocate an instance of an nsimage.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Information related to the instance is freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TkMacOSXNSImageFree(
+ ClientData clientData, /* Pointer to TkMacOSXNSImageInstance for instance. */
+ TCL_UNUSED(Display *)) /* display */
+{
+ TkMacOSXNSImageInstance *instPtr = (TkMacOSXNSImageInstance *) clientData;
+ ckfree(instPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXNSImageDelete --
+ *
+ * Deallocate an nsimage model.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * NSImages are released and memory is freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TkMacOSXNSImageDelete(
+ ClientData clientData) /* Pointer to TkMacOSXNSImageModel for image. When
+ * this function is called, no more instances
+ * exist. */
+{
+ TkMacOSXNSImageModel *modelPtr = (TkMacOSXNSImageModel *) clientData;
+
+ Tcl_DeleteCommand(modelPtr->interp, modelPtr->imageName);
+ ckfree(modelPtr->imageName);
+ ckfree(modelPtr->source);
+ ckfree(modelPtr->as);
+ [modelPtr->image release];
+ [modelPtr->darkModeImage release];
+ ckfree(modelPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkMacOSXNSImage_Init --
+ *
+ * Adds the TkMacOSXNSImage type to Tk.
+ *
+ * Results:
+ * Returns a standard Tcl completion code, and leaves an error message in
+ * the interp's result if an error occurs.
+ *
+ * Side effects:
+ * Creates the image create nsrect ... command.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkMacOSXNSImage_Init(
+ TCL_UNUSED(Tcl_Interp *)) /* interp */
+{
+ Tk_CreateImageType(&TkMacOSXNSImageType);
+ return 1;
+}
+
/*
* Local Variables:
* mode: objc
diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c
index dd7b18d..8eb1fb6 100644
--- a/macosx/tkMacOSXInit.c
+++ b/macosx/tkMacOSXInit.c
@@ -33,7 +33,9 @@ static char scriptPath[PATH_MAX + 1] = "";
* Forward declarations...
*/
-static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
+static int TkMacOSXGetAppPathObjCmd(TCL_UNUSED(void *), Tcl_Interp *ip,
+ int objc, Tcl_Obj *const objv[]);
+static int TkMacOSVersionObjCmd(ClientData cd, Tcl_Interp *ip,
int objc, Tcl_Obj *const objv[]);
#pragma mark TKApplication(TKInit)
@@ -42,7 +44,6 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
@synthesize poolLock = _poolLock;
@synthesize macOSVersion = _macOSVersion;
@synthesize isDrawing = _isDrawing;
-@synthesize needsToDraw = _needsToDraw;
@synthesize isSigned = _isSigned;
@synthesize tkLiveResizeEnded = _tkLiveResizeEnded;
@synthesize tkPointerWindow = _tkPointerWindow;
@@ -179,6 +180,7 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
/*
* Initialize the graphics context.
*/
+
TkMacOSXUseAntialiasedText(_eventInterp, -1);
TkMacOSXInitCGDrawing(_eventInterp, TRUE, 0);
@@ -190,6 +192,12 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
[self _setupMenus];
/*
+ * Run initialization routines that depend on the OS version.
+ */
+
+ Ttk_MacOSXInit();
+
+ /*
* It is not safe to force activation of the NSApp until this method is
* called. Activating too early can cause the menu bar to be unresponsive.
* The call to activateIgnoringOtherApps was moved here to avoid this.
@@ -597,12 +605,13 @@ TkpInit(
}
/*
- * Initialize the NSServices object here. Apple's docs say to do this
- * in applicationDidFinishLaunching, but the Tcl interpreter is not
- * initialized until this function call.
+ * Now we can run initialization routines which require that both the
+ * NSApplication and the Tcl interpreter have been created and
+ * initialized.
*/
TkMacOSXServices_Init(interp);
+ TkMacOSXNSImage_Init(interp);
/*
* The root window has been created and mapped, but XMapWindow deferred its
@@ -638,7 +647,6 @@ TkpInit(
signal(SIGHUP, TkMacOSXSignalHandler);
signal(SIGTERM, TkMacOSXSignalHandler);
}
-
/*
* Initialization steps that are needed for all interpreters.
*/
@@ -646,18 +654,20 @@ TkpInit(
if (tkLibPath[0] != '\0') {
Tcl_SetVar2(interp, "tk_library", NULL, tkLibPath, TCL_GLOBAL_ONLY);
}
-
if (scriptPath[0] != '\0') {
Tcl_SetVar2(interp, "auto_path", NULL, scriptPath,
TCL_GLOBAL_ONLY|TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
}
-
+ Tcl_CreateObjCommand(interp, "nsimage",
+ TkMacOSXNSImageObjCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "::tk::mac::standardAboutPanel",
TkMacOSXStandardAboutPanelObjCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "::tk::mac::iconBitmap",
TkMacOSXIconBitmapObjCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "::tk::mac::GetAppPath",
- TkMacOSXGetAppPathCmd, NULL, NULL);
+ TkMacOSXGetAppPathObjCmd, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tk::mac::macOSVersion",
+ TkMacOSVersionObjCmd, NULL, NULL);
MacSystrayInit(interp);
MacPrint_Init(interp);
@@ -667,6 +677,55 @@ TkpInit(
/*
*----------------------------------------------------------------------
*
+ * TkMacOSXGetAppPathObjCmd --
+ *
+ * Returns the path of the Wish application bundle.
+ *
+ * Results:
+ * Returns the application path.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TkMacOSXGetAppPathObjCmd(
+ TCL_UNUSED(void *),
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ if (objc != 1) {
+ Tcl_WrongNumArgs(interp, 1, objv, NULL);
+ return TCL_ERROR;
+ }
+
+ /*
+ * Get the application path URL and convert it to a string path reference.
+ */
+
+ CFURLRef mainBundleURL = CFBundleCopyBundleURL(CFBundleGetMainBundle());
+ CFStringRef appPath =
+ CFURLCopyFileSystemPath(mainBundleURL, kCFURLPOSIXPathStyle);
+
+ /*
+ * Convert (and copy) the string reference into a Tcl result.
+ */
+
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ CFStringGetCStringPtr(appPath, CFStringGetSystemEncoding()), -1));
+
+ CFRelease(mainBundleURL);
+ CFRelease(appPath);
+
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TkpGetAppName --
*
* Retrieves the name of the current application from a platform specific
@@ -704,48 +763,37 @@ TkpGetAppName(
/*
*----------------------------------------------------------------------
*
- * TkMacOSXGetAppPathCmd --
+ * TkMacOSVersionObjCmd --
*
- * Returns the path of the Wish application bundle.
+ * Tcl command which returns an integer encoding the major and minor
+ * version numbers of the currently running operating system in the
+ * form 10000*majorVersion + 100*minorVersion.
*
* Results:
- * Returns the application path.
+ * Returns the OS version.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
-
+
static int
-TkMacOSXGetAppPathCmd(
- TCL_UNUSED(void *),
+TkMacOSVersionObjCmd(
+ TCL_UNUSED(void *), /* ClientData */
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
- if (objc != 1) {
+ static char version[16] = "";
+ if (objc > 1) {
Tcl_WrongNumArgs(interp, 1, objv, NULL);
return TCL_ERROR;
}
-
- /*
- * Get the application path URL and convert it to a string path reference.
- */
-
- CFURLRef mainBundleURL = CFBundleCopyBundleURL(CFBundleGetMainBundle());
- CFStringRef appPath =
- CFURLCopyFileSystemPath(mainBundleURL, kCFURLPOSIXPathStyle);
-
- /*
- * Convert (and copy) the string reference into a Tcl result.
- */
-
- Tcl_SetObjResult(interp, Tcl_NewStringObj(
- CFStringGetCStringPtr(appPath, CFStringGetSystemEncoding()), -1));
-
- CFRelease(mainBundleURL);
- CFRelease(appPath);
+ if (version[0] == '\0') {
+ snprintf(version, 16, "%d", [NSApp macOSVersion]);
+ }
+ Tcl_SetResult(interp, version, NULL);
return TCL_OK;
}
@@ -852,10 +900,11 @@ TkMacOSXDefaultStartupScript(void)
MODULE_SCOPE void*
TkMacOSXGetNamedSymbol(
- TCL_UNUSED(const char *),
- const char *symbol)
+ const char* module,
+ const char* symbol)
{
void *addr = dlsym(RTLD_NEXT, symbol);
+ (void)module;
if (!addr) {
(void) dlerror(); /* Clear dlfcn error state */
diff --git a/macosx/tkMacOSXMenu.c b/macosx/tkMacOSXMenu.c
index 1e81fbf..f75db3e 100644
--- a/macosx/tkMacOSXMenu.c
+++ b/macosx/tkMacOSXMenu.c
@@ -164,7 +164,6 @@ static int ModifierCharWidth(Tk_Font tkfont);
TKBackgroundLoop *backgroundLoop = nil;
-
#pragma mark TKMenu
/*
diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h
index fec267a..71cceb9 100644
--- a/macosx/tkMacOSXPrivate.h
+++ b/macosx/tkMacOSXPrivate.h
@@ -279,15 +279,19 @@ MODULE_SCOPE void TkMacOSXApplyWindowAttributes(TkWindow *winPtr,
NSWindow *macWindow);
MODULE_SCOPE Tcl_ObjCmdProc TkMacOSXStandardAboutPanelObjCmd;
MODULE_SCOPE Tcl_ObjCmdProc TkMacOSXIconBitmapObjCmd;
+MODULE_SCOPE Tcl_ObjCmdProc TkMacOSXNSImageObjCmd;
MODULE_SCOPE void TkMacOSXDrawSolidBorder(Tk_Window tkwin, GC gc,
int inset, int thickness);
MODULE_SCOPE int TkMacOSXServices_Init(Tcl_Interp *interp);
MODULE_SCOPE Tcl_ObjCmdProc TkMacOSXRegisterServiceWidgetObjCmd;
MODULE_SCOPE unsigned TkMacOSXAddVirtual(unsigned int keycode);
+MODULE_SCOPE int TkMacOSXNSImage_Init(Tcl_Interp *interp);
MODULE_SCOPE void TkMacOSXWinNSBounds(TkWindow *winPtr, NSView *view,
NSRect *bounds);
MODULE_SCOPE Bool TkMacOSXInDarkMode(Tk_Window tkwin);
MODULE_SCOPE void TkMacOSXDrawAllViews(void *clientData);
+MODULE_SCOPE NSColor* controlAccentColor(void);
+MODULE_SCOPE void Ttk_MacOSXInit(void);
MODULE_SCOPE unsigned long TkMacOSXClearPixel(void);
MODULE_SCOPE int MacSystrayInit(Tcl_Interp *);
MODULE_SCOPE int MacPrint_Init(Tcl_Interp *);
diff --git a/macosx/tkMacOSXRegion.c b/macosx/tkMacOSXRegion.c
index b44073a..0537c7b 100644
--- a/macosx/tkMacOSXRegion.c
+++ b/macosx/tkMacOSXRegion.c
@@ -586,9 +586,9 @@ TkMacOSXCountRectsInRegion(
{
int rect_count = 0;
if (!HIShapeIsEmpty(shape)) {
- ChkErr(HIShapeEnumerate, shape,
- kHIShapeParseFromBottom|kHIShapeParseFromLeft,
- rectCounter, &rect_count);
+ HIShapeEnumerate(shape,
+ kHIShapeParseFromBottom|kHIShapeParseFromLeft,
+ (HIShapeEnumerateProcPtr) rectCounter, (void *) &rect_count);
}
return rect_count;
}
@@ -598,9 +598,9 @@ TkMacOSXPrintRectsInRegion(
HIShapeRef shape)
{
if (!HIShapeIsEmpty(shape)) {
- ChkErr(HIShapeEnumerate, shape,
- kHIShapeParseFromBottom|kHIShapeParseFromLeft,
- rectPrinter, NULL);
+ HIShapeEnumerate( shape,
+ kHIShapeParseFromBottom|kHIShapeParseFromLeft,
+ (HIShapeEnumerateProcPtr) rectPrinter, NULL);
}
}
diff --git a/macosx/tkMacOSXSubwindows.c b/macosx/tkMacOSXSubwindows.c
index f631b5d..5a16e23 100644
--- a/macosx/tkMacOSXSubwindows.c
+++ b/macosx/tkMacOSXSubwindows.c
@@ -358,7 +358,7 @@ XUnmapWindow(
if (parentPtr && parentPtr->privatePtr->visRgn) {
TkMacOSXInvalidateViewRegion(
- TkMacOSXGetNSViewForDrawable(parentPtr->privatePtr),
+ TkMacOSXGetNSViewForDrawable(parentPtr->window),
parentPtr->privatePtr->visRgn);
}
TkMacOSXInvalClipRgns((Tk_Window)parentPtr);
diff --git a/macosx/ttkMacOSXTheme.c b/macosx/ttkMacOSXTheme.c
index ceeeea4..eb82b88 100644
--- a/macosx/ttkMacOSXTheme.c
+++ b/macosx/ttkMacOSXTheme.c
@@ -3,12 +3,12 @@
*
* Tk theme engine for Mac OSX, using the Appearance Manager API.
*
- * Copyright © 2004 Joe English
- * Copyright © 2005 Neil Madden
- * Copyright © 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
- * Copyright © 2008-2009 Apple Inc.
- * Copyright © 2009 Kevin Walzer/WordTech Communications LLC.
- * Copyright © 2019 Marc Culler
+ * Copyright (c) 2004 Joe English
+ * Copyright (c) 2005 Neil Madden
+ * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
+ * Copyright (c) 2008-2009 Apple Inc.
+ * Copyright (c) 2009 Kevin Walzer/WordTech Communications LLC.
+ * Copyright (c) 2019 Marc Culler
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -31,133 +31,157 @@
#include "tkMacOSXPrivate.h"
#include "ttk/ttkTheme.h"
+#include "ttkMacOSXTheme.h"
+#include "tkColor.h"
+#include <math.h>
+
+MODULE_SCOPE NSColor *controlAccentColor(void) {
+ static int accentPixel = -1;
+ if (accentPixel == -1) {
+ TkColor *temp = TkpGetColor(NULL, "systemControlAccentColor");
+ accentPixel = temp->color.pixel;
+ ckfree(temp);
+ }
+ return TkMacOSXGetNSColor(NULL, accentPixel);
+}
/*
- * Macros for handling drawing contexts.
+ * Values which depend on the OS version. These are initialized
+ * in Ttk_MacOSXInit.
*/
-#define BEGIN_DRAWING(d) { \
- TkMacOSXDrawingContext dc; \
- if (!TkMacOSXSetupDrawingContext((d), NULL, &dc)) { \
- return; \
- } \
+static Ttk_Padding entryElementPadding;
+static CGFloat Ttk_ContrastDelta;
-#define END_DRAWING \
- TkMacOSXRestoreDrawingContext(&dc);}
+/*----------------------------------------------------------------------
+ * +++ ComputeButtonDrawInfo --
+ *
+ * Fill in an appearance manager HIThemeButtonDrawInfo record
+ * from a Ttk state and the ThemeButtonParams used as the
+ * clientData.
+ */
-#define HIOrientation kHIThemeOrientationNormal
-#define NoThemeMetric 0xFFFFFFFF
+static inline HIThemeButtonDrawInfo ComputeButtonDrawInfo(
+ ThemeButtonParams *params,
+ Ttk_State state,
+ TCL_UNUSED(Tk_Window))
+{
+ /*
+ * See ButtonElementDraw for the explanation of why we always draw
+ * some buttons in the active state.
+ */
-#ifdef __LP64__
-#define RangeToFactor(maximum) (((double) (INT_MAX >> 1)) / (maximum))
-#else
-#define RangeToFactor(maximum) (((double) (LONG_MAX >> 1)) / (maximum))
-#endif /* __LP64__ */
+ SInt32 HIThemeState;
+ int adornment = 0;
-#define TTK_STATE_FIRST_TAB TTK_STATE_USER1
-#define TTK_STATE_LAST_TAB TTK_STATE_USER2
-#define TTK_TREEVIEW_STATE_SORTARROW TTK_STATE_USER1
+ HIThemeState = Ttk_StateTableLookup(ThemeStateTable, state);
-/*
- * Colors and gradients used in Dark Mode.
- */
+ /*
+ * HITheme uses the adornment to decide the direction of the
+ * arrow on a Disclosure Button. Also HITheme draws inactive
+ * (TTK_STATE_BACKGROUND) buttons in a gray color but macOS
+ * no longer does that. So we adjust the HIThemeState.
+ */
-static CGFloat darkButtonFace[4] = {
- 90.0 / 255, 86.0 / 255, 95.0 / 255, 1.0
-};
-static CGFloat darkPressedButtonFace[4] = {
- 114.0 / 255, 110.0 / 255, 118.0 / 255, 1.0
-};
-static CGFloat darkPressedBevelFace[4] = {
- 135.0 / 255, 136.0 / 255, 138.0 / 255, 1.0
-};
-static CGFloat darkSelectedBevelFace[4] = {
- 162.0 / 255, 163.0 / 255, 165.0 / 255, 1.0
-};
-static CGFloat darkDisabledButtonFace[4] = {
- 86.0 / 255, 87.0 / 255, 89.0 / 255, 1.0
-};
-static CGFloat darkInactiveSelectedTab[4] = {
- 159.0 / 255, 160.0 / 255, 161.0 / 255, 1.0
-};
-static CGFloat darkSelectedTab[4] = {
- 97.0 / 255, 94.0 / 255, 102.0 / 255, 1.0
-};
-static CGFloat darkTab[4] = {
- 44.0 / 255, 41.0 / 255, 50.0 / 255, 1.0
-};
-static CGFloat darkFocusRing[4] = {
- 38.0 / 255, 113.0 / 255, 159.0 / 255, 1.0
-};
-static CGFloat darkFocusRingTop[4] = {
- 50.0 / 255, 124.0 / 255, 171.0 / 255, 1.0
-};
-static CGFloat darkFocusRingBottom[4] = {
- 57.0 / 255, 130.0 / 255, 176.0 / 255, 1.0
-};
-static CGFloat darkTabSeparator[4] = {0.0, 0.0, 0.0, 0.25};
-static CGFloat darkTrack[4] = {1.0, 1.0, 1.0, 0.25};
-static CGFloat darkFrameTop[4] = {1.0, 1.0, 1.0, 0.0625};
-static CGFloat darkFrameBottom[4] = {1.0, 1.0, 1.0, 0.125};
-static CGFloat darkFrameAccent[4] = {0.0, 0.0, 0.0, 0.0625};
-static CGFloat darkTopGradient[8] = {
- 1.0, 1.0, 1.0, 0.3,
- 1.0, 1.0, 1.0, 0.0
-};
-static CGFloat darkBackgroundGradient[8] = {
- 0.0, 0.0, 0.0, 0.1,
- 0.0, 0.0, 0.0, 0.25
-};
-static CGFloat darkInactiveGradient[8] = {
- 89.0 / 255, 90.0 / 255, 93.0 / 255, 1.0,
- 119.0 / 255, 120.0 / 255, 122.0 / 255, 1.0
-};
-static CGFloat darkSelectedGradient[8] = {
- 23.0 / 255, 111.0 / 255, 232.0 / 255, 1.0,
- 20.0 / 255, 94.0 / 255, 206.0 / 255, 1.0
-};
-static CGFloat pressedPushButtonGradient[8] = {
- 35.0 / 255, 123.0 / 255, 244.0 / 255, 1.0,
- 30.0 / 255, 114.0 / 255, 235.0 / 255, 1.0
-};
+ switch (params->kind) {
+ case kThemeArrowButton:
+ adornment = kThemeAdornmentDrawIndicatorOnly;
+ if (state & TTK_STATE_SELECTED) {
+ adornment |= kThemeAdornmentArrowUpArrow;
+ }
+ /* Fall through. */
+ case kThemeRadioButton:
+ /*
+ * The gray color is better than the blue color for a
+ * background selected Radio Button.
+ */
+
+ if (state & TTK_STATE_SELECTED) {
+ break;
+ }
+ default:
+ if (state & TTK_STATE_BACKGROUND) {
+ HIThemeState |= kThemeStateActive;
+ }
+ break;
+ }
+
+ const HIThemeButtonDrawInfo info = {
+ .version = 0,
+ .state = HIThemeState,
+ .kind = params ? params->kind : 0,
+ .value = Ttk_StateTableLookup(ButtonValueTable, state),
+ .adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state) | adornment,
+ };
+ return info;
+}
/*
- * When building on systems earlier than 10.8 there is no reasonable way to
- * convert an NSColor to a CGColor. We do run-time checking of the OS version,
- * and never need the CGColor property on older systems, so we can use this
- * CGCOLOR macro, which evaluates to NULL without raising compiler warnings.
- * Similarly, we never draw rounded rectangles on older systems which did not
- * have CGPathCreateWithRoundedRect, so we just redefine it to return NULL.
+ * When we draw simulated Apple widgets we use the Core Graphics framework.
+ * Core Graphics uses CGColorRefs, not NSColors. A CGColorRef must be retained
+ * and released explicitly while an NSColor is autoreleased. In version 10.8
+ * of macOS Apple introduced a CGColor property of an NSColor which is guaranteed
+ * to be a valid CGColorRef for (approximately) the same color and is released
+ * when the NSColor is autoreleased.
+ *
+ * When building on systems earlier than 10.8 there is no painless way to
+ * convert an NSColor to a CGColor. On the other hand, on those systems we use
+ * the HIToolbox to draw all widgets, so we never need to call Core Graphics
+ * drawing routines directly. This means that the functions and macros below
+ * which construct CGColorRefs can be defined to return nil on systems before
+ * 10.8.
+ *
+ * Similarly, those older systems did not have CGPathCreateWithRoundedRect, but
+ * since we never need to draw rounded rectangles on those systems we can just
+ * define it to return nil.
*/
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
+static CGColorRef
+CGColorFromRGBA(
+ CGFloat *rgba)
+{
+ NSColorSpace *colorSpace = [NSColorSpace sRGBColorSpace];
+ NSColor *nscolor = [NSColor colorWithColorSpace: colorSpace
+ components: rgba
+ count: 4];
+ return nscolor.CGColor;
+}
+
+static CGColorRef
+CGColorFromGray(
+ GrayColor g)
+{
+ CGFloat rgba[4] = {g.grayscale, g.grayscale, g.grayscale, g.alpha};
+ NSColorSpace *colorSpace = [NSColorSpace sRGBColorSpace];
+ NSColor *nscolor = [NSColor colorWithColorSpace: colorSpace
+ components: rgba
+ count: 4];
+ return nscolor.CGColor;
+}
+
#define CGCOLOR(nscolor) (nscolor).CGColor
+
#else
-#define CGCOLOR(nscolor) (0 ? (CGColorRef) (nscolor) : NULL)
-#define CGPathCreateWithRoundedRect(w, x, y, z) NULL
-#endif
-/*
- * If we try to draw a rounded rectangle with too large of a radius
- * CoreGraphics will raise a fatal exception. This macro returns if
- * the width or height is less than twice the radius. Presumably this
- * only happens when a widget has not yet been configured and has size
- * 1x1.
- */
+#define CGCOLOR(nscolor) NULL
+#define CGColorFromRGBA(rgba) NULL
+#define CGColorFromGray(gray) NULL
+#define CGPathCreateWithRoundedRect(w, x, y, z) NULL
-#define CHECK_RADIUS(radius, bounds) \
- if ((radius) > (bounds).size.width / 2 || (radius) > (bounds).size.height / 2) { \
- return; \
- }
+#endif
/*----------------------------------------------------------------------
* +++ Utilities.
*/
-/*
+/*----------------------------------------------------------------------
* BoxToRect --
- * Convert a Ttk_Box in Tk coordinates relative to the given Drawable
- * to a native Rect relative to the containing port.
+ *
+ * Convert a Ttk_Box in Tk coordinates relative to the given Drawable to a
+ * native CGRect relative to the containing NSView. (The coordinate system
+ * is the one used by CGContextRef, which has origin at the upper left
+ * corner, and y increasing downward.)
*/
static inline CGRect BoxToRect(
@@ -175,60 +199,85 @@ static inline CGRect BoxToRect(
return rect;
}
-/*
- * Table mapping Tk states to Appearance manager ThemeStates
+/*----------------------------------------------------------------------
+ * LookupGrayPalette
+ *
+ * Retrieves the palette of grayscale colors needed to draw a particular
+ * type of button, in a particular state, in light or dark mode.
+ *
*/
-static Ttk_StateTable ThemeStateTable[] = {
- {kThemeStateActive, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
- {kThemeStateUnavailable, TTK_STATE_DISABLED, 0},
- {kThemeStatePressed, TTK_STATE_PRESSED, 0},
- {kThemeStateInactive, TTK_STATE_BACKGROUND, 0},
- {kThemeStateActive, 0, 0}
-
- /* Others: Not sure what these are supposed to mean. Up/Down have
- * something to do with "little arrow" increment controls... Dunno what
- * a "Rollover" is.
- * NEM: Rollover is TTK_STATE_ACTIVE... but we don't handle that yet, by
- * the looks of things
- *
- * {kThemeStateRollover, 0, 0},
- * {kThemeStateUnavailableInactive, 0, 0}
- * {kThemeStatePressedUp, 0, 0},
- * {kThemeStatePressedDown, 0, 0}
- */
-};
+static GrayPalette LookupGrayPalette(
+ const ButtonDesign *design,
+ unsigned int state,
+ int isDark)
+{
+ const PaletteStateTable *entry = design->palettes;
+ while ((state & entry->onBits) != entry->onBits ||
+ (~state & entry->offBits) != entry->offBits)
+ {
+ ++entry;
+ }
+ return isDark ? entry->dark : entry->light;
+}
/*----------------------------------------------------------------------
* NormalizeButtonBounds --
*
- * Apple's Human Interface Guidelines only allow three specific heights
- * for most buttons: Regular, small and mini. We always use the regular
- * size. However, Ttk may provide an arbitrary bounding rectangle. We
- * always draw the button centered vertically on the rectangle, and
- * having the same width as the rectangle. This function returns the
- * actual bounding rectangle that will be used in drawing the button.
+ * This function returns the actual bounding rectangle that will be used
+ * in drawing the button.
+ *
+ * Apple only allows three specific heights for most buttons: regular,
+ * small and mini. We always use the regular size. However, Ttk may
+ * provide a bounding rectangle with arbitrary height. We draw the Mac
+ * button centered vertically in the Ttk rectangle, with the same width as
+ * the rectangle. (But we take care to produce an integer y coordinate,
+ * to avoid unexpected anti-aliasing.)
+ *
+ * In addition, the button types which are not known to HIToolbox need some
+ * adjustments to their bounds.
*
- * The BevelButton is allowed to have arbitrary size, and also has
- * external padding. This is handled separately here.
*/
static CGRect NormalizeButtonBounds(
- SInt32 heightMetric,
- CGRect bounds)
+ ThemeButtonParams *params,
+ CGRect bounds,
+ int isDark)
{
SInt32 height;
- if (heightMetric != (SInt32) NoThemeMetric) {
- ChkErr(GetThemeMetric, heightMetric, &height);
- bounds.origin.y += (bounds.size.height - height) / 2;
+ if (params->heightMetric != NoThemeMetric) {
+ ChkErr(GetThemeMetric, params->heightMetric, &height);
+ height += 2;
+ bounds.origin.y += round(1 + (bounds.size.height - height) / 2);
bounds.size.height = height;
}
+ switch (params->kind) {
+ case TkRoundedRectButton:
+ bounds.size.height -= 1;
+ break;
+ case TkInlineButton:
+ bounds.size.height -= 4;
+ bounds.origin.y += 1;
+ break;
+ case TkRecessedButton:
+ bounds.size.height -= 2;
+ break;
+ case kThemeRoundButtonHelp:
+ if (isDark) {
+ bounds.size.height = bounds.size.width = 22;
+ } else {
+ bounds.size.height = bounds.size.width = 22;
+ }
+ break;
+ default:
+ break;
+ }
return bounds;
}
/*----------------------------------------------------------------------
- * +++ Backgrounds
+ * +++ Background Colors
*
* Support for contrasting background colors when GroupBoxes or Tabbed
* panes are nested inside each other. Early versions of macOS used ridged
@@ -243,31 +292,31 @@ static CGRect NormalizeButtonBounds(
* support Dark Mode anyway.
*/
-static const CGFloat WINDOWBACKGROUND[4] = {
- 235.0 / 255, 235.0 / 255, 235.0 / 255, 1.0
-};
-static const CGFloat WHITERGBA[4] = {1.0, 1.0, 1.0, 1.0};
-static const CGFloat BLACKRGBA[4] = {0.0, 0.0, 0.0, 1.0};
+RGBACOLOR windowBackground[4] = RGBA256(235.0, 235.0, 235.0, 1.0);
/*----------------------------------------------------------------------
* GetBackgroundColor --
*
* Fills the array rgba with the color coordinates for a background color.
- * Start with the background color of a window's geometry container, or
- * the standard ttk window background if there is no container. If the
+ * Start with the background color of a window's container, or the
+ * standard ttk window background if there is no container. If the
* contrast parameter is nonzero, modify this color to be darker, for the
* aqua appearance, or lighter for the DarkAqua appearance. This is
- * primarily used by the Fill and Background elements.
+ * primarily used by the Fill and Background elements. The save parameter
+ * is normally YES, so the contrasting color is saved in the private
+ * data of the widget. This behavior can be disabled in special cases,
+ * such as when drawing notebook tabs in macOS 11.
*/
-static void GetBackgroundColor(
+static void GetBackgroundColorRGBA(
TCL_UNUSED(CGContextRef),
Tk_Window tkwin,
int contrast,
+ Bool save,
CGFloat *rgba)
{
- TkWindow *winPtr = (TkWindow *)tkwin;
- TkWindow *containerPtr = (TkWindow *)TkGetContainer(tkwin);
+ TkWindow *winPtr = (TkWindow *) tkwin;
+ TkWindow *containerPtr = (TkWindow *) TkGetContainer(tkwin);
while (containerPtr && containerPtr->privatePtr) {
if (containerPtr->privatePtr->flags & TTK_HAS_CONTRASTING_BG) {
@@ -287,23 +336,24 @@ static void GetBackgroundColor(
[windowColor getComponents: rgba];
} else {
for (int i = 0; i < 4; i++) {
- rgba[i] = WINDOWBACKGROUND[i];
+ rgba[i] = windowBackground[i];
}
}
}
+
if (contrast) {
int isDark = (rgba[0] + rgba[1] + rgba[2] < 1.5);
if (isDark) {
for (int i = 0; i < 3; i++) {
- rgba[i] += 8.0 / 255.0;
+ rgba[i] += Ttk_ContrastDelta*contrast / 255.0;
}
} else {
for (int i = 0; i < 3; i++) {
- rgba[i] -= 8.0 / 255.0;
+ rgba[i] -= Ttk_ContrastDelta*contrast / 255.0;
}
}
- if (winPtr->privatePtr) {
+ if (save && winPtr->privatePtr) {
winPtr->privatePtr->flags |= TTK_HAS_CONTRASTING_BG;
for (int i = 0; i < 4; i++) {
winPtr->privatePtr->fillRGBA[i] = rgba[i];
@@ -312,12 +362,313 @@ static void GetBackgroundColor(
}
}
+static CGColorRef GetBackgroundCGColor(
+ CGContextRef context,
+ Tk_Window tkwin,
+ int contrast,
+ Bool save)
+{
+ CGFloat rgba[4];
+ GetBackgroundColorRGBA(context, tkwin, contrast, save, rgba);
+ return CGColorFromRGBA(rgba);
+}
+
+/*----------------------------------------------------------------------
+ * +++ Buttons
+ */
+
+/*----------------------------------------------------------------------
+ * FillRoundedRectangle --
+ *
+ * Fill a rounded rectangle with a specified solid color.
+ */
+
+static void FillRoundedRectangle(
+ CGContextRef context,
+ CGRect bounds,
+ CGFloat radius,
+ CGColorRef color)
+{
+ CGPathRef path;
+ CHECK_RADIUS(radius, bounds)
+
+ path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
+ if (!path) {
+ return;
+ }
+ CGContextSetFillColorWithColor(context, color);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextFillPath(context);
+ CFRelease(path);
+}
+
+/*----------------------------------------------------------------------
+ * FillBorder --
+ *
+ * Draw a 1-pixel border around a rounded rectangle using a 3-step
+ * gradient of shades of gray.
+ */
+
+static void FillBorder(
+ CGContextRef context,
+ CGRect bounds,
+ GrayPalette palette,
+ CGFloat radius)
+{
+ if (bounds.size.width < 2) {
+ return;
+ }
+ NSColorSpace *sRGB = [NSColorSpace sRGBColorSpace];
+ CGPoint end = CGPointMake(bounds.origin.x, bounds.origin.y + bounds.size.height);
+ CGFloat corner = (radius > 0 ? radius : 2.0) / bounds.size.height;
+ CGFloat locations[4] = {0.0, corner, 1.0 - corner, 1.0};
+ CGPathRef path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
+ CGFloat colors[16];
+ colors[0] = colors[1] = colors[2] = palette.top / 255.0;
+ colors[4] = colors[5] = colors[6] = palette.side / 255.0;
+ colors[8] = colors[9] = colors[10] = palette.side / 255.0;
+ colors[12] = colors[13] = colors[14] = palette.bottom / 255.0;
+ colors[3] = colors[7] = colors[11] = colors[15] = 1.0;
+ CGGradientRef gradient = CGGradientCreateWithColorComponents(
+ sRGB.CGColorSpace, colors, locations, 4);
+ if (!gradient) {
+ return;
+ }
+ CGContextSaveGState(context);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextClip(context);
+ CGContextDrawLinearGradient(context, gradient, bounds.origin, end, 0.0);
+ CGContextRestoreGState(context);
+ CFRelease(path);
+ CFRelease(gradient);
+}
+
+/*----------------------------------------------------------------------
+ * DrawFocusRing --
+ *
+ * Draw a 4-pixel wide rounded focus ring enclosing a rounded
+ * rectangle, using the current system accent color.
+ */
+
+static void DrawFocusRing(
+ CGContextRef context,
+ CGRect bounds,
+ const ButtonDesign *design)
+{
+ CGColorRef highlightColor;
+ CGFloat highlight[4] = {1.0, 1.0, 1.0, 0.2};
+ CGColorRef focusColor;
+
+ focusColor = CGCOLOR([controlAccentColor() colorWithAlphaComponent:0.6]);
+ FillRoundedRectangle(context, bounds, design->radius, focusColor);
+ bounds = CGRectInset(bounds, 3, 3);
+ highlightColor = CGColorFromRGBA(highlight);
+ CGContextSetFillColorWithColor(context, highlightColor);
+ CGContextFillRect(context, bounds);
+}
+
+/*----------------------------------------------------------------------
+ * DrawGrayButton --
+ *
+ * Draw a button in normal gray colors.
+ *
+ * Aqua buttons are normally drawn in a grayscale color. The buttons,
+ * which are shaped as rounded rectangles have a 1-pixel border which is
+ * drawn in a 3-step gradient and a solid gray face.
+ *
+ * Note that this will produce a round button if length = width =
+ * 2*radius.
+ */
+
+static void DrawGrayButton(
+ CGContextRef context,
+ CGRect bounds,
+ const ButtonDesign *design,
+ unsigned int state,
+ Tk_Window tkwin)
+{
+ int isDark = TkMacOSXInDarkMode(tkwin);
+ GrayPalette palette = LookupGrayPalette(design, state, isDark);
+ GrayColor faceGray = {.grayscale = 0.0, .alpha = 1.0};
+ CGFloat radius = 2 * design->radius <= bounds.size.height ?
+ design->radius : bounds.size.height / 2;
+ if (palette.top <= 255.0) {
+ FillBorder(context, bounds, palette, radius);
+ }
+ if (palette.face <= 255.0) {
+ faceGray.grayscale = palette.face / 255.0;
+ } else {
+
+ /*
+ * Color values > 255 are "transparent" which really means that we
+ * fill with the background color.
+ */
+
+ CGFloat rgba[4], gray;
+ GetBackgroundColorRGBA(context, tkwin, 0, NO, rgba);
+ gray = (rgba[0] + rgba[1] + rgba[2]) / 3.0;
+ faceGray.grayscale = gray;
+ }
+ FillRoundedRectangle(context, CGRectInset(bounds, 1, 1), radius - 1,
+ CGColorFromGray(faceGray));
+}
+
+/*----------------------------------------------------------------------
+ * DrawAccentedButton --
+ *
+ * The accent color is only used when drawing buttons in the active
+ * window. Push Buttons and segmented Arrow Buttons are drawn in color
+ * when in the pressed state. Selected Check Buttons, Radio Buttons and
+ * notebook Tabs are also drawn in color. The color is based on the
+ * user's current choice for the controlAccentColor, but is actually a
+ * linear gradient with a 1-pixel darker line at the top and otherwise
+ * changing from lighter at the top to darker at the bottom. This
+ * function draws a colored rounded rectangular button.
+ */
+
+static void DrawAccentedButton(
+ CGContextRef context,
+ CGRect bounds,
+ const ButtonDesign *design,
+ int state,
+ int isDark)
+{
+ NSColorSpace *sRGB = [NSColorSpace sRGBColorSpace];
+ CGColorRef faceColor = CGCOLOR(controlAccentColor());
+ CGFloat radius = design->radius;
+ CGPathRef path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
+ // This gradient should only be used for PushButtons and Tabs, and it needs
+ // to be lighter at the top.
+ static CGFloat components[12] = {1.0, 1.0, 1.0, 0.05,
+ 1.0, 1.0, 1.0, 0.2,
+ 1.0, 1.0, 1.0, 0.0};
+ CGFloat locations[3] = {0.0, 0.05, 1.0};
+ CGGradientRef gradient = CGGradientCreateWithColorComponents(
+ sRGB.CGColorSpace, components, locations, 3);
+ CGPoint end;
+ if (bounds.size.height > 2*radius) {
+ bounds.size.height -= 1;
+ }
+ end = CGPointMake(bounds.origin.x, bounds.origin.y + bounds.size.height);
+ CGContextSaveGState(context);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextClip(context);
+ FillRoundedRectangle(context, bounds, radius, faceColor);
+ CGContextDrawLinearGradient(context, gradient, bounds.origin, end, 0.0);
+ if (state & TTK_STATE_PRESSED &&
+ state & TTK_STATE_ALTERNATE) {
+ CGColorRef color = isDark ?
+ CGColorFromGray(darkPressedDefaultButton) :
+ CGColorFromGray(pressedDefaultButton);
+ FillRoundedRectangle(context, bounds, radius, color);
+ }
+ CGContextRestoreGState(context);
+ CFRelease(path);
+ CFRelease(gradient);
+}
/*----------------------------------------------------------------------
- * +++ Single Arrow Images --
+ * DrawAccentedSegment --
*
- * Used in ListHeaders and Comboboxes as well as disclosure triangles in
- * macOS 11.
+ * Draw the colored ends of widgets like popup buttons and combo buttons.
+ */
+
+static void DrawAccentedSegment(
+ CGContextRef context,
+ CGRect bounds,
+ const ButtonDesign *design,
+ unsigned int state,
+ Tk_Window tkwin)
+{
+ /*
+ * Clip to the bounds and then draw an accented button which is extended so
+ * that the rounded corners on the left will be clipped off. This assumes
+ * that the bounds include room for the focus ring.
+ */
+ int isDark = TkMacOSXInDarkMode(tkwin);
+ GrayColor sepGray = isDark ? darkComboSeparator : lightComboSeparator;
+ CGColorRef sepColor = CGColorFromGray(sepGray);
+ CGRect clip = bounds;
+ clip.size.height += 10;
+ bounds.origin.x -= 10;
+ bounds.size.width += 10;
+ CGPoint separator[2] = {
+ CGPointMake(clip.origin.x - 1, bounds.origin.y + 5),
+ CGPointMake(clip.origin.x - 1,
+ bounds.origin.y + bounds.size.height - 3)};
+ CGContextSaveGState(context);
+ CGContextSetStrokeColorWithColor(context, sepColor);
+ CGContextSetShouldAntialias(context, false);
+ CGContextSetLineWidth(context, 0.5);
+ CGContextAddLines(context, separator, 2);
+ CGContextStrokePath(context);
+ CGContextSetShouldAntialias(context, true);
+ if (state & TTK_STATE_FOCUS) {
+ CGRect focusClip = clip;
+ clip.size.width += 4;
+ CGContextClipToRect(context, focusClip);
+ bounds = CGRectInset(bounds, 0, 1);
+ DrawFocusRing(context, bounds, design);
+ }
+ bounds = CGRectInset(bounds, 4, 4);
+ if (state & TTK_STATE_BACKGROUND) {
+ bounds.size.height += 2;
+ } else {
+ bounds.size.height += 1;
+ }
+ CGContextClipToRect(context, clip);
+ if ((state & TTK_STATE_BACKGROUND) || (state & TTK_STATE_DISABLED)) {
+ DrawGrayButton(context, bounds, design, state, tkwin);
+ } else {
+ DrawAccentedButton(context, bounds, design, state | TTK_STATE_ALTERNATE,
+ isDark);
+ }
+ CGContextRestoreGState(context);
+}
+
+/*----------------------------------------------------------------------
+ * +++ Entry boxes
+ */
+
+static void DrawEntry(
+ CGContextRef context,
+ CGRect bounds,
+ const ButtonDesign *design,
+ int state,
+ Tk_Window tkwin)
+{
+ int isDark = TkMacOSXInDarkMode(tkwin);
+ GrayPalette palette = LookupGrayPalette(design, state, isDark);
+ CGColorRef backgroundColor;
+ CGFloat bgRGBA[4];
+ if (isDark) {
+ GetBackgroundColorRGBA(context, tkwin, 0, NO, bgRGBA);
+
+ /*
+ * Lighten the entry background to provide contrast.
+ */
+
+ for (int i = 0; i < 3; i++) {
+ bgRGBA[i] += 8.0 / 255.0;
+ }
+ backgroundColor = CGColorFromRGBA(bgRGBA);
+ } else {
+ backgroundColor = CG_WHITE;
+ }
+ if (state & TTK_STATE_FOCUS) {
+ DrawFocusRing(context, bounds, design);
+ } else {
+ FillBorder(context, CGRectInset(bounds,3,3), palette, design->radius);
+ }
+ bounds = CGRectInset(bounds, 4, 4);
+ FillRoundedRectangle(context, bounds, design->radius, backgroundColor);
+}
+
+/*----------------------------------------------------------------------
+ * +++ Chevrons, CheckMarks, etc. --
*/
static void DrawDownArrow(
@@ -325,14 +676,23 @@ static void DrawDownArrow(
CGRect bounds,
CGFloat inset,
CGFloat size,
- const CGFloat *rgba)
+ int state)
{
+ CGColorRef strokeColor;
CGFloat x, y;
- CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
+
+ if (state & TTK_STATE_DISABLED) {
+ strokeColor = CGCOLOR([NSColor disabledControlTextColor]);
+ } else if (state & TTK_STATE_IS_ACCENTED) {
+ strokeColor = CG_WHITE;
+ } else {
+ strokeColor = CGCOLOR([NSColor controlTextColor]);
+ }
+ CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextSetLineWidth(context, 1.5);
x = bounds.origin.x + inset;
- y = bounds.origin.y + trunc(bounds.size.height / 2);
+ y = bounds.origin.y + trunc(bounds.size.height / 2) + 1;
CGContextBeginPath(context);
CGPoint arrow[3] = {
{x, y - size / 4}, {x + size / 2, y + size / 4},
@@ -342,16 +702,28 @@ static void DrawDownArrow(
CGContextStrokePath(context);
}
+/*----------------------------------------------------------------------
+ * DrawUpArrow --
+ *
+ * Draws a single upward pointing arrow for ListHeaders and Disclosure Buttons.
+ */
+
static void DrawUpArrow(
CGContextRef context,
CGRect bounds,
CGFloat inset,
CGFloat size,
- const CGFloat *rgba)
+ int state)
{
+ NSColor *strokeColor;
CGFloat x, y;
- CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
+ if (state & TTK_STATE_DISABLED) {
+ strokeColor = [NSColor disabledControlTextColor];
+ } else {
+ strokeColor = [NSColor controlTextColor];
+ }
+ CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
CGContextSetLineWidth(context, 1.5);
x = bounds.origin.x + inset;
y = bounds.origin.y + trunc(bounds.size.height / 2);
@@ -364,28 +736,60 @@ static void DrawUpArrow(
CGContextStrokePath(context);
}
-static void DrawClosedDisclosure(
+/*----------------------------------------------------------------------
+ * DrawUpDownArrows --
+ *
+ * Draws the double arrows used in menu buttons and spin buttons.
+ */
+
+static void DrawUpDownArrows(
CGContextRef context,
CGRect bounds,
CGFloat inset,
CGFloat size,
- CGFloat *rgba)
+ CGFloat gap,
+ int state,
+ ThemeDrawState drawState)
{
CGFloat x, y;
-
- CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]);
+ NSColor *topStrokeColor, *bottomStrokeColor;
+ if (drawState == BOTH_ARROWS && !(state & TTK_STATE_BACKGROUND)) {
+ topStrokeColor = bottomStrokeColor = [NSColor whiteColor];
+ } else if (drawState == kThemeStatePressedDown) {
+ topStrokeColor = [NSColor controlTextColor];
+ bottomStrokeColor = [NSColor whiteColor];
+ } else if (drawState == kThemeStatePressedUp) {
+ topStrokeColor = [NSColor whiteColor];
+ bottomStrokeColor = [NSColor controlTextColor];
+ } else if (state & TTK_STATE_DISABLED) {
+ topStrokeColor = bottomStrokeColor = [NSColor disabledControlTextColor];
+ } else {
+ topStrokeColor = bottomStrokeColor = [NSColor controlTextColor];
+ }
CGContextSetLineWidth(context, 1.5);
x = bounds.origin.x + inset;
y = bounds.origin.y + trunc(bounds.size.height / 2);
CGContextBeginPath(context);
- CGPoint arrow[3] = {
- {x, y - size / 4 - 1}, {x + size / 2, y}, {x, y + size / 4 + 1}
- };
- CGContextAddLines(context, arrow, 3);
+ CGPoint bottomArrow[3] =
+ {{x, y + gap}, {x + size / 2, y + gap + size / 2}, {x + size, y + gap}};
+ CGContextAddLines(context, bottomArrow, 3);
+ CGContextSetStrokeColorWithColor(context, CGCOLOR(bottomStrokeColor));
+ CGContextStrokePath(context);
+ CGContextBeginPath(context);
+ CGPoint topArrow[3] =
+ {{x, y - gap}, {x + size / 2, y - gap - size / 2}, {x + size, y - gap}};
+ CGContextAddLines(context, topArrow, 3);
+ CGContextSetStrokeColorWithColor(context, CGCOLOR(topStrokeColor));
CGContextStrokePath(context);
}
-static void DrawOpenDisclosure(
+/*----------------------------------------------------------------------
+ * DrawClosedDisclosure --
+ *
+ * Draws a disclosure chevron in the Big Sur style, for Treeviews.
+ */
+
+static void DrawClosedDisclosure(
CGContextRef context,
CGRect bounds,
CGFloat inset,
@@ -400,24 +804,24 @@ static void DrawOpenDisclosure(
y = bounds.origin.y + trunc(bounds.size.height / 2);
CGContextBeginPath(context);
CGPoint arrow[3] = {
- {x, y - size / 4}, {x + size / 2, y + size / 2}, {x + size, y - size / 4}
+ {x, y - size / 4 - 1}, {x + size / 2, y}, {x, y + size / 4 + 1}
};
CGContextAddLines(context, arrow, 3);
CGContextStrokePath(context);
}
/*----------------------------------------------------------------------
- * +++ Double Arrow Buttons --
+ * DrawOpenDisclosure --
*
- * Used in MenuButtons and SpinButtons.
+ * Draws an open disclosure chevron in the Big Sur style, for Treeviews.
*/
-static void DrawUpDownArrows(
+static void DrawOpenDisclosure(
CGContextRef context,
CGRect bounds,
CGFloat inset,
CGFloat size,
- const CGFloat *rgba)
+ CGFloat *rgba)
{
CGFloat x, y;
@@ -426,602 +830,657 @@ static void DrawUpDownArrows(
x = bounds.origin.x + inset;
y = bounds.origin.y + trunc(bounds.size.height / 2);
CGContextBeginPath(context);
- CGPoint bottomArrow[3] =
- {{x, y + 2}, {x + size / 2, y + 2 + size / 2}, {x + size, y + 2}};
- CGContextAddLines(context, bottomArrow, 3);
- CGPoint topArrow[3] =
- {{x, y - 2}, {x + size / 2, y - 2 - size / 2}, {x + size, y - 2}};
- CGContextAddLines(context, topArrow, 3);
+ CGPoint arrow[3] = {
+ {x, y - size / 4}, {x + size / 2, y + size / 2}, {x + size, y - size / 4}
+ };
+ CGContextAddLines(context, arrow, 3);
CGContextStrokePath(context);
}
-
/*----------------------------------------------------------------------
- * +++ FillButtonBackground --
+ * IndicatorColor --
*
- * Fills a rounded rectangle with a transparent black gradient.
- * This is a no-op if building on 10.8 or older.
+ * Returns a CGColorRef of the appropriate shade for a check button or
+ * radio button in a given state.
*/
-static void FillButtonBackground(
- CGContextRef context,
- CGRect bounds,
- CGFloat radius)
+static CGColorRef IndicatorColor(
+ int state,
+ int isDark)
{
- CHECK_RADIUS(radius, bounds)
-
- CGPathRef path;
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- CGGradientRef backgroundGradient = CGGradientCreateWithColorComponents(
- deviceRGB.CGColorSpace, darkBackgroundGradient, NULL, 2);
- CGPoint backgroundEnd = {
- bounds.origin.x,
- bounds.origin.y + bounds.size.height
- };
- CGContextBeginPath(context);
- path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
- CGContextAddPath(context, path);
- CGContextClip(context);
- CGContextDrawLinearGradient(context, backgroundGradient,
- bounds.origin, backgroundEnd, 0);
- CFRelease(path);
- CFRelease(backgroundGradient);
+ if (state & TTK_STATE_DISABLED) {
+ return isDark ?
+ CGColorFromGray(darkDisabledIndicator) :
+ CGColorFromGray(lightDisabledIndicator);
+ } else if ((state & TTK_STATE_SELECTED || state & TTK_STATE_ALTERNATE) &&
+ !(state & TTK_STATE_BACKGROUND)) {
+ return CG_WHITE;
+ } else {
+ return CGCOLOR([NSColor controlTextColor]);
+ }
}
/*----------------------------------------------------------------------
- * +++ HighlightButtonBorder --
+ * DrawCheckIndicator --
*
- * Accent the top border of a rounded rectangle with a transparent
- * white gradient.
+ * Draws the checkmark or horizontal bar in a check box.
*/
-static void HighlightButtonBorder(
+static void DrawCheckIndicator(
CGContextRef context,
- CGRect bounds)
-{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- CGPoint topEnd = {bounds.origin.x, bounds.origin.y + 3};
- CGGradientRef topGradient = CGGradientCreateWithColorComponents(
- deviceRGB.CGColorSpace, darkTopGradient, NULL, 2);
-
- CGContextSaveGState(context);
- CGContextBeginPath(context);
- CGContextAddArc(context, bounds.origin.x + 4, bounds.origin.y + 4,
- 4, PI, 3 * PI / 2, 0);
- CGContextAddArc(context, bounds.origin.x + bounds.size.width - 4,
- bounds.origin.y + 4, 4, 3 * PI / 2, 0, 0);
- CGContextReplacePathWithStrokedPath(context);
- CGContextClip(context);
- CGContextDrawLinearGradient(context, topGradient, bounds.origin, topEnd,
- 0.0);
- CGContextRestoreGState(context);
- CFRelease(topGradient);
-}
-
-/*----------------------------------------------------------------------
- * DrawGroupBox --
- *
- * This is a standalone drawing procedure which draws the contrasting
- * rounded rectangular box for LabelFrames and Notebook panes used in
- * more recent versions of macOS.
- */
-
-static void DrawGroupBox(
CGRect bounds,
- CGContextRef context,
- Tk_Window tkwin)
+ int state,
+ int isDark)
{
- CHECK_RADIUS(4, bounds)
-
- CGPathRef path;
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *borderColor, *bgColor;
- static CGFloat border[4] = {1.0, 1.0, 1.0, 0.25};
- CGFloat fill[4];
-
- GetBackgroundColor(context, tkwin, 1, fill);
- bgColor = [NSColor colorWithColorSpace: deviceRGB components: fill
- count: 4];
- CGContextSetFillColorSpace(context, deviceRGB.CGColorSpace);
- CGContextSetFillColorWithColor(context, CGCOLOR(bgColor));
- path = CGPathCreateWithRoundedRect(bounds, 4, 4, NULL);
- CGContextClipToRect(context, bounds);
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGContextFillPath(context);
- borderColor = [NSColor colorWithColorSpace: deviceRGB components: border
- count: 4];
- CGContextSetFillColorWithColor(context, CGCOLOR(borderColor));
- [borderColor getComponents: fill];
- CGContextSetRGBFillColor(context, fill[0], fill[1], fill[2], fill[3]);
+ CGFloat x = bounds.origin.x, y = bounds.origin.y;
+ CGColorRef strokeColor = IndicatorColor(state, isDark);
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGContextReplacePathWithStrokedPath(context);
- CGContextFillPath(context);
- CFRelease(path);
+ CGContextSetStrokeColorWithColor(context, strokeColor);
+ if (state & TTK_STATE_SELECTED) {
+ CGContextSetLineWidth(context, 1.5);
+ CGContextBeginPath(context);
+ CGPoint check[3] = {{x + 3, y + 7}, {x + 6, y + 10}, {x + 10, y + 3}};
+ CGContextAddLines(context, check, 3);
+ CGContextStrokePath(context);
+ } else if (state & TTK_STATE_ALTERNATE) {
+ CGContextSetLineWidth(context, 2.0);
+ CGContextBeginPath(context);
+ CGPoint bar[2] = {{x + 3, y + 7}, {x + 11, y + 7}};
+ CGContextAddLines(context, bar, 2);
+ CGContextStrokePath(context);
+ }
}
/*----------------------------------------------------------------------
- * SolidFillRoundedRectangle --
+ * DrawRadioIndicator --
*
- * Fill a rounded rectangle with a specified solid color.
+ * Draws the dot in the middle of a selected radio button.
*/
-static void SolidFillRoundedRectangle(
+static void DrawRadioIndicator(
CGContextRef context,
CGRect bounds,
- CGFloat radius,
- NSColor *color)
+ int state,
+ int isDark)
{
- CGPathRef path;
+ CGFloat x = bounds.origin.x, y = bounds.origin.y;
+ CGColorRef fillColor = IndicatorColor(state, isDark);
- CHECK_RADIUS(radius, bounds)
- path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
- if (!path) {
- return;
+ CGContextSetFillColorWithColor(context, fillColor);
+ if (state & TTK_STATE_SELECTED) {
+ CGContextBeginPath(context);
+ CGRect dot = {{x + 5, y + 5}, {6, 6}};
+ CGContextAddEllipseInRect(context, dot);
+ CGContextFillPath(context);
+ } else if (state & TTK_STATE_ALTERNATE) {
+ CGRect bar = {{x + 4, y + 7}, {8, 2}};
+ CGContextFillRect(context, bar);
}
- CGContextSetFillColorWithColor(context, CGCOLOR(color));
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGContextFillPath(context);
- CFRelease(path);
}
-/*----------------------------------------------------------------------
- * +++ DrawListHeader --
- *
- * This is a standalone drawing procedure which draws column headers for
- * a Treeview in the Aqua appearance. The HITheme headers have not
- * matched the native ones since OSX 10.8. Note that the header image is
- * ignored, but we draw arrows according to the state.
- */
-
-static void DrawListHeader(
- CGRect bounds,
+static void
+DrawHelpSymbol(
CGContextRef context,
- Tk_Window tkwin,
+ CGRect bounds,
int state)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *strokeColor, *bgColor;
- static CGFloat borderRGBA[4] = {
- 200.0 / 255, 200.0 / 255, 200.0 / 255, 1.0
- };
- static CGFloat separatorRGBA[4] = {
- 220.0 / 255, 220.0 / 255, 220.0 / 255, 1.0
+ NSFont *font = [NSFont controlContentFontOfSize:15];
+ NSColor *foreground = state & TTK_STATE_DISABLED ?
+ [NSColor disabledControlTextColor] : [NSColor controlTextColor];
+ NSDictionary *attrs = @{
+ NSForegroundColorAttributeName : foreground,
+ NSFontAttributeName : font
};
- 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 = TkMacOSXGetNSWindowForDrawable(Tk_WindowId(tkwin));
- CGFloat *bgRGBA = [win isKeyWindow] ? activeBgRGBA : inactiveBgRGBA;
- CGFloat x = bounds.origin.x, y = bounds.origin.y;
- CGFloat w = bounds.size.width, h = bounds.size.height;
- CGPoint top[2] = {{x, y + 1}, {x + w, y + 1}};
- CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
- CGPoint separator[2] = {{x + w - 1, y + 3}, {x + w - 1, y + h - 3}};
-
- bgColor = [NSColor colorWithColorSpace: deviceRGB
- components: bgRGBA
- count: 4];
+ NSAttributedString *attributedString = [[NSAttributedString alloc]
+ initWithString:@"?"
+ attributes:attrs];
+ CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString(
+ (CFAttributedStringRef)attributedString);
+ CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(0, 1));
+ CGAffineTransform t = CGAffineTransformMake(
+ 1.0, 0.0, 0.0, -1.0, 0.0, bounds.size.height);
CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- CGContextSetFillColorSpace(context, deviceRGB.CGColorSpace);
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- CGContextBeginPath(context);
- CGContextSetFillColorWithColor(context, CGCOLOR(bgColor));
- CGContextAddRect(context, bounds);
- CGContextFillPath(context);
- strokeColor = [NSColor colorWithColorSpace: deviceRGB
- components: separatorRGBA
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
- CGContextAddLines(context, separator, 2);
- CGContextStrokePath(context);
- strokeColor = [NSColor colorWithColorSpace: deviceRGB
- components: borderRGBA
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
- CGContextAddLines(context, top, 2);
- CGContextStrokePath(context);
- CGContextAddLines(context, bottom, 2);
- CGContextStrokePath(context);
+ CGContextSetTextMatrix(context, t);
+ CGContextSetTextPosition(context,
+ bounds.origin.x + 6.5,
+ bounds.origin.y + bounds.size.height - 5);
+ CTLineDraw(line, context);
CGContextRestoreGState(context);
-
- if (state & TTK_TREEVIEW_STATE_SORTARROW) {
- CGRect arrowBounds = bounds;
- arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16;
- arrowBounds.size.width = 16;
- if (state & TTK_STATE_ALTERNATE) {
- DrawUpArrow(context, arrowBounds, 3, 8, BLACKRGBA);
- } else if (state & TTK_STATE_SELECTED) {
- DrawDownArrow(context, arrowBounds, 3, 8, BLACKRGBA);
- }
- }
+ CFRelease(line);
+ CFRelease(typesetter);
+ [attributedString release];
}
+
+
/*----------------------------------------------------------------------
- * +++ Drawing procedures for widgets in Apple's "Dark Mode" (10.14 and up).
- *
- * The HIToolbox does not support Dark Mode, and apparently never will,
- * so to make widgets look "native" we have to provide analogues of the
- * HITheme drawing functions to be used in DarkAqua. We continue to use
- * HITheme in Aqua, since it understands earlier versions of the OS.
- *
- * Drawing the dark widgets requires NSColors that were introduced in OSX
- * 10.14, so we make some of these functions be no-ops when building on
- * systems older than 10.14.
+ * +++ Progress bars.
*/
/*----------------------------------------------------------------------
- * GradientFillRoundedRectangle --
+ * DrawProgressBar --
*
- * Fill a rounded rectangle with a specified gradient.
+ * Draws a progress bar, with parameters supplied by a HIThemeTrackDrawInfo
+ * struct. Draws a rounded rectangular track overlayed by a colored
+ * rounded rectangular indicator. An indeterminate progress bar is
+ * animated.
*/
-static void GradientFillRoundedRectangle(
+static void DrawProgressBar(
CGContextRef context,
CGRect bounds,
- CGFloat radius,
- CGFloat *colors,
- int numColors)
+ HIThemeTrackDrawInfo info,
+ int state,
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- CGPathRef path;
- CHECK_RADIUS(radius, bounds)
+ CGRect colorBounds;
+ CGFloat rgba[4];
+ CGColorRef trackColor, highlightColor, fillColor;
+ NSColor *accent;
+ CGFloat ratio = (CGFloat) info.value / (CGFloat) (info.max - info.min);
- CGPoint end = {
- bounds.origin.x,
- bounds.origin.y + bounds.size.height
- };
- CGGradientRef gradient = CGGradientCreateWithColorComponents(
- deviceRGB.CGColorSpace, colors, NULL, numColors);
-
- path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL);
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGContextClip(context);
- CGContextDrawLinearGradient(context, gradient, bounds.origin, end, 0);
- CFRelease(path);
- CFRelease(gradient);
-}
-
-/*----------------------------------------------------------------------
- * +++ DrawDarkButton --
- *
- * This is a standalone drawing procedure which draws PushButtons and
- * PopupButtons in the Dark Mode style.
- */
-
-static void DrawDarkButton(
- CGRect bounds,
- ThemeButtonKind kind,
- Ttk_State state,
- CGContextRef context)
-{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *faceColor;
+ GetBackgroundColorRGBA(context, tkwin, 0, NO, rgba);
/*
- * To match the appearance of Apple's buttons we need to increase the
- * height by 1 pixel.
+ * Compute the bounds for the track and indicator. The track is 6 pixels
+ * wide in the center of the widget bounds.
*/
- bounds.size.height += 1;
-
- CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 5);
+ if (info.attributes & kThemeTrackHorizontal) {
+ bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 3);
+ colorBounds = bounds;
+ if (info.kind == kThemeIndeterminateBar) {
+ CGFloat width = 0.25*bounds.size.width;
+ CGFloat travel = 0.75*bounds.size.width;
+ CGFloat center = bounds.origin.x + (width / 2) + ratio*travel;
+ colorBounds.origin.x = center - width / 2;
+ colorBounds.size.width = width;
+ } else {
+ colorBounds.size.width = ratio*bounds.size.width;
+ }
+ if (colorBounds.size.width > 0 && colorBounds.size.width < 6) {
+ colorBounds.size.width = 6;
+ }
+ } else {
+ bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 1);
+ colorBounds = bounds;
+ if (info.kind == kThemeIndeterminateBar) {
+ CGFloat height = 0.25*bounds.size.height;
+ CGFloat travel = 0.75*bounds.size.height;
+ CGFloat center = bounds.origin.y + (height / 2) + ratio*travel;
+ colorBounds.origin.y = center - height / 2;
+ colorBounds.size.height = height;
+ } else {
+ colorBounds.size.height = ratio*(bounds.size.height);
+ }
+ if (colorBounds.size.height > 0 && colorBounds.size.height < 6) {
+ colorBounds.size.height = 6;
+ }
+ colorBounds.origin.y += bounds.size.height - colorBounds.size.height;
+ }
/*
- * Fill the button face with the appropriate color.
+ * Compute the colors for the track and indicator.
*/
- bounds = CGRectInset(bounds, 1, 1);
- if (kind == kThemePushButton && (state & TTK_STATE_PRESSED)) {
- if ([NSApp macOSVersion] < 120000) {
- GradientFillRoundedRectangle(context, bounds, 4,
- pressedPushButtonGradient, 2);
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkPressedButtonFace
- count: 4];
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
+ if (TkMacOSXInDarkMode(tkwin)) {
+ for(int i=0; i < 3; i++) {
+ rgba[i] += 30.0 / 255.0;
}
- } else if (kind == kThemePushButton &&
- (state & TTK_STATE_ALTERNATE) &&
- !(state & TTK_STATE_BACKGROUND)) {
- GradientFillRoundedRectangle(context, bounds, 4,
- darkSelectedGradient, 2);
+ trackColor = CGColorFromRGBA(rgba);
+ for(int i=0; i < 3; i++) {
+ rgba[i] -= 5.0 / 255.0;
+ }
+ highlightColor = CGColorFromRGBA(rgba);
+ FillRoundedRectangle(context, bounds, 3, trackColor);
} else {
- if (state & TTK_STATE_DISABLED) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkDisabledButtonFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkButtonFace
- count: 4];
+ for(int i=0; i < 3; i++) {
+ rgba[i] -= 14.0 / 255.0;
}
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
+ trackColor = CGColorFromRGBA(rgba);
+ for(int i=0; i < 3; i++) {
+ rgba[i] += 3.0 / 255.0;
+ }
+ highlightColor = CGColorFromRGBA(rgba);
+ bounds.size.height -= 1;
+ bounds = CGRectInset(bounds, 0, -1);
+ }
+ if (state & TTK_STATE_BACKGROUND) {
+ accent = [NSColor colorWithRed:0.72 green:0.72 blue:0.72 alpha:0.72];
+ } else {
+ accent = controlAccentColor();
}
/*
- * If this is a popup, draw the arrow button.
+ * Draw the track, with highlighting around the edge.
*/
- if ((kind == kThemePopupButton) | (kind == kThemeComboBox)) {
- CGRect arrowBounds = bounds;
- arrowBounds.size.width = 16;
- arrowBounds.origin.x += bounds.size.width - 16;
+ FillRoundedRectangle(context, bounds, 3, trackColor);
+ bounds = CGRectInset(bounds, 0, 0.5);
+ FillRoundedRectangle(context, bounds, 2.5, highlightColor);
+ bounds = CGRectInset(bounds, 0.5, 0.5);
+ FillRoundedRectangle(context, bounds, 2, trackColor);
+ bounds = CGRectInset(bounds, -0.5, -1);
- /*
- * If the toplevel is front, paint the button blue.
- */
+ /*
+ * Draw the indicator. Make it slightly transparent around the
+ * edge so the highlightng shows through.
+ */
- if (!(state & TTK_STATE_BACKGROUND) &&
- !(state & TTK_STATE_DISABLED)) {
- GradientFillRoundedRectangle(context, arrowBounds, 4,
- darkSelectedGradient, 2);
- }
- if (kind == kThemePopupButton) {
- DrawUpDownArrows(context, arrowBounds, 3, 7, WHITERGBA);
- } else {
- DrawDownArrow(context, arrowBounds, 4, 8, WHITERGBA);
- }
+ if (info.kind == kThemeIndeterminateBar &&
+ (state & TTK_STATE_SELECTED) == 0) {
+ return;
}
- HighlightButtonBorder(context, bounds);
+ fillColor = CGCOLOR([accent colorWithAlphaComponent:0.9]);
+ FillRoundedRectangle(context, colorBounds, 3, fillColor);
+ colorBounds = CGRectInset(colorBounds, 1, 1);
+ fillColor = CGCOLOR([accent colorWithAlphaComponent:1.0]);
+ FillRoundedRectangle(context, colorBounds, 2.5, fillColor);
}
+
+/*----------------------------------------------------------------------
+ * +++ Sliders.
+ */
/*----------------------------------------------------------------------
- * +++ DrawDarkIncDecButton --
+ * DrawSlider --
+ *
+ * Draws a slider track and round thumb for a Ttk scale widget. The accent
+ * color is used on the left or top part of the track, so the fraction of
+ * the track which is colored is equal to (value - from) / (to - from).
*
- * This is a standalone drawing procedure which draws an IncDecButton
- * (as used in a Spinbox) in the Dark Mode style.
*/
-static void DrawDarkIncDecButton(
+static void DrawSlider(
+ CGContextRef context,
CGRect bounds,
- ThemeDrawState drawState,
- Ttk_State state,
- CGContextRef context)
+ HIThemeTrackDrawInfo info,
+ int state,
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *faceColor;
-
- bounds = CGRectInset(bounds, 0, -1);
- CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 6);
+ CGColorRef trackColor;
+ CGRect clipBounds, trackBounds, thumbBounds;
+ CGPoint thumbPoint;
+ CGFloat position;
+ CGColorRef accentColor;
+ Bool fromIsSmaller = info.reserved;
+ double from = info.min, to = fabs((double) info.max), value = info.value;
/*
- * Fill the button face with the appropriate color.
+ * info.min, info.max and info.value are integers. When this is called
+ * we will have arranged that min = 0 and max is a large positive integer.
*/
- bounds = CGRectInset(bounds, 1, 1);
- if (state & TTK_STATE_DISABLED) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkDisabledButtonFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkButtonFace
- count: 4];
- }
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
-
- /*
- * If pressed, paint the appropriate half blue.
- */
+ double fraction = (from < to) ? (value - from) / (to - from) : 0.5;
+ int isDark = TkMacOSXInDarkMode(tkwin);
- if (state & TTK_STATE_PRESSED) {
- CGRect clip = bounds;
- clip.size.height /= 2;
- CGContextSaveGState(context);
- if (drawState == kThemeStatePressedDown) {
- clip.origin.y += clip.size.height;
+ if (info.attributes & kThemeTrackHorizontal) {
+ trackBounds = CGRectInset(bounds, 0, bounds.size.height / 2 - 3);
+ trackBounds.size.height = 3;
+ position = 8 + fraction * (trackBounds.size.width - 16);
+ clipBounds = trackBounds;
+ if (fromIsSmaller) {
+ clipBounds.size.width = position;
+ } else {
+ clipBounds.origin.x += position;
+ clipBounds.size.width -= position;
}
- CGContextClipToRect(context, clip);
- GradientFillRoundedRectangle(context, bounds, 5,
- darkSelectedGradient, 2);
- CGContextRestoreGState(context);
+ thumbPoint = CGPointMake(trackBounds.origin.x + position,
+ trackBounds.origin.y + 1);
+ } else {
+ trackBounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 0);
+ trackBounds.size.width = 3;
+ position = 8 + fraction * (trackBounds.size.height - 16);
+ clipBounds = trackBounds;
+ if (fromIsSmaller) {
+ clipBounds.size.height = position;
+ } else {
+ clipBounds.origin.y += position;
+ clipBounds.size.height -= position;
+ }
+ thumbPoint = CGPointMake(trackBounds.origin.x + 1,
+ trackBounds.origin.y + position);
+ }
+ trackColor = isDark ? CGColorFromGray(darkTrack):
+ CGColorFromGray(lightTrack);
+ thumbBounds = CGRectMake(thumbPoint.x - 8, thumbPoint.y - 8, 17, 17);
+ CGContextSaveGState(context);
+ FillRoundedRectangle(context, trackBounds, 1.5, trackColor);
+ CGContextClipToRect(context, clipBounds);
+ if (state & (TTK_STATE_BACKGROUND | TTK_STATE_DISABLED)) {
+ accentColor = isDark ? CGColorFromGray(darkInactiveTrack) :
+ CGColorFromGray(lightInactiveTrack);
+ } else {
+ accentColor = CGCOLOR(controlAccentColor());
}
- DrawUpDownArrows(context, bounds, 3, 5, WHITERGBA);
- HighlightButtonBorder(context, bounds);
+ FillRoundedRectangle(context, trackBounds, 1.5, accentColor);
+ CGContextRestoreGState(context);
+ DrawGrayButton(context, thumbBounds, &sliderDesign, state, tkwin);
}
/*----------------------------------------------------------------------
- * +++ DrawDarkBevelButton --
+ * +++ Drawing procedures for native widgets.
*
- * This is a standalone drawing procedure which draws RoundedBevelButtons
- * in the Dark Mode style.
+ * The HIToolbox does not support Dark Mode, and apparently never will.
+ * It also draws some widgets in discontinued older styles even when used
+ * on new OS releases. So to make widgets look "native" we have to provide
+ * analogues of the HIToolbox drawing functions to be used on newer systems.
+ * We continue to use NIToolbox for older versions of the OS.
+ *
+ * Drawing the dark widgets requires NSColors that were introduced in OSX
+ * 10.14, so we make some of these functions be no-ops when building on
+ * systems older than 10.14.
*/
-static void DrawDarkBevelButton(
+/*----------------------------------------------------------------------
+ * DrawButton --
+ *
+ * This is a standalone drawing procedure which draws most types of macOS
+ * buttons for newer OS releases. The button style is specified in the
+ * "kind" field of a HIThemeButtonDrawInfo struct, although some of the
+ * identifiers are not recognized by HIToolbox.
+ */
+
+static void DrawButton(
CGRect bounds,
+ HIThemeButtonDrawInfo info,
Ttk_State state,
- CGContextRef context)
+ CGContextRef context,
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *faceColor;
+ ThemeButtonKind kind = info.kind;
+ ThemeDrawState drawState = info.state;
+ CGRect arrowBounds = bounds = CGRectInset(bounds, 1, 1);
+ int hasIndicator, isDark = TkMacOSXInDarkMode(tkwin);
- CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 5);
+ switch (kind) {
+ case TkRoundedRectButton:
+ DrawGrayButton(context, bounds, &roundedrectDesign, state, tkwin);
+ break;
+ case TkInlineButton:
+ DrawGrayButton(context, bounds, &inlineDesign, state, tkwin);
+ break;
+ case TkRecessedButton:
+ DrawGrayButton(context, bounds, &recessedDesign, state, tkwin);
+ break;
+ case kThemeRoundedBevelButton:
+ DrawGrayButton(context, bounds, &bevelDesign, state, tkwin);
+ break;
+ case kThemePushButton:
- /*
- * Fill the button face with the appropriate color.
- */
+ /*
+ * The TTK_STATE_ALTERNATE bit means -default active. Apple only
+ * indicates the default state (which means that the key equivalent is
+ * "\n") for Push Buttons.
+ */
- bounds = CGRectInset(bounds, 1, 1);
- if (state & TTK_STATE_PRESSED) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkPressedBevelFace
- count: 4];
- } else if ((state & TTK_STATE_DISABLED) ||
- (state & TTK_STATE_ALTERNATE)) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkDisabledButtonFace
- count: 4];
- } else if (state & TTK_STATE_SELECTED) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkSelectedBevelFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkButtonFace
- count: 4];
+ if ((state & TTK_STATE_PRESSED || state & TTK_STATE_ALTERNATE) &&
+ !(state & TTK_STATE_BACKGROUND)) {
+ DrawAccentedButton(context, bounds, &pushbuttonDesign, state, isDark);
+ } else {
+ DrawGrayButton(context, bounds, &pushbuttonDesign, state, tkwin);
+ }
+ break;
+ case kThemeRoundButtonHelp:
+ DrawGrayButton(context, bounds, &helpDesign, state, tkwin);
+ DrawHelpSymbol(context, bounds, state);
+ break;
+ case kThemePopupButton:
+ drawState = 0;
+ DrawGrayButton(context, bounds, &popupDesign, state, tkwin);
+ arrowBounds.size.width = 17;
+ arrowBounds.origin.x += bounds.size.width - 17;
+ if (!(state & TTK_STATE_BACKGROUND) &&
+ !(state & TTK_STATE_DISABLED)) {
+ CGRect popupBounds = arrowBounds;
+
+ /*
+ * Allow room for nonexistent focus ring.
+ */
+
+ popupBounds.size.width += 4;
+ popupBounds.origin.y -= 4;
+ popupBounds.size.height += 8;
+ DrawAccentedSegment(context, popupBounds, &popupDesign, state, tkwin);
+ drawState = BOTH_ARROWS;
+ }
+ arrowBounds.origin.x += 2;
+ DrawUpDownArrows(context, arrowBounds, 3, 7, 2, state, drawState);
+ break;
+ case kThemeComboBox:
+ if (state & TTK_STATE_DISABLED) {
+ // Need to add the disabled case to entryDesign.
+ DrawEntry(context, bounds, &entryDesign, state, tkwin);
+ } else {
+ DrawEntry(context, bounds, &entryDesign, state, tkwin);
+ }
+ arrowBounds.size.width = 17;
+ if (state & TTK_STATE_BACKGROUND) {
+ arrowBounds.origin.x += bounds.size.width - 20;
+ arrowBounds.size.width += 4;
+ arrowBounds.origin.y -= 1;
+ } else {
+ arrowBounds.origin.y -= 1;
+ arrowBounds.origin.x += bounds.size.width - 20;
+ arrowBounds.size.width += 4;
+ arrowBounds.size.height += 2;
+ }
+ DrawAccentedSegment(context, arrowBounds, &comboDesign, state, tkwin);
+ if (!(state & TTK_STATE_BACKGROUND)) {
+ state |= TTK_STATE_IS_ACCENTED;
+ }
+ DrawDownArrow(context, arrowBounds, 6, 6, state);
+ break;
+ case kThemeCheckBox:
+ bounds = CGRectOffset(CGRectMake(0, bounds.size.height / 2 - 8, 16, 16),
+ bounds.origin.x, bounds.origin.y);
+ bounds = CGRectInset(bounds, 1, 1);
+ hasIndicator = state & TTK_STATE_SELECTED || state & TTK_STATE_ALTERNATE;
+ if (hasIndicator &&
+ !(state & TTK_STATE_BACKGROUND) &&
+ !(state & TTK_STATE_DISABLED)) {
+ DrawAccentedButton(context, bounds, &checkDesign, 0, isDark);
+ } else {
+ DrawGrayButton(context, bounds, &checkDesign, state, tkwin);
+ }
+ if (hasIndicator) {
+ DrawCheckIndicator(context, bounds, state, isDark);
+ }
+ break;
+ case kThemeRadioButton:
+ bounds = CGRectOffset(CGRectMake(0, bounds.size.height / 2 - 9, 18, 18),
+ bounds.origin.x, bounds.origin.y);
+ bounds = CGRectInset(bounds, 1, 1);
+ hasIndicator = state & TTK_STATE_SELECTED || state & TTK_STATE_ALTERNATE;
+ if (hasIndicator &&
+ !(state & TTK_STATE_BACKGROUND) &&
+ !(state & TTK_STATE_DISABLED)) {
+ DrawAccentedButton(context, bounds, &radioDesign, 0, isDark);
+ } else {
+ DrawGrayButton(context, bounds, &radioDesign, state, tkwin);
+ }
+ if (hasIndicator) {
+ DrawRadioIndicator(context, bounds, state, isDark);
+ }
+ break;
+ case kThemeArrowButton:
+ DrawGrayButton(context, bounds, &pushbuttonDesign, state, tkwin);
+ arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 17;
+ arrowBounds.size.width = 16;
+ arrowBounds.origin.y -= 1;
+ if (state & TTK_STATE_SELECTED) {
+ DrawUpArrow(context, arrowBounds, 5, 6, state);
+ } else {
+ DrawDownArrow(context, arrowBounds, 5, 6, state);
+ }
+ break;
+ case kThemeIncDecButton:
+ DrawGrayButton(context, bounds, &incdecDesign, state, tkwin);
+ if (state & TTK_STATE_PRESSED) {
+ CGRect clip;
+ if (drawState == kThemeStatePressedDown) {
+ clip = bounds;
+ clip.size.height /= 2;
+ clip.origin.y += clip.size.height;
+ bounds.size.height += 1;
+ clip.size.height += 1;
+ } else {
+ clip = bounds;
+ clip.size.height /= 2;
+ }
+ CGContextSaveGState(context);
+ CGContextClipToRect(context, clip);
+ DrawAccentedButton(context, bounds, &incdecDesign, 0, isDark);
+ CGContextRestoreGState(context);
+ }
+ {
+ CGFloat inset = (bounds.size.width - 5) / 2;
+ DrawUpDownArrows(context, bounds, inset, 5, 3, state, drawState);
+ }
+ break;
+ default:
+ break;
}
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
- HighlightButtonBorder(context, bounds);
}
-
+
/*----------------------------------------------------------------------
- * +++ DrawDarkCheckBox --
+ * DrawGroupBox --
*
- * This is a standalone drawing procedure which draws Checkboxes in the
- * Dark Mode style.
+ * This is a standalone drawing procedure which draws the contrasting rounded
+ * rectangular box for LabelFrames and Notebook panes used in more recent
+ * versions of macOS. Normally the contrast is set to one, since the nesting
+ * level of the Group Box is higher by 1 compared to its container. But we
+ * allow higher contrast for special cases, notably notebook tabs in macOS 11.
+ * The save parameter is passed to GetBackgroundColor and should probably be
+ * NO in such special cases.
*/
-static void DrawDarkCheckBox(
+static void DrawGroupBox(
CGRect bounds,
- Ttk_State state,
- CGContextRef context)
+ CGContextRef context,
+ Tk_Window tkwin,
+ int contrast,
+ Bool save)
{
- CGRect checkbounds = {{0, bounds.size.height / 2 - 8}, {16, 16}};
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *stroke;
- CGFloat x, y;
+ CHECK_RADIUS(5, bounds)
- bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y);
- x = bounds.origin.x;
- y = bounds.origin.y;
+ CGPathRef path;
+ CGColorRef backgroundColor, borderColor;
+ backgroundColor = GetBackgroundCGColor(context, tkwin, contrast, save);
+ borderColor = CGColorFromGray(boxBorder);
+ CGContextSetFillColorWithColor(context, backgroundColor);
+ path = CGPathCreateWithRoundedRect(bounds, 5, 5, NULL);
CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 4);
- bounds = CGRectInset(bounds, 1, 1);
- if (!(state & TTK_STATE_BACKGROUND) &&
- !(state & TTK_STATE_DISABLED) &&
- ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE))) {
- GradientFillRoundedRectangle(context, bounds, 3,
- darkSelectedGradient, 2);
- } else {
- GradientFillRoundedRectangle(context, bounds, 3,
- darkInactiveGradient, 2);
- }
- HighlightButtonBorder(context, bounds);
- if ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE)) {
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- if (state & TTK_STATE_DISABLED) {
- stroke = [NSColor disabledControlTextColor];
- } else {
- stroke = [NSColor controlTextColor];
- }
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- }
- if (state & TTK_STATE_SELECTED) {
- CGContextSetLineWidth(context, 1.5);
- CGContextBeginPath(context);
- CGPoint check[3] = {{x + 4, y + 8}, {x + 7, y + 11}, {x + 11, y + 4}};
- CGContextAddLines(context, check, 3);
- CGContextStrokePath(context);
- } else if (state & TTK_STATE_ALTERNATE) {
- CGContextSetLineWidth(context, 2.0);
- CGContextBeginPath(context);
- CGPoint bar[2] = {{x + 4, y + 8}, {x + 12, y + 8}};
- CGContextAddLines(context, bar, 2);
- CGContextStrokePath(context);
- }
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextFillPath(context);
+ CGContextSetFillColorWithColor(context, borderColor);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextReplacePathWithStrokedPath(context);
+ CGContextFillPath(context);
+ CFRelease(path);
}
-
+
/*----------------------------------------------------------------------
- * +++ DrawDarkRadioButton --
+ * DrawListHeader --
*
- * This is a standalone drawing procedure which draws RadioButtons
- * in the Dark Mode style.
+ * This is a standalone drawing procedure which draws column headers for a
+ * Treeview in the Aqua appearance. (The HIToolbox headers have not matched the
+ * native ones since OSX 10.8) Note that the header image is ignored, but we
+ * draw arrows according to the state.
*/
-static void DrawDarkRadioButton(
+static void DrawListHeader(
CGRect bounds,
- Ttk_State state,
- CGContextRef context)
+ CGContextRef context,
+ Tk_Window tkwin,
+ int state)
{
- CGRect checkbounds = {{0, bounds.size.height / 2 - 9}, {18, 18}};
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *fill;
- CGFloat x, y;
+ int isDark = TkMacOSXInDarkMode(tkwin);
+ CGFloat x = bounds.origin.x, y = bounds.origin.y;
+ CGFloat w = bounds.size.width, h = bounds.size.height;
+ CGPoint top[2] = {{x, y + 1}, {x + w, y + 1}};
+ CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
+ CGPoint separator[2] = {{x + w - 1, y + 3}, {x + w - 1, y + h - 3}};
+ CGColorRef strokeColor, backgroundColor;
- bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y);
- x = bounds.origin.x;
- y = bounds.origin.y;
+ /*
+ * Apple changes the background color of a list header when the window is
+ * not active. But Ttk does not indicate that in the state of a
+ * TreeHeader. So we have to query the Apple window manager.
+ */
- CGContextClipToRect(context, bounds);
- FillButtonBackground(context, bounds, 9);
- bounds = CGRectInset(bounds, 1, 1);
- if (!(state & TTK_STATE_BACKGROUND) &&
- !(state & TTK_STATE_DISABLED) &&
- ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE))) {
- GradientFillRoundedRectangle(context, bounds, 8,
- darkSelectedGradient, 2);
- } else {
- GradientFillRoundedRectangle(context, bounds, 8,
- darkInactiveGradient, 2);
- }
- HighlightButtonBorder(context, bounds);
- if ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE)) {
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- if (state & TTK_STATE_DISABLED) {
- fill = [NSColor disabledControlTextColor];
- } else {
- fill = [NSColor controlTextColor];
- }
- CGContextSetFillColorWithColor(context, CGCOLOR(fill));
+ NSWindow *win = TkMacOSXGetNSWindowForDrawable(Tk_WindowId(tkwin));
+ if (!isDark) {
+ GrayColor bgGray = [win isKeyWindow] ?
+ listheaderActiveBG : listheaderInactiveBG;
+ backgroundColor = CGColorFromGray(bgGray);
}
- if (state & TTK_STATE_SELECTED) {
+
+ CGContextSaveGState(context);
+ CGContextSetShouldAntialias(context, false);
+ if (!isDark) {
CGContextBeginPath(context);
- CGRect dot = {{x + 6, y + 6}, {6, 6}};
- CGContextAddEllipseInRect(context, dot);
+ CGContextSetFillColorWithColor(context, backgroundColor);
+ CGContextAddRect(context, bounds);
CGContextFillPath(context);
- } else if (state & TTK_STATE_ALTERNATE) {
- CGRect bar = {{x + 5, y + 8}, {8, 2}};
- CGContextFillRect(context, bar);
}
-}
+ strokeColor = isDark ?
+ CGColorFromGray(darkListheaderBorder) :
+ CGColorFromGray(listheaderSeparator);
+ CGContextSetStrokeColorWithColor(context, strokeColor);
+ CGContextAddLines(context, separator, 2);
+ CGContextStrokePath(context);
+ strokeColor = isDark ?
+ CGColorFromGray(darkListheaderBorder) :
+ CGColorFromGray(lightListheaderBorder);
+ CGContextSetStrokeColorWithColor(context, strokeColor);
+ CGContextAddLines(context, top, 2);
+ CGContextStrokePath(context);
+ CGContextAddLines(context, bottom, 2);
+ CGContextStrokePath(context);
+ CGContextRestoreGState(context);
+ if (state & TTK_TREEVIEW_STATE_SORTARROW) {
+ CGRect arrowBounds = bounds;
+ arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16;
+ arrowBounds.size.width = 16;
+ if (state & TTK_STATE_ALTERNATE) {
+ DrawUpArrow(context, arrowBounds, 3, 8, state);
+ } else if (state & TTK_STATE_SELECTED) {
+ DrawDownArrow(context, arrowBounds, 3, 8, state);
+ }
+ }
+}
+
/*----------------------------------------------------------------------
- * +++ DrawDarkTab --
+ * DrawTab --
*
- * This is a standalone drawing procedure which draws Tabbed Pane
- * Tabs in the Dark Mode style.
+ * This is a standalone drawing procedure which draws Tabbed Pane Tabs for the
+ * notebook widget.
*/
-static void DrawDarkTab(
+static void
+DrawTab(
CGRect bounds,
Ttk_State state,
- CGContextRef context)
+ CGContextRef context,
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *faceColor, *stroke;
CGRect originalBounds = bounds;
+ CGColorRef strokeColor;
int OSVersion = [NSApp macOSVersion];
- CGContextSetLineWidth(context, 1.0);
- CGContextClipToRect(context, bounds);
-
/*
* Extend the bounds to one or both sides so the rounded part will be
- * clipped off.
+ * clipped off if the right of the left tab, the left of the right tab,
+ * and both sides of the middle tabs.
*/
+ CGContextClipToRect(context, bounds);
if (OSVersion < 110000 || !(state & TTK_STATE_SELECTED)) {
if (!(state & TTK_STATE_FIRST_TAB)) {
bounds.origin.x -= 10;
@@ -1031,37 +1490,14 @@ static void DrawDarkTab(
bounds.size.width += 10;
}
}
-
/*
* Fill the tab face with the appropriate color or gradient. Use a solid
- * color if the tab is not selected, otherwise use a blue or gray
- * gradient.
+ * color if the tab is not selected, otherwise use the accent color with
+ * highlights
*/
- bounds = CGRectInset(bounds, 1, 1);
if (!(state & TTK_STATE_SELECTED)) {
- if (state & TTK_STATE_DISABLED) {
- if (OSVersion < 110000) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkDisabledButtonFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkTab
- count: 4];
- }
- } else {
- if (OSVersion < 110000) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkButtonFace
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkTab
- count: 4];
- }
- }
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
+ DrawGrayButton(context, bounds, &tabDesign, state, tkwin);
/*
* Draw a separator line on the left side of the tab if it
@@ -1070,11 +1506,8 @@ static void DrawDarkTab(
if (!(state & TTK_STATE_FIRST_TAB)) {
CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkTabSeparator
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
+ strokeColor = CGColorFromGray(darkTabSeparator);
+ CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextBeginPath(context);
CGContextMoveToPoint(context, originalBounds.origin.x,
originalBounds.origin.y + 1);
@@ -1085,301 +1518,119 @@ static void DrawDarkTab(
}
} else {
- /*
- * This is the selected tab. If it is first, cover up the separator
- * line drawn by the second one. (The selected tab is always drawn
- * last.)
- */
+ /*
+ * This is the selected tab; paint it with the current accent color.
+ * If it is first, cover up the separator line drawn by the second one.
+ * (The selected tab is always drawn last.)
+ */
if ((state & TTK_STATE_FIRST_TAB) && !(state & TTK_STATE_LAST_TAB)) {
bounds.size.width += 1;
}
if (!(state & TTK_STATE_BACKGROUND)) {
- if (OSVersion < 110000) {
- GradientFillRoundedRectangle(context, bounds, 4,
- darkSelectedGradient, 2);
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkSelectedTab
- count: 4];
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
- }
+ DrawAccentedButton(context, bounds, &tabDesign, 0, 0);
} else {
- if (OSVersion < 110000) {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkInactiveSelectedTab
- count: 4];
- } else {
- faceColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkSelectedTab
- count: 4];
- }
- SolidFillRoundedRectangle(context, bounds, 4, faceColor);
+ DrawGrayButton(context, bounds, &tabDesign, state, tkwin);
}
- HighlightButtonBorder(context, bounds);
}
}
-/*----------------------------------------------------------------------
- * +++ DrawDarkSeparator --
- *
- * This is a standalone drawing procedure which draws a separator widget
- * in Dark Mode.
- */
-
-static void DrawDarkSeparator(
- CGRect bounds,
- CGContextRef context,
- TCL_UNUSED(Tk_Window))
-{
- static CGFloat fill[4] = {1.0, 1.0, 1.0, 0.3};
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *fillColor = [NSColor colorWithColorSpace: deviceRGB
- components: fill
- count:4];
-
- CGContextSetFillColorWithColor(context, CGCOLOR(fillColor));
- CGContextFillRect(context, bounds);
-}
-
-/*----------------------------------------------------------------------
- * +++ DrawDarkFocusRing --
- *
- * This is a standalone drawing procedure which draws a focus ring around
- * an Entry widget in Dark Mode.
- */
-
-static void DrawDarkFocusRing(
- CGRect bounds,
- CGContextRef context)
-{
- CGRect insetBounds = CGRectInset(bounds, -3, -3);
- CHECK_RADIUS(4, insetBounds)
-
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *strokeColor;
- NSColor *fillColor = [NSColor colorWithColorSpace:deviceRGB
- components:darkFocusRing
- count:4];
- CGFloat x = bounds.origin.x, y = bounds.origin.y;
- CGFloat w = bounds.size.width, h = bounds.size.height;
- CGPoint topPart[4] = {
- {x, y + h}, {x, y + 1}, {x + w - 1, y + 1}, {x + w - 1, y + h}
- };
- CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
-
- CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- CGContextBeginPath(context);
- strokeColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkFocusRingTop
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
- CGContextAddLines(context, topPart, 4);
- CGContextStrokePath(context);
- strokeColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkFocusRingBottom
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor));
- CGContextAddLines(context, bottom, 2);
- CGContextStrokePath(context);
- CGContextSetShouldAntialias(context, true);
- CGContextSetFillColorWithColor(context, CGCOLOR(fillColor));
- CGPathRef path = CGPathCreateWithRoundedRect(insetBounds, 4, 4, NULL);
- CGContextBeginPath(context);
- CGContextAddPath(context, path);
- CGPathRelease(path);
- CGContextAddRect(context, bounds);
- CGContextEOFillPath(context);
- CGContextRestoreGState(context);
-}
-/*----------------------------------------------------------------------
- * +++ DrawDarkFrame --
- *
- * This is a standalone drawing procedure which draws various
- * types of borders in Dark Mode.
- */
-
-static void DrawDarkFrame(
+static void
+DrawTab11(
CGRect bounds,
+ Ttk_State state,
CGContextRef context,
- HIThemeFrameKind kind)
+ Tk_Window tkwin)
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *stroke;
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- CGFloat x = bounds.origin.x, y = bounds.origin.y;
- CGFloat w = bounds.size.width, h = bounds.size.height;
- CGPoint topPart[4] = {
- {x, y + h - 1}, {x, y + 1}, {x + w, y + 1}, {x + w, y + h - 1}
- };
- CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
- CGPoint accent[2] = {{x, y + 1}, {x + w, y + 1}};
+ if (state & TTK_STATE_SELECTED) {
+ DrawGrayButton(context, bounds, &pushbuttonDesign, state, tkwin);
+ } else {
+ CGRect clipRect = bounds;
+ /*
+ * Draw a segment of a Group Box as a background for non-selected tabs.
+ * Clip the Group Box so that the segments fit together to form a long
+ * rounded rectangle behind the entire tab bar.
+ */
- switch (kind) {
- case kHIThemeFrameTextFieldSquare:
+ if (!(state & TTK_STATE_FIRST_TAB)) {
+ clipRect.origin.x -= 5;
+ bounds.origin.x -= 5;
+ bounds.size.width += 5;
+ }
+ if (!(state & TTK_STATE_LAST_TAB)) {
+ clipRect.size.width += 5;
+ bounds.size.width += 5;
+ }
CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- CGContextBeginPath(context);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkFrameTop
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- CGContextAddLines(context, topPart, 4);
- CGContextStrokePath(context);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkFrameBottom
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- CGContextAddLines(context, bottom, 2);
- CGContextStrokePath(context);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkFrameAccent
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- CGContextAddLines(context, accent, 2);
- CGContextStrokePath(context);
+ CGContextClipToRect(context, clipRect);
+ DrawGroupBox(bounds, context, tkwin, 3, NO);
CGContextRestoreGState(context);
- break;
- default:
- break;
}
}
/*----------------------------------------------------------------------
- * +++ DrawListHeader --
+ * DrawDarkSeparator --
*
- * This is a standalone drawing procedure which draws column
- * headers for a Treeview in the Dark Mode.
+ * This is a standalone drawing procedure which draws a separator widget
+ * in Dark Mode. HIToolbox is used in light mode.
*/
-static void DrawDarkListHeader(
+static void DrawDarkSeparator(
CGRect bounds,
CGContextRef context,
- TCL_UNUSED(Tk_Window),
- int state)
+ TCL_UNUSED(Tk_Window))
{
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *stroke;
-
- CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace);
- CGFloat x = bounds.origin.x, y = bounds.origin.y;
- CGFloat w = bounds.size.width, h = bounds.size.height;
-
- CGPoint top[2] = {{x, y + 1}, {x + w, y + 1}};
- CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}};
- CGPoint separator[2] = {{x + w - 1, y + 3}, {x + w - 1, y + h - 3}};
- CGContextSaveGState(context);
- CGContextSetShouldAntialias(context, false);
- stroke = [NSColor colorWithColorSpace: deviceRGB
- components: darkFrameBottom
- count: 4];
- CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke));
- CGContextBeginPath(context);
- CGContextAddLines(context, top, 2);
- CGContextStrokePath(context);
- CGContextAddLines(context, bottom, 2);
- CGContextStrokePath(context);
- CGContextAddLines(context, separator, 2);
- CGContextStrokePath(context);
- CGContextRestoreGState(context);
-
- if (state & TTK_TREEVIEW_STATE_SORTARROW) {
- CGRect arrowBounds = bounds;
+ CGColorRef sepColor = CGColorFromGray(darkSeparator);
- arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16;
- arrowBounds.size.width = 16;
- if (state & TTK_STATE_ALTERNATE) {
- DrawUpArrow(context, arrowBounds, 3, 8, WHITERGBA);
- } else if (state & TTK_STATE_SELECTED) {
- DrawDownArrow(context, arrowBounds, 3, 8, WHITERGBA);
- }
- }
+ CGContextSetFillColorWithColor(context, sepColor);
+ CGContextFillRect(context, bounds);
}
/*----------------------------------------------------------------------
- * +++ Button element: Used for elements drawn with DrawThemeButton.
- */
-
-/*
- * When Ttk draws the various types of buttons, a pointer to one of these
- * is passed as the clientData.
- */
-
-typedef struct {
- ThemeButtonKind kind;
- ThemeMetric heightMetric;
-} ThemeButtonParams;
-static ThemeButtonParams
- PushButtonParams = {kThemePushButton, kThemeMetricPushButtonHeight},
- CheckBoxParams = {kThemeCheckBox, kThemeMetricCheckBoxHeight},
- RadioButtonParams = {kThemeRadioButton, kThemeMetricRadioButtonHeight},
- BevelButtonParams = {kThemeRoundedBevelButton, NoThemeMetric},
- PopupButtonParams = {kThemePopupButton, kThemeMetricPopupButtonHeight},
- DisclosureParams = {
- kThemeDisclosureButton, kThemeMetricDisclosureTriangleHeight
-},
- ListHeaderParams =
-{kThemeListHeaderButton, kThemeMetricListHeaderHeight};
-static Ttk_StateTable ButtonValueTable[] = {
- {kThemeButtonOff, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
- {kThemeButtonMixed, TTK_STATE_ALTERNATE, 0},
- {kThemeButtonOn, TTK_STATE_SELECTED, 0},
- {kThemeButtonOff, 0, 0}
-
- /*
- * Others: kThemeDisclosureRight, kThemeDisclosureDown,
- * kThemeDisclosureLeft
- */
-
-};
-static Ttk_StateTable ButtonAdornmentTable[] = {
- {kThemeAdornmentNone, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
- {kThemeAdornmentDefault | kThemeAdornmentFocus,
- TTK_STATE_ALTERNATE | TTK_STATE_FOCUS, 0},
- {kThemeAdornmentFocus, TTK_STATE_FOCUS, 0},
- {kThemeAdornmentDefault, TTK_STATE_ALTERNATE, 0},
- {kThemeAdornmentNone, 0, 0}
-};
-
-/*----------------------------------------------------------------------
- * +++ computeButtonDrawInfo --
+ * +++ DrawGradientButton --
*
- * Fill in an appearance manager HIThemeButtonDrawInfo record.
+ * This is a standalone drawing procedure which draws a
+ * a Gradient Button.
*/
-static inline HIThemeButtonDrawInfo computeButtonDrawInfo(
- ThemeButtonParams *params,
- Ttk_State state,
- TCL_UNUSED(Tk_Window))
+static void DrawGradientBorder(
+ CGRect bounds,
+ CGContextRef context,
+ Tk_Window tkwin,
+ Ttk_State state)
{
+ CGColorRef faceColor, borderColor;
+ GrayColor faceGray, borderGray;
+ CGRect inside = CGRectInset(bounds, 1, 1);
- /*
- * See ButtonElementDraw for the explanation of why we always draw
- * PushButtons in the active state.
- */
-
- SInt32 HIThemeState;
-
- HIThemeState = Ttk_StateTableLookup(ThemeStateTable, state);
- switch (params->kind) {
- case kThemePushButton:
- HIThemeState &= ~kThemeStateInactive;
- HIThemeState |= kThemeStateActive;
- break;
- default:
- break;
+ if (TkMacOSXInDarkMode(tkwin)) {
+ if (state & TTK_STATE_DISABLED) {
+ faceGray = darkGradientDisabled;
+ borderGray = darkGradientBorderDisabled;
+ } else {
+ faceGray = state & TTK_STATE_PRESSED ?
+ darkGradientPressed : darkGradientNormal;
+ borderGray = darkGradientBorder;
+ }
+ } else {
+ if (state & TTK_STATE_DISABLED) {
+ faceGray = lightGradientDisabled;
+ borderGray = lightGradientBorderDisabled;
+ } else {
+ faceGray = state & TTK_STATE_PRESSED ?
+ lightGradientPressed : lightGradientNormal;
+ borderGray = lightGradientBorder;
+ }
}
-
- const HIThemeButtonDrawInfo info = {
- .version = 0,
- .state = HIThemeState,
- .kind = params ? params->kind : 0,
- .value = Ttk_StateTableLookup(ButtonValueTable, state),
- .adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state),
- };
- return info;
+ faceColor = CGColorFromGray(faceGray);
+ borderColor = CGColorFromGray(borderGray);
+ CGContextSetFillColorWithColor(context, faceColor);
+ CGContextFillRect(context, inside);
+ CGContextSetFillColorWithColor(context, borderColor);
+ CGContextAddRect(context, bounds);
+ CGContextAddRect(context, inside);
+ CGContextEOFillPath(context);
}
/*----------------------------------------------------------------------
@@ -1388,16 +1639,13 @@ static inline HIThemeButtonDrawInfo computeButtonDrawInfo(
static void ButtonElementMinSize(
void *clientData,
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
int *minWidth,
- int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ int *minHeight)
{
ThemeButtonParams *params = (ThemeButtonParams *)clientData;
if (params->heightMetric != NoThemeMetric) {
- ChkErr(GetThemeMetric, params->heightMetric, (SInt *) minHeight);
+ ChkErr(GetThemeMetric, params->heightMetric, minHeight);
/*
* The theme height does not include the 1-pixel border around
@@ -1408,33 +1656,54 @@ static void ButtonElementMinSize(
*minHeight += 2;
/*
- * The minwidth must be 0 to force the generic ttk code to compute the
+ * For buttons with labels the minwidth must be 0 to force the
* correct text layout. For example, a non-zero value will cause the
* text to be left justified, no matter what -anchor setting is used in
* the style.
*/
- *minWidth = 0;
+ if (params->widthMetric != NoThemeMetric) {
+ ChkErr(GetThemeMetric, params->widthMetric, minWidth);
+ *minWidth += 2;
+ *minHeight += 2;
+ } else {
+ *minWidth = 0;
+ }
}
}
static void ButtonElementSize(
void *clientData,
- void *elementRecord,
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
int *minWidth,
int *minHeight,
Ttk_Padding *paddingPtr)
{
ThemeButtonParams *params = (ThemeButtonParams *)clientData;
- const HIThemeButtonDrawInfo info =
- computeButtonDrawInfo(params, 0, tkwin);
+ HIThemeButtonDrawInfo info =
+ ComputeButtonDrawInfo(params, 0, tkwin);
static const CGRect scratchBounds = {{0, 0}, {100, 100}};
CGRect contentBounds, backgroundBounds;
int verticalPad;
- ButtonElementMinSize(clientData, elementRecord, tkwin,
- minWidth, minHeight, paddingPtr);
+ ButtonElementMinSize(clientData, minWidth, minHeight);
+ switch (info.kind) {
+ case TkGradientButton:
+ *paddingPtr = Ttk_MakePadding(1, 1, 1, 1);
+ /* Fall through. */
+ case kThemeArrowButton:
+ case kThemeRoundButtonHelp:
+ return;
+ /* Buttons which are sized like PushButtons but unknown to HITheme. */
+ case TkRoundedRectButton:
+ case TkRecessedButton:
+ case TkInlineButton:
+ info.kind = kThemePushButton;
+ break;
+ default:
+ break;
+ }
/*
* Given a hypothetical bounding rectangle for a button, HIToolbox will
@@ -1443,7 +1712,7 @@ static void ButtonElementSize(
* enough to contain the image of the button in any state, which might
* include highlight borders, shadows, etc. The content rectangle is not
* centered vertically within the background rectangle, presumably because
- * shadows only appear on the bottom. Nonetheless, when HITools is asked
+ * shadows only appear on the bottom. Nonetheless, when HIToolbox is asked
* to draw a button with a certain bounding rectangle it draws the button
* centered within the rectangle.
*
@@ -1462,11 +1731,15 @@ static void ButtonElementSize(
CGRectGetMaxX(backgroundBounds) - CGRectGetMaxX(contentBounds);
verticalPad = backgroundBounds.size.height - contentBounds.size.height;
paddingPtr->top = paddingPtr->bottom = verticalPad / 2;
+ if (info.kind == kThemePopupButton) {
+ paddingPtr->top += 1;
+ paddingPtr->bottom -= 1;
+ }
}
static void ButtonElementDraw(
void *clientData,
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -1474,71 +1747,97 @@ static void ButtonElementDraw(
{
ThemeButtonParams *params = (ThemeButtonParams *)clientData;
CGRect bounds = BoxToRect(d, b);
- HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, state, tkwin);
+ HIThemeButtonDrawInfo info = ComputeButtonDrawInfo(params, state, tkwin);
+ int isDark = TkMacOSXInDarkMode(tkwin);
- bounds = NormalizeButtonBounds(params->heightMetric, bounds);
+ switch (info.kind) {
- BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- switch (info.kind) {
- case kThemePushButton:
- case kThemePopupButton:
- DrawDarkButton(bounds, info.kind, state, dc.context);
- break;
- case kThemeCheckBox:
- DrawDarkCheckBox(bounds, state, dc.context);
- break;
- case kThemeRadioButton:
- DrawDarkRadioButton(bounds, state, dc.context);
- break;
- case kThemeRoundedBevelButton:
- DrawDarkBevelButton(bounds, state, dc.context);
- break;
- default:
- ChkErr(HIThemeDrawButton, &bounds, &info, dc.context,
- HIOrientation, NULL);
- }
- } else if (info.kind == kThemePushButton &&
- (state & TTK_STATE_PRESSED)) {
- bounds.size.height += 2;
- if ([NSApp macOSVersion] > 100800) {
- GradientFillRoundedRectangle(dc.context, bounds, 4,
- pressedPushButtonGradient, 2);
- }
- } else {
+ /*
+ * A Gradient Button should have an image and no text. The size is set to
+ * that of the image. All we need to do is draw a 1-pixel border.
+ */
- /*
- * Apple's PushButton and PopupButton do not change their fill color
- * when the window is inactive. However, except in 10.7 (Lion), the
- * color of the arrow button on a PopupButton does change. For some
- * reason HITheme fills inactive buttons with a transparent color that
- * allows the window background to show through, leading to
- * inconsistent behavior. We work around this by filling behind an
- * inactive PopupButton with a text background color before asking
- * HIToolbox to draw it. For PushButtons, we simply draw them in the
- * active state.
- */
+ case TkGradientButton:
+ BEGIN_DRAWING(d)
+ DrawGradientBorder(bounds, dc.context, tkwin, state);
+ END_DRAWING
+ return;
+ /*
+ * Buttons with no height restrictions are ready to draw.
+ */
- if (info.kind == kThemePopupButton &&
- (state & TTK_STATE_BACKGROUND)) {
- CGRect innerBounds = CGRectInset(bounds, 1, 1);
- NSColor *whiteRGBA = [NSColor whiteColor];
- SolidFillRoundedRectangle(dc.context, innerBounds, 4, whiteRGBA);
- }
+ case kThemeArrowButton:
+ case kThemeCheckBox:
+ case kThemeRadioButton:
+ break;
- /*
- * A BevelButton with mixed value is drawn borderless, which does make
- * much sense for us.
- */
+ /*
+ * Other buttons have a maximum height. We have to deal with that.
+ */
- if (info.kind == kThemeRoundedBevelButton &&
- info.value == kThemeButtonMixed) {
- info.value = kThemeButtonOff;
- info.state = kThemeStateInactive;
- }
- ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
- NULL);
+ default:
+ bounds = NormalizeButtonBounds(params, bounds, isDark);
+ break;
+ }
+
+ /* We do our own drawing on new systems.*/
+
+ if ([NSApp macOSVersion] > 100800) {
+ BEGIN_DRAWING(d)
+ DrawButton(bounds, info, state, dc.context, tkwin);
+ END_DRAWING
+ return;
+ }
+
+ /*
+ * If execution reaches here it means we should use HIToolbox to draw the
+ * button. Buttons that HIToolbox doesn't know are rendered as
+ * PushButtons.
+ */
+
+ switch (info.kind) {
+ case TkRoundedRectButton:
+ case TkRecessedButton:
+ info.kind = kThemePushButton;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Apple's PushButton and PopupButton do not change their fill color
+ * when the window is inactive. However, except in 10.7 (Lion), the
+ * color of the arrow button on a PopupButton does change. For some
+ * reason HITheme fills inactive buttons with a transparent color that
+ * allows the window background to show through, leading to
+ * inconsistent behavior. We work around this by filling behind an
+ * inactive PopupButton with a text background color before asking
+ * HIToolbox to draw it. For PushButtons, we simply draw them in the
+ * active state.
+ */
+
+ BEGIN_DRAWING(d)
+ if (info.kind == kThemePopupButton &&
+ (state & TTK_STATE_BACKGROUND)) {
+ CGRect innerBounds = CGRectInset(bounds, 1, 1);
+ FillRoundedRectangle(dc.context, innerBounds, 4, CG_WHITE);
+ }
+
+ /*
+ * A BevelButton with mixed value is drawn borderless, which does make
+ * much sense for us.
+ */
+
+ if (info.kind == kThemeRoundedBevelButton &&
+ info.value == kThemeButtonMixed) {
+ info.value = kThemeButtonOff;
+ info.state = kThemeStateInactive;
}
+ if (info.kind == kThemePushButton) {
+ bounds.origin.y -= 2;
+ }
+ ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
+ NULL);
END_DRAWING
}
@@ -1555,7 +1854,7 @@ static Ttk_ElementSpec ButtonElementSpec = {
*/
/* Tab position logic, c.f. ttkNotebook.c TabState() */
-static Ttk_StateTable TabStyleTable[] = {
+static const Ttk_StateTable TabStyleTable[] = {
{kThemeTabFrontInactive, TTK_STATE_SELECTED | TTK_STATE_BACKGROUND, 0},
{kThemeTabNonFrontInactive, TTK_STATE_BACKGROUND, 0},
{kThemeTabFrontUnavailable, TTK_STATE_DISABLED | TTK_STATE_SELECTED, 0},
@@ -1564,13 +1863,13 @@ static Ttk_StateTable TabStyleTable[] = {
{kThemeTabNonFrontPressed, TTK_STATE_PRESSED, 0},
{kThemeTabNonFront, 0, 0}
};
-static Ttk_StateTable TabAdornmentTable[] = {
+static const Ttk_StateTable TabAdornmentTable[] = {
{kHIThemeTabAdornmentNone, TTK_STATE_FIRST_TAB | TTK_STATE_LAST_TAB, 0},
{kHIThemeTabAdornmentTrailingSeparator, TTK_STATE_FIRST_TAB, 0},
{kHIThemeTabAdornmentNone, TTK_STATE_LAST_TAB, 0},
{kHIThemeTabAdornmentTrailingSeparator, 0, 0},
};
-static Ttk_StateTable TabPositionTable[] = {
+static const Ttk_StateTable TabPositionTable[] = {
{kHIThemeTabPositionOnly, TTK_STATE_FIRST_TAB | TTK_STATE_LAST_TAB, 0},
{kHIThemeTabPositionFirst, TTK_STATE_FIRST_TAB, 0},
{kHIThemeTabPositionLast, TTK_STATE_LAST_TAB, 0},
@@ -1618,41 +1917,40 @@ static Ttk_StateTable TabPositionTable[] = {
*/
static void TabElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- int *minHeight,
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
- GetThemeMetric(kThemeMetricLargeTabHeight, (SInt32 *) minHeight);
- *paddingPtr = Ttk_MakePadding(0, 0, 0, 2);
-
+ *paddingPtr = Ttk_MakePadding(0, -2, 0, 1);
}
static void TabElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
Ttk_State state)
{
CGRect bounds = BoxToRect(d, b);
- HIThemeTabDrawInfo info = {
- .version = 1,
- .style = Ttk_StateTableLookup(TabStyleTable, state),
- .direction = kThemeTabNorth,
- .size = kHIThemeTabSizeNormal,
- .adornment = Ttk_StateTableLookup(TabAdornmentTable, state),
- .kind = kHIThemeTabKindNormal,
- .position = Ttk_StateTableLookup(TabPositionTable, state),
- };
-
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- DrawDarkTab(bounds, state, dc.context);
+ if ([NSApp macOSVersion] >= 110000) {
+ DrawTab11(bounds, state, dc.context, tkwin);
+ } else if ([NSApp macOSVersion] > 100800) {
+ DrawTab(bounds, state, dc.context, tkwin);
} else {
+ HIThemeTabDrawInfo info = {
+ .version = 1,
+ .style = Ttk_StateTableLookup(TabStyleTable, state),
+ .direction = kThemeTabNorth,
+ .size = kHIThemeTabSizeNormal,
+ .adornment = Ttk_StateTableLookup(TabAdornmentTable, state),
+ .kind = kHIThemeTabKindNormal,
+ .position = Ttk_StateTableLookup(TabPositionTable, state),
+ };
ChkErr(HIThemeDrawTab, &bounds, &info, dc.context, HIOrientation,
NULL);
}
@@ -1672,19 +1970,19 @@ static Ttk_ElementSpec TabElementSpec = {
*/
static void PaneElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- TCL_UNUSED(int *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
*paddingPtr = Ttk_MakePadding(9, 5, 9, 9);
}
static void PaneElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -1696,7 +1994,7 @@ static void PaneElementDraw(
bounds.size.height += kThemeMetricTabFrameOverlap;
BEGIN_DRAWING(d)
if ([NSApp macOSVersion] > 100800) {
- DrawGroupBox(bounds, dc.context, tkwin);
+ DrawGroupBox(bounds, dc.context, tkwin, 1, YES);
} else {
HIThemeTabPaneDrawInfo info = {
.version = 1,
@@ -1728,24 +2026,22 @@ static Ttk_ElementSpec PaneElementSpec = {
* DrawThemePrimaryGroup reference: "The primary group box frame is drawn
* inside the specified rectangle and is a maximum of 2 pixels thick."
*
- * "Maximum of 2 pixels thick" is apparently a lie; looks more like 4 to me
- * with shading.
*/
static void GroupElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- TCL_UNUSED(int *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
- *paddingPtr = Ttk_UniformPadding(4);
+ *paddingPtr = Ttk_MakePadding(0, 0, 0, 0);
}
static void GroupElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -1755,7 +2051,7 @@ static void GroupElementDraw(
BEGIN_DRAWING(d)
if ([NSApp macOSVersion] > 100800) {
- DrawGroupBox(bounds, dc.context, tkwin);
+ DrawGroupBox(bounds, dc.context, tkwin, 1, YES);
} else {
const HIThemeGroupBoxDrawInfo info = {
.version = 0,
@@ -1798,18 +2094,18 @@ static Ttk_ElementOptionSpec EntryElementOptions[] = {
};
static void EntryElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- TCL_UNUSED(int *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
- *paddingPtr = Ttk_MakePadding(7, 5, 7, 6);
+ *paddingPtr = entryElementPadding;
}
static void EntryElementDraw(
- TCL_UNUSED(void *),
+ void *clientData,
void *elementRecord,
Tk_Window tkwin,
Drawable d,
@@ -1817,40 +2113,31 @@ static void EntryElementDraw(
Ttk_State state)
{
EntryElement *e = (EntryElement *)elementRecord;
- Ttk_Box inner = Ttk_PadBox(b, Ttk_UniformPadding(3));
- CGRect bounds = BoxToRect(d, inner);
- NSColor *background;
+ ThemeFrameParams *params = (ThemeFrameParams *)clientData;
+ HIThemeFrameKind kind = params ? params->kind :
+ kHIThemeFrameTextFieldSquare;
+ CGRect bounds = BoxToRect(d, b);
+ CGColorRef background;
Tk_3DBorder backgroundPtr = NULL;
static const char *defaultBG = ENTRY_DEFAULT_BACKGROUND;
- if (TkMacOSXInDarkMode(tkwin)) {
+ if ([NSApp macOSVersion] > 100800) {
BEGIN_DRAWING(d)
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- CGFloat fill[4];
- GetBackgroundColor(dc.context, tkwin, 1, fill);
-
- /*
- * Lighten the background to provide contrast.
- */
-
- for (int i = 0; i < 3; i++) {
- fill[i] += 9.0 / 255.0;
+ switch(kind) {
+ case kHIThemeFrameTextFieldRound:
+ DrawEntry(dc.context, bounds, &searchDesign, state, tkwin);
+ break;
+ case kHIThemeFrameTextFieldSquare:
+ DrawEntry(dc.context, bounds, &entryDesign, state, tkwin);
+ break;
+ default:
+ return;
}
- background = [NSColor colorWithColorSpace: deviceRGB
- components: fill
- count: 4];
- CGContextSetFillColorWithColor(dc.context, CGCOLOR(background));
- CGContextFillRect(dc.context, bounds);
- if (state & TTK_STATE_FOCUS) {
- DrawDarkFocusRing(bounds, dc.context);
- } else {
- DrawDarkFrame(bounds, dc.context, kHIThemeFrameTextFieldSquare);
- }
END_DRAWING
} else {
const HIThemeFrameDrawInfo info = {
.version = 0,
- .kind = kHIThemeFrameTextFieldSquare,
+ .kind = params->kind,
.state = Ttk_StateTableLookup(ThemeStateTable, state),
.isFocused = state & TTK_STATE_FOCUS,
};
@@ -1873,13 +2160,13 @@ static void EntryElementDraw(
if (backgroundPtr != NULL) {
XFillRectangle(Tk_Display(tkwin), d,
Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC),
- inner.x, inner.y, inner.width, inner.height);
+ b.x, b.y, b.width, b.height);
}
BEGIN_DRAWING(d)
if (backgroundPtr == NULL) {
if ([NSApp macOSVersion] > 100800) {
- background = [NSColor textBackgroundColor];
- CGContextSetFillColorWithColor(dc.context, CGCOLOR(background));
+ background = CGCOLOR([NSColor textBackgroundColor]);
+ CGContextSetFillColorWithColor(dc.context, background);
} else {
CGContextSetRGBFillColor(dc.context, 1.0, 1.0, 1.0, 1.0);
}
@@ -1918,29 +2205,25 @@ static Ttk_ElementSpec EntryElementSpec = {
* 1 pixel to account for the fact that the button is not centered.
*/
-static Ttk_Padding ComboboxPadding = {4, 4, 20, 4};
-static Ttk_Padding DarkComboboxPadding = {6, 6, 22, 6};
+// OS dependent ???
+static Ttk_Padding ComboboxPadding = {7, 5, 24, 5};
static void ComboboxElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- Tk_Window tkwin,
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
Ttk_Padding *paddingPtr)
{
*minWidth = 24;
- *minHeight = 23;
- if (TkMacOSXInDarkMode(tkwin)) {
- *paddingPtr = DarkComboboxPadding;
- } else {
- *paddingPtr = ComboboxPadding;
- }
+ *minHeight = 0;
+ *paddingPtr = ComboboxPadding;
}
static void ComboboxElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -1956,23 +2239,13 @@ static void ComboboxElementDraw(
};
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- bounds = CGRectInset(bounds, 3, 3);
- if (state & TTK_STATE_FOCUS) {
- DrawDarkFocusRing(bounds, dc.context);
- }
- DrawDarkButton(bounds, info.kind, state, dc.context);
+ if ([NSApp macOSVersion] > 100800) {
+ bounds = CGRectInset(bounds, -1, -1);
+ DrawButton(bounds, info, state, dc.context, tkwin);
} else {
- if ([NSApp macOSVersion] > 100800) {
- if ((state & TTK_STATE_BACKGROUND) &&
- !(state & TTK_STATE_DISABLED)) {
- NSColor *background = [NSColor textBackgroundColor];
- CGRect innerBounds = CGRectInset(bounds, 1, 4);
- bounds.origin.y += 1;
- SolidFillRoundedRectangle(dc.context, innerBounds, 4, background);
- }
- }
- ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL);
+ bounds.origin.y += 1;
+ ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
+ NULL);
}
END_DRAWING
}
@@ -2006,27 +2279,42 @@ static Ttk_ElementSpec ComboboxElementSpec = {
* that order. So the up button must be listed first in the layout.
*/
-static Ttk_Padding SpinbuttonMargins = {0, 0, 2, 0};
+static Ttk_Padding SpinbuttonMargins = {2, 0, 0, 0};
-static void SpinButtonUpElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+static void SpinButtonReBounds(
+ Tk_Window tkwin,
+ CGRect *bounds)
+{
+ if (TkMacOSXInDarkMode(tkwin)) {
+ bounds->origin.x -= 2;
+ bounds->origin.y += 1;
+ bounds->size.height -= 0.5;
+ } else {
+ bounds->origin.x -= 3;
+ bounds->origin.y += 1;
+ bounds->size.width += 1;
+ }
+}
+
+static void SpinButtonElementSize(
+ TCL_UNUSED(void *), /* clientdata */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* PaddingPtr */
{
SInt32 s;
ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s);
*minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins);
ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s);
- *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2;
+ *minHeight = 2 + (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2;
}
static void SpinButtonUpElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -2035,6 +2323,7 @@ static void SpinButtonUpElementDraw(
CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins));
int infoState;
+ SpinButtonReBounds(tkwin, &bounds);
bounds.size.height *= 2;
if (state & TTK_STATE_PRESSED) {
infoState = kThemeStatePressedUp;
@@ -2049,8 +2338,8 @@ static void SpinButtonUpElementDraw(
.adornment = kThemeAdornmentNone,
};
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- DrawDarkIncDecButton(bounds, infoState, state, dc.context);
+ if ([NSApp macOSVersion] > 100800) {
+ DrawButton(bounds, info, state, dc.context, tkwin);
} else {
ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
NULL);
@@ -2062,28 +2351,13 @@ static Ttk_ElementSpec SpinButtonUpElementSpec = {
TK_STYLE_VERSION_2,
sizeof(NullElement),
TtkNullElementOptions,
- SpinButtonUpElementSize,
+ SpinButtonElementSize,
SpinButtonUpElementDraw
};
-static void SpinButtonDownElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- int *minWidth,
- int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
-{
- SInt32 s;
-
- ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s);
- *minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins);
- ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s);
- *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2;
-}
static void SpinButtonDownElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -2092,8 +2366,9 @@ static void SpinButtonDownElementDraw(
CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins));
int infoState = 0;
+ SpinButtonReBounds(tkwin, &bounds);
bounds.origin.y -= bounds.size.height;
- bounds.size.height *= 2;
+ bounds.size.height += bounds.size.height;
if (state & TTK_STATE_PRESSED) {
infoState = kThemeStatePressedDown;
} else {
@@ -2108,8 +2383,8 @@ static void SpinButtonDownElementDraw(
};
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- DrawDarkIncDecButton(bounds, infoState, state, dc.context);
+ if ([NSApp macOSVersion] > 100800) {
+ DrawButton(bounds, info, state, dc.context, tkwin);
} else {
ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
NULL);
@@ -2121,7 +2396,7 @@ static Ttk_ElementSpec SpinButtonDownElementSpec = {
TK_STYLE_VERSION_2,
sizeof(NullElement),
TtkNullElementOptions,
- SpinButtonDownElementSize,
+ SpinButtonElementSize,
SpinButtonDownElementDraw
};
@@ -2136,7 +2411,7 @@ static Ttk_ElementSpec SpinButtonDownElementSpec = {
* inactive. So we shouldn't either.
*/
-static Ttk_StateTable ThemeTrackEnableTable[] = {
+static const Ttk_StateTable ThemeTrackEnableTable[] = {
{kThemeTrackDisabled, TTK_STATE_DISABLED, 0},
{kThemeTrackActive, TTK_STATE_BACKGROUND, 0},
{kThemeTrackActive, 0, 0}
@@ -2168,11 +2443,11 @@ static Ttk_ElementOptionSpec TrackElementOptions[] = {
};
static void TrackElementSize(
void *clientData,
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
TrackElementData *data = (TrackElementData *)clientData;
SInt32 size = 24; /* reasonable default ... */
@@ -2192,33 +2467,28 @@ static void TrackElementDraw(
TrackElementData *data = (TrackElementData *)clientData;
TrackElement *elem = (TrackElement *)elementRecord;
Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
- double from = 0, to = 100, value = 0, factor;
- CGRect bounds;
+ double from = 0, to = 100, value = 0, fraction, max;
+ CGRect bounds = BoxToRect(d, b);
TtkGetOrientFromObj(NULL, elem->orientObj, &orientation);
Tcl_GetDoubleFromObj(NULL, elem->fromObj, &from);
Tcl_GetDoubleFromObj(NULL, elem->toObj, &to);
Tcl_GetDoubleFromObj(NULL, elem->valueObj, &value);
- factor = RangeToFactor(to);
-
- /*
- * HIThemeTrackDrawInfo uses 2-byte alignment; assigning to a separate
- * bounds variable avoids UBSan (-fsanitize=alignment) complaints.
- */
- bounds = BoxToRect(d, b);
+ fraction = (value - from) / (to - from);
+ max = RangeToFactor(fabs(to - from));
HIThemeTrackDrawInfo info = {
.version = 0,
.kind = data->kind,
.bounds = bounds,
- .min = from * factor,
- .max = to * factor,
- .value = value * factor,
+ .min = 0,
+ .max = max,
+ .value = fraction * max,
.attributes = kThemeTrackShowThumb |
(orientation == TTK_ORIENT_HORIZONTAL ?
kThemeTrackHorizontal : 0),
.enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state),
- .trackInfo.progress.phase = 0,
+ .trackInfo.progress.phase = 0
};
if (info.kind == kThemeSlider) {
@@ -2231,20 +2501,19 @@ static void TrackElementDraw(
}
}
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- bounds = BoxToRect(d, b);
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *trackColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkTrack
- count: 4];
- if (orientation == TTK_ORIENT_HORIZONTAL) {
- bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 2);
- } else {
- bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 2);
- }
- SolidFillRoundedRectangle(dc.context, bounds, 2, trackColor);
+ if (([NSApp macOSVersion] > 100800) && !(state & TTK_STATE_ALTERNATE)) {
+
+ /*
+ * We use the reserved field to indicate whether "from" is less than
+ * "to". It should be 0 if passing the info to HIThemeDrawInfo, but
+ * we aren't doing that.
+ */
+
+ info.reserved = (from < to);
+ DrawSlider(dc.context, bounds, info, state, tkwin);
+ } else {
+ ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
}
- ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
END_DRAWING
}
@@ -2266,12 +2535,12 @@ static Ttk_ElementSpec TrackElementSpec = {
*/
static void SliderElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
*minWidth = *minHeight = 24;
}
@@ -2314,12 +2583,12 @@ static Ttk_ElementOptionSpec PbarElementOptions[] = {
{NULL, TK_OPTION_BOOLEAN, 0, NULL}
};
static void PbarElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
SInt32 size = 24; /* @@@ Check HIG for correct default */
@@ -2328,7 +2597,7 @@ static void PbarElementSize(
}
static void PbarElementDraw(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
Tk_Window tkwin,
Drawable d,
@@ -2337,70 +2606,50 @@ static void PbarElementDraw(
{
PbarElement *pbar = (PbarElement *)elementRecord;
Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
-
- /*
- * Using 1000 as the maximum should give better than 1 pixel
- * resolution for most progress bars.
- */
-
- int kind, phase = 0, ivalue, imaximum = 1000;
- CGRect bounds;
+ int phase;
+ double value = 0, maximum = 100, factor;
+ CGRect bounds = BoxToRect(d, b);
+ int isIndeterminate = !strcmp("indeterminate",
+ Tcl_GetString(pbar->modeObj));
TtkGetOrientFromObj(NULL, pbar->orientObj, &orientation);
- kind = !strcmp("indeterminate", Tcl_GetString(pbar->modeObj)) ?
- kThemeIndeterminateBar : kThemeProgressBar;
- if (kind == kThemeIndeterminateBar) {
- Tcl_GetIntFromObj(NULL, pbar->phaseObj, &phase);
+ Tcl_GetDoubleFromObj(NULL, pbar->valueObj, &value);
+ Tcl_GetDoubleFromObj(NULL, pbar->maximumObj, &maximum);
+ Tcl_GetIntFromObj(NULL, pbar->phaseObj, &phase);
+
+ if (isIndeterminate) {
/*
- * On macOS 11 the fraction of an indeterminate progress bar which is
- * traversed by the oscillating thumb is value / maximum. The phase
- * determines the position of the moving thumb in that range and is
- * apparently expected to vary between 0 and 120. On earlier systems
- * it is unclear how the phase is used in generating the animation.
+ * When an indeterminate progress bar is animated the phase is
+ * (currently) always 0 and the value increases from min to max
+ * and then decreases back to min. We scale the value by 3 to
+ * speed the animation up a bit.
*/
- ivalue = imaximum;
- } else {
- double value, maximum;
- Tcl_GetDoubleFromObj(NULL, pbar->valueObj, &value);
- Tcl_GetDoubleFromObj(NULL, pbar->maximumObj, &maximum);
- ivalue = (value / maximum)*1000;
+ double remainder = fmod(3*value, 2*maximum);
+ value = remainder > maximum ? 2*maximum - remainder : remainder;
}
-
- /*
- * HIThemeTrackDrawInfo uses 2-byte alignment; assigning to a separate
- * bounds variable avoids UBSan (-fsanitize=alignment) complaints.
- */
-
- bounds = BoxToRect(d, b);
+ factor = RangeToFactor(maximum);
HIThemeTrackDrawInfo info = {
.version = 0,
- .kind = kind,
+ .kind = isIndeterminate? kThemeIndeterminateBar : kThemeProgressBar,
.bounds = bounds,
.min = 0,
- .max = imaximum,
- .value = ivalue,
+ .max = maximum * factor,
+ .value = value * factor,
.attributes = kThemeTrackShowThumb |
- (orientation == TTK_ORIENT_HORIZONTAL ? kThemeTrackHorizontal : 0),
+ (orientation == TTK_ORIENT_HORIZONTAL ?
+ kThemeTrackHorizontal : 0),
.enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state),
- .trackInfo.progress.phase = phase
+ .trackInfo.progress.phase = phase,
};
+
BEGIN_DRAWING(d)
- if (TkMacOSXInDarkMode(tkwin)) {
- bounds = BoxToRect(d, b);
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *trackColor = [NSColor colorWithColorSpace: deviceRGB
- components: darkTrack
- count: 4];
- if (orientation == TTK_ORIENT_HORIZONTAL) {
- bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 3);
- } else {
- bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 1);
- }
- SolidFillRoundedRectangle(dc.context, bounds, 3, trackColor);
+ if ([NSApp macOSVersion] > 100800) {
+ DrawProgressBar(dc.context, bounds, info, state, tkwin);
+ } else {
+ ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
}
- ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation);
END_DRAWING
}
@@ -2427,9 +2676,9 @@ static Ttk_ElementOptionSpec ScrollbarElementOptions[] = {
{NULL, TK_OPTION_BOOLEAN, 0, NULL}
};
static void TroughElementSize(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
Ttk_Padding *paddingPtr)
@@ -2453,34 +2702,18 @@ static void TroughElementSize(
}
}
-static CGFloat lightTrough[4] = {250.0 / 255, 250.0 / 255, 250.0 / 255, 1.0};
-static CGFloat darkTrough[4] = {45.0 / 255, 46.0 / 255, 49.0 / 255, 1.0};
-static CGFloat lightInactiveThumb[4] = {
- 200.0 / 255, 200.0 / 255, 200.0 / 255, 1.0
-};
-static CGFloat lightActiveThumb[4] = {
- 133.0 / 255, 133.0 / 255, 133.0 / 255, 1.0
-};
-static CGFloat darkInactiveThumb[4] = {
- 116.0 / 255, 117.0 / 255, 118.0 / 255, 1.0
-};
-static CGFloat darkActiveThumb[4] = {
- 158.0 / 255, 158.0 / 255, 159.0 / 255, 1.0
-};
static void TroughElementDraw(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
- TCL_UNUSED(Ttk_State))
+ TCL_UNUSED(Ttk_State)) /* state */
{
ScrollbarElement *scrollbar = (ScrollbarElement *)elementRecord;
Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
CGRect bounds = BoxToRect(d, b);
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *troughColor;
- CGFloat *rgba = TkMacOSXInDarkMode(tkwin) ? darkTrough : lightTrough;
+ GrayColor bgGray;
TtkGetOrientFromObj(NULL, scrollbar->orientObj, &orientation);
if (orientation == TTK_ORIENT_HORIZONTAL) {
@@ -2488,12 +2721,10 @@ static void TroughElementDraw(
} else {
bounds = CGRectInset(bounds, 1, 0);
}
- troughColor = [NSColor colorWithColorSpace: deviceRGB
- components: rgba
- count: 4];
BEGIN_DRAWING(d)
if ([NSApp macOSVersion] > 100800) {
- CGContextSetFillColorWithColor(dc.context, CGCOLOR(troughColor));
+ bgGray = TkMacOSXInDarkMode(tkwin) ? darkTrough : lightTrough;
+ CGContextSetFillColorWithColor(dc.context, CGColorFromGray(bgGray));
} else {
ChkErr(HIThemeSetFill, kThemeBrushDocumentWindowBackground, NULL,
dc.context, HIOrientation);
@@ -2510,12 +2741,12 @@ static Ttk_ElementSpec TroughElementSpec = {
TroughElementDraw
};
static void ThumbElementSize(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
ScrollbarElement *scrollbar = (ScrollbarElement *)elementRecord;
Ttk_Orient orientation = TTK_ORIENT_HORIZONTAL;
@@ -2531,7 +2762,7 @@ static void ThumbElementSize(
}
static void ThumbElementDraw(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
Tk_Window tkwin,
Drawable d,
@@ -2550,16 +2781,20 @@ static void ThumbElementDraw(
* determine the thumb geometry from the input values of min, max, value
* and viewSize is undocumented. A seemingly natural algorithm is
* implemented below. This code uses that algorithm for older OS versions,
- * because using HITools also handles drawing the buttons and 3D thumb used
+ * because using HIToolbox also handles drawing the buttons and 3D thumb used
* on those systems. For newer systems the cleanest approach is to just
* draw the thumb directly.
*/
if ([NSApp macOSVersion] > 100800) {
CGRect thumbBounds = BoxToRect(d, b);
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *thumbColor;
- CGFloat *rgba;
+ CGColorRef thumbColor;
+ GrayColor bgGray;
+
+ /*
+ * Apple does not draw the thumb when scrolling is not possible.
+ */
+
if ((orientation == TTK_ORIENT_HORIZONTAL &&
thumbBounds.size.width >= Tk_Width(tkwin) - 8) ||
(orientation == TTK_ORIENT_VERTICAL &&
@@ -2569,15 +2804,13 @@ static void ThumbElementDraw(
int isDark = TkMacOSXInDarkMode(tkwin);
if ((state & TTK_STATE_PRESSED) ||
(state & TTK_STATE_HOVER)) {
- rgba = isDark ? darkActiveThumb : lightActiveThumb;
+ bgGray = isDark ? darkActiveThumb : lightActiveThumb;
} else {
- rgba = isDark ? darkInactiveThumb : lightInactiveThumb;
+ bgGray = isDark ? darkInactiveThumb : lightInactiveThumb;
}
- thumbColor = [NSColor colorWithColorSpace: deviceRGB
- components: rgba
- count: 4];
+ thumbColor = CGColorFromGray(bgGray);
BEGIN_DRAWING(d)
- SolidFillRoundedRectangle(dc.context, thumbBounds, 4, thumbColor);
+ FillRoundedRectangle(dc.context, thumbBounds, 4, thumbColor);
END_DRAWING
} else {
double thumbSize, trackSize, visibleSize, factor, fraction;
@@ -2646,12 +2879,12 @@ static Ttk_ElementSpec ThumbElementSpec = {
ThumbElementDraw
};
static void ArrowElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
if ([NSApp macOSVersion] < 100800) {
*minHeight = *minWidth = 14;
@@ -2677,19 +2910,19 @@ static Ttk_ElementSpec ArrowElementSpec = {
*/
static void SeparatorElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
*minWidth = *minHeight = 1;
}
static void SeparatorElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -2729,12 +2962,12 @@ static const ThemeGrowDirection sizegripGrowDirection
= kThemeGrowRight | kThemeGrowDown;
static void SizegripElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
HIThemeGrowBoxDrawInfo info = {
.version = 0,
@@ -2751,9 +2984,9 @@ static void SizegripElementSize(
}
static void SizegripElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
Drawable d,
Ttk_Box b,
unsigned int state)
@@ -2828,8 +3061,8 @@ static Ttk_ElementSpec SizegripElementSpec = {
*/
static void FillElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -2838,15 +3071,10 @@ static void FillElementDraw(
CGRect bounds = BoxToRect(d, b);
if ([NSApp macOSVersion] > 100800) {
- NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace];
- NSColor *bgColor;
- CGFloat fill[4];
+ CGColorRef bgColor;
BEGIN_DRAWING(d)
- GetBackgroundColor(dc.context, tkwin, 0, fill);
- bgColor = [NSColor colorWithColorSpace: deviceRGB components: fill
- count: 4];
- CGContextSetFillColorSpace(dc.context, deviceRGB.CGColorSpace);
- CGContextSetFillColorWithColor(dc.context, CGCOLOR(bgColor));
+ bgColor = GetBackgroundCGColor(dc.context, tkwin, NO, 0);
+ CGContextSetFillColorWithColor(dc.context, bgColor);
CGContextFillRect(dc.context, bounds);
END_DRAWING
} else {
@@ -2855,7 +3083,6 @@ static void FillElementDraw(
: kThemeBrushModelessDialogBackgroundActive;
BEGIN_DRAWING(d)
ChkErr(HIThemeSetFill, brush, NULL, dc.context, HIOrientation);
- //QDSetPatternOrigin(PatternOrigin(tkwin, d));
CGContextFillRect(dc.context, bounds);
END_DRAWING
}
@@ -2903,8 +3130,8 @@ static Ttk_ElementSpec BackgroundElementSpec = {
*/
static void ToolbarBackgroundElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
TCL_UNUSED(Ttk_Box),
@@ -2946,7 +3173,7 @@ static Ttk_ElementOptionSpec FieldElementOptions[] = {
};
static void FieldElementDraw(
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
void *elementRecord,
Tk_Window tkwin,
Drawable d,
@@ -2973,18 +3200,18 @@ static Ttk_ElementSpec FieldElementSpec = {
/*----------------------------------------------------------------------
* +++ Treeview headers --
*
- * On systems older than 10.9 The header is a kThemeListHeaderButton drawn
+ * On systems older than 10.9 the header is a kThemeListHeaderButton drawn
* by HIToolbox. On newer systems those buttons do not match the Apple
* buttons, so we draw them from scratch.
*/
-static Ttk_StateTable TreeHeaderValueTable[] = {
+static const Ttk_StateTable TreeHeaderValueTable[] = {
{kThemeButtonOn, TTK_STATE_ALTERNATE, 0},
{kThemeButtonOn, TTK_STATE_SELECTED, 0},
{kThemeButtonOff, 0, 0}
};
-static Ttk_StateTable TreeHeaderAdornmentTable[] = {
+static const Ttk_StateTable TreeHeaderAdornmentTable[] = {
{kThemeAdornmentHeaderButtonSortUp,
TTK_STATE_ALTERNATE | TTK_TREEVIEW_STATE_SORTARROW, 0},
{kThemeAdornmentDefault,
@@ -2996,11 +3223,11 @@ static Ttk_StateTable TreeHeaderAdornmentTable[] = {
};
static void TreeAreaElementSize (
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
- TCL_UNUSED(int *),
- TCL_UNUSED(int *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
+ TCL_UNUSED(int *), /* minWidth */
+ TCL_UNUSED(int *), /* minHeight */
Ttk_Padding *paddingPtr)
{
@@ -3010,7 +3237,7 @@ static void TreeAreaElementSize (
*/
if ([NSApp macOSVersion] > 100800) {
- paddingPtr->top = 4;
+ *paddingPtr = Ttk_MakePadding(0, 4, 0, 0);
}
}
@@ -3039,7 +3266,7 @@ static void TreeHeaderElementSize(
static void TreeHeaderElementDraw(
void *clientData,
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -3064,11 +3291,7 @@ static void TreeHeaderElementDraw(
*/
bounds.origin.y -= 4;
- if (TkMacOSXInDarkMode(tkwin)) {
- DrawDarkListHeader(bounds, dc.context, tkwin, state);
- } else {
- DrawListHeader(bounds, dc.context, tkwin, state);
- }
+ DrawListHeader(bounds, dc.context, tkwin, state);
} else {
ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation,
NULL);
@@ -3090,17 +3313,17 @@ static Ttk_ElementSpec TreeHeaderElementSpec = {
#define TTK_TREEVIEW_STATE_OPEN TTK_STATE_USER1
#define TTK_TREEVIEW_STATE_LEAF TTK_STATE_USER2
-static Ttk_StateTable DisclosureValueTable[] = {
+static const Ttk_StateTable DisclosureValueTable[] = {
{kThemeDisclosureDown, TTK_TREEVIEW_STATE_OPEN, 0},
{kThemeDisclosureRight, 0, 0},
};
static void DisclosureElementSize(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
- TCL_UNUSED(Tk_Window),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
+ TCL_UNUSED(Tk_Window), /* tkwin */
int *minWidth,
int *minHeight,
- TCL_UNUSED(Ttk_Padding *))
+ TCL_UNUSED(Ttk_Padding *)) /* paddingPtr */
{
SInt32 s;
@@ -3111,8 +3334,8 @@ static void DisclosureElementSize(
}
static void DisclosureElementDraw(
- TCL_UNUSED(void *),
- TCL_UNUSED(void *),
+ TCL_UNUSED(void *), /* clientData */
+ TCL_UNUSED(void *), /* elementRecord */
Tk_Window tkwin,
Drawable d,
Ttk_Box b,
@@ -3192,6 +3415,44 @@ TTK_LAYOUT("TCombobox",
TTK_GROUP("Combobox.padding", TTK_FILL_BOTH,
TTK_NODE("Combobox.textarea", TTK_FILL_BOTH))))
+/* Image Button - no button */
+TTK_LAYOUT("ImageButton",
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH)))
+
+/* Inline Button */
+TTK_LAYOUT("InlineButton",
+ TTK_GROUP("InlineButton.button", TTK_FILL_BOTH,
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH))))
+
+/* Rounded Rect Button -- transparent face */
+TTK_LAYOUT("RoundedRectButton",
+ TTK_GROUP("RoundedRectButton.button", TTK_FILL_BOTH,
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH))))
+
+/* Gradient Button */
+TTK_LAYOUT("GradientButton",
+ TTK_GROUP("GradientButton.button", TTK_FILL_BOTH,
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH))))
+
+/* Recessed Button - text only radio button */
+
+TTK_LAYOUT("RecessedButton",
+ TTK_GROUP("RecessedButton.button", TTK_FILL_BOTH,
+ TTK_GROUP("Button.padding", TTK_FILL_BOTH,
+ TTK_NODE("Button.label", TTK_FILL_BOTH))))
+
+/* DisclosureButton (not a triangle) -- No label, no border*/
+TTK_LAYOUT("DisclosureButton",
+ TTK_NODE("DisclosureButton.button", TTK_FILL_BOTH))
+
+/* HelpButton -- No label, no border*/
+TTK_LAYOUT("HelpButton",
+ TTK_NODE("HelpButton.button", TTK_FILL_BOTH))
+
/* Notebook tabs -- no focus ring */
TTK_LAYOUT("Tab",
TTK_GROUP("Notebook.tab", TTK_FILL_BOTH,
@@ -3206,6 +3467,17 @@ TTK_LAYOUT("TSpinbox",
TTK_GROUP("Spinbox.field", TTK_FILL_X,
TTK_NODE("Spinbox.textarea", TTK_FILL_X)))
+TTK_LAYOUT("TEntry",
+ TTK_GROUP("Entry.field", TTK_FILL_BOTH|TTK_BORDER,
+ TTK_GROUP("Entry.padding", TTK_FILL_BOTH,
+ TTK_NODE("Entry.textarea", TTK_FILL_BOTH))))
+
+/* Searchbox */
+TTK_LAYOUT("Searchbox",
+ TTK_GROUP("Searchbox.field", TTK_FILL_BOTH|TTK_BORDER,
+ TTK_GROUP("Entry.padding", TTK_FILL_BOTH,
+ TTK_NODE("Entry.textarea", TTK_FILL_BOTH))))
+
/* Progress bars -- track only */
TTK_LAYOUT("TProgressbar",
TTK_NODE("Progressbar.track", TTK_FILL_BOTH))
@@ -3249,6 +3521,34 @@ TTK_END_LAYOUT_TABLE
* +++ Initialization --
*/
+/*----------------------------------------------------------------------
+ * +++ Ttk_MacOSXInit --
+ *
+ * Initialize variables which depend on [NSApp macOSVersion]. Called from
+ * [NSApp applicationDidFinishLaunching].
+ */
+
+MODULE_SCOPE
+void Ttk_MacOSXInit(
+ void)
+{
+ if ([NSApp macOSVersion] < 101400) {
+ entryElementPadding = Ttk_MakePadding(7, 6, 7, 5);
+ } else {
+ entryElementPadding = Ttk_MakePadding(7, 5, 7, 6);
+ }
+ if ([NSApp macOSVersion] < 110000) {
+ Ttk_ContrastDelta = 8.0;
+ } else {
+
+ /*
+ * The subtle contrast became event more subtle in 11.0.
+ */
+
+ Ttk_ContrastDelta = 5.0;
+ }
+}
+
static int AquaTheme_Init(
Tcl_Interp *interp)
{
@@ -3271,14 +3571,26 @@ static int AquaTheme_Init(
Ttk_RegisterElementSpec(themePtr, "Button.button",
&ButtonElementSpec, &PushButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "InlineButton.button",
+ &ButtonElementSpec, &InlineButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "RoundedRectButton.button",
+ &ButtonElementSpec, &RoundedRectButtonParams);
Ttk_RegisterElementSpec(themePtr, "Checkbutton.button",
&ButtonElementSpec, &CheckBoxParams);
Ttk_RegisterElementSpec(themePtr, "Radiobutton.button",
&ButtonElementSpec, &RadioButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "RecessedButton.button",
+ &ButtonElementSpec, &RecessedButtonParams);
Ttk_RegisterElementSpec(themePtr, "Toolbutton.border",
&ButtonElementSpec, &BevelButtonParams);
Ttk_RegisterElementSpec(themePtr, "Menubutton.button",
&ButtonElementSpec, &PopupButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "DisclosureButton.button",
+ &ButtonElementSpec, &DisclosureButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "HelpButton.button",
+ &ButtonElementSpec, &HelpButtonParams);
+ Ttk_RegisterElementSpec(themePtr, "GradientButton.button",
+ &ButtonElementSpec, &GradientButtonParams);
Ttk_RegisterElementSpec(themePtr, "Spinbox.uparrow",
&SpinButtonUpElementSpec, 0);
Ttk_RegisterElementSpec(themePtr, "Spinbox.downarrow",
@@ -3297,8 +3609,12 @@ static int AquaTheme_Init(
Ttk_RegisterElementSpec(themePtr, "Labelframe.border", &GroupElementSpec,
0);
- Ttk_RegisterElementSpec(themePtr, "Entry.field", &EntryElementSpec, 0);
- Ttk_RegisterElementSpec(themePtr, "Spinbox.field", &EntryElementSpec, 0);
+ Ttk_RegisterElementSpec(themePtr, "Entry.field", &EntryElementSpec,
+ &EntryFieldParams);
+ Ttk_RegisterElementSpec(themePtr, "Searchbox.field", &EntryElementSpec,
+ &SearchboxFieldParams);
+ Ttk_RegisterElementSpec(themePtr, "Spinbox.field", &EntryElementSpec,
+ &EntryFieldParams);
Ttk_RegisterElementSpec(themePtr, "separator", &SeparatorElementSpec, 0);
Ttk_RegisterElementSpec(themePtr, "hseparator", &SeparatorElementSpec, 0);
diff --git a/macosx/ttkMacOSXTheme.h b/macosx/ttkMacOSXTheme.h
new file mode 100644
index 0000000..977c168
--- /dev/null
+++ b/macosx/ttkMacOSXTheme.h
@@ -0,0 +1,563 @@
+/*
+ * ttkMacOSXTheme.h --
+ *
+ * Static data and macros used in ttkMacOSXTheme.c
+ *
+ * Copyright 2019 Marc Culler
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+/*
+ * Macros for handling drawing contexts.
+ */
+
+#define BEGIN_DRAWING(d) { \
+ TkMacOSXDrawingContext dc; \
+ if (!TkMacOSXSetupDrawingContext((d), NULL, &dc)) { \
+ return; \
+ } \
+
+#define END_DRAWING \
+ TkMacOSXRestoreDrawingContext(&dc);}
+
+
+#define HIOrientation kHIThemeOrientationNormal
+#define NoThemeMetric 0xFFFFFFFF
+
+/*
+ * A scale factor used to map a range of non-negative doubles into a large
+ * range of non-negative 32-bit integers without losing too much information.
+ * (Use 1073741824 == 2^30 so we don't get surprised by roundoff.)
+ */
+
+#define RangeToFactor(max) ((double) (1073741824.0) / (max < 1.0 ? 1.0 : max))
+
+/*
+ * Meanings of Ttk states represented by User1 and User2.
+ */
+
+#define TTK_STATE_FIRST_TAB TTK_STATE_USER1
+#define TTK_STATE_LAST_TAB TTK_STATE_USER2
+#define TTK_STATE_IS_ACCENTED TTK_STATE_USER2
+#define TTK_TREEVIEW_STATE_SORTARROW TTK_STATE_USER1
+
+/*
+ * Colors and gradients used when drawing buttons.
+ */
+
+typedef struct GrayColor {
+ CGFloat grayscale;
+ CGFloat alpha;
+} GrayColor;
+
+#define RGBACOLOR static CGFloat
+#define RGBA256(r, g, b, a) {r / 255.0, g / 255.0, b / 255.0, a}
+#define GRAYCOLOR static GrayColor
+#define GRAY256(grayscale) {grayscale / 255.0, 1.0}
+
+/*
+ * Opaque Grays used for Gradient Buttons, Scrollbars and List Headers
+ */
+
+GRAYCOLOR darkDisabledIndicator = GRAY256(122.0);
+GRAYCOLOR lightDisabledIndicator = GRAY256(152.0);
+
+GRAYCOLOR darkGradientNormal = GRAY256(95.0);
+GRAYCOLOR darkGradientPressed = GRAY256(118.0);
+GRAYCOLOR darkGradientDisabled = GRAY256(82.0);
+GRAYCOLOR darkGradientBorder = GRAY256(118.0);
+GRAYCOLOR darkGradientBorderDisabled = GRAY256(94.0);
+GRAYCOLOR lightGradientNormal = GRAY256(244.0);
+GRAYCOLOR lightGradientPressed = GRAY256(175.0);
+GRAYCOLOR lightGradientDisabled = GRAY256(235.0);
+GRAYCOLOR lightGradientBorder = GRAY256(165.0);
+GRAYCOLOR lightGradientBorderDisabled = GRAY256(204.0);
+
+GRAYCOLOR lightTrough = GRAY256(250.0);
+GRAYCOLOR darkTrough = GRAY256(47.0);
+GRAYCOLOR lightInactiveThumb = GRAY256(200.0);
+GRAYCOLOR lightActiveThumb = GRAY256(133.0);
+GRAYCOLOR darkInactiveThumb = GRAY256(117.0);
+GRAYCOLOR darkActiveThumb = GRAY256(158.0);
+
+GRAYCOLOR lightListheaderBorder = GRAY256(200.0);
+GRAYCOLOR listheaderSeparator = GRAY256(220.0);
+GRAYCOLOR listheaderActiveBG = GRAY256(238.0);
+GRAYCOLOR listheaderInactiveBG = GRAY256(246.0);
+
+GRAYCOLOR lightComboSeparator = GRAY256(236.0);
+GRAYCOLOR darkComboSeparator = GRAY256(66.0);
+
+GRAYCOLOR darkTrack = GRAY256(84.0);
+GRAYCOLOR darkInactiveTrack = GRAY256(107.0);
+GRAYCOLOR lightTrack = GRAY256(177.0);
+GRAYCOLOR lightInactiveTrack = GRAY256(139.0);
+
+/*
+ * Transparent Grays
+ */
+
+GRAYCOLOR boxBorder = {1.0, 0.20};
+GRAYCOLOR darkSeparator = {1.0, 0.3};
+GRAYCOLOR darkTabSeparator = {0.0, 0.25};
+GRAYCOLOR darkListheaderBorder = {1.0, 0.125};
+GRAYCOLOR pressedDefaultButton = {0.0, 0.1};
+GRAYCOLOR darkPressedDefaultButton = {1.0, 0.1};
+
+#define CG_WHITE CGColorGetConstantColor(kCGColorWhite)
+
+
+/*
+ * Structures which comprise a database of corner radii and state-dependent
+ * colors used when drawing various types of buttons or entry widgets.
+ */
+
+typedef struct GrayPalette {
+ CGFloat face;
+ CGFloat top;
+ CGFloat side;
+ CGFloat bottom;
+} GrayPalette;
+
+typedef struct PaletteStateTable {
+ GrayPalette light; /* Light palette to use if this entry matches */
+ GrayPalette dark; /* dark palette to use if this entry matches */
+ unsigned int onBits; /* Bits which must be set */
+ unsigned int offBits; /* Bits which must be cleared */
+} PaletteStateTable;
+
+typedef struct ButtonDesign {
+ CGFloat radius;
+ PaletteStateTable palettes[];
+} ButtonDesign;
+
+/*
+ * The data.
+ */
+
+static const ButtonDesign pushbuttonDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 213.0, .side = 210.0, .bottom = 200.0},
+ .dark = {.face = 94.0, .top = 98.0, .side = 94.0, .bottom = 58.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0},
+ {
+ .light = {.face = 205.0, .top = 215.0, .side = 211.0, .bottom = 173.0},
+ .dark = {.face = 140.0, .top = 150.0, .side = 140.0, .bottom = 42.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 198.0, .side = 192.0, .bottom = 173.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign helpDesign = {
+ .radius = 11,
+ .palettes = {
+ {
+ .light = {.face = 241.0, .top = 218.0, .side = 217.0, .bottom = 206.0},
+ .dark = {.face = 83.0, .top = 89.0, .side = 82.0, .bottom = 82.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0},
+ {
+ .light = {.face = 240.0, .top = 208.0, .side = 205.0, .bottom = 188.0},
+ .dark = {.face = 129.0, .top = 121.0, .side = 106.0, .bottom = 106.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 208.0, .side = 205.0, .bottom = 188.0},
+ .dark = {.face = 107.0, .top = 121.0, .side = 106.0, .bottom = 106.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign inlineDesign = {
+ .radius = 8.0,
+ .palettes = {
+ {
+ .light = {.face = 170.0, .top = 170.0, .side = 170.0, .bottom = 170.0},
+ .dark = {.face = 106.0, .top = 106.0, .side = 106.0, .bottom = 106.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0},
+ {
+ .light = {.face = 34.0, .top = 34.0, .side = 34.0, .bottom = 34.0},
+ .dark = {.face = 225.0, .top = 225.0, .side = 225.0, .bottom = 225.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 113.0, .top = 113.0, .side = 113.0, .bottom = 113.0},
+ .dark = {.face = 165.0, .top = 165.0, .side = 165.0, .bottom = 165.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign roundedrectDesign = {
+ .radius = 3.0,
+ .palettes = {
+ {
+ .light = {.face = 256.0, .top = 192.0, .side = 192.0, .bottom = 192.0},
+ .dark = {.face = 256.0, .top = 165.0, .side = 163.0, .bottom = 42.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 204.0, .top = 158.0, .side = 158.0, .bottom = 158.0},
+ .dark = {.face = 85.0, .top = 115.0, .side = 115.0, .bottom = 115.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 205.0, .top = 215.0, .side = 211.0, .bottom = 173.0},
+ .dark = {.face = 140.0, .top = 150.0, .side = 140.0, .bottom = 42.0},
+ .onBits = TTK_STATE_ALTERNATE, .offBits = TTK_STATE_BACKGROUND
+ },
+
+ /*
+ * Gray values > 255 are replaced by the background color.
+ */
+
+ {
+ .light = {.face = 256.0, .top = 158.0, .side = 158.0, .bottom = 158.0},
+ .dark = {.face = 256.0, .top = 115.0, .side = 115.0, .bottom = 115.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign popupDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 213.0, .side = 210.0, .bottom = 200.0},
+ .dark = {.face = 94.0, .top = 98.0, .side = 94.0, .bottom = 58.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 198.0, .side = 192.0, .bottom = 173.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign checkDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 192.0, .side = 199.0, .bottom = 199.0},
+ .dark = {.face = 80.0, .top = 90.0, .side = 80.0, .bottom = 49.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 165.0, .side = 184.0, .bottom = 184.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign radioDesign = {
+ .radius = 8.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 189.0, .side = 198.0, .bottom = 199.0},
+ .dark = {.face = 80.0, .top = 84.0, .side = 88.0, .bottom = 60.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 165.0, .side = 184.0, .bottom = 184.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign recessedDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 117.0, .top = 117.0, .side = 117.0, .bottom = 117.0},
+ .dark = {.face = 129.0, .top = 129.0, .side = 129.0, .bottom = 129.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 182.0, .top = 182.0, .side = 182.0, .bottom = 182.0},
+ .dark = {.face = 105.0, .top = 105.0, .side = 105.0, .bottom = 105.0},
+ .onBits = TTK_STATE_ACTIVE, .offBits = TTK_STATE_SELECTED
+ },
+ {
+ .light = {.face = 145.0, .top = 145.0, .side = 145.0, .bottom = 145.0},
+ .dark = {.face = 166.0, .top = 166.0, .side = 166.0, .bottom = 166.0},
+ .onBits = TTK_STATE_SELECTED, .offBits = 0
+ },
+ {
+ .light = {.face = 256.0, .top = 256.0, .side = 256.0, .bottom = 256.0},
+ .dark = {.face = 256.0, .top = 256.0, .side = 256.0, .bottom = 256.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign incdecDesign = {
+ .radius = 5.0,
+ .palettes = {
+ {
+ .light = {.face = 246.0, .top = 236.0, .side = 227.0, .bottom = 213.0},
+ .dark = {.face = 80.0, .top = 90.0, .side = 80.0, .bottom = 49.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 198.0, .side = 192.0, .bottom = 173.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign bevelDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 213.0, .side = 210.0, .bottom = 200.0},
+ .dark = {.face = 94.0, .top = 98.0, .side = 94.0, .bottom = 58.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 205.0, .top = 215.0, .side = 211.0, .bottom = 173.0},
+ .dark = {.face = 140.0, .top = 150.0, .side = 140.0, .bottom = 42.0},
+ .onBits = TTK_STATE_PRESSED, .offBits = 0
+ },
+ {
+ .light = {.face = 228.0, .top = 215.0, .side = 211.0, .bottom = 173.0},
+ .dark = {.face = 163.0, .top = 150.0, .side = 140.0, .bottom = 42.0},
+ .onBits = TTK_STATE_SELECTED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 198.0, .side = 192.0, .bottom = 173.0},
+ .dark = {.face = 118.0, .top = 132.0, .side = 118.0, .bottom = 48.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign tabDesign = {
+ .radius = 4.0,
+ .palettes = {
+
+ /*
+ * Apple does not have such a thing as a disabled tab. If it is
+ * disabled, it should be removed. But we provide one based on the
+ * disabled button.
+ */
+
+ {
+ .light = {.face = 229.0, .top = 213.0, .side = 242.0, .bottom = 200.0},
+ .dark = {.face = 163.0, .top = 90.0, .side = 80.0, .bottom = 49.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 229.0, .top = 205.0, .side = 211.0, .bottom = 183.0},
+ .dark = {.face = 163.0, .top = 165.0, .side = 163.0, .bottom = 42.0},
+ .onBits = TTK_STATE_SELECTED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 215.0, .side = 211.0, .bottom = 183.0},
+ .dark = {.face = 108.0, .top = 129.0, .side = 108.0, .bottom = 47.0},
+ .onBits = 0, .offBits = 0
+ },
+ }
+};
+
+static const ButtonDesign entryDesign = {
+ .radius = 0.0,
+ .palettes = {
+ {
+ .light = {.face = 256.0, .top = 198.0, .side = 198.0, .bottom = 198.0},
+ .dark = {.face = 256.0, .top = 66.0, .side = 66.0, .bottom = 84.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign searchDesign = {
+ .radius = 3.5,
+ .palettes = {
+ {
+ .light = {.face = 256.0, .top = 198.0, .side = 198.0, .bottom = 198.0},
+ .dark = {.face = 256.0, .top = 66.0, .side = 66.0, .bottom = 84.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign comboDesign = {
+ .radius = 4.0,
+ .palettes = {
+ {
+ .light = {.face = 256.0, .top = 190.0, .side = 190.0, .bottom = 190.0},
+ .dark = {.face = 256.0, .top = 66.0, .side = 66.0, .bottom = 90.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+static const ButtonDesign sliderDesign = {
+ .radius = 8.0,
+ .palettes = {
+ {
+ .light = {.face = 242.0, .top = 189.0, .side = 198.0, .bottom = 199.0},
+ .dark = {.face = 80.0, .top = 84.0, .side = 88.0, .bottom = 60.0},
+ .onBits = TTK_STATE_DISABLED, .offBits = 0
+ },
+ {
+ .light = {.face = 255.0, .top = 165.0, .side = 184.0, .bottom = 184.0},
+ .dark = {.face = 205.0, .top = 205.0, .side = 205.0, .bottom = 198.0},
+ .onBits = 0, .offBits = 0
+ }
+ }
+};
+
+
+/*
+ * Table mapping Tk states to Appearance manager ThemeStates
+ */
+
+static const Ttk_StateTable ThemeStateTable[] = {
+ {kThemeStateActive, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
+ {kThemeStateUnavailable, TTK_STATE_DISABLED, 0},
+ {kThemeStatePressed, TTK_STATE_PRESSED, 0},
+ {kThemeStateInactive, TTK_STATE_BACKGROUND, 0},
+ {kThemeStateUnavailableInactive, TTK_STATE_DISABLED | TTK_STATE_BACKGROUND, 0},
+ {kThemeStateActive, 0, 0}
+
+ /* Others:
+ * The kThemeStatePressedUp and kThemeStatePressedDown bits indicate
+ * which of the two segments of an IncDec button is being pressed.
+ * We don't use these. kThemeStateRollover roughly corresponds to
+ * TTK_STATE_ACTIVE, but does not do what we want with the help button.
+ *
+ * {kThemeStatePressedUp, 0, 0},
+ * {kThemeStatePressedDown, 0, 0}
+ * {kThemeStateRollover, TTK_STATE_ACTIVE, 0},
+ */
+};
+
+/*
+ * Translation between Ttk and HIToolbox.
+ */
+
+static const Ttk_StateTable ButtonValueTable[] = {
+ {kThemeButtonOff, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
+ {kThemeButtonMixed, TTK_STATE_ALTERNATE, 0},
+ {kThemeButtonOn, TTK_STATE_SELECTED, 0},
+ {kThemeButtonOff, 0, 0}
+};
+
+static const Ttk_StateTable ButtonAdornmentTable[] = {
+ {kThemeAdornmentNone, TTK_STATE_ALTERNATE | TTK_STATE_BACKGROUND, 0},
+ {kThemeAdornmentDefault | kThemeAdornmentFocus,
+ TTK_STATE_ALTERNATE | TTK_STATE_FOCUS, 0},
+ {kThemeAdornmentFocus, TTK_STATE_FOCUS, 0},
+ {kThemeAdornmentDefault, TTK_STATE_ALTERNATE, 0},
+ {kThemeAdornmentNone, 0, 0}
+};
+
+/*
+ * Our enums for button styles not known to HIToolbox.
+ */
+
+#define TkGradientButton 0x8001
+#define TkRoundedRectButton 0x8002
+#define TkRecessedButton 0x8003
+#define TkInlineButton 0x8004
+/*
+ * The struct passed as clientData when drawing Ttk buttons.
+ */
+
+typedef struct {
+ ThemeButtonKind kind;
+ ThemeMetric heightMetric;
+ ThemeMetric widthMetric;
+} ThemeButtonParams;
+
+static ThemeButtonParams
+ PushButtonParams = {kThemePushButton, kThemeMetricPushButtonHeight,
+ NoThemeMetric},
+ CheckBoxParams = {kThemeCheckBox, kThemeMetricCheckBoxHeight,
+ NoThemeMetric},
+ RadioButtonParams = {kThemeRadioButton, kThemeMetricRadioButtonHeight,
+ NoThemeMetric},
+ BevelButtonParams = {kThemeRoundedBevelButton, NoThemeMetric, NoThemeMetric},
+ PopupButtonParams = {kThemePopupButton, kThemeMetricPopupButtonHeight,
+ NoThemeMetric},
+ DisclosureParams = {kThemeDisclosureButton,
+ kThemeMetricDisclosureTriangleHeight,
+ kThemeMetricDisclosureTriangleWidth},
+ DisclosureButtonParams = {kThemeArrowButton,
+ kThemeMetricSmallDisclosureButtonHeight,
+ kThemeMetricSmallDisclosureButtonWidth},
+ HelpButtonParams = {kThemeRoundButtonHelp, kThemeMetricRoundButtonSize,
+ kThemeMetricRoundButtonSize},
+ ListHeaderParams = {kThemeListHeaderButton, kThemeMetricListHeaderHeight,
+ NoThemeMetric},
+ GradientButtonParams = {TkGradientButton, NoThemeMetric, NoThemeMetric},
+ RoundedRectButtonParams = {TkRoundedRectButton, kThemeMetricPushButtonHeight,
+ NoThemeMetric},
+ RecessedButtonParams = {TkRecessedButton, kThemeMetricPushButtonHeight,
+ NoThemeMetric},
+ InlineButtonParams = {TkInlineButton, kThemeMetricPushButtonHeight,
+ NoThemeMetric};
+
+ /*
+ * Others: kThemeDisclosureRight, kThemeDisclosureDown,
+ * kThemeDisclosureLeft
+ */
+
+/*
+ * The struct passed as clientData when drawing Ttk Entry widgets.
+ */
+
+typedef struct {
+ HIThemeFrameKind kind;
+ ThemeMetric heightMetric;
+ ThemeMetric widthMetric;
+} ThemeFrameParams;
+
+static ThemeFrameParams
+ EntryFieldParams = {kHIThemeFrameTextFieldSquare, NoThemeMetric, NoThemeMetric},
+ SearchboxFieldParams = {kHIThemeFrameTextFieldRound, NoThemeMetric, NoThemeMetric};
+
+/*
+ * If we try to draw a rounded rectangle with too large of a radius, the Core
+ * Graphics library will sometimes raise a fatal exception. This macro
+ * protects against this by returning if the width or height is less than
+ * twice the radius. Presumably this only happens when a widget has not yet
+ * been configured and has size 1x1, so there is nothing to draw anyway.
+ */
+
+#define CHECK_RADIUS(radius, bounds) \
+ if ((radius) > (bounds).size.width / 2 || (radius) > (bounds).size.height / 2) { \
+ return; \
+ }
+
+/*
+ * The spinbox widget needs to draw the two arrows in different colors when
+ * one half or the other is being pressed, but the menubutton always draws
+ * them in the same color. This constant is used to distinguish those two
+ * situations.
+ */
+
+#define BOTH_ARROWS 1 << 30
+
+/*
+ * Local Variables:
+ * mode: objc
+ * c-basic-offset: 4
+ * fill-column: 79
+ * coding: utf-8
+ * End:
+ */
diff --git a/tests/ttk/combobox.test b/tests/ttk/combobox.test
index a972164..d5e031f 100644
--- a/tests/ttk/combobox.test
+++ b/tests/ttk/combobox.test
@@ -70,16 +70,24 @@ test combobox-2.end "Cleanup" -body { destroy .cb }
test combobox-3 "Read postoffset value dynamically from current style" -body {
ttk::combobox .cb -values [list a b c] -style "DerivedStyle.TCombobox"
pack .cb -expand true -fill both
+ update idletasks
ttk::style configure DerivedStyle.TCombobox -postoffset [list 25 0 0 0]
+ if {[tk windowingsystem] == "aqua"} {
+ after 500 {
+ pressbutton [expr {[winfo rootx .cb] + 25}] [expr {[winfo rooty .cb] + 25}]
+ }
+ }
ttk::combobox::Post .cb
expr {[winfo rootx .cb.popdown] - [winfo rootx .cb]}
} -result 25 -cleanup {
destroy .cb
}
-test combobox-1890211 "ComboboxSelected event after listbox unposted" -body {
+test combobox-1890211 "ComboboxSelected event after listbox unposted" \
+ -constraints {notAqua} -body {
# whitebox test...
pack [ttk::combobox .cb -values [list a b c]]
+ update idletasks
set result [list]
bind .cb <<ComboboxSelected>> {
lappend result Event [winfo ismapped .cb.popdown] [.cb get]