Implement action-needed queue bar at bottom of screen with oldest-first ordering and dismissal
This commit is contained in:
@@ -2267,6 +2267,29 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
|
||||
return; // In City View, ignore all other clicks
|
||||
}
|
||||
|
||||
// Action Queue items (Bottom HUD notification bar)
|
||||
for (const auto &hit : m_actionItemHits) {
|
||||
if (hit.dismissRect.contains(event->pos()) && event->button() == Qt::LeftButton) {
|
||||
GameManager::instance().actionNeededManager().dismissItem(hit.type, hit.entityId);
|
||||
update();
|
||||
return;
|
||||
}
|
||||
if (hit.rect.contains(event->pos())) {
|
||||
if (event->button() == Qt::RightButton) {
|
||||
GameManager::instance().actionNeededManager().dismissItem(hit.type, hit.entityId);
|
||||
update();
|
||||
return;
|
||||
} else if (event->button() == Qt::LeftButton) {
|
||||
handleActionItemClick(hit.type, hit.entityId);
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_actionQueueHeaderRect.contains(event->pos())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if clicked on a City Banner to open City View
|
||||
for (const auto &cb : m_cityBannerHits) {
|
||||
if (cb.rect.contains(event->pos())) {
|
||||
@@ -2478,6 +2501,20 @@ void QSceneRenderer::mouseMoveEvent(QMouseEvent *event)
|
||||
if (zRot >= 360.0f) zRot -= 360.0f;
|
||||
update();
|
||||
}
|
||||
|
||||
bool onActionItem = false;
|
||||
for (const auto &hit : m_actionItemHits) {
|
||||
if (hit.rect.contains(event->pos())) {
|
||||
onActionItem = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (onActionItem) {
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
} else {
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
lastPos = event->pos();
|
||||
}
|
||||
|
||||
@@ -2783,6 +2820,7 @@ void QSceneRenderer::paintGL()
|
||||
if (selected) {
|
||||
drawUnitCard(painter, selected);
|
||||
}
|
||||
drawActionNeededQueue(painter);
|
||||
} else {
|
||||
drawUnitsOverlay(painter, vMatrix);
|
||||
drawFloatingTexts(painter, vMatrix);
|
||||
@@ -2790,3 +2828,190 @@ void QSceneRenderer::paintGL()
|
||||
}
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void QSceneRenderer::drawActionNeededQueue(QPainter &painter)
|
||||
{
|
||||
m_actionItemHits.clear();
|
||||
m_actionQueueHeaderRect = QRect();
|
||||
|
||||
const ActionNeededConfig &cfg = GameManager::instance().actionNeededManager().config();
|
||||
if (!cfg.enabled) return;
|
||||
|
||||
std::vector<ActionItem> visibleItems = GameManager::instance().actionNeededManager().getVisibleItems();
|
||||
if (visibleItems.empty()) return;
|
||||
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
QRect minimapCard = getMinimapCardRect();
|
||||
int minLeft = minimapCard.right() + 14;
|
||||
Unit *selected = GameManager::instance().getSelectedUnit();
|
||||
int maxRight = selected ? (m_unitCardRect.left() - 14) : (width() - 16);
|
||||
int availableW = maxRight - minLeft;
|
||||
if (availableW < 120) return;
|
||||
|
||||
int itemW = cfg.itemWidth;
|
||||
int itemH = cfg.itemHeight;
|
||||
int spacing = cfg.itemSpacing;
|
||||
int headerW = 82;
|
||||
|
||||
int remainingForItems = availableW - headerW - spacing;
|
||||
int maxItemsThatFit = std::max(1, remainingForItems / (itemW + spacing));
|
||||
int numToRender = std::min((int)visibleItems.size(), std::min(cfg.maxVisibleItems, maxItemsThatFit));
|
||||
bool hasOverflow = (int)visibleItems.size() > numToRender;
|
||||
int overflowW = hasOverflow ? 54 : 0;
|
||||
if (hasOverflow && remainingForItems < numToRender * (itemW + spacing) + overflowW) {
|
||||
numToRender = std::max(1, numToRender - 1);
|
||||
}
|
||||
|
||||
int totalW = headerW + spacing + numToRender * itemW + (numToRender - 1) * spacing + (hasOverflow ? (spacing + overflowW) : 0);
|
||||
int startX = minLeft + (availableW - totalW) / 2;
|
||||
if (startX < minLeft) startX = minLeft;
|
||||
int bottomY = height() - itemH - cfg.bottomOffset;
|
||||
|
||||
// 1. Header badge
|
||||
QRect headerRect(startX, bottomY, headerW, itemH);
|
||||
m_actionQueueHeaderRect = headerRect;
|
||||
painter.setBrush(cfg.cardBgColor);
|
||||
painter.setPen(QPen(QColor(70, 95, 130, 180), 1.2));
|
||||
painter.drawRoundedRect(headerRect, cfg.borderRadius, cfg.borderRadius);
|
||||
|
||||
float pulse = cfg.pulseAnimation ? (sin(GameManager::instance().actionNeededManager().getElapsedGameTime() * cfg.pulseSpeed) * 0.5f + 0.5f) : 1.0f;
|
||||
QRect badgeRect(headerRect.left() + 8, headerRect.top() + (itemH - 20) / 2, 20, 20);
|
||||
QColor badgeCol(255, 170, 40);
|
||||
badgeCol.setAlpha((int)(170 + 80 * pulse));
|
||||
painter.setBrush(badgeCol);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.drawEllipse(badgeRect);
|
||||
|
||||
painter.setFont(QFont("sans-serif", 9, QFont::Bold));
|
||||
painter.setPen(Qt::white);
|
||||
painter.drawText(badgeRect, Qt::AlignCenter, QString::number(visibleItems.size()));
|
||||
|
||||
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
|
||||
painter.setPen(QColor(215, 230, 250));
|
||||
QRect headerTextRect(badgeRect.right() + 6, headerRect.top(), headerRect.right() - badgeRect.right() - 8, itemH);
|
||||
painter.drawText(headerTextRect, Qt::AlignLeft | Qt::AlignVCenter, Localization::instance().get("ui.action_needed.header_title", "ACTIONS"));
|
||||
|
||||
// 2. Render cards (oldest first)
|
||||
for (int i = 0; i < numToRender; ++i) {
|
||||
const ActionItem &item = visibleItems[i];
|
||||
int itemX = startX + headerW + spacing + i * (itemW + spacing);
|
||||
QRect itemRect(itemX, bottomY, itemW, itemH);
|
||||
QRect dismissRect(itemRect.right() - 16, itemRect.top() + 3, 13, 13);
|
||||
m_actionItemHits.push_back({itemRect, dismissRect, item.type, item.entityId});
|
||||
|
||||
bool isHovered = itemRect.contains(m_mouseCurrentPos);
|
||||
|
||||
QString title;
|
||||
QString subtitle;
|
||||
QColor accent;
|
||||
QImage icon;
|
||||
|
||||
if (item.type == ActionNeededType::UnitIdle) {
|
||||
Unit *u = GameManager::instance().getUnitById(item.entityId);
|
||||
title = u ? u->config.name.toUpper() : Localization::instance().get("ui.unit.civilian_unit", "UNIT");
|
||||
subtitle = Localization::instance().get("ui.action_needed.unit_idle_desc", "Needs orders");
|
||||
accent = cfg.unitIdleColor;
|
||||
const Player *p = u ? GameManager::instance().players().getPlayer(u->playerId) : nullptr;
|
||||
QColor pCol = p ? p->primaryColor : Qt::blue;
|
||||
icon = u ? getTintedIcon(u->config.iconPath, pCol) : QImage();
|
||||
} else if (item.type == ActionNeededType::CityNoProduction) {
|
||||
City *c = GameManager::instance().getCityById(item.entityId);
|
||||
title = c ? c->name : Localization::instance().get("ui.city.center", "CITY");
|
||||
subtitle = Localization::instance().get("ui.action_needed.city_no_production_desc", "No production");
|
||||
accent = cfg.cityNoProdColor;
|
||||
icon = getTintedIcon("images/resources/arbeid.png", accent);
|
||||
} else if (item.type == ActionNeededType::CityFreePopulation) {
|
||||
City *c = GameManager::instance().getCityById(item.entityId);
|
||||
title = c ? c->name : Localization::instance().get("ui.city.center", "CITY");
|
||||
subtitle = Localization::instance().get("ui.action_needed.city_free_pop_desc", "+%1 Free Pop").arg(item.auxValue);
|
||||
accent = cfg.cityFreePopColor;
|
||||
icon = getTintedIcon("images/resources/citizen.png", accent);
|
||||
}
|
||||
|
||||
// Draw card background
|
||||
QColor bg = cfg.cardBgColor;
|
||||
if (isHovered) {
|
||||
bg = bg.lighter(130);
|
||||
}
|
||||
painter.setBrush(bg);
|
||||
painter.setPen(QPen(isHovered ? accent : QColor(70, 95, 130, 160), isHovered ? 1.5 : 1.0));
|
||||
painter.drawRoundedRect(itemRect, cfg.borderRadius, cfg.borderRadius);
|
||||
|
||||
// Accent stripe on the left edge
|
||||
QRect stripeRect(itemRect.left() + 2, itemRect.top() + 3, 3, itemH - 6);
|
||||
painter.fillRect(stripeRect, accent);
|
||||
|
||||
// Icon badge
|
||||
QRect iconRect(itemRect.left() + 9, itemRect.top() + (itemH - 24) / 2, 24, 24);
|
||||
QColor iconBg = accent;
|
||||
iconBg.setAlpha(40);
|
||||
painter.setBrush(iconBg);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.drawEllipse(iconRect);
|
||||
if (!icon.isNull()) {
|
||||
painter.drawImage(iconRect.adjusted(3, 3, -3, -3), icon);
|
||||
}
|
||||
|
||||
// Title and subtitle text
|
||||
int textLeft = iconRect.right() + 7;
|
||||
int textW = dismissRect.left() - textLeft - 2;
|
||||
|
||||
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
|
||||
painter.setPen(QColor(240, 245, 255));
|
||||
painter.drawText(QRect(textLeft, itemRect.top() + 4, textW, 16), Qt::AlignLeft | Qt::AlignVCenter, title);
|
||||
|
||||
painter.setFont(QFont("sans-serif", 7));
|
||||
painter.setPen(accent);
|
||||
painter.drawText(QRect(textLeft, itemRect.top() + 20, textW, 15), Qt::AlignLeft | Qt::AlignVCenter, subtitle);
|
||||
|
||||
// Dismiss [x]
|
||||
bool isDismissHovered = dismissRect.contains(m_mouseCurrentPos);
|
||||
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
|
||||
painter.setPen(isDismissHovered ? QColor(255, 120, 120) : QColor(130, 155, 180, 180));
|
||||
painter.drawText(dismissRect, Qt::AlignCenter, "×");
|
||||
}
|
||||
|
||||
// 3. Overflow indicator
|
||||
if (hasOverflow) {
|
||||
int moreCount = (int)visibleItems.size() - numToRender;
|
||||
QRect ofRect(startX + headerW + spacing + numToRender * (itemW + spacing), bottomY, overflowW, itemH);
|
||||
painter.setBrush(QColor(20, 26, 38, 220));
|
||||
painter.setPen(QPen(QColor(70, 95, 130, 150), 1.0));
|
||||
painter.drawRoundedRect(ofRect, cfg.borderRadius, cfg.borderRadius);
|
||||
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
|
||||
painter.setPen(QColor(180, 210, 240));
|
||||
QString ofText = Localization::instance().get("ui.action_needed.more_items", "+%1").arg(moreCount);
|
||||
painter.drawText(ofRect, Qt::AlignCenter, ofText);
|
||||
}
|
||||
}
|
||||
|
||||
void QSceneRenderer::handleActionItemClick(ActionNeededType type, int entityId)
|
||||
{
|
||||
if (type == ActionNeededType::UnitIdle) {
|
||||
if (GameManager::instance().isInCityView()) {
|
||||
GameManager::instance().exitCityView();
|
||||
}
|
||||
Unit *u = GameManager::instance().getUnitById(entityId);
|
||||
if (u) {
|
||||
GameManager::instance().selectUnit(entityId);
|
||||
cameraPosition.setX(u->worldPos.x());
|
||||
cameraPosition.setY(u->worldPos.y());
|
||||
}
|
||||
} else if (type == ActionNeededType::CityNoProduction || type == ActionNeededType::CityFreePopulation) {
|
||||
City *c = GameManager::instance().getCityById(entityId);
|
||||
if (c) {
|
||||
if (!GameManager::instance().isInCityView()) {
|
||||
m_savedCamPos = cameraPosition;
|
||||
m_savedZoom = m_zoom;
|
||||
m_savedZRot = zRot;
|
||||
}
|
||||
GameManager::instance().enterCityView(entityId);
|
||||
cameraPosition.setX(c->worldPos.x());
|
||||
cameraPosition.setY(c->worldPos.y());
|
||||
if (type == ActionNeededType::CityNoProduction) {
|
||||
m_cityProjectTab = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user