Attachment "ttk-layout-node-cache.diff" to
ticket [7caf9e9e]
added by
mtmcp_
2026-06-08 17:09:02.
Index: generic/ttk/ttkImage.c
==================================================================
--- generic/ttk/ttkImage.c
+++ generic/ttk/ttkImage.c
@@ -9,23 +9,25 @@
* list is a sequence of statespec/imagename options as per
* [style map].
*/
#include "tkInt.h"
-#include "ttkTheme.h"
+#include "ttkThemeInt.h"
#define MIN(a,b) ((a) < (b) ? (a) : (b))
/*------------------------------------------------------------------------
* +++ ImageSpec management.
*/
struct TtkImageSpec {
Tk_Image baseImage; /* Base image to use */
+ Tcl_Obj *baseName; /* Name of base image */
Tcl_Size mapCount; /* #state-specific overrides */
Ttk_StateSpec *states; /* array[mapCount] of states ... */
Tk_Image *images; /* ... per-state images to use */
+ Tcl_Obj **names; /* array[mapCount] of image names */
Tk_ImageChangedProc *imageChanged;
void *imageChangedClientData;
};
/* NullImageChanged --
@@ -83,13 +85,15 @@
Tcl_Size objc;
Tcl_Obj **objv;
imageSpec = (Ttk_ImageSpec *)Tcl_Alloc(sizeof(*imageSpec));
imageSpec->baseImage = 0;
+ imageSpec->baseName = 0;
imageSpec->mapCount = 0;
imageSpec->states = 0;
imageSpec->images = 0;
+ imageSpec->names = 0;
imageSpec->imageChanged = imageChangedProc;
imageSpec->imageChangedClientData = imageChangedClientData;
if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
goto error;
@@ -106,18 +110,21 @@
}
n = (objc - 1) / 2;
imageSpec->states = (Ttk_StateSpec *)Tcl_Alloc(n * sizeof(Ttk_StateSpec));
imageSpec->images = (Tk_Image *)Tcl_Alloc(n * sizeof(Tk_Image));
+ imageSpec->names = (Tcl_Obj **)Tcl_Alloc(n * sizeof(Tcl_Obj *));
/* Get base image:
*/
imageSpec->baseImage = Tk_GetImage(
interp, tkwin, Tcl_GetString(objv[0]), ImageSpecImageChanged, imageSpec);
if (!imageSpec->baseImage) {
goto error;
}
+ imageSpec->baseName = objv[0];
+ Tcl_IncrRefCount(imageSpec->baseName);
/* Extract state and image specifications:
*/
for (i = 0; i < n; ++i) {
Tcl_Obj *stateSpec = objv[2*i + 1];
@@ -132,10 +139,12 @@
imageSpec->images[i] = Tk_GetImage(
interp, tkwin, imageName, NullImageChanged, NULL);
if (imageSpec->images[i] == NULL) {
goto error;
}
+ imageSpec->names[i] = objv[2*i + 2];
+ Tcl_IncrRefCount(imageSpec->names[i]);
imageSpec->mapCount = i+1;
}
return imageSpec;
@@ -151,15 +160,20 @@
{
int i;
for (i=0; i < imageSpec->mapCount; ++i) {
Tk_FreeImage(imageSpec->images[i]);
+ if (imageSpec->names && imageSpec->names[i]) {
+ Tcl_DecrRefCount(imageSpec->names[i]);
+ }
}
if (imageSpec->baseImage) { Tk_FreeImage(imageSpec->baseImage); }
+ if (imageSpec->baseName) { Tcl_DecrRefCount(imageSpec->baseName); }
if (imageSpec->states) { Tcl_Free(imageSpec->states); }
if (imageSpec->images) { Tcl_Free(imageSpec->images); }
+ if (imageSpec->names) { Tcl_Free(imageSpec->names); }
Tcl_Free(imageSpec);
}
/* TtkSelectImage --
@@ -177,10 +191,28 @@
}
}
return imageSpec->baseImage;
}
+#ifndef TK_NO_DOUBLE_BUFFERING
+/* TtkSelectImageName --
+ * Name of the image TtkSelectImage would pick, for photo lookup.
+ */
+static Tcl_Obj *TtkSelectImageName(
+ Ttk_ImageSpec *imageSpec,
+ Ttk_State state)
+{
+ int i;
+ for (i = 0; i < imageSpec->mapCount; ++i) {
+ if (Ttk_StateMatches(state, imageSpec->states+i)) {
+ return imageSpec->names[i];
+ }
+ }
+ return imageSpec->baseName;
+}
+#endif /* !TK_NO_DOUBLE_BUFFERING */
+
/*------------------------------------------------------------------------
* +++ Drawing utilities.
*/
/* LPadding, CPadding, RPadding --
@@ -226,11 +258,11 @@
return;
}
for (x = dst.x; x < dr; x += src.width) {
int cw = MIN(src.width, dr - x);
- for (y = dst.y; y <= db; y += src.height) {
+ for (y = dst.y; y < db; y += src.height) {
int ch = MIN(src.height, db - y);
Tk_RedrawImage(image, src.x, src.y, cw, ch, d, x, y);
}
}
}
@@ -259,25 +291,47 @@
Ttk_Stripe(tkwin, d, image, BPadding(src,p), BPadding(dst,p), p);
}
/*------------------------------------------------------------------------
* +++ Image element definition.
+ *
+ * The image element is a pure renderer (ImageElementDraw tiles into the given
+ * drawable); the per-node cache owns the composited result. This element only
+ * advertises that it is cacheable and reports opacity + epoch via
+ * ImageElementCacheInfo.
*/
typedef struct { /* ClientData for image elements */
Ttk_ImageSpec *imageSpec; /* Image(s) to use */
int minWidth; /* Minimum width; overrides image width */
int minHeight; /* Minimum height; overrides image height */
Ttk_Sticky sticky; /* -stickiness specification */
Ttk_Padding border; /* Fixed border region */
Ttk_Padding padding; /* Internal padding */
+ unsigned epoch; /* Bumps when a source image's pixels change */
#ifdef TILE_07_COMPAT
Ttk_ResourceCache cache; /* Resource cache for images */
Ttk_StateMap imageMap; /* State-based lookup table for images */
#endif
} ImageData;
+
+/* ImageElementImageChanged --
+ * A source image's pixels changed; bump the content epoch.
+ */
+static void ImageElementImageChanged(
+ void *clientData,
+ TCL_UNUSED(int),
+ TCL_UNUSED(int),
+ TCL_UNUSED(int),
+ TCL_UNUSED(int),
+ TCL_UNUSED(int),
+ TCL_UNUSED(int))
+{
+ ImageData *imageData = (ImageData *)clientData;
+ imageData->epoch++;
+}
static void FreeImageData(void *clientData)
{
ImageData *imageData = (ImageData *)clientData;
if (imageData->imageSpec) { TtkFreeImageSpec(imageData->imageSpec); }
@@ -285,10 +339,78 @@
if (imageData->imageMap) { Tcl_DecrRefCount(imageData->imageMap); }
#endif
Tcl_Free(clientData);
}
+#ifndef TK_NO_DOUBLE_BUFFERING
+/* ImageIsOpaque --
+ * Return 1 if the named photo is fully opaque (every pixel alpha 255).
+ * Anything not a readable photo is treated as translucent.
+ */
+static int ImageIsOpaque(Tcl_Interp *interp, Tcl_Obj *imageName)
+{
+ Tk_PhotoHandle photo;
+ Tk_PhotoImageBlock block;
+ int x, y, alphaOff;
+
+ if (interp == NULL || imageName == NULL) {
+ return 0;
+ }
+ photo = Tk_FindPhoto(interp, Tcl_GetString(imageName));
+ if (photo == NULL || !Tk_PhotoGetImage(photo, &block)) {
+ return 0;
+ }
+ if (block.pixelSize < 4) {
+ return 1; /* No alpha channel: opaque. */
+ }
+ alphaOff = block.offset[3];
+ for (y = 0; y < block.height; y++) {
+ unsigned char *pixPtr = block.pixelPtr + y * block.pitch + alphaOff;
+ for (x = 0; x < block.width; x++) {
+ if (*pixPtr != 255) {
+ return 0;
+ }
+ pixPtr += block.pixelSize;
+ }
+ }
+ return 1;
+}
+
+/* ImageElementCacheInfo --
+ * Report the element's content epoch and whether it opaquely covers the
+ * parcel -- the selected image is opaque AND -sticky fills the box. A
+ * non-filling -sticky leaves margins, so it counts as translucent.
+ */
+static void ImageElementCacheInfo(
+ void *clientData,
+ TCL_UNUSED(void *), /* elementRecord */
+ Tk_Window tkwin,
+ Ttk_Box b,
+ Ttk_State state,
+ Ttk_ElementCacheInfo *info)
+{
+ ImageData *imageData = (ImageData *)clientData;
+ Tk_Image image = TtkSelectImage(imageData->imageSpec, tkwin, state);
+ int imgWidth, imgHeight;
+ Ttk_Box dst;
+
+ info->epoch = imageData->epoch;
+ info->opaque = 0;
+
+ if (image == NULL) {
+ return;
+ }
+ Tk_SizeOfImage(image, &imgWidth, &imgHeight);
+ dst = Ttk_StickBox(b, imgWidth, imgHeight, imageData->sticky);
+ if (dst.x == b.x && dst.y == b.y
+ && dst.width == b.width && dst.height == b.height) {
+ info->opaque = ImageIsOpaque(Tk_Interp(tkwin),
+ TtkSelectImageName(imageData->imageSpec, state));
+ }
+}
+#endif /* !TK_NO_DOUBLE_BUFFERING */
+
static void ImageElementSize(
void *clientData,
TCL_UNUSED(void *), /* elementRecord */
TCL_UNUSED(Tk_Window),
TCL_UNUSED(Ttk_State), /* state */
@@ -374,10 +496,11 @@
{ "-border","-height","-padding","-sticky","-width",NULL };
enum { O_BORDER, O_HEIGHT, O_PADDING, O_STICKY, O_WIDTH };
Ttk_ImageSpec *imageSpec = 0;
ImageData *imageData = 0;
+ Ttk_ElementClass *elementClass;
int padding_specified = 0;
Tcl_Size i;
if (objc + 1 < 2) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
@@ -384,16 +507,20 @@
"Must supply a base image", -1));
Tcl_SetErrorCode(interp, "TTK", "IMAGE", "BASE", (char *)NULL);
return TCL_ERROR;
}
- imageSpec = TtkGetImageSpec(interp, Tk_MainWindow(interp), objv[0]);
+ imageData = (ImageData *)Tcl_Alloc(sizeof(*imageData));
+ memset(imageData, 0, sizeof(*imageData));
+
+ imageSpec = TtkGetImageSpecEx(interp, Tk_MainWindow(interp), objv[0],
+ ImageElementImageChanged, imageData);
if (!imageSpec) {
+ Tcl_Free(imageData);
return TCL_ERROR;
}
- imageData = (ImageData *)Tcl_Alloc(sizeof(*imageData));
imageData->imageSpec = imageSpec;
imageData->minWidth = imageData->minHeight = -1;
imageData->sticky = TTK_FILL_BOTH;
imageData->border = imageData->padding = Ttk_UniformPadding(0);
#ifdef TILE_07_COMPAT
@@ -451,15 +578,19 @@
if (Ttk_GetStickyFromObj(interp, objv[i+1], &imageData->sticky)
!= TCL_OK) { goto error; }
}
}
- if (!Ttk_RegisterElement(interp, theme, elementName, &ImageElementSpec,
- imageData))
- {
+ elementClass = Ttk_RegisterElement(interp, theme, elementName,
+ &ImageElementSpec, imageData);
+ if (!elementClass) {
goto error;
}
+#ifndef TK_NO_DOUBLE_BUFFERING
+ TtkSetElementCachePolicy(elementClass, TTK_ELEMENT_CACHEABLE,
+ ImageElementCacheInfo);
+#endif
Ttk_RegisterCleanup(interp, imageData, FreeImageData);
Tcl_SetObjResult(interp, Tcl_NewStringObj(elementName, -1));
return TCL_OK;
Index: generic/ttk/ttkLayout.c
==================================================================
--- generic/ttk/ttkLayout.c
+++ generic/ttk/ttkLayout.c
@@ -526,10 +526,11 @@
unsigned flags; /* Packing and sticky flags */
Ttk_ElementClass *eclass; /* Class record */
Ttk_State state; /* Current state */
Ttk_Box parcel; /* allocated parcel */
Ttk_LayoutNode *next, *child;
+ NodeDrawCache *drawCache; /* Per-node render cache (ttkNodeCache.c) */
};
static Ttk_LayoutNode *Ttk_NewLayoutNode(
unsigned flags, Ttk_ElementClass *elementClass)
{
@@ -538,19 +539,21 @@
node->flags = flags;
node->eclass = elementClass;
node->state = 0u;
node->next = node->child = 0;
node->parcel = Ttk_MakeBox(0,0,0,0);
+ node->drawCache = NULL;
return node;
}
static void Ttk_FreeLayoutNode(Ttk_LayoutNode *node)
{
while (node) {
Ttk_LayoutNode *next = node->next;
Ttk_FreeLayoutNode(node->child);
+ TtkFreeNodeDrawCache(node->drawCache);
Tcl_Free(node);
node = next;
}
}
@@ -1118,13 +1121,17 @@
*/
/*
* Ttk_DrawLayout --
* Draw a layout tree.
+ *
+ * Each node's element is drawn through TtkDrawCachedElement (ttkNodeCache.c),
+ * which owns the per-node render cache. This file only walks the tree.
*/
+
static void Ttk_DrawNodeList(
- Ttk_Layout layout, Ttk_State state, Ttk_LayoutNode *node, Drawable d)
+ NodeDrawContext *ctx, Ttk_State state, Ttk_LayoutNode *node)
{
for (; node; node = node->next)
{
int border = node->flags & TTK_BORDER;
int substate = state;
@@ -1132,27 +1139,31 @@
if (node->flags & TTK_UNIT) {
substate |= node->state;
}
if (node->child && border) {
- Ttk_DrawNodeList(layout, substate, node->child, d);
+ Ttk_DrawNodeList(ctx, substate, node->child);
}
- Ttk_DrawElement(
- node->eclass,
- layout->style,layout->recordPtr,layout->optionTable,layout->tkwin,
- d, node->parcel, state | node->state);
+ TtkDrawCachedElement(ctx, node->eclass, &node->drawCache,
+ node->parcel, state | node->state);
if (node->child && !border) {
- Ttk_DrawNodeList(layout, substate, node->child, d);
+ Ttk_DrawNodeList(ctx, substate, node->child);
}
}
}
void Ttk_DrawLayout(Ttk_Layout layout, Ttk_State state, Drawable d)
{
- Ttk_DrawNodeList(layout, state, layout->root, d);
+ NodeDrawContext *ctx = TtkNodeDrawBegin(
+ layout->style, layout->recordPtr, layout->optionTable,
+ layout->tkwin, d);
+
+ Ttk_DrawNodeList(ctx, state, layout->root);
+
+ TtkNodeDrawEnd(ctx);
}
/*------------------------------------------------------------------------
* +++ Inquiry and modification.
*/
ADDED generic/ttk/ttkNodeCache.c
Index: generic/ttk/ttkNodeCache.c
==================================================================
--- generic/ttk/ttkNodeCache.c
+++ generic/ttk/ttkNodeCache.c
@@ -0,0 +1,258 @@
+/*
+ * ttkNodeCache.c --
+ *
+ * Per-node element *render* cache for the Ttk layout-draw traversal.
+ * ttkLayout.c walks a widget's node tree in z-order and draws each element
+ * through this module, which caches the composited result on the layout
+ * node -- so it is per-widget-instance -- replacing the per-tile redraw
+ * with a single copy on a hit. A node is reused while its size, position,
+ * state and content epoch hold; a translucent node also rebuilds when a
+ * lower node drawn this pass overlaps it, so a changed background shows
+ * through. Invalidation needs no help from widgets or elements.
+ *
+ * Compiled in only where XCopyArea accepts a Pixmap; without double
+ * buffering every entry point degrades to a direct element draw.
+ *
+ * Copyright © 2026 Tk contributors. Freely redistributable.
+ */
+
+#include "tkInt.h"
+#include "ttkThemeInt.h"
+
+/*
+ * NodeDrawContext --
+ * Per-draw-pass state (TtkNodeDrawBegin/End): element-draw parameters, the
+ * destination, a lazy GXcopy GC, and the parcels drawn this pass.
+ */
+struct NodeDrawContext {
+ Ttk_Style style;
+ void *recordPtr;
+ Tk_OptionTable optionTable;
+ Tk_Window tkwin;
+ Drawable d;
+#ifndef TK_NO_DOUBLE_BUFFERING
+ Display *display;
+ GC copyGC; /* Lazily allocated GXcopy GC, or NULL */
+ Ttk_Box *dirty; /* Parcels (re)drawn fresh this pass */
+ int ndirty, dirtycap;
+#endif
+};
+
+#ifndef TK_NO_DOUBLE_BUFFERING
+
+/*
+ * NodeDrawCache --
+ * One cached composited element, hung off a layout node.
+ */
+struct NodeDrawCache {
+ Pixmap pixmap; /* Cached composited element, or None */
+ Display *display; /* Display owning pixmap (for Tk_FreePixmap) */
+ int x, y, w, h; /* Parcel the pixmap was built for */
+ Ttk_State state; /* State the pixmap was built for */
+ int opaque; /* 1: background-independent; 0: bg baked in */
+ unsigned epoch; /* Element content epoch when built */
+ int valid; /* Nonzero when the cache holds the key */
+};
+
+static int BoxesIntersect(Ttk_Box a, Ttk_Box b)
+{
+ return a.x < b.x + b.width && b.x < a.x + a.width
+ && a.y < b.y + b.height && b.y < a.y + a.height;
+}
+
+/* DirtyOverlaps, DirtyAdd --
+ * The parcels drawn this pass; a translucent node rebuilds when one
+ * overlaps its parcel.
+ */
+static int DirtyOverlaps(NodeDrawContext *ctx, Ttk_Box b)
+{
+ int i;
+ for (i = 0; i < ctx->ndirty; ++i) {
+ if (BoxesIntersect(ctx->dirty[i], b)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void DirtyAdd(NodeDrawContext *ctx, Ttk_Box b)
+{
+ if (ctx->ndirty >= ctx->dirtycap) {
+ ctx->dirtycap = ctx->dirtycap ? 2 * ctx->dirtycap : 8;
+ ctx->dirty = (Ttk_Box *)Tcl_Realloc(ctx->dirty,
+ ctx->dirtycap * sizeof(Ttk_Box));
+ }
+ ctx->dirty[ctx->ndirty++] = b;
+}
+
+static GC NodeCopyGC(NodeDrawContext *ctx)
+{
+ if (ctx->copyGC == NULL) {
+ XGCValues gcv;
+ gcv.function = GXcopy;
+ gcv.graphics_exposures = False;
+ ctx->copyGC = Tk_GetGC(ctx->tkwin,
+ GCFunction | GCGraphicsExposures, &gcv);
+ }
+ return ctx->copyGC;
+}
+
+#endif /* !TK_NO_DOUBLE_BUFFERING */
+
+/* TtkNodeDrawBegin --
+ * Open a draw pass; graphics state is allocated lazily.
+ */
+NodeDrawContext *TtkNodeDrawBegin(
+ Ttk_Style style,
+ void *recordPtr,
+ Tk_OptionTable optionTable,
+ Tk_Window tkwin,
+ Drawable d)
+{
+ NodeDrawContext *ctx = (NodeDrawContext *)Tcl_Alloc(sizeof(*ctx));
+
+ ctx->style = style;
+ ctx->recordPtr = recordPtr;
+ ctx->optionTable = optionTable;
+ ctx->tkwin = tkwin;
+ ctx->d = d;
+#ifndef TK_NO_DOUBLE_BUFFERING
+ ctx->display = Tk_Display(tkwin);
+ ctx->copyGC = NULL;
+ ctx->dirty = NULL;
+ ctx->ndirty = ctx->dirtycap = 0;
+#endif
+ return ctx;
+}
+
+/* TtkNodeDrawEnd --
+ * Close a draw pass: free the shared GC and dirty set. Node pixmaps
+ * persist; they are freed by TtkFreeNodeDrawCache.
+ */
+void TtkNodeDrawEnd(NodeDrawContext *ctx)
+{
+#ifndef TK_NO_DOUBLE_BUFFERING
+ if (ctx->copyGC != NULL) {
+ Tk_FreeGC(ctx->display, ctx->copyGC);
+ }
+ if (ctx->dirty != NULL) {
+ Tcl_Free(ctx->dirty);
+ }
+#endif
+ Tcl_Free(ctx);
+}
+
+/* TtkFreeNodeDrawCache --
+ * Free a node's cached pixmap and record; NULL-tolerant.
+ */
+void TtkFreeNodeDrawCache(NodeDrawCache *cache)
+{
+ if (cache == NULL) {
+ return;
+ }
+#ifndef TK_NO_DOUBLE_BUFFERING
+ if (cache->pixmap != None) {
+ Tk_FreePixmap(cache->display, cache->pixmap);
+ }
+ Tcl_Free(cache);
+#endif
+}
+
+/* TtkDrawCachedElement --
+ * Draw one node's element through its per-node cache: copy the cached
+ * pixmap on a hit, else (re)render and store it. Falls back to a direct
+ * draw for non-cacheable elements, an unrealized window, or alloc failure.
+ */
+void TtkDrawCachedElement(
+ NodeDrawContext *ctx,
+ Ttk_ElementClass *eclass,
+ NodeDrawCache **cacheSlot,
+ Ttk_Box b,
+ Ttk_State state)
+{
+ if (b.width <= 0 || b.height <= 0) {
+ return;
+ }
+
+#ifndef TK_NO_DOUBLE_BUFFERING
+ if (Ttk_ElementClassCacheable(eclass)
+ && Tk_WindowId(ctx->tkwin) != None) {
+ Ttk_ElementCacheInfo info;
+ NodeDrawCache *cache = *cacheSlot;
+ GC gc = NULL;
+
+ Ttk_ElementGetCacheInfo(eclass, ctx->style, ctx->recordPtr,
+ ctx->optionTable, ctx->tkwin, b, state, &info);
+
+ /* Hit: size, position, state and epoch match, and -- if translucent
+ * -- nothing beneath changed this pass. */
+ if (cache && cache->valid
+ && cache->x == b.x && cache->y == b.y
+ && cache->w == b.width && cache->h == b.height
+ && cache->state == state
+ && cache->opaque == info.opaque
+ && cache->epoch == info.epoch
+ && (info.opaque || !DirtyOverlaps(ctx, b))
+ && (gc = NodeCopyGC(ctx)) != NULL) {
+ XCopyArea(ctx->display, cache->pixmap, ctx->d, gc,
+ 0, 0, (unsigned)b.width, (unsigned)b.height, b.x, b.y);
+ return; /* clean: parcel not marked dirty */
+ }
+
+ /* Miss: (re)build the node pixmap. */
+ if (cache == NULL) {
+ cache = *cacheSlot = (NodeDrawCache *)Tcl_Alloc(sizeof(*cache));
+ memset(cache, 0, sizeof(*cache));
+ }
+ if (cache->pixmap != None
+ && (cache->w != b.width || cache->h != b.height)) {
+ Tk_FreePixmap(cache->display, cache->pixmap);
+ cache->pixmap = None;
+ }
+ if (cache->pixmap == None) {
+ cache->pixmap = Tk_GetPixmap(ctx->display, Tk_WindowId(ctx->tkwin),
+ b.width, b.height, Tk_Depth(ctx->tkwin));
+ cache->display = ctx->display;
+ }
+ gc = NodeCopyGC(ctx);
+ if (cache->pixmap != None && gc != NULL) {
+ if (!info.opaque) {
+ /* Seed the live background for translucent pixels and
+ * non-filling -sticky margins. */
+ XCopyArea(ctx->display, ctx->d, cache->pixmap, gc,
+ b.x, b.y, (unsigned)b.width, (unsigned)b.height, 0, 0);
+ }
+ Ttk_DrawElement(eclass, ctx->style, ctx->recordPtr,
+ ctx->optionTable, ctx->tkwin, cache->pixmap,
+ Ttk_MakeBox(0, 0, b.width, b.height), state);
+ XCopyArea(ctx->display, cache->pixmap, ctx->d, gc,
+ 0, 0, (unsigned)b.width, (unsigned)b.height, b.x, b.y);
+
+ cache->x = b.x; cache->y = b.y;
+ cache->w = b.width; cache->h = b.height;
+ cache->state = state;
+ cache->opaque = info.opaque;
+ cache->epoch = info.epoch;
+ cache->valid = 1;
+ DirtyAdd(ctx, b);
+ return;
+ }
+
+ /* Pixmap or GC allocation failed: fall through to a direct draw. */
+ if (cache) {
+ cache->valid = 0;
+ }
+ }
+
+ /* Direct draw: non-cacheable element, unrealized window, or alloc fail. */
+ Ttk_DrawElement(eclass, ctx->style, ctx->recordPtr, ctx->optionTable,
+ ctx->tkwin, ctx->d, b, state);
+ DirtyAdd(ctx, b);
+#else
+ (void)cacheSlot;
+ Ttk_DrawElement(eclass, ctx->style, ctx->recordPtr, ctx->optionTable,
+ ctx->tkwin, ctx->d, b, state);
+#endif /* !TK_NO_DOUBLE_BUFFERING */
+}
+
+/*EOF*/
Index: generic/ttk/ttkTheme.c
==================================================================
--- generic/ttk/ttkTheme.c
+++ generic/ttk/ttkTheme.c
@@ -122,10 +122,12 @@
void *clientData; /* Client data passed in at registration time */
void *elementRecord; /* Scratch buffer for element record storage */
int nResources; /* #Element options */
Tcl_Obj **defaultValues; /* Array of option default values */
Tcl_HashTable optMapCache; /* Map: Tk_OptionTable * -> OptionMap */
+ unsigned cacheFlags; /* Render-cache policy (TTK_ELEMENT_* flags) */
+ Ttk_ElementCacheProc *cacheProc; /* Cache-info query, or NULL */
};
/* TTKGetOptionSpec --
* Look up a Tk_OptionSpec by name from a Tk_OptionTable,
* and verify that it's compatible with the specified Tk_OptionType,
@@ -250,12 +252,37 @@
/* Initialize option map cache:
*/
Tcl_InitHashTable(&elementClass->optMapCache, TCL_ONE_WORD_KEYS);
+ /* Render-cache policy: off until the element opts in. */
+ elementClass->cacheFlags = 0;
+ elementClass->cacheProc = NULL;
+
return elementClass;
}
+
+/* TtkSetElementCachePolicy --
+ * Record an element class's render-cache policy and optional cache-info
+ * query. Called once by an element after registration.
+ */
+void TtkSetElementCachePolicy(
+ Ttk_ElementClass *eclass,
+ unsigned cacheFlags,
+ Ttk_ElementCacheProc *cacheProc)
+{
+ eclass->cacheFlags = cacheFlags;
+ eclass->cacheProc = cacheProc;
+}
+
+/* Ttk_ElementClassCacheable --
+ * Return nonzero if the element class opted into per-node render caching.
+ */
+int Ttk_ElementClassCacheable(Ttk_ElementClass *eclass)
+{
+ return (eclass->cacheFlags & TTK_ELEMENT_CACHEABLE) != 0;
+}
/*
* FreeElementClass --
* Release resources associated with an element class record.
*/
@@ -1134,10 +1161,38 @@
}
eclass->specPtr->draw(
eclass->clientData, eclass->elementRecord,
tkwin, d, b, state);
}
+
+/* Ttk_ElementGetCacheInfo --
+ * Query a cacheable element's opacity and content epoch for (tkwin, state).
+ * Defaults to {not opaque, epoch 0} when no cache proc is registered.
+ */
+void
+Ttk_ElementGetCacheInfo(
+ Ttk_ElementClass *eclass,
+ Ttk_Style style,
+ void *recordPtr,
+ Tk_OptionTable optionTable,
+ Tk_Window tkwin,
+ Ttk_Box b,
+ Ttk_State state,
+ Ttk_ElementCacheInfo *info)
+{
+ info->opaque = 0;
+ info->epoch = 0;
+ if (eclass->cacheProc == NULL) {
+ return;
+ }
+ if (!InitializeElementRecord(
+ eclass, style, recordPtr, optionTable, tkwin, state)) {
+ return;
+ }
+ eclass->cacheProc(
+ eclass->clientData, eclass->elementRecord, tkwin, b, state, info);
+}
/*------------------------------------------------------------------------
* +++ 'style' command ensemble procedures.
*/
Index: generic/ttk/ttkThemeInt.h
==================================================================
--- generic/ttk/ttkThemeInt.h
+++ generic/ttk/ttkThemeInt.h
@@ -20,10 +20,52 @@
int *widthPtr, int *heightPtr, Ttk_Padding*);
MODULE_SCOPE void Ttk_DrawElement(
Ttk_ElementClass *, Ttk_Style, void *recordPtr, Tk_OptionTable,
Tk_Window tkwin, Drawable d, Ttk_Box b, Ttk_State state);
+/*
+ * Element render-cache policy. An element class may opt into per-node caching
+ * of its composited output and supply a query reporting its opacity and content
+ * epoch. Off by default.
+ */
+#define TTK_ELEMENT_CACHEABLE 0x1 /* opts into per-node render caching */
+
+/*
+ * Cache info an element reports for the current (tkwin, state): whether it is
+ * fully opaque, and a content epoch that bumps when its own pixels change.
+ */
+typedef struct Ttk_ElementCacheInfo {
+ int opaque; /* element fully opaque (bg-independent) */
+ unsigned epoch; /* bumps when the element's own content changes */
+} Ttk_ElementCacheInfo;
+
+typedef void Ttk_ElementCacheProc(void *clientData, void *elementRecord,
+ Tk_Window tkwin, Ttk_Box b, Ttk_State state, Ttk_ElementCacheInfo *info);
+
+MODULE_SCOPE void TtkSetElementCachePolicy(
+ Ttk_ElementClass *, unsigned cacheFlags, Ttk_ElementCacheProc *);
+MODULE_SCOPE int Ttk_ElementClassCacheable(Ttk_ElementClass *);
+MODULE_SCOPE void Ttk_ElementGetCacheInfo(
+ Ttk_ElementClass *, Ttk_Style, void *recordPtr, Tk_OptionTable,
+ Tk_Window tkwin, Ttk_Box b, Ttk_State state, Ttk_ElementCacheInfo *);
+
+/*
+ * Per-node element render cache (ttkNodeCache.c). The layout-draw traversal
+ * opens a context, draws each node's element through it, and closes it. Both
+ * types are opaque; a layout node stores a NodeDrawCache* slot.
+ */
+typedef struct NodeDrawCache NodeDrawCache;
+typedef struct NodeDrawContext NodeDrawContext;
+
+MODULE_SCOPE NodeDrawContext *TtkNodeDrawBegin(
+ Ttk_Style style, void *recordPtr, Tk_OptionTable optionTable,
+ Tk_Window tkwin, Drawable d);
+MODULE_SCOPE void TtkDrawCachedElement(NodeDrawContext *,
+ Ttk_ElementClass *, NodeDrawCache **cacheSlot, Ttk_Box b, Ttk_State);
+MODULE_SCOPE void TtkNodeDrawEnd(NodeDrawContext *);
+MODULE_SCOPE void TtkFreeNodeDrawCache(NodeDrawCache *);
+
MODULE_SCOPE Tcl_Obj *Ttk_QueryStyle(
Ttk_Style, void *, Tk_OptionTable, const char *, Ttk_State state);
MODULE_SCOPE Ttk_LayoutTemplate Ttk_ParseLayoutTemplate(
Tcl_Interp *, Tcl_Obj *);
Index: unix/Makefile.in
==================================================================
--- unix/Makefile.in
+++ unix/Makefile.in
@@ -394,11 +394,12 @@
tkVisual.o tkWindow.o
TTK_OBJS = \
ttkBlink.o ttkButton.o ttkCache.o ttkClamTheme.o ttkClassicTheme.o \
ttkDefaultTheme.o ttkElements.o ttkEntry.o ttkFrame.o ttkImage.o \
- ttkInit.o ttkLabel.o ttkLayout.o ttkManager.o ttkNotebook.o \
+ ttkInit.o ttkLabel.o ttkLayout.o ttkManager.o ttkNodeCache.o \
+ ttkNotebook.o \
ttkPanedwindow.o ttkProgress.o ttkScale.o ttkScrollbar.o ttkScroll.o \
ttkSeparator.o ttkSquare.o ttkState.o \
ttkTagSet.o ttkTheme.o ttkToggleswitch.o ttkTrace.o ttkTrack.o \
ttkTreeview.o ttkWidget.o ttkStubInit.o
@@ -497,10 +498,11 @@
$(TTK_DIR)/ttkImage.c \
$(TTK_DIR)/ttkInit.c \
$(TTK_DIR)/ttkLabel.c \
$(TTK_DIR)/ttkLayout.c \
$(TTK_DIR)/ttkManager.c \
+ $(TTK_DIR)/ttkNodeCache.c \
$(TTK_DIR)/ttkNotebook.c \
$(TTK_DIR)/ttkPanedwindow.c \
$(TTK_DIR)/ttkProgress.c \
$(TTK_DIR)/ttkScale.c \
$(TTK_DIR)/ttkScrollbar.c \
@@ -1542,10 +1544,13 @@
$(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkLabel.c
ttkLayout.o: $(TTK_DIR)/ttkLayout.c
$(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkLayout.c
+ttkNodeCache.o: $(TTK_DIR)/ttkNodeCache.c
+ $(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkNodeCache.c
+
ttkManager.o: $(TTK_DIR)/ttkManager.c
$(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkManager.c
ttkNotebook.o: $(TTK_DIR)/ttkNotebook.c
$(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkNotebook.c
Index: win/Makefile.in
==================================================================
--- win/Makefile.in
+++ win/Makefile.in
@@ -421,10 +421,11 @@
ttkImage.$(OBJEXT) \
ttkInit.$(OBJEXT) \
ttkLabel.$(OBJEXT) \
ttkLayout.$(OBJEXT) \
ttkManager.$(OBJEXT) \
+ ttkNodeCache.$(OBJEXT) \
ttkNotebook.$(OBJEXT) \
ttkPanedwindow.$(OBJEXT) \
ttkProgress.$(OBJEXT) \
ttkScale.$(OBJEXT) \
ttkScrollbar.$(OBJEXT) \
Index: win/makefile.vc
==================================================================
--- win/makefile.vc
+++ win/makefile.vc
@@ -287,10 +287,11 @@
$(TMP_DIR)\ttkImage.obj \
$(TMP_DIR)\ttkInit.obj \
$(TMP_DIR)\ttkLabel.obj \
$(TMP_DIR)\ttkLayout.obj \
$(TMP_DIR)\ttkManager.obj \
+ $(TMP_DIR)\ttkNodeCache.obj \
$(TMP_DIR)\ttkNotebook.obj \
$(TMP_DIR)\ttkPanedwindow.obj \
$(TMP_DIR)\ttkProgress.obj \
$(TMP_DIR)\ttkScale.obj \
$(TMP_DIR)\ttkScrollbar.obj \