UI: Improvements and fixes for Windows dark mode (#1728)

This commit is contained in:
Crementif 2025-11-22 23:26:27 +01:00 committed by GitHub
parent 1382ee0381
commit 934cc3eb9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 180 additions and 203 deletions

View file

@ -141,17 +141,15 @@ wxGameList::wxGameList(wxWindow* parent, wxWindowID id)
const auto& config = GetWxGUIConfig();
char transparent_bitmap[kIconWidth * kIconWidth * 4] = {};
memset((void*)transparent_bitmap, wxSystemSettings::GetAppearance().IsDark() ? 0xFF : 0x00, sizeof(transparent_bitmap));
memset(transparent_bitmap, wxSystemSettings::GetAppearance().IsDark() ? 0xFF : 0x00, sizeof(transparent_bitmap));
wxBitmap blank(transparent_bitmap, kIconWidth, kIconWidth);
m_image_list_data = {};
m_image_list_data.emplace_back(wxBitmapBundle::FromBitmap(blank));
wxListCtrl::SetNormalImages(m_image_list_data);
m_image_list_data.Add(blank);
wxListCtrl::SetImageList(&m_image_list_data, wxIMAGE_LIST_NORMAL);
m_image_list_small_data = {};
wxBitmap::Rescale(blank, {kListIconWidth, kListIconWidth});
m_image_list_small_data.emplace_back(wxBitmapBundle::FromBitmap(blank));
wxListCtrl::SetSmallImages(m_image_list_small_data);
m_image_list_small_data.Add(blank);
wxListCtrl::SetImageList(&m_image_list_small_data, wxIMAGE_LIST_SMALL);
InsertColumn(ColumnHiddenName, "", wxLIST_FORMAT_LEFT, 0);
if(config.show_icon_column)
@ -251,13 +249,13 @@ void wxGameList::OnGameListSize(wxSizeEvent &event)
for(int i = GetColumnCount() - 1; i > 0; i--)
{
#ifdef wxHAS_LISTCTRL_COLUMN_ORDER
if(GetColumnWidth(GetColumnIndexFromOrder(i)) > 0)
if(GetColumnWidth(GetColumnIndexFromOrder(i)) > 0)
{
last_col_index = GetColumnIndexFromOrder(i);
break;
}
#else
if(GetColumnWidth(i) > 0)
if(GetColumnWidth(i) > 0)
{
last_col_index = i;
break;
@ -302,12 +300,6 @@ int wxGameList::GetColumnDefaultWidth(int column)
switch (column)
{
case ColumnIcon:
#if __WXMSW__
// note: this is another workaround that could be used to fix the icon offset, but instead of this there's a vcpkg patch for wxWidgets that fixes the icon offset
// wxWidgets offsets the icon in the REPORT view so it's cut off if the column width is set to 64px, see https://github.com/wxWidgets/wxWidgets/blob/09f433faf39aab3f25b3c564b82448bb845fae56/src/msw/listctrl.cpp#L3091
// so add 6px to the column width to compensate, and add another 6px to the right side so that it has equal whitespace on both sides
// return kListIconWidth + 6 + 6;
#endif
return kListIconWidth;
case ColumnName:
return DefaultColumnSize::name;
@ -410,11 +402,13 @@ void wxGameList::SetStyle(Style style, bool save)
switch(style)
{
case Style::kIcons:
wxListCtrl::SetNormalImages(m_image_list_data);
wxListCtrl::SetImageList(&m_image_list_data, wxIMAGE_LIST_NORMAL);
break;
case Style::kSmallIcons:
wxListCtrl::SetImageList(&m_image_list_small_data, wxIMAGE_LIST_NORMAL);
break;
case Style::kList:
wxListCtrl::SetSmallImages(m_image_list_small_data);
wxListCtrl::SetImageList(&m_image_list_small_data, wxIMAGE_LIST_SMALL);
break;
}
@ -443,7 +437,7 @@ long wxGameList::GetStyleFlags(Style style) const
switch (style)
{
case Style::kList:
return (wxLC_SINGLE_SEL | wxLC_REPORT);
return (wxLC_SINGLE_SEL | wxLC_VRULES | wxLC_REPORT);
case Style::kIcons:
return (wxLC_SINGLE_SEL | wxLC_ICON);
case Style::kSmallIcons:
@ -458,25 +452,22 @@ void wxGameList::UpdateItemColors(sint32 startIndex)
{
wxWindowUpdateLocker lock(this);
wxColour bgColourPrimary = GetBackgroundColour();
wxColour bgColourSecondary = wxHelper::CalculateAccentColour(bgColourPrimary);
for (int i = startIndex; i < GetItemCount(); ++i)
{
const auto titleId = (uint64)GetItemData(i);
const uint64 titleId = GetItemData(i);
if (GetConfig().IsGameListFavorite(titleId))
{
SetItemBackgroundColour(i, kFavoriteColor);
SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
}
else if ((i&1) != 0)
else if ((i % 2) != 0)
{
SetItemBackgroundColour(i, bgColourPrimary);
SetItemBackgroundColour(i, kPrimaryColor);
SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
}
else
{
SetItemBackgroundColour(i, bgColourSecondary);
SetItemBackgroundColour(i, kAlternateColor);
SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
}
}
@ -484,7 +475,7 @@ void wxGameList::UpdateItemColors(sint32 startIndex)
static inline int order_to_int(const std::weak_ordering &wo)
{
// no easy conversion seems to exists in C++20
// no easy conversion seems to exist in C++20
if (wo == std::weak_ordering::less)
return -1;
else if (wo == std::weak_ordering::greater)
@ -496,27 +487,24 @@ std::weak_ordering wxGameList::SortComparator(uint64 titleId1, uint64 titleId2,
{
auto titleLastPlayed = [](uint64_t id)
{
iosu::pdm::GameListStat playTimeStat{};
iosu::pdm::GetStatForGamelist(id, playTimeStat);
return playTimeStat;
iosu::pdm::GameListStat playTimeStat{};
iosu::pdm::GetStatForGamelist(id, playTimeStat);
return playTimeStat;
};
auto titlePlayMinutes = [](uint64_t id)
{
iosu::pdm::GameListStat playTimeStat;
if (!iosu::pdm::GetStatForGamelist(id, playTimeStat))
return 0u;
return playTimeStat.numMinutesPlayed;
iosu::pdm::GameListStat playTimeStat;
if (!iosu::pdm::GetStatForGamelist(id, playTimeStat))
return 0u;
return playTimeStat.numMinutesPlayed;
};
auto titleRegion = [](uint64_t id)
{
return CafeTitleList::GetGameInfo(id).GetRegion();
return CafeTitleList::GetGameInfo(id).GetRegion();
};
if (!sortData->asc)
std::swap(titleId1, titleId2);
switch(sortData->column)
{
default:
@ -545,7 +533,7 @@ std::weak_ordering wxGameList::SortComparator(uint64 titleId1, uint64 titleId2,
int wxGameList::SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData)
{
const auto sort_data = (SortData*)sortData;
return order_to_int(sort_data->thisptr->SortComparator((uint64)item1, (uint64)item2, sort_data));
return sort_data->dir * order_to_int(sort_data->thisptr->SortComparator((uint64)item1, (uint64)item2, sort_data));
}
void wxGameList::SortEntries(int column)
@ -569,7 +557,7 @@ void wxGameList::SortEntries(int column)
case ColumnRegion:
case ColumnTitleID:
{
SortData data{this, ItemColumns{column}, ascending};
SortData data{this, ItemColumns{column}, ascending ? 1 : -1};
SortItems(SortFunction, (wxIntPtr)&data);
ShowSortIndicator(column, ascending);
break;
@ -840,8 +828,8 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event)
int icon_small;
if (!QueryIconForTitle(title_id, icon_large, icon_small))
break;
auto icon = m_image_list_data[icon_large];
auto newClipboardData = wxBitmapDataObject(icon.GetBitmap(icon.GetDefaultSize()));
auto icon = m_image_list_data.GetIcon(icon_large);
auto newClipboardData = wxBitmapDataObject(icon);
wxClipboard::Get()->SetData(&newClipboardData);
wxClipboard::Get()->Close();
}
@ -1002,7 +990,7 @@ void wxGameList::ApplyGameListColumnWidths()
const auto& config = GetWxGUIConfig();
wxWindowUpdateLocker lock(this);
if(config.show_icon_column)
SetColumnWidth(ColumnIcon, GetColumnDefaultWidth(ColumnIcon));
SetColumnWidth(ColumnIcon, kListIconWidth);
else
SetColumnWidth(ColumnIcon, 0);
SetColumnWidth(ColumnName, config.column_width.name);
@ -1024,13 +1012,13 @@ void wxGameList::OnColumnBeginResize(wxListEvent& event)
for(int i = GetColumnCount() - 1; i > 0; i--)
{
#ifdef wxHAS_LISTCTRL_COLUMN_ORDER
if(GetColumnWidth(GetColumnIndexFromOrder(i)) > 0)
if(GetColumnWidth(GetColumnIndexFromOrder(i)) > 0)
{
last_col_index = GetColumnIndexFromOrder(i);
break;
}
#else
if(GetColumnWidth(i) > 0)
if(GetColumnWidth(i) > 0)
{
last_col_index = i;
break;
@ -1117,19 +1105,6 @@ void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event)
TitleId baseTitleId = gameInfo.GetBaseTitleId();
bool isNewEntry = false;
if (m_style == Style::kIcons)
{
wxListCtrl::SetNormalImages(m_image_list_data);
}
else if (m_style == Style::kSmallIcons)
{
wxListCtrl::SetSmallImages(m_image_list_small_data);
}
else if (m_style == Style::kList)
{
wxListCtrl::SetSmallImages(m_image_list_small_data);
}
int icon = -1; /* 0 is the default empty icon */
int icon_small = -1; /* 0 is the default empty icon */
QueryIconForTitle(baseTitleId, icon, icon_small);
@ -1175,7 +1150,7 @@ void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event)
wxString minutesText = formatWxString(wxPLURAL("{} minute", "{} minutes", minutes), minutes);
SetItem(index, ColumnGameTime, hoursText + " " + minutesText);
}
// last played
if (playTimeStat.last_played.year != 0)
{
@ -1345,11 +1320,11 @@ void wxGameList::AsyncWorkerThread()
wxMemoryInputStream tmp_stream(tgaData->data(), tgaData->size());
const wxImage image(tmp_stream);
// todo - is wxImageList thread safe?
m_image_list_data.emplace_back(image.Scale(kIconWidth, kIconWidth, wxIMAGE_QUALITY_BICUBIC));
m_image_list_small_data.emplace_back(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC));
int icon = m_image_list_data.Add(image.Scale(kIconWidth, kIconWidth, wxIMAGE_QUALITY_BICUBIC));
int icon_small = m_image_list_small_data.Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC));
// store in cache
m_icon_cache_mtx.lock();
m_icon_cache.try_emplace(titleId, m_image_list_data.size() - 1, m_image_list_small_data.size() - 1);
m_icon_cache.try_emplace(titleId, icon, icon_small);
m_icon_cache_mtx.unlock();
iconSuccessfullyLoaded = true;
}
@ -1390,7 +1365,7 @@ bool wxGameList::QueryIconForTitle(TitleId titleId, int& icon, int& iconSmall)
return true;
}
void wxGameList::DeleteCachedStrings()
void wxGameList::DeleteCachedStrings()
{
m_name_cache.clear();
}
@ -1415,9 +1390,9 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo)
// Obtain and convert icon
[&]()
{
int iconIndex, smallIconIndex;
int iconIdx, smallIconIdx;
if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex))
if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx))
{
cemuLog_log(LogType::Force, "Icon hasn't loaded");
return;
@ -1433,8 +1408,8 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo)
iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value()));
const auto icon = m_image_list_data[iconIndex];
wxBitmap bitmap{icon.GetBitmap(wxDefaultSize)};
const auto icon = m_image_list_data.GetIcon(iconIdx);
wxBitmap bitmap{icon};
wxImage image = bitmap.ConvertToImage();
wxPNGHandler pngHandler;
if (!pngHandler.SaveFile(&image, pngFileStream, false))
@ -1508,9 +1483,9 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo)
// Obtain and convert icon
[&]()
{
int iconIndex, smallIconIndex;
int iconIdx, smallIconIdx;
if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex))
if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx))
{
cemuLog_log(LogType::Force, "Icon hasn't loaded");
return;
@ -1526,8 +1501,8 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo)
iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value()));
const auto icon = m_image_list_data[iconIndex];
wxBitmap bitmap{icon.GetBitmap(wxDefaultSize)};
const auto icon = m_image_list_data.GetIcon(iconIdx);
wxBitmap bitmap{icon};
wxImage image = bitmap.ConvertToImage();
wxPNGHandler pngHandler;
if (!pngHandler.SaveFile(&image, pngFileStream, false))
@ -1635,14 +1610,14 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo)
cemuLog_log(LogType::Force, "Icon hasn't loaded");
return;
}
const auto icon = m_image_list_data[iconIdx];
const auto icon = m_image_list_data.GetIcon(iconIdx);
const auto folder = ActiveSettings::GetUserDataPath("icons");
if (!fs::exists(folder) && !fs::create_directories(folder))
{
cemuLog_log(LogType::Force, "Failed to create icon directory");
return;
}
wxBitmap bitmap{icon.GetBitmap(wxDefaultSize)};
wxBitmap bitmap{icon};
icon_path = folder / fmt::format("{:016x}.ico", titleId);
auto stream = wxFileOutputStream(icon_path->wstring());