#pragma once // IconListCtrl // // `wxListCtrl` subclass that custom-draws icon sub-items so they are // pixel-perfect centered within the cell, matching our themed-header icon // centering exactly even after column resize. // // Why we need this: // - Native MSW `LVS_REPORT` sub-item image rendering anchors the image at // the cell's left edge with a small built-in inset. The header icons in // this app are centered via wx sizers, so the two never line up. // - We override `NM_CUSTOMDRAW` to paint icons ourselves at the exact // center of each icon sub-item rect. // - Default text cell rendering is left untouched. // // Rendering path (MSW): // We keep one premultiplied 32 bpp BGRA DIB section per (iconIdx, selected) // variant and composite it onto the listctrl's HDC with `AlphaBlend` // (`AC_SRC_OVER` + `AC_SRC_ALPHA`) from `NM_CUSTOMDRAW`. We deliberately // do **not** route through `ImageList_Draw` / `HIMAGELIST`: in our test // environment `ImageList_Draw` on an `ILC_COLOR32` list ignored the alpha // channel and the "transparent" canvas around each glyph painted as // opaque black behind the icon — see `ui_wx/AGENTS.md` convention 11. // // Usage: // 1) Construct with the same wxListCtrl flags as before. // 2) Call `setIconColumns(firstIconCol, count)` so the subclass knows which // sub-item indices it owns. // 3) Call `setIconPredicate(...)` with a callback that decides if the icon // should be drawn for a given (row, iconIdx). // 4) Call `setIconBitmaps(normal, selected)` with two equally-sized vectors // of pre-rendered icon bitmaps — one variant per selection state. // // This class is MSW-specific in behavior (NM_CUSTOMDRAW). On other platforms // `MSWOnNotify` is a no-op override and the listctrl falls back to default // rendering — which is fine because this app only ships on Windows. #include #include #include #include #include namespace ccm::ui { class IconListCtrl : public wxListCtrl { public: using wxListCtrl::wxListCtrl; using IconPredicate = std::function; ~IconListCtrl() override; void setIconColumns(int firstIconCol, int iconCount) noexcept { firstIconCol_ = firstIconCol; iconColCount_ = iconCount; } void setIconPredicate(IconPredicate p) { predicate_ = std::move(p); } // Replace the cached icon bitmaps. Both vectors must have the same size // (one entry per icon column). Internal premultiplied DIB cache rebuilds. void setIconBitmaps(std::vector normal, std::vector selected); protected: #ifdef __WXMSW__ bool MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM* result) override; #endif private: int firstIconCol_{-1}; int iconColCount_{0}; IconPredicate predicate_; std::vector normalBmps_; std::vector selectedBmps_; // Premultiplied BGRA DIB sections used as the source for `AlphaBlend`. // Stored as `void*` (HBITMAP) so the header stays free of ``. // Index layout: // [0 .. iconColCount_) -> normal variants // [iconColCount_ .. 2 * iconColCount_) -> selected variants std::vector dibBitmaps_; int dibWidth_{0}; int dibHeight_{0}; void rebuildDibCache(); void destroyDibCache(); }; } // namespace ccm::ui