diff options
author | csaba <csaba> | 2023-02-14 15:28:12 (GMT) |
---|---|---|
committer | csaba <csaba> | 2023-02-14 15:28:12 (GMT) |
commit | 62ce539ea824a75c4abd4e1e42ec48301344df28 (patch) | |
tree | bf07f722f1e227eb4ab18a7e5cee901bc3181bbb | |
parent | a7afbbdccfd1cb779e4197ac089ef7691b9c2f27 (diff) | |
download | tk-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.c | 302 | ||||
-rw-r--r-- | library/ttk/clamTheme.tcl | 2 |
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 \ |