mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-29 09:18:48 +00:00
major: initial release
* 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>
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user