mirror of
https://github.com/citron-neo/emulator.git
synced 2026-07-05 15:21:57 -07:00
feat: Carousel & Controller Nav. SVG's / Added Favorites Column to Grid/Carousel / Various Bug Fixes
This commit is contained in:
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
|
||||
<rect x="0.5" y="6" width="7" height="13" rx="2" fill="#848484"/>
|
||||
<rect x="16.5" y="6" width="7" height="13" rx="2" fill="#848484"/>
|
||||
<rect x="8" y="3" width="8" height="18" rx="2" fill="#FEFEFE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 300 B |
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
|
||||
<rect x="1" y="7" width="22" height="12" rx="5" fill="#FEFEFE"/>
|
||||
<rect x="2.5" y="8.5" width="19" height="9" rx="3.5" fill="#1E1E1E"/>
|
||||
<rect x="4.5" y="11.25" width="5" height="1.5" rx="0.75" fill="#FEFEFE"/>
|
||||
<rect x="6.5" y="9.25" width="1.5" height="5.5" rx="0.75" fill="#FEFEFE"/>
|
||||
<circle cx="15.5" cy="10.75" r="1.1" fill="#FEFEFE"/>
|
||||
<circle cx="17.5" cy="12.5" r="1.1" fill="#FEFEFE"/>
|
||||
<circle cx="15.5" cy="14.25" r="1.1" fill="#FEFEFE"/>
|
||||
<circle cx="13.5" cy="12.5" r="1.1" fill="#FEFEFE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 621 B |
@@ -10,6 +10,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</qresource>
|
||||
<qresource prefix="/">
|
||||
<file alias="dist/dice.svg">../../dist/dice.svg</file>
|
||||
<file alias="dist/carousel.svg">../../dist/carousel.svg</file>
|
||||
<file alias="dist/controller_navigation.svg">../../dist/controller_navigation.svg</file>
|
||||
<file alias="citron.svg">../../dist/citron.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -31,7 +31,8 @@ void GameGridDelegate::setGridMode(GridMode mode) {
|
||||
|
||||
QSize GameGridDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
|
||||
const int icon_size = UISettings::values.game_icon_size.GetValue();
|
||||
return QSize(icon_size + 40, icon_size + 85);
|
||||
const float scale = static_cast<float>(icon_size) / 128.0f;
|
||||
return QSize(icon_size + static_cast<int>(40 * scale), icon_size + static_cast<int>(85 * scale));
|
||||
}
|
||||
|
||||
void GameGridDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
|
||||
@@ -98,6 +99,8 @@ void GameGridDelegate::PaintGridItem(QPainter* painter, const QStyleOptionViewIt
|
||||
const QModelIndex& index) const {
|
||||
const bool is_selected = option.state & QStyle::State_Selected;
|
||||
QRect rect = option.rect;
|
||||
const int icon_size = UISettings::values.game_icon_size.GetValue();
|
||||
const float scale = static_cast<float>(icon_size) / 128.0f;
|
||||
|
||||
qreal entry_val = 1.0;
|
||||
const QPersistentModelIndex key(index);
|
||||
@@ -106,10 +109,12 @@ void GameGridDelegate::PaintGridItem(QPainter* painter, const QStyleOptionViewIt
|
||||
qreal final_opacity = entry_val * m_population_fade_global;
|
||||
painter->setOpacity(final_opacity);
|
||||
|
||||
QRect card_rect = rect.adjusted(12, 12, -12, -12);
|
||||
const int icon_size = UISettings::values.game_icon_size.GetValue();
|
||||
const int card_h = icon_size + 64;
|
||||
card_rect.setHeight(card_h);
|
||||
const int card_w = icon_size + static_cast<int>(16 * scale);
|
||||
const int card_h = icon_size + static_cast<int>(64 * scale);
|
||||
const int cx = rect.x() + (rect.width() - card_w) / 2;
|
||||
QRect card_rect(cx, rect.y() + static_cast<int>(12 * scale), card_w, card_h);
|
||||
|
||||
const int radius = static_cast<int>(14 * scale);
|
||||
|
||||
if (is_selected) {
|
||||
QColor glow = AccentColor();
|
||||
@@ -117,31 +122,53 @@ void GameGridDelegate::PaintGridItem(QPainter* painter, const QStyleOptionViewIt
|
||||
painter->save();
|
||||
painter->setBrush(glow);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->drawRoundedRect(card_rect.adjusted(-4, -4, 4, 4), 16, 16);
|
||||
painter->drawRoundedRect(card_rect.adjusted(static_cast<int>(-4 * scale), static_cast<int>(-4 * scale),
|
||||
static_cast<int>(4 * scale), static_cast<int>(4 * scale)),
|
||||
radius + 2, radius + 2);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QColor onyx(22, 22, 26);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(onyx);
|
||||
painter->drawRoundedRect(card_rect, 14, 14);
|
||||
painter->drawRoundedRect(card_rect, radius, radius);
|
||||
|
||||
if (is_selected) {
|
||||
QColor border = AccentColor();
|
||||
qreal pulse = (m_pulse_states.contains(key)) ? m_pulse_states[key] : 0.0;
|
||||
painter->setPen(QPen(border, 3.5 + pulse * 1.5));
|
||||
painter->setPen(QPen(border, (3.5f + pulse * 1.5f) * scale));
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->drawRoundedRect(card_rect, 14, 14);
|
||||
painter->drawRoundedRect(card_rect, radius, radius);
|
||||
}
|
||||
|
||||
QRectF label_rect = card_rect.adjusted(6, 6, -6, -22);
|
||||
QRectF label_rect = card_rect.adjusted(6 * scale, 6 * scale, -6 * scale, -22 * scale);
|
||||
QPainterPath label_path;
|
||||
label_path.addRoundedRect(label_rect, 8, 8);
|
||||
label_path.addRoundedRect(label_rect, radius - 6, radius - 6);
|
||||
|
||||
// --- Favorites Indicator ---
|
||||
bool is_fav = (index.data(GameListItem::TypeRole).toInt() == static_cast<int>(GameListItemType::Favorites));
|
||||
if (is_fav) {
|
||||
painter->save();
|
||||
QColor fav_gold(255, 215, 0, 220); // Vibrant Gold
|
||||
painter->setPen(QPen(fav_gold, 1.2f * scale));
|
||||
painter->setBrush(QColor(40, 40, 45, 180));
|
||||
qreal star_size = 22.0f * scale;
|
||||
QRectF star_rect(card_rect.right() - (10.0f * scale) - star_size, card_rect.top() + (10.0f * scale),
|
||||
star_size, star_size);
|
||||
painter->drawRoundedRect(star_rect, 6 * scale, 6 * scale);
|
||||
painter->setPen(fav_gold);
|
||||
QFont sf = painter->font(); sf.setBold(true); sf.setPointSizeF(11.0f * scale); painter->setFont(sf);
|
||||
painter->drawText(star_rect.adjusted(0, -1 * scale, 0, 0), Qt::AlignCenter, QStringLiteral("★"));
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
// Removed old vertical divider logic in favor of section headers
|
||||
|
||||
painter->save();
|
||||
painter->setClipPath(label_path);
|
||||
|
||||
QRectF bottom_bar(label_rect.left(), label_rect.bottom() - 28, label_rect.width(), 28);
|
||||
qreal bar_h = 28.0f * scale;
|
||||
QRectF bottom_bar(label_rect.left(), label_rect.bottom() - bar_h, label_rect.width(), bar_h);
|
||||
painter->fillRect(bottom_bar, QColor(10, 10, 12));
|
||||
|
||||
QPixmap pixmap = index.data(GameListItemPath::HighResIconRole).value<QPixmap>();
|
||||
@@ -155,21 +182,21 @@ void GameGridDelegate::PaintGridItem(QPainter* painter, const QStyleOptionViewIt
|
||||
painter->setPen(Qt::white);
|
||||
QFont tf = option.font;
|
||||
tf.setBold(true);
|
||||
tf.setPointSizeF(8.5);
|
||||
tf.setPointSizeF(8.5f * scale);
|
||||
painter->setFont(tf);
|
||||
|
||||
QRectF text_rect = bottom_bar.adjusted(10, 0, -10, 0);
|
||||
QRectF text_rect = bottom_bar.adjusted(10 * scale, 0, -10 * scale, 0);
|
||||
QString elided = painter->fontMetrics().elidedText(title, Qt::ElideRight, text_rect.width());
|
||||
painter->drawText(text_rect, Qt::AlignCenter, elided);
|
||||
|
||||
painter->restore();
|
||||
|
||||
int pins_y = card_rect.bottom() - 10;
|
||||
int pins_y = card_rect.bottom() - static_cast<int>(10 * scale);
|
||||
int p_c = 6;
|
||||
int p_w = (card_rect.width() - 40) / p_c;
|
||||
int p_w = (card_rect.width() - static_cast<int>(40 * scale)) / p_c;
|
||||
painter->setOpacity(0.8 * final_opacity);
|
||||
for (int i = 0; i < p_c; ++i) {
|
||||
painter->fillRect(card_rect.left() + 20 + i * p_w, pins_y, p_w - 6, 6, QColor(40, 40, 45));
|
||||
painter->fillRect(card_rect.left() + static_cast<int>(20 * scale) + i * p_w, pins_y, p_w - static_cast<int>(6 * scale), static_cast<int>(6 * scale), QColor(40, 40, 45));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+154
-127
@@ -721,106 +721,107 @@ void GameList::OnTextChanged(const QString& new_text) {
|
||||
}
|
||||
|
||||
void GameList::FilterGridView(const QString& filter_text) {
|
||||
auto cleanup = [&](QAbstractItemModel* m) { if (m && m != item_model) m->deleteLater(); };
|
||||
cleanup(grid_view->favModel());
|
||||
cleanup(grid_view->mainModel());
|
||||
cleanup(carousel_view->model());
|
||||
|
||||
QStandardItemModel* hierarchical_model = item_model;
|
||||
QStandardItemModel* flat_model = nullptr;
|
||||
|
||||
const u32 icon_size = UISettings::values.game_icon_size.GetValue();
|
||||
QAbstractItemModel* current_model = grid_view->view()->model();
|
||||
if (current_model && current_model != item_model) {
|
||||
QStandardItemModel* existing_flat = qobject_cast<QStandardItemModel*>(current_model);
|
||||
if (existing_flat) {
|
||||
existing_flat->clear();
|
||||
flat_model = existing_flat;
|
||||
}
|
||||
}
|
||||
int visible_count = 0, total_count = 0;
|
||||
|
||||
QStandardItemModel* fav_model = new QStandardItemModel(this);
|
||||
QStandardItemModel* main_model = new QStandardItemModel(this);
|
||||
QStandardItemModel* carousel_model = new QStandardItemModel(this);
|
||||
QSet<QString> fav_paths;
|
||||
QStandardItem* h_fav_folder = nullptr;
|
||||
|
||||
if (!flat_model) {
|
||||
if (current_model && current_model != item_model) {
|
||||
current_model->deleteLater();
|
||||
}
|
||||
flat_model = new QStandardItemModel(this);
|
||||
}
|
||||
int visible_count = 0;
|
||||
int total_count = 0;
|
||||
for (int i = 0; i < hierarchical_model->rowCount(); ++i) {
|
||||
QStandardItem* folder = hierarchical_model->item(i, 0);
|
||||
if (!folder || folder->data(GameListItem::TypeRole).value<GameListItemType>() ==
|
||||
GameListItemType::AddDir) {
|
||||
continue;
|
||||
}
|
||||
for (int j = 0; j < folder->rowCount(); ++j) {
|
||||
QStandardItem* game_item = folder->child(j, 0);
|
||||
if (!game_item || game_item->data(GameListItem::TypeRole).value<GameListItemType>() !=
|
||||
GameListItemType::Game)
|
||||
continue;
|
||||
|
||||
total_count++;
|
||||
const QString full_path = game_item->data(GameListItemPath::FullPathRole).toString();
|
||||
bool should_show = !UISettings::values.hidden_paths.contains(full_path);
|
||||
|
||||
if (should_show && !filter_text.isEmpty()) {
|
||||
const QString file_title =
|
||||
game_item->data(GameListItemPath::TitleRole).toString().toLower();
|
||||
const auto program_id =
|
||||
game_item->data(GameListItemPath::ProgramIdRole).toULongLong();
|
||||
const QString file_program_id =
|
||||
QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char('0'));
|
||||
const QString file_name =
|
||||
full_path.mid(full_path.lastIndexOf(QLatin1Char{'/'}) + 1).toLower() +
|
||||
QLatin1Char{' '} + file_title;
|
||||
should_show =
|
||||
ContainsAllWords(file_name, filter_text) ||
|
||||
(file_program_id.size() == 16 && file_program_id.contains(filter_text));
|
||||
}
|
||||
|
||||
if (should_show) {
|
||||
QStandardItem* cloned_item = game_item->clone();
|
||||
QString game_title = game_item->data(GameListItemPath::TitleRole).toString();
|
||||
if (game_title.isEmpty()) {
|
||||
std::string filename;
|
||||
Common::SplitPath(full_path.toStdString(), nullptr, &filename, nullptr);
|
||||
game_title = QString::fromStdString(filename);
|
||||
}
|
||||
cloned_item->setText(game_title);
|
||||
flat_model->appendRow(cloned_item);
|
||||
visible_count++;
|
||||
}
|
||||
if (hierarchical_model->item(i)->data(GameListItem::TypeRole).value<GameListItemType>() == GameListItemType::Favorites) {
|
||||
h_fav_folder = hierarchical_model->item(i); break;
|
||||
}
|
||||
}
|
||||
grid_view->setModel(flat_model);
|
||||
carousel_view->setModel(flat_model);
|
||||
flat_model->setSortRole(GameListItemPath::SortRole);
|
||||
flat_model->sort(0, current_sort_order);
|
||||
|
||||
for (int i = 0; i < flat_model->rowCount(); ++i) {
|
||||
QStandardItem* item = flat_model->item(i);
|
||||
if (item) {
|
||||
auto cloneTo = [&](QStandardItem* src, QStandardItemModel* target, bool force_fav = false) {
|
||||
QStandardItem* item = src->clone();
|
||||
QString title = src->data(GameListItemPath::TitleRole).toString();
|
||||
if (title.isEmpty()) {
|
||||
std::string fn; Common::SplitPath(src->data(GameListItemPath::FullPathRole).toString().toStdString(), nullptr, &fn, nullptr);
|
||||
title = QString::fromStdString(fn);
|
||||
}
|
||||
item->setText(title);
|
||||
item->setData(title, GameListItemPath::SortRole);
|
||||
if (force_fav) {
|
||||
item->setData(static_cast<int>(GameListItemType::Favorites), GameListItem::TypeRole);
|
||||
} else {
|
||||
item->setData(static_cast<int>(GameListItemType::Game), GameListItem::TypeRole);
|
||||
}
|
||||
target->appendRow(item);
|
||||
};
|
||||
|
||||
auto isFavorite = [&](QStandardItem* item) {
|
||||
u64 pid = item->data(GameListItemPath::ProgramIdRole).toULongLong();
|
||||
return UISettings::values.favorited_ids.contains(pid);
|
||||
};
|
||||
|
||||
if (h_fav_folder) {
|
||||
for (int j = 0; j < h_fav_folder->rowCount(); ++j) {
|
||||
QStandardItem* game = h_fav_folder->child(j, 0);
|
||||
if (!game || game->data(GameListItem::TypeRole).value<GameListItemType>() != GameListItemType::Game) continue;
|
||||
if (!isFavorite(game)) continue;
|
||||
total_count++;
|
||||
QString path = game->data(GameListItemPath::FullPathRole).toString();
|
||||
if (UISettings::values.hidden_paths.contains(path)) continue;
|
||||
if (!filter_text.isEmpty()) {
|
||||
QString title = game->data(GameListItemPath::TitleRole).toString().toLower();
|
||||
if (!ContainsAllWords(title, filter_text.toLower())) continue;
|
||||
}
|
||||
cloneTo(game, fav_model, true);
|
||||
cloneTo(game, carousel_model, true);
|
||||
fav_paths.insert(path);
|
||||
visible_count++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < hierarchical_model->rowCount(); ++i) {
|
||||
QStandardItem* folder = hierarchical_model->item(i);
|
||||
if (!folder || folder->data(GameListItem::TypeRole).value<GameListItemType>() == GameListItemType::AddDir ||
|
||||
folder->data(GameListItem::TypeRole).value<GameListItemType>() == GameListItemType::Favorites) continue;
|
||||
for (int j = 0; j < folder->rowCount(); ++j) {
|
||||
QStandardItem* game = folder->child(j, 0);
|
||||
if (!game || game->data(GameListItem::TypeRole).value<GameListItemType>() != GameListItemType::Game) continue;
|
||||
total_count++;
|
||||
QString path = game->data(GameListItemPath::FullPathRole).toString();
|
||||
if (fav_paths.contains(path) || UISettings::values.hidden_paths.contains(path)) continue;
|
||||
if (!filter_text.isEmpty()) {
|
||||
QString title = game->data(GameListItemPath::TitleRole).toString().toLower();
|
||||
if (!ContainsAllWords(title, filter_text.toLower())) continue;
|
||||
}
|
||||
cloneTo(game, main_model);
|
||||
cloneTo(game, carousel_model);
|
||||
visible_count++;
|
||||
}
|
||||
}
|
||||
|
||||
auto scaleIcons = [&](QStandardItemModel* model) {
|
||||
for (int i = 0; i < model->rowCount(); i++) {
|
||||
QStandardItem* item = model->item(i);
|
||||
QVariant icon_data = item->data(Qt::DecorationRole);
|
||||
if (icon_data.isValid() && icon_data.canConvert<QPixmap>()) {
|
||||
QPixmap pixmap = icon_data.value<QPixmap>();
|
||||
if (!pixmap.isNull()) {
|
||||
#ifdef __linux__
|
||||
QPixmap scaled = pixmap.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
item->setData(scaled, Qt::DecorationRole);
|
||||
#else
|
||||
QPixmap rounded(icon_size, icon_size);
|
||||
rounded.fill(Qt::transparent);
|
||||
QPainter painter(&rounded);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
const int radius = icon_size / 8;
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(0, 0, icon_size, icon_size, radius, radius);
|
||||
painter.setClipPath(path);
|
||||
QPixmap scaled = pixmap.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
painter.drawPixmap(0, 0, scaled);
|
||||
item->setData(rounded, Qt::DecorationRole);
|
||||
#endif
|
||||
item->setData(pixmap.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::DecorationRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
scaleIcons(fav_model); scaleIcons(main_model); scaleIcons(carousel_model);
|
||||
|
||||
fav_model->setSortRole(GameListItemPath::SortRole); fav_model->sort(0, current_sort_order);
|
||||
main_model->setSortRole(GameListItemPath::SortRole); main_model->sort(0, current_sort_order);
|
||||
carousel_model->setSortRole(GameListItemPath::SortRole); // Carousel is usually manual or logic-based, but we sort it for consistency
|
||||
grid_view->setModels(fav_model, main_model);
|
||||
carousel_view->setModel(carousel_model);
|
||||
search_field->setFilterResult(visible_count, total_count);
|
||||
}
|
||||
|
||||
@@ -995,6 +996,7 @@ GameList::GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
||||
|
||||
tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
grid_view->view()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
grid_view->favView()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
carousel_view->view()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
// Load the persistent index instantly to provide a console-grade experience
|
||||
@@ -1003,6 +1005,8 @@ GameList::GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
||||
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
||||
connect(grid_view->view(), &QListView::customContextMenuRequested, this,
|
||||
&GameList::PopupContextMenu);
|
||||
connect(grid_view->favView(), &QListView::customContextMenuRequested, this,
|
||||
&GameList::PopupContextMenu);
|
||||
connect(carousel_view->view(), &QWidget::customContextMenuRequested, this,
|
||||
&GameList::PopupContextMenu);
|
||||
|
||||
@@ -1121,9 +1125,14 @@ GameList::GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
||||
[this]() { SetViewMode(GameList::ViewMode::Grid); });
|
||||
|
||||
btn_carousel_view = new QToolButton(toolbar);
|
||||
QIcon carousel_icon = QIcon::fromTheme(QStringLiteral("view-presentation"));
|
||||
if (carousel_icon.isNull())
|
||||
carousel_icon = QIcon::fromTheme(QStringLiteral("media-playlist-play"));
|
||||
QIcon carousel_icon(QStringLiteral(":/dist/carousel.svg"));
|
||||
if (carousel_icon.isNull()) {
|
||||
carousel_icon = QIcon::fromTheme(QStringLiteral("view-presentation"));
|
||||
if (carousel_icon.isNull())
|
||||
carousel_icon = QIcon::fromTheme(QStringLiteral("media-playlist-play"));
|
||||
if (carousel_icon.isNull())
|
||||
carousel_icon = style()->standardIcon(QStyle::SP_MediaPlay);
|
||||
}
|
||||
btn_carousel_view->setIcon(carousel_icon);
|
||||
btn_carousel_view->setToolTip(tr("Carousel View"));
|
||||
btn_carousel_view->setCheckable(true);
|
||||
@@ -1315,7 +1324,13 @@ GameList::GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
||||
|
||||
// Controller Settings Button
|
||||
auto* btn_controller_settings = new QToolButton(toolbar);
|
||||
btn_controller_settings->setIcon(QIcon::fromTheme(QStringLiteral("input-gaming")));
|
||||
QIcon ctrl_icon(QStringLiteral(":/dist/controller_navigation.svg"));
|
||||
if (ctrl_icon.isNull()) {
|
||||
ctrl_icon = QIcon::fromTheme(QStringLiteral("input-gaming"));
|
||||
if (ctrl_icon.isNull())
|
||||
ctrl_icon = style()->standardIcon(QStyle::SP_ComputerIcon);
|
||||
}
|
||||
btn_controller_settings->setIcon(ctrl_icon);
|
||||
btn_controller_settings->setToolTip(tr("Controller Navigation Settings"));
|
||||
btn_controller_settings->setAutoRaise(true);
|
||||
btn_controller_settings->setIconSize(QSize(16, 16));
|
||||
@@ -1668,7 +1683,8 @@ void GameList::AddEntry(const QList<QStandardItem*>& entry_items, const QString&
|
||||
for (int i = 0; i < parent->rowCount(); ++i) {
|
||||
auto* existing_path_item = static_cast<GameListItemPath*>(parent->child(i, COLUMN_NAME));
|
||||
if (existing_path_item &&
|
||||
(existing_path_item->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id ||
|
||||
(existing_path_item->data(GameListItemPath::ProgramIdRole).toULongLong() ==
|
||||
program_id ||
|
||||
existing_path_item->data(GameListItemPath::FullPathRole).toString() == new_path)) {
|
||||
// Update existing row columns
|
||||
for (int col = 0; col < entry_items.size(); ++col) {
|
||||
@@ -1732,10 +1748,12 @@ void GameList::RefreshGame(u64 program_id, u64 play_time) {
|
||||
|
||||
for (int j = 0; j < folder->rowCount(); ++j) {
|
||||
auto* path_item = static_cast<GameListItemPath*>(folder->child(j, COLUMN_NAME));
|
||||
if (path_item && path_item->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) {
|
||||
if (path_item &&
|
||||
path_item->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) {
|
||||
QStandardItem* play_time_item = folder->child(j, COLUMN_PLAY_TIME);
|
||||
if (play_time_item) {
|
||||
play_time_item->setData(static_cast<qulonglong>(play_time), GameListItemPlayTime::PlayTimeRole);
|
||||
play_time_item->setData(static_cast<qulonglong>(play_time),
|
||||
GameListItemPlayTime::PlayTimeRole);
|
||||
play_time_item->setData(false, GameListItem::IsRefreshingRole);
|
||||
}
|
||||
return;
|
||||
@@ -2199,30 +2217,22 @@ void GameList::DonePopulating(const QStringList& watch_list) {
|
||||
emit ShowList(!IsEmpty());
|
||||
// Only add the "Add Directory" item to the main model for List View.
|
||||
// Grid and Carousel use a flat model and explicitly skip this type.
|
||||
// Deduplicate special rows (Add Directory and Favorites)
|
||||
bool has_add_dir = false;
|
||||
bool has_favorites = false;
|
||||
QStandardItem* fav_folder = nullptr;
|
||||
for (int i = 0; i < item_model->rowCount(); ++i) {
|
||||
auto type = item_model->item(i)->data(GameListItem::TypeRole).value<GameListItemType>();
|
||||
if (type == GameListItemType::AddDir)
|
||||
has_add_dir = true;
|
||||
if (type == GameListItemType::Favorites)
|
||||
has_favorites = true;
|
||||
if (item_model->item(i)->data(GameListItem::TypeRole).value<GameListItemType>() == GameListItemType::Favorites) {
|
||||
fav_folder = item_model->item(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_add_dir) {
|
||||
item_model->invisibleRootItem()->appendRow(new GameListAddDir());
|
||||
}
|
||||
if (!has_favorites) {
|
||||
item_model->invisibleRootItem()->insertRow(0, new GameListFavorites());
|
||||
}
|
||||
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),
|
||||
UISettings::values.favorited_ids.size() == 0);
|
||||
tree_view->setExpanded(item_model->invisibleRootItem()->child(0)->index(),
|
||||
UISettings::values.favorites_expanded.GetValue());
|
||||
for (const auto id : UISettings::values.favorited_ids) {
|
||||
AddFavorite(id);
|
||||
if (!fav_folder) {
|
||||
fav_folder = new GameListFavorites();
|
||||
item_model->invisibleRootItem()->insertRow(0, fav_folder);
|
||||
} else {
|
||||
fav_folder->removeRows(0, fav_folder->rowCount());
|
||||
}
|
||||
tree_view->setRowHidden(fav_folder->row(), item_model->invisibleRootItem()->index(), UISettings::values.favorited_ids.size() == 0);
|
||||
tree_view->setExpanded(fav_folder->index(), UISettings::values.favorites_expanded.GetValue());
|
||||
for (const auto id : UISettings::values.favorited_ids) AddFavorite(id);
|
||||
auto watch_dirs = watcher->directories();
|
||||
if (!watch_dirs.isEmpty()) {
|
||||
watcher->removePaths(watch_dirs);
|
||||
@@ -2281,7 +2291,9 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
||||
if (current == 0) {
|
||||
item = tree_view->indexAt(menu_location);
|
||||
} else if (current == 1) {
|
||||
item = grid_view->indexAt(menu_location);
|
||||
QListView* sending_view = qobject_cast<QListView*>(sender());
|
||||
item = sending_view ? sending_view->indexAt(menu_location)
|
||||
: grid_view->view()->indexAt(menu_location);
|
||||
} else {
|
||||
item = carousel_view->view()->indexAt(menu_location);
|
||||
}
|
||||
@@ -2306,27 +2318,27 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
||||
"QMenu::item:selected { background-color: #32323a; border: 1px solid #42424a; }"
|
||||
"QMenu::separator { height: 1px; background: #32323a; margin: 4px 10px; }"));
|
||||
switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
|
||||
case GameListItemType::Favorites:
|
||||
case GameListItemType::Game: {
|
||||
const u64 program_id = selected.data(GameListItemPath::ProgramIdRole).toULongLong();
|
||||
const std::string path =
|
||||
selected.data(GameListItemPath::FullPathRole).toString().toStdString();
|
||||
const auto game_name = selected.data(GameListItemPath::OriginalTitleRole).toString();
|
||||
AddGamePopup(context_menu, selected, program_id, path, game_name);
|
||||
break;
|
||||
if (program_id != 0 || !path.empty()) {
|
||||
AddGamePopup(context_menu, selected, program_id, path, game_name);
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case GameListItemType::CustomDir:
|
||||
AddPermDirPopup(context_menu, selected);
|
||||
AddCustomDirPopup(context_menu, selected,
|
||||
false); // Pass false to skip adding "Show Hidden Games"
|
||||
AddCustomDirPopup(context_menu, selected, false);
|
||||
break;
|
||||
case GameListItemType::SdmcDir:
|
||||
case GameListItemType::UserNandDir:
|
||||
case GameListItemType::SysNandDir:
|
||||
AddPermDirPopup(context_menu, selected);
|
||||
break;
|
||||
case GameListItemType::Favorites:
|
||||
AddFavoritesPopup(context_menu);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -2334,7 +2346,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
||||
if (current == 0) {
|
||||
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
|
||||
} else if (current == 1) {
|
||||
context_menu.exec(grid_view->view()->viewport()->mapToGlobal(menu_location));
|
||||
QListView* exec_view = qobject_cast<QListView*>(sender());
|
||||
QPoint global_pt = exec_view ? exec_view->viewport()->mapToGlobal(menu_location)
|
||||
: grid_view->view()->viewport()->mapToGlobal(menu_location);
|
||||
context_menu.exec(global_pt);
|
||||
} else {
|
||||
context_menu.exec(carousel_view->view()->viewport()->mapToGlobal(menu_location));
|
||||
}
|
||||
@@ -2848,11 +2863,14 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
|
||||
void GameList::AddFavoritesPopup(QMenu& context_menu) {
|
||||
QAction* clear = context_menu.addAction(tr("Clear"));
|
||||
connect(clear, &QAction::triggered, [this] {
|
||||
for (const auto id : UISettings::values.favorited_ids) {
|
||||
QList<u64> ids_to_remove(UISettings::values.favorited_ids.begin(),
|
||||
UISettings::values.favorited_ids.end());
|
||||
for (const auto id : ids_to_remove) {
|
||||
RemoveFavorite(id);
|
||||
}
|
||||
UISettings::values.favorited_ids.clear();
|
||||
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true);
|
||||
FilterGridView(search_field->filterText());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2957,7 +2975,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs, bool is_sm
|
||||
}
|
||||
auto* delegate = qobject_cast<GameListDelegate*>(tree_view->itemDelegate());
|
||||
if (delegate) {
|
||||
delegate->SetPopulating(true);
|
||||
delegate->SetPopulating(!is_smart_update);
|
||||
}
|
||||
|
||||
if (!is_smart_update) {
|
||||
@@ -3098,8 +3116,17 @@ void GameList::ToggleFavorite(u64 program_id) {
|
||||
}
|
||||
|
||||
void GameList::AddFavorite(u64 program_id) {
|
||||
auto* favorites_row = item_model->item(0);
|
||||
for (int i = 1; i < item_model->rowCount() - 1; i++) {
|
||||
QStandardItem* favorites_row = nullptr;
|
||||
for (int i = 0; i < item_model->rowCount(); ++i) {
|
||||
auto* folder = item_model->item(i, 0);
|
||||
if (folder && folder->data(GameListItem::TypeRole).value<GameListItemType>() == GameListItemType::Favorites) {
|
||||
favorites_row = folder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!favorites_row) return;
|
||||
|
||||
for (int i = 0; i < item_model->rowCount(); i++) {
|
||||
const auto* folder = item_model->item(i);
|
||||
for (int j = 0; j < folder->rowCount(); j++) {
|
||||
if (folder->child(j)->data(GameListItemPath::ProgramIdRole).toULongLong() ==
|
||||
|
||||
+3
-3
@@ -1148,7 +1148,7 @@ void GMainWindow::InitializeWidgets() {
|
||||
const QString accent_dim = accent_color.darker(120).name();
|
||||
|
||||
unified_top_bar_layout = new QHBoxLayout(unified_top_bar);
|
||||
unified_top_bar_layout->setContentsMargins(10, 0, 10, 0);
|
||||
unified_top_bar_layout->setContentsMargins(10, 4, 10, 4);
|
||||
unified_top_bar_layout->setSpacing(0);
|
||||
|
||||
auto add_menu = [this, accent_str](QMenu* menu) {
|
||||
@@ -1170,9 +1170,9 @@ void GMainWindow::InitializeWidgets() {
|
||||
btn->setFlat(true);
|
||||
btn->setFocusPolicy(Qt::NoFocus);
|
||||
btn->setCursor(Qt::PointingHandCursor);
|
||||
btn->setFixedHeight(32);
|
||||
btn->setFixedHeight(42);
|
||||
btn->setStyleSheet(QString::asprintf(
|
||||
"QPushButton { border: none; padding: 0 10px; font-weight: normal; font-size: 8.5pt; "
|
||||
"QPushButton { border: none; padding: 0 14px; font-weight: normal; font-size: 9pt; "
|
||||
"background: transparent; color: #e0e0e4; text-align: center; margin: 0; outline: none; }"
|
||||
"QPushButton:hover { background: rgba(255, 255, 255, 0.05); color: #ffffff; "
|
||||
"border-bottom: 2px solid %s; }"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "citron/ui/game_carousel_view.h"
|
||||
#include "citron/game_list_p.h"
|
||||
#include "citron/uisettings.h"
|
||||
#include "citron/game_list_p.h"
|
||||
|
||||
@@ -100,7 +101,12 @@ void CinematicCarousel::paintEvent(QPaintEvent* event) {
|
||||
if (!m_model || m_model->rowCount() == 0) return;
|
||||
QPainter p(this); p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing);
|
||||
const int count = m_model->rowCount(); const qreal vcx = width() / 2.0; const qreal vcy = height() / 2.0;
|
||||
const int is = UISettings::values.game_icon_size.GetValue(); const qreal bs = is + 35.0; const qreal th = is * 2.0;
|
||||
const int is = UISettings::values.game_icon_size.GetValue();
|
||||
const float scale = static_cast<float>(is) / 128.0f;
|
||||
const qreal bs = is + (35.0f * scale);
|
||||
const qreal th = is * 2.0;
|
||||
const qreal arrow_ay = std::max(55.0, vcy - (1.4 * ((is + static_cast<int>(25 * scale)) / 2.0)) - (28.0f * scale));
|
||||
|
||||
QVector<int> order;
|
||||
for (int i = 0; i < count; ++i) order << i;
|
||||
std::sort(order.begin(), order.end(), [this](int a, int b) { return std::abs(a - m_focal_index) > std::abs(b - m_focal_index); });
|
||||
@@ -111,31 +117,56 @@ void CinematicCarousel::paintEvent(QPaintEvent* event) {
|
||||
if (dist < th) { qreal f = 1.0 - (dist / th); s = 1.0 + (f * 0.40); dx = d * (is / 2.0) * f; }
|
||||
const qreal x = vcx + (d * bs) + dx; const qreal y = vcy;
|
||||
p.save(); p.translate(x, y); p.scale(s, s);
|
||||
const int cs_w = is + 25; const int cs_h = is + 25;
|
||||
const int cs_w = is + static_cast<int>(25 * scale);
|
||||
const int cs_h = is + static_cast<int>(25 * scale);
|
||||
QRectF cr(-cs_w / 2.0, -cs_h / 2.0, cs_w, cs_h);
|
||||
QPainterPath path; path.addRoundedRect(cr, 16, 16);
|
||||
QPainterPath path; path.addRoundedRect(cr, 16 * scale, 16 * scale);
|
||||
const bool focal = std::abs(i - m_focal_index) < 0.5;
|
||||
if (focal) {
|
||||
p.save(); QColor acc = AccentColor(); qreal pulse = (std::sin(m_pulse_tick * 0.1) + 1.0) / 2.0;
|
||||
if (m_has_focus) { p.setPen(QPen(acc, 4.5 + pulse * 1.5)); acc.setAlphaF(static_cast<float>(0.12 + pulse * 0.08)); p.setBrush(acc); }
|
||||
else { acc.setAlphaF(0.4f); p.setPen(QPen(acc, 3.0)); p.setBrush(Qt::NoBrush); }
|
||||
if (m_has_focus) { p.setPen(QPen(acc, (4.5f + pulse * 1.5f) * scale)); acc.setAlphaF(static_cast<float>(0.12 + pulse * 0.08)); p.setBrush(acc); }
|
||||
else { acc.setAlphaF(0.4f); p.setPen(QPen(acc, 3.0f * scale)); p.setBrush(Qt::NoBrush); }
|
||||
p.drawPath(path); p.restore();
|
||||
|
||||
// Draw the alphabetical header ONLY when it changes (category boundary)
|
||||
// 1. Determine Section Boundary & Header Drawing
|
||||
bool draw_header = (i == 0);
|
||||
int type = m_model->index(i, 0).data(GameListItem::TypeRole).toInt();
|
||||
int prev_type = (i > 0) ? m_model->index(i - 1, 0).data(GameListItem::TypeRole).toInt() : -1;
|
||||
bool is_fav = (type == static_cast<int>(GameListItemType::Favorites));
|
||||
bool was_fav = (prev_type == static_cast<int>(GameListItemType::Favorites));
|
||||
|
||||
if (i > 0) {
|
||||
QString t1 = m_model->index(i, 0).data(Qt::DisplayRole).toString();
|
||||
QString t2 = m_model->index(i - 1, 0).data(Qt::DisplayRole).toString();
|
||||
if (t1.isEmpty() || t2.isEmpty() || t1[0].toUpper() != t2[0].toUpper()) draw_header = true;
|
||||
if (is_fav != was_fav) {
|
||||
draw_header = true;
|
||||
} else if (!is_fav) {
|
||||
QString t1 = m_model->index(i, 0).data(Qt::DisplayRole).toString();
|
||||
QString t2 = m_model->index(i - 1, 0).data(Qt::DisplayRole).toString();
|
||||
if (t1.isEmpty() || t2.isEmpty() || t1[0].toUpper() != t2[0].toUpper()) draw_header = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (draw_header) {
|
||||
p.save(); p.resetTransform(); p.setPen(acc); p.setOpacity(0.9);
|
||||
qreal ay = std::max(90.0, y - (s * (cs_h / 2.0)) - 42);
|
||||
QString title = m_model->index(i, 0).data(Qt::DisplayRole).toString();
|
||||
QChar cl = title.isEmpty() ? QLatin1Char('#') : title[0].toUpper();
|
||||
if (!cl.isLetter()) cl = QLatin1Char('#');
|
||||
QFont hf = font(); hf.setBold(true); hf.setPointSizeF(48.0); p.setFont(hf); p.setPen(acc);
|
||||
p.drawText(QRectF(x - 80, ay - 130, 160, 100), Qt::AlignCenter, cl); p.restore();
|
||||
qreal header_ay = arrow_ay - (15.0f * scale);
|
||||
|
||||
if (is_fav) {
|
||||
QFont hf = font(); hf.setBold(true); hf.setPointSizeF(24.0f * scale); p.setFont(hf);
|
||||
p.drawText(QRectF(x - 150 * scale, header_ay - 90 * scale, 300 * scale, 60 * scale), Qt::AlignCenter, tr("★ FAVORITES"));
|
||||
} else {
|
||||
QString title = m_model->index(i, 0).data(Qt::DisplayRole).toString();
|
||||
QChar cl = title.isEmpty() ? QLatin1Char('#') : title[0].toUpper();
|
||||
if (!cl.isLetter()) cl = QLatin1Char('#');
|
||||
QFont hf = font(); hf.setBold(true); hf.setPointSizeF(48.0f * scale); p.setFont(hf);
|
||||
p.drawText(QRectF(x - 80 * scale, header_ay - 110 * scale, 160 * scale, 100 * scale), Qt::AlignCenter, cl);
|
||||
}
|
||||
p.restore();
|
||||
}
|
||||
|
||||
if (i > 0 && was_fav && !is_fav) {
|
||||
p.save(); p.setOpacity(0.4); p.setPen(QPen(acc, 2.5f * scale));
|
||||
qreal dx_line = -bs / 2.0;
|
||||
p.drawLine(QPointF(dx_line, -cs_h / 1.5), QPointF(dx_line, cs_h / 1.5));
|
||||
p.restore();
|
||||
}
|
||||
}
|
||||
if (!focal) { p.setPen(Qt::NoPen); p.setBrush(CardBg()); p.setOpacity(0.85); p.drawPath(path); }
|
||||
@@ -152,10 +183,12 @@ void CinematicCarousel::paintEvent(QPaintEvent* event) {
|
||||
p.restore();
|
||||
}
|
||||
|
||||
// Static Down Arrow Indicator (ALWAYS centered at the top)
|
||||
p.save(); QColor acc = AccentColor(); p.setPen(acc); p.setOpacity(0.9);
|
||||
qreal ay = std::max(55.0, vcy - (1.4 * ((is + 60) / 2.0)) - 28);
|
||||
QPainterPath static_arr; static_arr.moveTo(vcx, ay); static_arr.lineTo(vcx - 12, ay - 12); static_arr.lineTo(vcx + 12, ay - 12); static_arr.closeSubpath();
|
||||
qreal arr_size = 12.0f * scale;
|
||||
QPainterPath static_arr; static_arr.moveTo(vcx, arrow_ay);
|
||||
static_arr.lineTo(vcx - arr_size, arrow_ay - arr_size);
|
||||
static_arr.lineTo(vcx + arr_size, arrow_ay - arr_size);
|
||||
static_arr.closeSubpath();
|
||||
p.drawPath(static_arr); p.restore();
|
||||
const int aw = 60, ah = 60;
|
||||
auto drawArrow = [&](bool left, bool hover) {
|
||||
@@ -176,7 +209,9 @@ void CinematicCarousel::paintEvent(QPaintEvent* event) {
|
||||
void CinematicCarousel::mousePressEvent(QMouseEvent* event) {
|
||||
if (m_left_arrow_hover || m_right_arrow_hover) { m_scroll_timer->start(); return; }
|
||||
if (m_snap_animation->state() == QAbstractAnimation::Running) m_snap_animation->stop();
|
||||
m_last_mouse_pos = event->pos(); m_drag_start_pos = event->pos(); m_is_dragging = true;
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
m_last_mouse_pos = event->pos(); m_drag_start_pos = event->pos(); m_is_dragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CinematicCarousel::mouseMoveEvent(QMouseEvent* event) {
|
||||
@@ -226,14 +261,21 @@ void CinematicCarousel::keyPressEvent(QKeyEvent* event) {
|
||||
if (event->key() == Qt::Key_X || event->key() == Qt::Key_Y) {
|
||||
QModelIndex cur = currentIndex();
|
||||
if (cur.isValid()) {
|
||||
int type = cur.data(GameListItem::TypeRole).toInt();
|
||||
QString title = cur.data(Qt::DisplayRole).toString();
|
||||
QChar cc = title.isEmpty() ? QLatin1Char(' ') : title[0].toUpper();
|
||||
int tot = m_model->rowCount(); int sr = cur.row();
|
||||
for (int i = 1; i <= tot; ++i) {
|
||||
int nr = (sr + i) % tot;
|
||||
QString nt = m_model->index(nr, 0).data(Qt::DisplayRole).toString();
|
||||
QChar nc = nt.isEmpty() ? QLatin1Char(' ') : nt[0].toUpper();
|
||||
if (nc != cc) { scrollTo(nr); return; }
|
||||
QModelIndex nidx = m_model->index(nr, 0);
|
||||
int nt = nidx.data(GameListItem::TypeRole).toInt();
|
||||
if (nt != type) { scrollTo(nr); return; } // Section jump
|
||||
|
||||
if (nt != static_cast<int>(GameListItemType::Favorites)) {
|
||||
QString ntit = nidx.data(Qt::DisplayRole).toString();
|
||||
QChar nc = ntit.isEmpty() ? QLatin1Char(' ') : ntit[0].toUpper();
|
||||
if (nc != cc) { scrollTo(nr); return; } // Alpha jump
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,6 +290,10 @@ void CinematicCarousel::startSnapAnimation(qreal target) { m_snap_animation->sto
|
||||
|
||||
void CinematicCarousel::updateFocalItem() { if (!m_model) return; int idx = std::round(m_focal_index); if (idx >= 0 && idx < m_model->rowCount()) emit focalItemChanged(m_model->index(idx, 0)); }
|
||||
|
||||
void CinematicCarousel::focusOutEvent(QFocusEvent* event) { m_is_dragging = false; m_scroll_timer->stop(); update(); QWidget::focusOutEvent(event); }
|
||||
void CinematicCarousel::leaveEvent(QEvent* event) { m_is_dragging = false; m_left_arrow_hover = false; m_right_arrow_hover = false; m_hover_icon_index = -1; update(); QWidget::leaveEvent(event); }
|
||||
|
||||
|
||||
QColor CinematicCarousel::CardBg() const { return QColor(25, 25, 28, 205); }
|
||||
QColor CinematicCarousel::TextColor() const { return QColor(255, 255, 255); }
|
||||
QColor CinematicCarousel::AccentColor() const { const QString h = QString::fromStdString(UISettings::values.accent_color.GetValue()); return QColor(h).isValid() ? QColor(h) : QColor(0, 150, 255); }
|
||||
|
||||
@@ -52,6 +52,8 @@ protected:
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void focusOutEvent(QFocusEvent* event) override;
|
||||
void leaveEvent(QEvent* event) override;
|
||||
|
||||
private:
|
||||
void startSnapAnimation(qreal target);
|
||||
@@ -91,6 +93,7 @@ public:
|
||||
void ApplyTheme();
|
||||
|
||||
CinematicCarousel* view() const { return m_carousel; }
|
||||
QAbstractItemModel* model() const { return m_carousel->model(); }
|
||||
|
||||
void setControllerFocus(bool focus) { m_carousel->setControllerFocus(focus); }
|
||||
bool hasControllerFocus() const { return m_carousel->hasControllerFocus(); }
|
||||
|
||||
+224
-129
@@ -1,188 +1,230 @@
|
||||
#include <QResizeEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QKeyEvent>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWheelEvent>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QLabel>
|
||||
#include <QItemSelectionModel>
|
||||
|
||||
#include <QScrollArea>
|
||||
#include "citron/ui/game_grid_view.h"
|
||||
#include "citron/game_grid_delegate.h"
|
||||
#include "citron/game_list_p.h"
|
||||
#include "citron/uisettings.h"
|
||||
|
||||
class ContentHeightListView : public QListView {
|
||||
public:
|
||||
using QListView::QListView;
|
||||
protected:
|
||||
void wheelEvent(QWheelEvent* e) override { e->ignore(); }
|
||||
};
|
||||
|
||||
GameGridView::GameGridView(QWidget* parent) : QWidget(parent) {
|
||||
m_layout = new QVBoxLayout(this);
|
||||
m_layout->setContentsMargins(30, 20, 30, 5); // Tighter bottom margin
|
||||
m_scroll_area = new QScrollArea(this);
|
||||
m_scroll_area->setWidgetResizable(true);
|
||||
m_scroll_area->setFrameShape(QFrame::NoFrame);
|
||||
m_scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
m_scroll_area->setStyleSheet(QStringLiteral("QScrollArea { background: transparent; }"));
|
||||
|
||||
m_container = new QWidget(m_scroll_area);
|
||||
m_layout = new QVBoxLayout(m_container);
|
||||
m_layout->setContentsMargins(0, 20, 0, 20);
|
||||
m_layout->setSpacing(0);
|
||||
|
||||
// --- TOP HELP OVERLAY ---
|
||||
m_top_help = new QLabel(this);
|
||||
m_top_help = new QLabel(m_container);
|
||||
m_top_help->setText(tr("if using controller* Press X for Next Alphabetical Letter | Press -/R/ZR for Details Tab | Press B for Back to List"));
|
||||
m_top_help->setStyleSheet(QStringLiteral("QLabel { color: rgba(255, 255, 255, 140); font-weight: bold; font-family: 'Outfit', 'Inter', sans-serif; font-size: 14px; }"));
|
||||
m_top_help->setAlignment(Qt::AlignCenter);
|
||||
m_layout->addSpacing(10);
|
||||
m_layout->addWidget(m_top_help);
|
||||
m_layout->addSpacing(30);
|
||||
m_layout->addSpacing(20);
|
||||
|
||||
m_view = new QListView(this);
|
||||
m_view->setViewMode(QListView::IconMode);
|
||||
m_view->setResizeMode(QListView::Adjust);
|
||||
m_view->setFlow(QListView::LeftToRight);
|
||||
m_view->setWrapping(true);
|
||||
m_view->setSpacing(20);
|
||||
m_view->setFrameStyle(QFrame::NoFrame);
|
||||
m_view->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
|
||||
// Modernized ScrollBar Styling
|
||||
m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
auto setupView = [&](QListView*& view, GameGridDelegate*& delegate) {
|
||||
view = new ContentHeightListView(m_container);
|
||||
view->setViewMode(QListView::IconMode);
|
||||
view->setResizeMode(QListView::Adjust);
|
||||
view->setFlow(QListView::LeftToRight);
|
||||
view->setWrapping(true);
|
||||
view->setFrameStyle(QFrame::NoFrame);
|
||||
view->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
view->setDragEnabled(false);
|
||||
view->setAcceptDrops(false);
|
||||
view->setFocusPolicy(Qt::StrongFocus);
|
||||
delegate = new GameGridDelegate(view, this);
|
||||
view->setItemDelegate(delegate);
|
||||
connect(view, &QListView::activated, this, &GameGridView::itemActivated);
|
||||
};
|
||||
|
||||
// Disable traditional Drag & Drop to ensure Kinetic Scroll takes precedence
|
||||
m_view->setDragEnabled(false);
|
||||
m_view->setAcceptDrops(false);
|
||||
m_view->setDropIndicatorShown(false);
|
||||
m_view->setDefaultDropAction(Qt::IgnoreAction);
|
||||
m_fav_label = new QLabel(tr("★ FAVORITES"), m_container);
|
||||
m_fav_label->setStyleSheet(QStringLiteral("QLabel { color: #ffd700; font-weight: bold; font-size: 18px; padding-left: 24px; margin-bottom: 10px; }"));
|
||||
m_layout->addWidget(m_fav_label);
|
||||
setupView(m_fav_view, m_fav_delegate);
|
||||
m_layout->addWidget(m_fav_view);
|
||||
|
||||
m_delegate = new GameGridDelegate(m_view, this);
|
||||
m_view->setItemDelegate(m_delegate);
|
||||
m_main_label = new QLabel(tr("ALL GAMES"), m_container);
|
||||
m_main_label->setStyleSheet(QStringLiteral("QLabel { color: #0096ff; font-weight: bold; font-size: 18px; padding-left: 24px; margin-top: 20px; margin-bottom: 10px; }"));
|
||||
m_layout->addWidget(m_main_label);
|
||||
setupView(m_main_view, m_main_delegate);
|
||||
m_layout->addWidget(m_main_view);
|
||||
|
||||
m_layout->addWidget(m_view, 1);
|
||||
|
||||
// --- BOTTOM HINT OVERLAY ---
|
||||
m_bottom_hint = new QLabel(this);
|
||||
m_bottom_hint = new QLabel(m_container);
|
||||
m_bottom_hint->setText(tr("*You can use your Mouse Wheel or the Scrollbar to navigate the Grid View*"));
|
||||
m_bottom_hint->setStyleSheet(QStringLiteral("QLabel { color: rgba(255, 255, 255, 100); font-style: italic; font-size: 13px; }"));
|
||||
m_bottom_hint->setAlignment(Qt::AlignCenter);
|
||||
m_layout->addWidget(m_bottom_hint);
|
||||
|
||||
m_scroll_area->setWidget(m_container);
|
||||
QVBoxLayout* main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
main_layout->addWidget(m_scroll_area);
|
||||
|
||||
ApplyTheme();
|
||||
|
||||
connect(m_view, &QListView::activated, this, &GameGridView::itemActivated);
|
||||
}
|
||||
|
||||
void GameGridView::ApplyTheme() {
|
||||
m_view->setStyleSheet(QStringLiteral(
|
||||
"QListView {"
|
||||
" background: transparent;"
|
||||
" border: none;"
|
||||
" outline: 0;"
|
||||
" padding: 20px;"
|
||||
"}"
|
||||
"QListView::item {"
|
||||
" padding: 0px;"
|
||||
" margin: 4px;"
|
||||
" background: transparent;"
|
||||
"}"
|
||||
));
|
||||
QString style = QStringLiteral(
|
||||
"QListView { background: transparent; border: none; outline: 0; padding: 0px; }"
|
||||
"QListView::item { padding: 0px; margin: 0px; background: transparent; }"
|
||||
);
|
||||
m_fav_view->setStyleSheet(style);
|
||||
m_main_view->setStyleSheet(style);
|
||||
}
|
||||
|
||||
QAbstractItemModel* GameGridView::model() const {
|
||||
return m_view->model();
|
||||
void GameGridView::setModels(QAbstractItemModel* fav_model, QAbstractItemModel* main_model) {
|
||||
m_fav_view->setModel(fav_model);
|
||||
m_main_view->setModel(main_model);
|
||||
|
||||
bool has_favs = fav_model && fav_model->rowCount() > 0;
|
||||
m_fav_label->setVisible(has_favs);
|
||||
m_fav_view->setVisible(has_favs);
|
||||
|
||||
if (m_fav_view->selectionModel()) {
|
||||
connect(m_fav_view->selectionModel(), &QItemSelectionModel::currentChanged,
|
||||
this, [this](const QModelIndex& current) {
|
||||
if (current.isValid()) {
|
||||
emit itemSelectionChanged(current);
|
||||
m_main_view->clearSelection();
|
||||
m_main_view->setCurrentIndex(QModelIndex());
|
||||
}
|
||||
});
|
||||
}
|
||||
if (m_main_view->selectionModel()) {
|
||||
connect(m_main_view->selectionModel(), &QItemSelectionModel::currentChanged,
|
||||
this, [this](const QModelIndex& current) {
|
||||
if (current.isValid()) {
|
||||
emit itemSelectionChanged(current);
|
||||
m_fav_view->clearSelection();
|
||||
m_fav_view->setCurrentIndex(QModelIndex());
|
||||
}
|
||||
});
|
||||
}
|
||||
UpdateGridSize();
|
||||
}
|
||||
|
||||
QItemSelectionModel* GameGridView::selectionModel() const {
|
||||
return m_view->selectionModel();
|
||||
QModelIndex GameGridView::currentIndex() const {
|
||||
if (m_fav_view->hasFocus()) return m_fav_view->currentIndex();
|
||||
return m_main_view->currentIndex();
|
||||
}
|
||||
|
||||
QRect GameGridView::visualRect(const QModelIndex& index) const {
|
||||
return m_view->visualRect(index);
|
||||
void GameGridView::setCurrentIndex(const QModelIndex& index) {
|
||||
if (index.model() == m_fav_view->model()) m_fav_view->setCurrentIndex(index);
|
||||
else m_main_view->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
QWidget* GameGridView::viewport() const {
|
||||
return m_view->viewport();
|
||||
}
|
||||
|
||||
void GameGridView::scrollTo(const QModelIndex& index) {
|
||||
m_view->scrollTo(index);
|
||||
QModelIndex GameGridView::indexAt(const QPoint& p) const {
|
||||
QPoint fp = m_fav_view->mapFrom(this, p);
|
||||
if (m_fav_view->rect().contains(fp)) return m_fav_view->indexAt(fp);
|
||||
QPoint mp = m_main_view->mapFrom(this, p);
|
||||
if (m_main_view->rect().contains(mp)) return m_main_view->indexAt(mp);
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
void GameGridView::setControllerFocus(bool focus) {
|
||||
m_has_focus = focus;
|
||||
if (focus) {
|
||||
m_view->setFocus();
|
||||
if (!m_view->currentIndex().isValid() && m_view->model() && m_view->model()->rowCount() > 0) {
|
||||
m_view->setCurrentIndex(m_view->model()->index(0, 0));
|
||||
if (m_fav_view->isVisible() && m_fav_view->model()->rowCount() > 0) {
|
||||
m_fav_view->setFocus();
|
||||
if (!m_fav_view->currentIndex().isValid()) m_fav_view->setCurrentIndex(m_fav_view->model()->index(0, 0));
|
||||
} else {
|
||||
m_main_view->setFocus();
|
||||
if (!m_main_view->currentIndex().isValid()) m_main_view->setCurrentIndex(m_main_view->model()->index(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameGridView::onNavigated(int dx, int dy) {
|
||||
if (!m_has_focus || !m_view->model()) return;
|
||||
|
||||
QModelIndex current = m_view->currentIndex();
|
||||
if (!current.isValid()) {
|
||||
m_view->setCurrentIndex(m_view->model()->index(0, 0));
|
||||
if (!m_has_focus) return;
|
||||
QListView* current = m_fav_view->hasFocus() ? m_fav_view : m_main_view;
|
||||
if (!current->model()) return;
|
||||
|
||||
QModelIndex idx = current->currentIndex();
|
||||
if (!idx.isValid()) {
|
||||
current->setCurrentIndex(current->model()->index(0, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
int row = idx.row();
|
||||
int total = current->model()->rowCount();
|
||||
int cols = qMax(1, current->viewport()->width() / current->gridSize().width());
|
||||
|
||||
if (dy > 0 && row + cols >= total && current == m_fav_view) {
|
||||
m_main_view->setFocus();
|
||||
m_main_view->setCurrentIndex(m_main_view->model()->index(0, 0));
|
||||
return;
|
||||
}
|
||||
if (dy < 0 && row < cols && current == m_main_view && m_fav_view->isVisible()) {
|
||||
m_fav_view->setFocus();
|
||||
m_fav_view->setCurrentIndex(m_fav_view->model()->index(m_fav_view->model()->rowCount() - 1, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Grid navigation logic (simplified for now, can be improved)
|
||||
int row = current.row();
|
||||
int total = m_view->model()->rowCount();
|
||||
|
||||
if (dx > 0) row++;
|
||||
else if (dx < 0) row--;
|
||||
|
||||
if (dy > 0) {
|
||||
// Find how many items per row
|
||||
int icons_per_row = qMax(1, m_view->viewport()->width() / m_view->gridSize().width());
|
||||
row += icons_per_row;
|
||||
} else if (dy < 0) {
|
||||
int icons_per_row = qMax(1, m_view->viewport()->width() / m_view->gridSize().width());
|
||||
row -= icons_per_row;
|
||||
}
|
||||
|
||||
if (dy > 0) row += cols;
|
||||
else if (dy < 0) row -= cols;
|
||||
|
||||
row = qBound(0, row, total - 1);
|
||||
m_view->setCurrentIndex(m_view->model()->index(row, 0));
|
||||
m_view->scrollTo(m_view->currentIndex());
|
||||
current->setCurrentIndex(current->model()->index(row, 0));
|
||||
scrollTo(current->currentIndex());
|
||||
}
|
||||
|
||||
void GameGridView::onActivated() {
|
||||
if (!m_has_focus) return;
|
||||
QModelIndex current = m_view->currentIndex();
|
||||
if (current.isValid()) {
|
||||
emit itemActivated(current);
|
||||
}
|
||||
QModelIndex cur = currentIndex();
|
||||
if (cur.isValid()) emit itemActivated(cur);
|
||||
}
|
||||
|
||||
void GameGridView::onCancelled() {
|
||||
if (!m_has_focus) return;
|
||||
m_has_focus = false;
|
||||
m_view->clearFocus();
|
||||
}
|
||||
|
||||
void GameGridView::setModel(QAbstractItemModel* model) {
|
||||
m_view->setModel(model);
|
||||
if (m_view->selectionModel()) {
|
||||
connect(m_view->selectionModel(), &QItemSelectionModel::currentChanged,
|
||||
this, [this](const QModelIndex& current) {
|
||||
emit itemSelectionChanged(current);
|
||||
});
|
||||
}
|
||||
m_fav_view->clearFocus();
|
||||
m_main_view->clearFocus();
|
||||
}
|
||||
|
||||
void GameGridView::keyPressEvent(QKeyEvent* event) {
|
||||
if (event->key() == Qt::Key_X || event->key() == Qt::Key_Y) {
|
||||
if (!m_view->model()) return;
|
||||
|
||||
QModelIndex current = m_view->currentIndex();
|
||||
if (!current.isValid()) {
|
||||
if (m_view->model()->rowCount() > 0) {
|
||||
m_view->setCurrentIndex(m_view->model()->index(0, 0));
|
||||
return;
|
||||
}
|
||||
QListView* current = m_fav_view->hasFocus() ? m_fav_view : m_main_view;
|
||||
if (current == m_fav_view && m_main_view->model()->rowCount() > 0) {
|
||||
m_main_view->setFocus();
|
||||
m_main_view->setCurrentIndex(m_main_view->model()->index(0, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
QString title = current.data(Qt::DisplayRole).toString();
|
||||
QChar current_char = title.isEmpty() ? QLatin1Char(' ') : title[0].toUpper();
|
||||
|
||||
int total = m_view->model()->rowCount();
|
||||
int start_row = current.row();
|
||||
for (int i = 1; i <= total; ++i) {
|
||||
int next_row = (start_row + i) % total;
|
||||
QString next_title = m_view->model()->index(next_row, 0).data(Qt::DisplayRole).toString();
|
||||
QChar next_char = next_title.isEmpty() ? QLatin1Char(' ') : next_title[0].toUpper();
|
||||
if (next_char != current_char) {
|
||||
m_view->setCurrentIndex(m_view->model()->index(next_row, 0));
|
||||
m_view->scrollTo(m_view->model()->index(next_row, 0));
|
||||
if (!current->model()) return;
|
||||
QModelIndex cur = current->currentIndex();
|
||||
if (!cur.isValid()) return;
|
||||
QString title = cur.data(Qt::DisplayRole).toString();
|
||||
QChar cc = title.isEmpty() ? QLatin1Char(' ') : title[0].toUpper();
|
||||
int tot = current->model()->rowCount();
|
||||
int sr = cur.row();
|
||||
for (int i = 1; i <= tot; ++i) {
|
||||
int nr = (sr + i) % tot;
|
||||
QModelIndex nidx = current->model()->index(nr, 0);
|
||||
QString nt = nidx.data(Qt::DisplayRole).toString();
|
||||
QChar nc = nt.isEmpty() ? QLatin1Char(' ') : nt[0].toUpper();
|
||||
if (nc != cc) {
|
||||
current->setCurrentIndex(nidx);
|
||||
current->scrollTo(nidx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -191,15 +233,68 @@ void GameGridView::keyPressEvent(QKeyEvent* event) {
|
||||
|
||||
void GameGridView::resizeEvent(QResizeEvent* event) {
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
// Adaptive Grid Scaling: Eliminate the Gap
|
||||
const int icon_size = UISettings::values.game_icon_size.GetValue();
|
||||
const int base_width = icon_size + 40;
|
||||
const int total_width = m_view->viewport()->width() - 30; // margins
|
||||
|
||||
if (total_width > base_width) {
|
||||
int columns = qMax(1, total_width / base_width);
|
||||
int adaptive_width = total_width / columns;
|
||||
m_view->setGridSize(QSize(adaptive_width, base_width + 45));
|
||||
}
|
||||
UpdateGridSize();
|
||||
}
|
||||
|
||||
void GameGridView::UpdateGridSize() {
|
||||
const int is = UISettings::values.game_icon_size.GetValue();
|
||||
const float s = static_cast<float>(is) / 128.0f;
|
||||
const int bw = qMax(is + static_cast<int>(40 * s), is + 24);
|
||||
const int tw = m_scroll_area->viewport()->width();
|
||||
if (tw > 50) {
|
||||
int cols = qMax(1, tw / bw);
|
||||
int aw = tw / cols;
|
||||
const int item_h = qMax(is + static_cast<int>(85 * s), is + 40);
|
||||
QSize gs(aw, item_h);
|
||||
m_fav_view->setGridSize(gs);
|
||||
m_main_view->setGridSize(gs);
|
||||
m_fav_view->setFixedWidth(tw);
|
||||
m_main_view->setFixedWidth(tw);
|
||||
}
|
||||
UpdateLayoutHeights();
|
||||
}
|
||||
|
||||
void GameGridView::UpdateLayoutHeights() {
|
||||
auto updateHeight = [&](QListView* view) {
|
||||
if (!view || !view->isVisible() || !view->model() || view->model()->rowCount() == 0) {
|
||||
view->setFixedHeight(0);
|
||||
return;
|
||||
}
|
||||
const int count = view->model()->rowCount();
|
||||
const QSize gs = view->gridSize();
|
||||
if (gs.width() <= 0 || gs.height() <= 0) return;
|
||||
const int tw = m_scroll_area->viewport()->width();
|
||||
const int cols = qMax(1, tw / gs.width());
|
||||
const int rows = (count + cols - 1) / cols;
|
||||
view->setFixedHeight(rows * gs.height() + 40);
|
||||
};
|
||||
updateHeight(m_fav_view);
|
||||
updateHeight(m_main_view);
|
||||
}
|
||||
|
||||
QAbstractItemModel* GameGridView::favModel() const { return m_fav_view->model(); }
|
||||
QAbstractItemModel* GameGridView::mainModel() const { return m_main_view->model(); }
|
||||
|
||||
QItemSelectionModel* GameGridView::selectionModel() const {
|
||||
return m_main_view->selectionModel();
|
||||
}
|
||||
|
||||
void GameGridView::setModel(QAbstractItemModel* model) {
|
||||
m_main_view->setModel(model);
|
||||
m_fav_view->setModel(nullptr);
|
||||
m_fav_label->hide();
|
||||
m_fav_view->hide();
|
||||
UpdateGridSize();
|
||||
}
|
||||
QRect GameGridView::visualRect(const QModelIndex& index) const {
|
||||
if (index.model() == m_fav_view->model()) return m_fav_view->visualRect(index);
|
||||
return m_main_view->visualRect(index);
|
||||
}
|
||||
QWidget* GameGridView::viewport() const { return m_scroll_area->viewport(); }
|
||||
void GameGridView::scrollTo(const QModelIndex& index) {
|
||||
QListView* view = (index.model() == m_fav_view->model()) ? m_fav_view : m_main_view;
|
||||
QRect rect = view->visualRect(index);
|
||||
rect.moveTop(rect.top() + view->y());
|
||||
m_scroll_area->ensureVisible(rect.center().x(), rect.center().y(), 50, 50);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QListView>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
@@ -19,23 +20,26 @@ class GameGridView : public QWidget {
|
||||
public:
|
||||
explicit GameGridView(QWidget* parent = nullptr);
|
||||
|
||||
void setModel(QAbstractItemModel* model);
|
||||
void setModels(QAbstractItemModel* fav_model, QAbstractItemModel* main_model);
|
||||
void ApplyTheme();
|
||||
|
||||
// Accessors for compatibility
|
||||
QListView* view() const { return m_view; }
|
||||
QAbstractItemModel* model() const;
|
||||
QListView* view() const { return m_main_view; }
|
||||
QListView* favView() const { return m_fav_view; }
|
||||
QAbstractItemModel* model() const { return mainModel(); }
|
||||
QItemSelectionModel* selectionModel() const;
|
||||
void setModel(QAbstractItemModel* model);
|
||||
|
||||
QAbstractItemModel* favModel() const;
|
||||
QAbstractItemModel* mainModel() const;
|
||||
|
||||
QRect visualRect(const QModelIndex& index) const;
|
||||
QWidget* viewport() const;
|
||||
void scrollTo(const QModelIndex& index);
|
||||
|
||||
// Forwarded from GameViewBase equivalents
|
||||
QModelIndex currentIndex() const { return m_view->currentIndex(); }
|
||||
void setCurrentIndex(const QModelIndex& index) { m_view->setCurrentIndex(index); }
|
||||
QModelIndex indexAt(const QPoint& p) const { return m_view->indexAt(p); }
|
||||
QModelIndex currentIndex() const;
|
||||
void setCurrentIndex(const QModelIndex& index);
|
||||
QModelIndex indexAt(const QPoint& p) const;
|
||||
|
||||
// Controller Navigation
|
||||
void setControllerFocus(bool focus);
|
||||
bool hasControllerFocus() const { return m_has_focus; }
|
||||
|
||||
@@ -53,10 +57,20 @@ protected:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private:
|
||||
QListView* m_view = nullptr;
|
||||
GameGridDelegate* m_delegate = nullptr;
|
||||
void UpdateGridSize();
|
||||
void UpdateLayoutHeights();
|
||||
|
||||
QScrollArea* m_scroll_area = nullptr;
|
||||
QWidget* m_container = nullptr;
|
||||
QListView* m_fav_view = nullptr;
|
||||
QListView* m_main_view = nullptr;
|
||||
QLabel* m_fav_label = nullptr;
|
||||
QLabel* m_main_label = nullptr;
|
||||
GameGridDelegate* m_fav_delegate = nullptr;
|
||||
GameGridDelegate* m_main_delegate = nullptr;
|
||||
QVBoxLayout* m_layout = nullptr;
|
||||
QLabel* m_top_help = nullptr;
|
||||
QLabel* m_bottom_hint = nullptr;
|
||||
bool m_has_focus = false;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user