Skip to content

Commit

Permalink
Merged branch better-indicators into trunk; a couple of additional vi…
Browse files Browse the repository at this point in the history
…sual improvements
  • Loading branch information
csaba committed Feb 18, 2023
2 parents 5265115 + ffe94d4 commit 2c934f1
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 84 deletions.
302 changes: 224 additions & 78 deletions generic/ttk/ttkClamTheme.c
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,77 @@ 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' fill='#000000' stroke='#000000' stroke-width='.942'/>\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 {
Tcl_Obj *sizeObj;
Tcl_Obj *marginObj;
Tcl_Obj *backgroundObj;
Tcl_Obj *foregroundObj;
Expand All @@ -293,8 +360,6 @@ typedef struct {
} IndicatorElement;

static const Ttk_ElementOptionSpec IndicatorElementOptions[] = {
{ "-indicatorsize", TK_OPTION_PIXELS,
offsetof(IndicatorElement,sizeObj), "10" },
{ "-indicatormargin", TK_OPTION_STRING,
offsetof(IndicatorElement,marginObj), "1" },
{ "-indicatorbackground", TK_OPTION_COLOR,
Expand All @@ -308,107 +373,188 @@ 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);
while (fgColorPtr != NULL) {
memcpy(fgColorPtr, fgColorStr, 6);
fgColorPtr = strstr(fgColorPtr + 6, "000000");
}

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
Expand Down Expand Up @@ -1000,12 +1146,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);
Expand Down
5 changes: 3 additions & 2 deletions generic/ttk/ttkDefaultTheme.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ static const char checkbtnOnData[] = "\
<path d='m14 1v13h-13v1h14v-14z' fill='#d9d9d9'/>\n\
<path d='m15 0v15h-15v1h16v-16z' fill='#eeeeee'/>\n\
<rect x='2' y='2' width='12' height='12' fill='#ffffff'/>\n\
<path d='m10.803 4.969a0.75002 0.75002 0 0 1 1.071 1.05l-3.992 4.9901a0.75002 0.75002 0 0 1-1.08 0.01999l-2.645-2.646a0.75002 0.75002 0 1 1 1.06-1.06l2.094 2.093 3.473-4.4251a0.235 0.235 0 0 1 0.01999-0.021997z' fill='#000000'/>\n\
<path d='m10.857 5.2815a0.49452 0.49452 0 0 1 0.70636 0c0.19295 0.19497 0.19565 0.51003 0.0068 0.70838l-3.9892 4.7158a0.49452 0.49452 0 0 1-0.7185 0.01349l-2.4274-2.4598a0.51071 0.51071 0 0 1 0-0.71513 0.49452 0.49452 0 0 1 0.70636 0l2.059 2.0867 3.6431-4.3346a0.16664 0.16664 0 0 1 0.01349-0.014842z' fill='#000000' stroke='#000000' stroke-width='.7'/>\n\
</svg>";

static const IndicatorSpec checkbutton_spec = {
Expand Down Expand Up @@ -638,8 +638,9 @@ static void IndicatorElementDraw(
memcpy(borderColorPtr, borderColorStr, 6);
memcpy(bgColorPtr, bgColorStr, 6);
memcpy(indicatorColorPtr, indicatorColorStr, 6);
if (fgColorPtr != NULL) {
while (fgColorPtr != NULL) {
memcpy(fgColorPtr, fgColorStr, 6);
fgColorPtr = strstr(fgColorPtr + 6, "000000");
}

/*
Expand Down
2 changes: 0 additions & 2 deletions library/ttk/clamTheme.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
Loading

0 comments on commit 2c934f1

Please sign in to comment.