summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcsaba <csaba>2023-02-14 15:28:12 (GMT)
committercsaba <csaba>2023-02-14 15:28:12 (GMT)
commit62ce539ea824a75c4abd4e1e42ec48301344df28 (patch)
treebf07f722f1e227eb4ab18a7e5cee901bc3181bbb
parenta7afbbdccfd1cb779e4197ac089ef7691b9c2f27 (diff)
downloadtk-62ce539ea824a75c4abd4e1e42ec48301344df28.zip
tk-62ce539ea824a75c4abd4e1e42ec48301344df28.tar.gz
tk-62ce539ea824a75c4abd4e1e42ec48301344df28.tar.bz2
Alternative implementation of the ttk::checkbutton and ttk::radiobutton indicators of the clam theme, using SVG images
-rw-r--r--generic/ttk/ttkClamTheme.c302
-rw-r--r--library/ttk/clamTheme.tcl2
2 files changed, 227 insertions, 77 deletions
diff --git a/generic/ttk/ttkClamTheme.c b/generic/ttk/ttkClamTheme.c
index 46e63f9..62a00f8 100644
--- a/generic/ttk/ttkClamTheme.c
+++ b/generic/ttk/ttkClamTheme.c
@@ -281,10 +281,80 @@ static const Ttk_ElementSpec ComboboxFieldElementSpec = {
/*------------------------------------------------------------------------
* +++ Indicator elements for check and radio buttons.
+ *
+ * The SVG images used here are partly based on some icons provided by
+ * the official open source SVG icon library for the Bootstrap project,
+ * licensed under the MIT license (https://opensource.org/licenses/MIT).
+ *
+ * See https://github.com/twbs/icons.
+ */
+
+/*
+ * Indicator image descriptor:
*/
+typedef struct {
+ int width; /* unscaled width */
+ int height; /* unscaled height */
+ const char *const offDataPtr;
+ const char *const onDataPtr;
+} IndicatorSpec;
+
+static const char checkbtnOffData[] = "\
+ <svg width='16' height='16' version='1.1' xmlns='http://www.w3.org/2000/svg'>\n\
+ <path d='m0 0v16h1v-15h15v-1z' fill='#9e9a91'/>\n\
+ <path d='m15 1v14h-14v1h15v-15z' fill='#cfcdc8'/>\n\
+ <rect x='1' y='1' width='14' height='14' fill='#ffffff'/>\n\
+ </svg>";
+
+static const char checkbtnOnData[] = "\
+ <svg width='16' height='16' version='1.1' xmlns='http://www.w3.org/2000/svg'>\n\
+ <path d='m0 0v16h1v-15h15v-1z' fill='#9e9a91'/>\n\
+ <path d='m15 1v14h-14v1h15v-15z' fill='#cfcdc8'/>\n\
+ <rect x='1' y='1' width='14' height='14' fill='#ffffff'/>\n\
+ <path d='m4.6263 4.6262a0.50294 0.50294 0 0 1 0.71217 0l2.6617 2.6627 2.6617-2.6627a0.50358 0.50358 0 0 1 0.71217 0.71217l-2.6627 2.6617 2.6627 2.6617a0.50358 0.50358 0 0 1-0.71217 0.71217l-2.6617-2.6627-2.6617 2.6627a0.50358 0.50358 0 0 1-0.71217-0.71217l2.6627-2.6617-2.6627-2.6617a0.50294 0.50294 0 0 1 0-0.71217z' stroke='#000000' stroke-width='.94154'/>\n\
+ </svg>";
+
+static const IndicatorSpec checkbutton_spec = {
+ 16, 16,
+ checkbtnOffData,
+ checkbtnOnData
+};
+
+static const char radiobtnOffData[] = "\
+ <svg width='16' height='16' version='1.1' xmlns='http://www.w3.org/2000/svg'>\n\
+ <defs>\n\
+ <linearGradient id='linearGradient' x1='7' x2='9' y1='8' y2='8' gradientTransform='rotate(45,8,8)' gradientUnits='userSpaceOnUse'>\n\
+ <stop stop-color='#9e9a91' offset='0'/>\n\
+ <stop stop-color='#cfcdc8' offset='1'/>\n\
+ </linearGradient>\n\
+ </defs>\n\
+ <circle cx='8' cy='8' r='8' fill='url(#linearGradient)'/>\n\
+ <circle cx='8' cy='8' r='7' fill='#ffffff'/>\n\
+ </svg>";
+
+static const char radiobtnOnData[] = "\
+ <svg width='16' height='16' version='1.1' xmlns='http://www.w3.org/2000/svg'>\n\
+ <defs>\n\
+ <linearGradient id='linearGradient' x1='7' x2='9' y1='8' y2='8' gradientTransform='rotate(45,8,8)' gradientUnits='userSpaceOnUse'>\n\
+ <stop stop-color='#9e9a91' offset='0'/>\n\
+ <stop stop-color='#cfcdc8' offset='1'/>\n\
+ </linearGradient>\n\
+ </defs>\n\
+ <circle cx='8' cy='8' r='8' fill='url(#linearGradient)'/>\n\
+ <circle cx='8' cy='8' r='7' fill='#ffffff'/>\n\
+ <circle cx='8' cy='8' r='4' fill='#000000'/>\n\
+ </svg>";
+
+static const IndicatorSpec radiobutton_spec = {
+ 16, 16,
+ radiobtnOffData,
+ radiobtnOnData
+};
typedef struct {
+#if 0
Tcl_Obj *sizeObj;
+#endif
Tcl_Obj *marginObj;
Tcl_Obj *backgroundObj;
Tcl_Obj *foregroundObj;
@@ -293,8 +363,10 @@ typedef struct {
} IndicatorElement;
static const Ttk_ElementOptionSpec IndicatorElementOptions[] = {
+#if 0
{ "-indicatorsize", TK_OPTION_PIXELS,
offsetof(IndicatorElement,sizeObj), "10" },
+#endif
{ "-indicatormargin", TK_OPTION_STRING,
offsetof(IndicatorElement,marginObj), "1" },
{ "-indicatorbackground", TK_OPTION_COLOR,
@@ -308,107 +380,187 @@ static const Ttk_ElementOptionSpec IndicatorElementOptions[] = {
{ NULL, TK_OPTION_BOOLEAN, 0, NULL }
};
+static double scalingFactor;
+
static void IndicatorElementSize(
- void *dummy, void *elementRecord, Tk_Window tkwin,
+ void *clientData, void *elementRecord, Tk_Window tkwin,
int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
{
+ const IndicatorSpec *spec = (const IndicatorSpec *)clientData;
+ Tcl_Interp *interp = Tk_Interp(tkwin);
+ const char *scalingPctPtr;
IndicatorElement *indicator = (IndicatorElement *)elementRecord;
Ttk_Padding margins;
- int size = 10;
- (void)dummy;
(void)paddingPtr;
+ /*
+ * Retrieve the scaling factor (1.0, 1.25, 1.5, ...)
+ */
+ scalingPctPtr = Tcl_GetVar(interp, "::tk::scalingPct", TCL_GLOBAL_ONLY);
+ scalingFactor = (scalingPctPtr == NULL ? 1.0 : atof(scalingPctPtr) / 100);
+
Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &margins);
- Tk_GetPixelsFromObj(NULL, tkwin, indicator->sizeObj, &size);
- *widthPtr = size + Ttk_PaddingWidth(margins);
- *heightPtr = size + Ttk_PaddingHeight(margins);
+ *widthPtr = spec->width * scalingFactor + Ttk_PaddingWidth(margins);
+ *heightPtr = spec->height * scalingFactor + Ttk_PaddingHeight(margins);
}
-static void RadioIndicatorElementDraw(
- void *dummy, void *elementRecord, Tk_Window tkwin,
- Drawable d, Ttk_Box b, unsigned state)
+static void ColorToStr(
+ const XColor *colorPtr, char *colorStr) /* in the format "RRGGBB" */
{
- IndicatorElement *indicator = (IndicatorElement *)elementRecord;
- GC gcb=Ttk_GCForColor(tkwin,indicator->backgroundObj,d);
- GC gcf=Ttk_GCForColor(tkwin,indicator->foregroundObj,d);
- GC gcu=Ttk_GCForColor(tkwin,indicator->upperColorObj,d);
- GC gcl=Ttk_GCForColor(tkwin,indicator->lowerColorObj,d);
- Ttk_Padding padding;
- (void)dummy;
-
- Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &padding);
- b = Ttk_PadBox(b, padding);
+ char str[13];
- XFillArc(Tk_Display(tkwin),d,gcb, b.x,b.y,b.width,b.height, 0,360*64);
- XDrawArc(Tk_Display(tkwin),d,gcl, b.x,b.y,b.width,b.height, 225*64,180*64);
- XDrawArc(Tk_Display(tkwin),d,gcu, b.x,b.y,b.width,b.height, 45*64,180*64);
+ snprintf(str, sizeof(str), "%04x%04x%04x",
+ colorPtr->red, colorPtr->green, colorPtr->blue);
+ snprintf(colorStr, 7, "%.2s%.2s%.2s", str, str + 4, str + 8);
+}
- if (state & TTK_STATE_SELECTED) {
- b = Ttk_PadBox(b,Ttk_UniformPadding(3));
- XFillArc(Tk_Display(tkwin),d,gcf, b.x,b.y,b.width,b.height, 0,360*64);
- XDrawArc(Tk_Display(tkwin),d,gcf, b.x,b.y,b.width,b.height, 0,360*64);
-#if WIN32_XDRAWLINE_HACK
- XDrawArc(Tk_Display(tkwin),d,gcf, b.x,b.y,b.width,b.height, 300*64,360*64);
-#endif
- }
+static void ImageChanged( /* to be passed to Tk_GetImage() */
+ ClientData clientData,
+ int x, int y, int width, int height,
+ int imageWidth, int imageHeight)
+{
+ (void)clientData;
+ (void)x; (void)y; (void)width; (void)height;
+ (void)imageWidth; (void)imageHeight;
}
-static void CheckIndicatorElementDraw(
- void *dummy, void *elementRecord, Tk_Window tkwin,
- Drawable d, Ttk_Box b, unsigned state)
+static void IndicatorElementDraw(
+ void *clientData, void *elementRecord, Tk_Window tkwin,
+ Drawable d, Ttk_Box b, unsigned int state)
{
- Display *display = Tk_Display(tkwin);
IndicatorElement *indicator = (IndicatorElement *)elementRecord;
-
- GC gcb=Ttk_GCForColor(tkwin,indicator->backgroundObj,d);
- GC gcf=Ttk_GCForColor(tkwin,indicator->foregroundObj,d);
- GC gcu=Ttk_GCForColor(tkwin,indicator->upperColorObj,d);
- GC gcl=Ttk_GCForColor(tkwin,indicator->lowerColorObj,d);
Ttk_Padding padding;
- const int w = WIN32_XDRAWLINE_HACK;
- (void)dummy;
+ const IndicatorSpec *spec = (const IndicatorSpec *)clientData;
+
+ char upperBdColorStr[7], lowerBdColorStr[7], bgColorStr[7], fgColorStr[7];
+ unsigned int selected = (state & TTK_STATE_SELECTED);
+ Tcl_Interp *interp = Tk_Interp(tkwin);
+ char imgName[60];
+ Tk_Image img;
+
+ const char *svgDataPtr;
+ size_t svgDataLen;
+ char *svgDataCopy;
+ char *upperBdColorPtr, *lowerBdColorPtr, *bgColorPtr, *fgColorPtr;
+ const char *cmdFmt;
+ size_t scriptSize;
+ char *script;
+ int code;
Ttk_GetPaddingFromObj(NULL, tkwin, indicator->marginObj, &padding);
b = Ttk_PadBox(b, padding);
- XFillRectangle(display,d,gcb, b.x,b.y,b.width,b.height);
- XDrawLine(display,d,gcl,b.x,b.y+b.height,b.x+b.width+w,b.y+b.height);/*S*/
- XDrawLine(display,d,gcl,b.x+b.width,b.y,b.x+b.width,b.y+b.height+w); /*E*/
- XDrawLine(display,d,gcu,b.x,b.y, b.x,b.y+b.height+w); /*W*/
- XDrawLine(display,d,gcu,b.x,b.y, b.x+b.width+w,b.y); /*N*/
-
- if (state & TTK_STATE_SELECTED) {
- int p,q,r,s;
+ /*
+ * Sanity check
+ */
+ if ( b.x < 0
+ || b.y < 0
+ || Tk_Width(tkwin) < b.x + spec->width * scalingFactor
+ || Tk_Height(tkwin) < b.y + spec->height * scalingFactor)
+ {
+ /* Oops! Not enough room to display the image.
+ * Don't draw anything.
+ */
+ return;
+ }
- b = Ttk_PadBox(b,Ttk_UniformPadding(2));
- p = b.x, q = b.y, r = b.x+b.width, s = b.y+b.height;
+ /*
+ * Construct the color strings upperBdColorStr, lowerBdColorStr,
+ * bgColorStr, and fgColorStr
+ */
+ ColorToStr(Tk_GetColorFromObj(tkwin, indicator->upperColorObj),
+ upperBdColorStr);
+ ColorToStr(Tk_GetColorFromObj(tkwin, indicator->lowerColorObj),
+ lowerBdColorStr);
+ ColorToStr(Tk_GetColorFromObj(tkwin, indicator->backgroundObj),
+ bgColorStr);
+ ColorToStr(Tk_GetColorFromObj(tkwin, indicator->foregroundObj),
+ fgColorStr);
- r+=w, s+=w;
- XDrawLine(display, d, gcf, p, q, r, s);
- XDrawLine(display, d, gcf, p+1, q, r, s-1);
- XDrawLine(display, d, gcf, p, q+1, r-1, s);
+ /*
+ * Check whether there is an SVG image for the indicator's
+ * type (0 = checkbtn, 1 = radiobtn) and these color strings
+ */
+ snprintf(imgName, sizeof(imgName),
+ "::tk::icons::indicator_clam%d_%s_%s_%s_%s",
+ spec->offDataPtr == radiobtnOffData,
+ upperBdColorStr, lowerBdColorStr, bgColorStr,
+ selected ? fgColorStr : "XXXXXX");
+ img = Tk_GetImage(interp, tkwin, imgName, ImageChanged, NULL);
+ if (img == NULL) {
+ /*
+ * Determine the SVG data to use for the photo image
+ */
+ svgDataPtr = (selected ? spec->onDataPtr : spec->offDataPtr);
+
+ /*
+ * Copy the string pointed to by svgDataPtr to a newly allocated memory
+ * area svgDataCopy and assign the latter's address to svgDataPtr
+ */
+ svgDataLen = strlen(svgDataPtr);
+ svgDataCopy = (char *)attemptckalloc(svgDataLen + 1);
+ if (svgDataCopy == NULL) {
+ return;
+ }
+ memcpy(svgDataCopy, svgDataPtr, svgDataLen);
+ svgDataCopy[svgDataLen] = '\0';
+ svgDataPtr = svgDataCopy;
+
+ /*
+ * Update the colors within svgDataCopy
+ */
+
+ upperBdColorPtr = strstr(svgDataPtr, "9e9a91");
+ lowerBdColorPtr = strstr(svgDataPtr, "cfcdc8");
+ bgColorPtr = strstr(svgDataPtr, "ffffff");
+ fgColorPtr = strstr(svgDataPtr, "000000");
+
+ assert(upperBdColorPtr);
+ assert(lowerBdColorPtr);
+ assert(bgColorPtr);
+
+ memcpy(upperBdColorPtr, upperBdColorStr, 6);
+ memcpy(lowerBdColorPtr, lowerBdColorStr, 6);
+ memcpy(bgColorPtr, bgColorStr, 6);
+ if (fgColorPtr != NULL) {
+ memcpy(fgColorPtr, fgColorStr, 6);
+ }
- s-=w, q-=w;
- XDrawLine(display, d, gcf, p, s, r, q);
- XDrawLine(display, d, gcf, p+1, s, r, q+1);
- XDrawLine(display, d, gcf, p, s-1, r-1, q);
+ /*
+ * Create an SVG photo image from svgDataCopy
+ */
+ cmdFmt = "image create photo %s -format $::tk::svgFmt -data {%s}";
+ scriptSize = strlen(cmdFmt) + strlen(imgName) + svgDataLen;
+ script = (char *)attemptckalloc(scriptSize);
+ if (script == NULL) {
+ ckfree(svgDataCopy);
+ return;
+ }
+ snprintf(script, scriptSize, cmdFmt, imgName, svgDataCopy);
+ ckfree(svgDataCopy);
+ code = Tcl_EvalEx(interp, script, -1, TCL_EVAL_GLOBAL);
+ ckfree(script);
+ if (code != TCL_OK) {
+ Tcl_BackgroundException(interp, code);
+ return;
+ }
+ img = Tk_GetImage(interp, tkwin, imgName, ImageChanged, NULL);
}
-}
-static const Ttk_ElementSpec RadioIndicatorElementSpec = {
- TK_STYLE_VERSION_2,
- sizeof(IndicatorElement),
- IndicatorElementOptions,
- IndicatorElementSize,
- RadioIndicatorElementDraw
-};
+ /*
+ * Display the image
+ */
+ Tk_RedrawImage(img, 0, 0, spec->width * scalingFactor,
+ spec->height * scalingFactor, d, b.x, b.y);
+ Tk_FreeImage(img);
+}
-static const Ttk_ElementSpec CheckIndicatorElementSpec = {
+static const Ttk_ElementSpec IndicatorElementSpec = {
TK_STYLE_VERSION_2,
sizeof(IndicatorElement),
IndicatorElementOptions,
IndicatorElementSize,
- CheckIndicatorElementDraw
+ IndicatorElementDraw
};
#define MENUBUTTON_ARROW_SIZE 5
@@ -1000,12 +1152,12 @@ TtkClamTheme_Init(Tcl_Interp *interp)
Ttk_RegisterElement(interp,
theme, "rightarrow", &ArrowElementSpec, INT2PTR(ARROW_RIGHT));
- Ttk_RegisterElement(interp,
- theme, "Radiobutton.indicator", &RadioIndicatorElementSpec, NULL);
- Ttk_RegisterElement(interp,
- theme, "Checkbutton.indicator", &CheckIndicatorElementSpec, NULL);
- Ttk_RegisterElement(interp,
- theme, "Menubutton.indicator", &MenuIndicatorElementSpec, NULL);
+ Ttk_RegisterElement(interp, theme, "Checkbutton.indicator",
+ &IndicatorElementSpec, (void *)&checkbutton_spec);
+ Ttk_RegisterElement(interp, theme, "Radiobutton.indicator",
+ &IndicatorElementSpec, (void *)&radiobutton_spec);
+ Ttk_RegisterElement(interp, theme, "Menubutton.indicator",
+ &MenuIndicatorElementSpec, NULL);
Ttk_RegisterElement(interp, theme, "tab", &TabElementSpec, NULL);
Ttk_RegisterElement(interp, theme, "client", &ClientElementSpec, NULL);
diff --git a/library/ttk/clamTheme.tcl b/library/ttk/clamTheme.tcl
index b968677..6379e81 100644
--- a/library/ttk/clamTheme.tcl
+++ b/library/ttk/clamTheme.tcl
@@ -73,12 +73,10 @@ namespace eval ttk::theme::clam {
ttk::style configure TCheckbutton \
-indicatorbackground "#ffffff" \
- -indicatorsize 7.5p \
-indicatormargin {0.75p 0.75p 3p 0.75p} \
-padding 1.5p
ttk::style configure TRadiobutton \
-indicatorbackground "#ffffff" \
- -indicatorsize 7.5p \
-indicatormargin {0.75p 0.75p 3p 0.75p} \
-padding 1.5p
ttk::style map TCheckbutton -indicatorbackground \