mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-29 09:18:48 +00:00
55ace147bc
* initial development * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * ci/cd * ci/cd * ci/cd * ci/cd * ci/cd * ci/cd * ci/cd * pokemon * pokemon * pokemon * pokemon * pokemon * pokemon * improvements * improvements * ci/cd * ci/cd * improvements * improvements * improvements * improvements * improvements * improvements * improvements * improvements * improvements --------- Co-authored-by: sdine <sdine@sdine.com>
244 lines
8.5 KiB
C++
244 lines
8.5 KiB
C++
#include "ccm/ui/IconListCtrl.hpp"
|
|
|
|
#include <wx/image.h>
|
|
|
|
#ifdef __WXMSW__
|
|
#include <windows.h>
|
|
#include <commctrl.h>
|
|
#endif
|
|
|
|
namespace ccm::ui {
|
|
|
|
#ifdef __WXMSW__
|
|
|
|
namespace {
|
|
|
|
// Convert a `wxBitmap` to a fresh 32 bpp BGRA DIB section with PREMULTIPLIED
|
|
// alpha, suitable for use as the source bitmap in a `AlphaBlend` call with
|
|
// `AC_SRC_OVER | AC_SRC_ALPHA`.
|
|
//
|
|
// Going through `wxImage` gives us a known-good straight-RGBA payload
|
|
// regardless of how the source `wxBitmap` was originally constructed
|
|
// (notably bitmaps from `wxBitmapBundle::FromSVG`). We then premultiply
|
|
// once, rounded.
|
|
//
|
|
// Math notes:
|
|
// - The rounded form `(c * a + 127) / 255` is required. Plain `c * a` (no
|
|
// divide) overflows the byte and pushes every channel toward 0xFF — the
|
|
// "white icons" regression an earlier dev ran into when they tried to
|
|
// premultiply manually. `(c * a) / 255` is also wrong for `c=a=0xFF`
|
|
// (rounds to 254 instead of 255 and creates 1-bit dimming on opaque
|
|
// pixels). The +127 form is the standard premultiply rounding.
|
|
HBITMAP makePremultipliedDib(const wxBitmap& bmp) {
|
|
if (!bmp.IsOk()) return NULL;
|
|
|
|
wxImage img = bmp.ConvertToImage();
|
|
if (!img.IsOk()) return NULL;
|
|
if (!img.HasAlpha()) img.InitAlpha();
|
|
|
|
const int w = img.GetWidth();
|
|
const int h = img.GetHeight();
|
|
if (w <= 0 || h <= 0) return NULL;
|
|
|
|
BITMAPINFO bi{};
|
|
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bi.bmiHeader.biWidth = w;
|
|
bi.bmiHeader.biHeight = -h; // top-down
|
|
bi.bmiHeader.biPlanes = 1;
|
|
bi.bmiHeader.biBitCount = 32;
|
|
bi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
HDC screenDc = ::GetDC(NULL);
|
|
void* dibPixels = nullptr;
|
|
HBITMAP dib = ::CreateDIBSection(screenDc, &bi, DIB_RGB_COLORS,
|
|
&dibPixels, NULL, 0);
|
|
::ReleaseDC(NULL, screenDc);
|
|
if (dib == NULL || dibPixels == nullptr) {
|
|
if (dib != NULL) ::DeleteObject(dib);
|
|
return NULL;
|
|
}
|
|
|
|
const unsigned char* rgb = img.GetData();
|
|
const unsigned char* alpha = img.GetAlpha();
|
|
auto* dest = static_cast<unsigned char*>(dibPixels);
|
|
const int pixels = w * h;
|
|
const auto premul = [](unsigned c, unsigned a) -> unsigned char {
|
|
return static_cast<unsigned char>((c * a + 127) / 255);
|
|
};
|
|
for (int p = 0; p < pixels; ++p) {
|
|
const unsigned char r = rgb[p * 3 + 0];
|
|
const unsigned char g = rgb[p * 3 + 1];
|
|
const unsigned char b = rgb[p * 3 + 2];
|
|
const unsigned char a = (alpha != nullptr) ? alpha[p] : 255;
|
|
dest[p * 4 + 0] = premul(b, a);
|
|
dest[p * 4 + 1] = premul(g, a);
|
|
dest[p * 4 + 2] = premul(r, a);
|
|
dest[p * 4 + 3] = a;
|
|
}
|
|
return dib;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
IconListCtrl::~IconListCtrl() {
|
|
destroyDibCache();
|
|
}
|
|
|
|
void IconListCtrl::setIconBitmaps(std::vector<wxBitmap> normal,
|
|
std::vector<wxBitmap> selected) {
|
|
normalBmps_ = std::move(normal);
|
|
selectedBmps_ = std::move(selected);
|
|
rebuildDibCache();
|
|
}
|
|
|
|
void IconListCtrl::destroyDibCache() {
|
|
for (void* p : dibBitmaps_) {
|
|
if (p != nullptr) ::DeleteObject(static_cast<HBITMAP>(p));
|
|
}
|
|
dibBitmaps_.clear();
|
|
dibWidth_ = 0;
|
|
dibHeight_ = 0;
|
|
}
|
|
|
|
void IconListCtrl::rebuildDibCache() {
|
|
destroyDibCache();
|
|
if (normalBmps_.empty() || normalBmps_.size() != selectedBmps_.size()) {
|
|
return;
|
|
}
|
|
dibWidth_ = normalBmps_.front().IsOk() ? normalBmps_.front().GetWidth() : 0;
|
|
dibHeight_ = normalBmps_.front().IsOk() ? normalBmps_.front().GetHeight() : 0;
|
|
if (dibWidth_ <= 0 || dibHeight_ <= 0) return;
|
|
|
|
dibBitmaps_.reserve(normalBmps_.size() + selectedBmps_.size());
|
|
for (const auto& b : normalBmps_) {
|
|
dibBitmaps_.push_back(static_cast<void*>(makePremultipliedDib(b)));
|
|
}
|
|
for (const auto& b : selectedBmps_) {
|
|
dibBitmaps_.push_back(static_cast<void*>(makePremultipliedDib(b)));
|
|
}
|
|
}
|
|
|
|
bool IconListCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM* result) {
|
|
auto* hdr = reinterpret_cast<NMHDR*>(lParam);
|
|
if (hdr != nullptr && hdr->code == NM_CUSTOMDRAW) {
|
|
auto* cd = reinterpret_cast<NMLVCUSTOMDRAW*>(lParam);
|
|
switch (cd->nmcd.dwDrawStage) {
|
|
case CDDS_PREPAINT:
|
|
*result = CDRF_NOTIFYITEMDRAW;
|
|
return true;
|
|
|
|
case CDDS_ITEMPREPAINT:
|
|
*result = CDRF_NOTIFYSUBITEMDRAW;
|
|
return true;
|
|
|
|
case CDDS_SUBITEM | CDDS_ITEMPREPAINT: {
|
|
const int col = cd->iSubItem;
|
|
if (col >= firstIconCol_ && col < firstIconCol_ + iconColCount_) {
|
|
// Let the default first paint background/selection, then we
|
|
// overlay the centered icon in POSTPAINT.
|
|
*result = CDRF_NOTIFYPOSTPAINT;
|
|
return true;
|
|
}
|
|
*result = CDRF_DODEFAULT;
|
|
return true;
|
|
}
|
|
|
|
case CDDS_SUBITEM | CDDS_ITEMPOSTPAINT: {
|
|
const int col = cd->iSubItem;
|
|
if (col < firstIconCol_ || col >= firstIconCol_ + iconColCount_) {
|
|
*result = CDRF_DODEFAULT;
|
|
return true;
|
|
}
|
|
if (!predicate_ || dibBitmaps_.empty()) {
|
|
*result = CDRF_DODEFAULT;
|
|
return true;
|
|
}
|
|
const long row = static_cast<long>(cd->nmcd.dwItemSpec);
|
|
const int iconIdx = col - firstIconCol_;
|
|
if (iconIdx < 0 || iconIdx >= iconColCount_) {
|
|
*result = CDRF_DODEFAULT;
|
|
return true;
|
|
}
|
|
if (!predicate_(row, iconIdx)) {
|
|
*result = CDRF_DODEFAULT;
|
|
return true;
|
|
}
|
|
const HWND lcHwnd = reinterpret_cast<HWND>(GetHandle());
|
|
// `cd->nmcd.uItemState & CDIS_SELECTED` is unreliable in the
|
|
// CDDS_SUBITEM | CDDS_ITEMPOSTPAINT stage on Windows — comctl32
|
|
// does not always propagate the item's CDIS_* flags down into
|
|
// sub-item draw stages, so we'd silently fall back to the normal
|
|
// (dark) variant on selected rows. Query LVIS_SELECTED directly
|
|
// off the listview, which is always accurate.
|
|
const UINT lvState = ListView_GetItemState(lcHwnd, row, LVIS_SELECTED);
|
|
const bool selected = (lvState & LVIS_SELECTED) != 0;
|
|
|
|
// Sub-item bounds in client coords. Initialize the request as
|
|
// documented for LVM_GETSUBITEMRECT: rc.top = sub-item index,
|
|
// rc.left = which rect (LVIR_BOUNDS).
|
|
RECT rc;
|
|
rc.top = col;
|
|
rc.left = LVIR_BOUNDS;
|
|
::SendMessageW(lcHwnd,
|
|
LVM_GETSUBITEMRECT,
|
|
static_cast<WPARAM>(row),
|
|
reinterpret_cast<LPARAM>(&rc));
|
|
|
|
const int cellCx = (rc.left + rc.right) / 2;
|
|
const int cellCy = (rc.top + rc.bottom) / 2;
|
|
const int x = cellCx - dibWidth_ / 2;
|
|
const int y = cellCy - dibHeight_ / 2;
|
|
|
|
const std::size_t imgIdx =
|
|
static_cast<std::size_t>(iconIdx) +
|
|
(selected ? static_cast<std::size_t>(iconColCount_) : 0);
|
|
if (imgIdx >= dibBitmaps_.size() || dibBitmaps_[imgIdx] == nullptr) {
|
|
*result = CDRF_DODEFAULT;
|
|
return true;
|
|
}
|
|
HBITMAP src = static_cast<HBITMAP>(dibBitmaps_[imgIdx]);
|
|
|
|
HDC dstDc = cd->nmcd.hdc;
|
|
HDC memDc = ::CreateCompatibleDC(dstDc);
|
|
HGDIOBJ oldBmp = ::SelectObject(memDc, src);
|
|
|
|
BLENDFUNCTION bf{};
|
|
bf.BlendOp = AC_SRC_OVER;
|
|
bf.BlendFlags = 0;
|
|
bf.SourceConstantAlpha = 0xFF;
|
|
bf.AlphaFormat = AC_SRC_ALPHA;
|
|
|
|
::AlphaBlend(dstDc, x, y, dibWidth_, dibHeight_,
|
|
memDc, 0, 0, dibWidth_, dibHeight_, bf);
|
|
|
|
::SelectObject(memDc, oldBmp);
|
|
::DeleteDC(memDc);
|
|
|
|
*result = CDRF_DODEFAULT;
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return wxListCtrl::MSWOnNotify(idCtrl, lParam, result);
|
|
}
|
|
|
|
#else // !__WXMSW__
|
|
|
|
IconListCtrl::~IconListCtrl() = default;
|
|
|
|
void IconListCtrl::setIconBitmaps(std::vector<wxBitmap> normal,
|
|
std::vector<wxBitmap> selected) {
|
|
normalBmps_ = std::move(normal);
|
|
selectedBmps_ = std::move(selected);
|
|
}
|
|
|
|
void IconListCtrl::destroyDibCache() {}
|
|
void IconListCtrl::rebuildDibCache() {}
|
|
|
|
#endif // __WXMSW__
|
|
|
|
} // namespace ccm::ui
|