mirror of
https://github.com/vcmi/vcmi.git
synced 2026-06-19 22:57:37 +02:00
545 lines
20 KiB
C++
545 lines
20 KiB
C++
/*
|
|
* WikiHeroContent.cpp, part of VCMI engine
|
|
*
|
|
* Authors: listed in file AUTHORS in main folder
|
|
*
|
|
* License: GNU General Public License v2.0 or later
|
|
* Full text of license available in license.txt file, in main folder
|
|
*
|
|
*/
|
|
#include "StdInc.h"
|
|
#include "WikiHeroContent.h"
|
|
#include "WikiCommon.h"
|
|
|
|
#include "../../widgets/CViewport.h"
|
|
#include "../../widgets/Images.h"
|
|
#include "../../widgets/TextControls.h"
|
|
#include "../../widgets/GraphicalPrimitiveCanvas.h"
|
|
#include "../../render/Canvas.h"
|
|
#include "../../render/Colors.h"
|
|
#include "../../GameEngine.h"
|
|
#include "../../gui/WindowHandler.h"
|
|
#include "../CHeroOverview.h"
|
|
#include "../CCreatureWindow.h"
|
|
|
|
#include "../../../lib/GameLibrary.h"
|
|
#include "../../../lib/CCreatureHandler.h"
|
|
#include "../../../lib/CSkillHandler.h"
|
|
#include "../../../lib/spells/CSpellHandler.h"
|
|
#include "../../../lib/entities/hero/CHero.h"
|
|
#include "../../../lib/entities/hero/CHeroClass.h"
|
|
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
|
#include "../../../lib/entities/hero/CHeroHandler.h"
|
|
#include "../../../lib/entities/hero/EHeroGender.h"
|
|
#include "../../../lib/entities/artifact/CArtifact.h"
|
|
#include "../../../lib/entities/artifact/CArtHandler.h"
|
|
#include "../../../lib/constants/Enumerations.h"
|
|
#include "../../../lib/texts/CGeneralTextHandler.h"
|
|
#include "WikiWindow.h"
|
|
#include "../InfoWindows.h"
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Layout constants
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
static constexpr int MARGIN = 4;
|
|
static constexpr int GAP = 10;
|
|
static constexpr int CELL_L = 4;
|
|
static constexpr int CELL_T = 2;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// buildHeroContent
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
std::vector<std::shared_ptr<CIntObject>> buildHeroContent(
|
|
CViewport & viewport,
|
|
const CHero * hero,
|
|
int viewportWidth,
|
|
bool blueStyle,
|
|
WikiHeroNavigateCallback navigateCallback,
|
|
const CGHeroInstance * mapHero)
|
|
{
|
|
std::vector<std::shared_ptr<CIntObject>> widgets;
|
|
if(!hero) return widgets;
|
|
|
|
OBJECT_CONSTRUCTION_TARGETED(viewport.content());
|
|
const Rect clipRect = viewport.clipRect();
|
|
const int W = viewportWidth;
|
|
int curY = 12;
|
|
|
|
const ColorRGBA valCol = blueStyle
|
|
? ColorRGBA(160, 210, 255, 255)
|
|
: Colors::YELLOW;
|
|
|
|
// ── 1. Title + class/gender subtitle ───────────────────────────────────
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
W / 2, curY,
|
|
FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW,
|
|
(mapHero && !mapHero->nameCustomTextId.empty()) ? mapHero->getNameTranslated() : hero->getNameTranslated()));
|
|
curY += 26;
|
|
|
|
{
|
|
// Gender: ♂/♀ Unicode glyphs if supported by font, else translatable fallback
|
|
const std::string genderSpan = wikiGenderSpan(hero->gender == EHeroGender::FEMALE);
|
|
|
|
const std::string className = hero->heroClass ? hero->heroClass->getNameTranslated() : "";
|
|
|
|
// Alignment as colored inline span using {#RRGGBB|text} syntax
|
|
std::string alignSpan;
|
|
if(hero->heroClass)
|
|
{
|
|
switch(hero->heroClass->getAlignment())
|
|
{
|
|
case EAlignment::GOOD: alignSpan = "{#00C800|" + LIBRARY->generaltexth->translate("vcmi.wiki.alignment.good") + "}"; break;
|
|
case EAlignment::EVIL: alignSpan = "{#DC3232|" + LIBRARY->generaltexth->translate("vcmi.wiki.alignment.evil") + "}"; break;
|
|
case EAlignment::NEUTRAL: alignSpan = "{#FFFFFF|" + LIBRARY->generaltexth->translate("vcmi.wiki.alignment.neutral") + "}"; break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
// Compose "{yellow|ClassName} · {color|♂/♀} · {color|Alignment}"
|
|
std::string subtitle = className.empty() ? genderSpan : (className + " \xc2\xb7 " + genderSpan);
|
|
if(!alignSpan.empty())
|
|
subtitle += " \xc2\xb7 " + alignSpan;
|
|
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
W / 2, curY,
|
|
FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, subtitle));
|
|
curY += 18;
|
|
}
|
|
|
|
// ── 2. Large portrait + biography side-by-side ────────────────────────
|
|
{
|
|
const int portraitW = 58; // PortraitsLarge native width
|
|
const int portraitH = 68; // clickable area height
|
|
const int bioX = MARGIN + portraitW + GAP;
|
|
const int bioW = W - bioX - MARGIN;
|
|
|
|
const bool hasCustomData = mapHero && (
|
|
!mapHero->nameCustomTextId.empty() ||
|
|
!mapHero->biographyCustomTextId.empty() ||
|
|
mapHero->customPortraitSource.isValid());
|
|
|
|
const int portraitFrame = (mapHero && mapHero->customPortraitSource.isValid())
|
|
? (int)mapHero->getPortraitSource().toHeroType()->getIconIndex()
|
|
: hero->imageIndex;
|
|
widgets.push_back(std::make_shared<CAnimImage>(
|
|
AnimationPath::builtin("PortraitsLarge"),
|
|
portraitFrame, 0,
|
|
MARGIN, curY));
|
|
|
|
if(!hasCustomData)
|
|
{
|
|
const HeroTypeID hId = hero->getId();
|
|
widgets.push_back(std::make_shared<WikiClickable>(
|
|
Rect(MARGIN, curY, portraitW, portraitH),
|
|
nullptr,
|
|
[hId](){ ENGINE->windows().createAndPushWindow<CHeroOverview>(hId); },
|
|
blueStyle));
|
|
}
|
|
|
|
const std::string bio = (mapHero && !mapHero->biographyCustomTextId.empty())
|
|
? mapHero->getBiographyTranslated()
|
|
: hero->getBiographyTranslated();
|
|
if(!bio.empty())
|
|
{
|
|
auto bioLabel = std::make_shared<CMultiLineLabel>(
|
|
Rect(bioX, curY, bioW, 4000),
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, bio);
|
|
bioLabel->pos.h = bioLabel->textSize.y;
|
|
const int blockH = std::max(portraitH, (int)bioLabel->textSize.y);
|
|
curY += blockH + GAP;
|
|
widgets.push_back(std::move(bioLabel));
|
|
}
|
|
else
|
|
{
|
|
curY += portraitH + GAP;
|
|
}
|
|
}
|
|
|
|
// ── 3. Primary skills table ─────────────────────────────────────────
|
|
if(hero->heroClass)
|
|
{
|
|
curY += 16;
|
|
const std::vector<std::string> psNames = {
|
|
LIBRARY->generaltexth->jktexts[1],
|
|
LIBRARY->generaltexth->jktexts[2],
|
|
LIBRARY->generaltexth->jktexts[3],
|
|
LIBRARY->generaltexth->jktexts[4],
|
|
};
|
|
const int nStats = static_cast<int>(psNames.size());
|
|
const int tableW = W - MARGIN * 2;
|
|
const int rowH = 18;
|
|
const int colLbl = tableW / 2;
|
|
|
|
widgets.push_back(std::make_shared<WikiTableGrid>(MARGIN, curY, tableW, std::vector<int>{colLbl, tableW - colLbl}, 0, rowH, nStats, blueStyle));
|
|
|
|
for(int i = 0; i < nStats; ++i)
|
|
{
|
|
const int ry = curY + i * rowH;
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + CELL_L, ry + CELL_T,
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE,
|
|
psNames[i]));
|
|
const int val = (i < (int)hero->heroClass->primarySkillInitial.size())
|
|
? hero->heroClass->primarySkillInitial[i] : 0;
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + colLbl + CELL_L, ry + CELL_T,
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, valCol,
|
|
std::to_string(val)));
|
|
}
|
|
curY += nStats * rowH + GAP;
|
|
}
|
|
|
|
// ── 4. Specialty ─────────────────────────────────────────────────────
|
|
{
|
|
const std::string specName = hero->getSpecialtyNameTranslated();
|
|
const std::string specDesc = hero->getSpecialtyDescriptionTranslated();
|
|
if(!specName.empty())
|
|
{
|
|
curY += 16;
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
W / 2, curY,
|
|
FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("vcmi.wiki.hero.specialty")));
|
|
curY += 20;
|
|
|
|
const int iconCol = 48;
|
|
const int tableW = W - MARGIN * 2;
|
|
const ColorRGBA bdr = wikiBorderColor(blueStyle);
|
|
auto rowBorder = std::make_shared<GraphicalPrimitiveCanvas>(
|
|
Rect(MARGIN, curY, tableW, 50));
|
|
rowBorder->addRectangle(Point(0, 0), Point(tableW, 50), bdr);
|
|
widgets.push_back(rowBorder);
|
|
|
|
widgets.push_back(std::make_shared<CAnimImage>(
|
|
AnimationPath::builtin("UN44"),
|
|
hero->imageIndex, 0,
|
|
MARGIN + 2, curY + 2));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconCol + CELL_L, curY + CELL_T,
|
|
FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::YELLOW,
|
|
specName));
|
|
curY += 50;
|
|
|
|
if(!specDesc.empty())
|
|
{
|
|
auto label = std::make_shared<CMultiLineLabel>(
|
|
Rect(MARGIN, curY, W - MARGIN * 2, 4000),
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, specDesc);
|
|
label->pos.h = label->textSize.y;
|
|
curY += label->textSize.y + GAP;
|
|
widgets.push_back(std::move(label));
|
|
}
|
|
else
|
|
{
|
|
curY += GAP;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── 5. Starting army table ───────────────────────────────────────────
|
|
if(!hero->initialArmy.empty())
|
|
{
|
|
curY += 16;
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
W / 2, curY,
|
|
FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("vcmi.heroOverview.startingArmy")));
|
|
curY += 20;
|
|
|
|
struct ArmyRow { const CCreature * cr; ui32 minAmt; ui32 maxAmt; };
|
|
std::vector<ArmyRow> armyRows;
|
|
for(const auto & stack : hero->initialArmy)
|
|
{
|
|
if(stack.creature.getNum() < 0 || stack.creature.getNum() >= (int)LIBRARY->creh->objects.size())
|
|
continue;
|
|
const auto & cr = LIBRARY->creh->objects[stack.creature.getNum()];
|
|
if(cr && cr->warMachine == ArtifactID::NONE)
|
|
armyRows.push_back({cr.get(), stack.minAmount, stack.maxAmount});
|
|
}
|
|
|
|
if(!armyRows.empty())
|
|
{
|
|
const int iconW = 36;
|
|
const int tableW = W - MARGIN * 2;
|
|
const int nameW = tableW - iconW - 60;
|
|
const std::vector<int> cols = { iconW, nameW, 60 };
|
|
const int headerH = 16;
|
|
const int rowH = 36;
|
|
|
|
widgets.push_back(std::make_shared<WikiTableGrid>(MARGIN, curY, tableW, cols,
|
|
headerH, rowH, static_cast<int>(armyRows.size()), blueStyle));
|
|
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + CELL_L, curY + CELL_T,
|
|
FONT_TINY, ETextAlignment::TOPLEFT, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("core.genrltxt.42")));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + nameW + CELL_L, curY + CELL_T,
|
|
FONT_TINY, ETextAlignment::TOPLEFT, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("vcmi.wiki.hero.column.amount")));
|
|
curY += headerH;
|
|
|
|
for(const auto & ar : armyRows)
|
|
{
|
|
widgets.push_back(std::make_shared<CAnimImage>(
|
|
AnimationPath::builtin("CPRSMALL"),
|
|
ar.cr->getIconIndex(), 0,
|
|
MARGIN + 2, curY + 2));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + CELL_L, curY + rowH / 2 - 5,
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE,
|
|
ar.cr->getNamePluralTranslated()));
|
|
const std::string amt = (ar.minAmt == ar.maxAmt)
|
|
? std::to_string(ar.minAmt)
|
|
: std::to_string(ar.minAmt) + "-" + std::to_string(ar.maxAmt);
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + nameW + CELL_L, curY + rowH / 2 - 5,
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, valCol, amt));
|
|
|
|
const CreatureID crId(ar.cr->getIndex());
|
|
std::function<void()> lclick;
|
|
if(navigateCallback)
|
|
{
|
|
const std::string crKey = ar.cr->getJsonKey();
|
|
lclick = [navigateCallback, crKey](){ navigateCallback(WikiCategory::CREATURE, crKey); };
|
|
}
|
|
widgets.push_back(std::make_shared<WikiClickable>(
|
|
Rect(MARGIN, curY, tableW, rowH),
|
|
std::move(lclick),
|
|
[crId](){
|
|
ENGINE->windows().createAndPushWindow<CStackWindow>(
|
|
LIBRARY->creh->objects[crId.getNum()].get(), true);
|
|
},
|
|
blueStyle, clipRect));
|
|
curY += rowH;
|
|
}
|
|
curY += GAP;
|
|
}
|
|
}
|
|
|
|
// ── 5.5. War machines ───────────────────────────────────────────────
|
|
{
|
|
// Like CHeroOverview: always show catapult + any war machines from initialArmy.
|
|
struct WMRow { const CCreature * cr; };
|
|
std::vector<WMRow> wmRows;
|
|
|
|
// Slot 0: catapult is always present
|
|
const CreatureID catapultId = CreatureID::CATAPULT;
|
|
if(catapultId.getNum() >= 0 && catapultId.getNum() < (int)LIBRARY->creh->objects.size())
|
|
{
|
|
const auto & cr = LIBRARY->creh->objects[catapultId.getNum()];
|
|
if(cr) wmRows.push_back({cr.get()});
|
|
}
|
|
|
|
// Additional war machines from hero's starting army
|
|
for(const auto & stack : hero->initialArmy)
|
|
{
|
|
if(stack.creature.getNum() < 0 || stack.creature.getNum() >= (int)LIBRARY->creh->objects.size())
|
|
continue;
|
|
const auto & cr = LIBRARY->creh->objects[stack.creature.getNum()];
|
|
if(cr && cr->warMachine != ArtifactID::NONE)
|
|
wmRows.push_back({cr.get()});
|
|
}
|
|
|
|
if(!wmRows.empty())
|
|
{
|
|
curY += 16;
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
W / 2, curY,
|
|
FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("vcmi.heroOverview.warMachine")));
|
|
curY += 20;
|
|
|
|
const int iconW = 36;
|
|
const int tableW = W - MARGIN * 2;
|
|
const int nameW = tableW - iconW;
|
|
const std::vector<int> cols = { iconW, nameW };
|
|
const int rowH = 36;
|
|
|
|
widgets.push_back(std::make_shared<WikiTableGrid>(MARGIN, curY, tableW, cols,
|
|
0, rowH, static_cast<int>(wmRows.size()), blueStyle));
|
|
|
|
for(const auto & wr : wmRows)
|
|
{
|
|
widgets.push_back(std::make_shared<CAnimImage>(
|
|
AnimationPath::builtin("CPRSMALL"),
|
|
wr.cr->getIconIndex(), 0,
|
|
MARGIN + 2, curY + 2));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + CELL_L, curY + rowH / 2 - 5,
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE,
|
|
wr.cr->getNameSingularTranslated()));
|
|
|
|
const CCreature * crPtr = wr.cr;
|
|
std::function<void()> lclick;
|
|
if(navigateCallback)
|
|
{
|
|
const std::string crKey = crPtr->getJsonKey();
|
|
lclick = [navigateCallback, crKey](){ navigateCallback(WikiCategory::CREATURE, crKey); };
|
|
}
|
|
widgets.push_back(std::make_shared<WikiClickable>(
|
|
Rect(MARGIN, curY, tableW, rowH),
|
|
std::move(lclick),
|
|
[crPtr](){ ENGINE->windows().createAndPushWindow<CStackWindow>(crPtr, true); },
|
|
blueStyle, clipRect));
|
|
curY += rowH;
|
|
}
|
|
curY += GAP;
|
|
}
|
|
}
|
|
|
|
// ── 6. Secondary skills ──────────────────────────────────────────────
|
|
if(!hero->secSkillsInit.empty())
|
|
{
|
|
curY += 16;
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
W / 2, curY,
|
|
FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("vcmi.heroOverview.secondarySkills")));
|
|
curY += 20;
|
|
|
|
const int iconW = 36;
|
|
const int tableW = W - MARGIN * 2;
|
|
const int nameW = tableW - iconW - 80;
|
|
const std::vector<int> cols = { iconW, nameW, 80 };
|
|
const int headerH = 16;
|
|
const int rowH = 36;
|
|
const int n = static_cast<int>(hero->secSkillsInit.size());
|
|
|
|
widgets.push_back(std::make_shared<WikiTableGrid>(MARGIN, curY, tableW, cols,
|
|
headerH, rowH, n, blueStyle));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + CELL_L, curY + CELL_T,
|
|
FONT_TINY, ETextAlignment::TOPLEFT, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("vcmi.wiki.hero.column.skill")));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + nameW + CELL_L, curY + CELL_T,
|
|
FONT_TINY, ETextAlignment::TOPLEFT, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("vcmi.wiki.hero.column.level")));
|
|
curY += headerH;
|
|
|
|
for(const auto & [skillId, level] : hero->secSkillsInit)
|
|
{
|
|
if(skillId.getNum() < 0 || skillId.getNum() >= (int)LIBRARY->skillh->objects.size())
|
|
{ curY += rowH; continue; }
|
|
const auto & sk = LIBRARY->skillh->objects[skillId.getNum()];
|
|
if(!sk) { curY += rowH; continue; }
|
|
|
|
const size_t frame = static_cast<size_t>(sk->getIconIndex(static_cast<uint8_t>(level - 1)));
|
|
widgets.push_back(std::make_shared<CAnimImage>(
|
|
AnimationPath::builtin("SECSK32"), frame, 0,
|
|
MARGIN + 2, curY + 2));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + CELL_L, curY + rowH / 2 - 5,
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE,
|
|
sk->getNameTranslated()));
|
|
const std::string lvlStr = (level >= 1 && level <= 3)
|
|
? LIBRARY->generaltexth->levels[level - 1]
|
|
: std::to_string(level);
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + nameW + CELL_L, curY + rowH / 2 - 5,
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, valCol, lvlStr));
|
|
|
|
const CSkill * skPtr = sk.get();
|
|
const int skLvl = level;
|
|
widgets.push_back(std::make_shared<WikiClickable>(
|
|
Rect(MARGIN, curY, tableW, rowH),
|
|
nullptr,
|
|
[skPtr, skLvl](){
|
|
CRClickPopup::createAndPush(skPtr->getDescriptionTranslated(skLvl));
|
|
},
|
|
blueStyle, clipRect));
|
|
curY += rowH;
|
|
}
|
|
curY += GAP;
|
|
}
|
|
|
|
// ── 7. Starting spells (always shown) ──────────────────────────────
|
|
{
|
|
curY += 16;
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
W / 2, curY,
|
|
FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("vcmi.heroOverview.spells")));
|
|
curY += 20;
|
|
|
|
if(!hero->haveSpellBook)
|
|
{
|
|
const int tableW = W - MARGIN * 2;
|
|
const int rowH = 28;
|
|
widgets.push_back(std::make_shared<WikiTableGrid>(MARGIN, curY, tableW, std::vector<int>{tableW}, 0, rowH, 1, blueStyle));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + tableW / 2, curY + rowH / 2 - 5,
|
|
FONT_SMALL, ETextAlignment::TOPCENTER, Colors::WHITE,
|
|
LIBRARY->generaltexth->translate("vcmi.wiki.hero.noSpellBook")));
|
|
curY += rowH + GAP;
|
|
}
|
|
else
|
|
{
|
|
std::vector<const CSpell *> validSpells;
|
|
for(const SpellID & sid : hero->spells)
|
|
{
|
|
if(sid.getNum() < 0 || sid.getNum() >= (int)LIBRARY->spellh->objects.size()) continue;
|
|
const auto & sp = LIBRARY->spellh->objects[sid.getNum()];
|
|
if(sp) validSpells.push_back(sp.get());
|
|
}
|
|
|
|
if(!validSpells.empty())
|
|
{
|
|
const int iconW = 36;
|
|
const int tableW = W - MARGIN * 2;
|
|
const int nameW = tableW - iconW;
|
|
const std::vector<int> cols = { iconW, nameW };
|
|
const int headerH = 16;
|
|
const int rowH = 36;
|
|
|
|
widgets.push_back(std::make_shared<WikiTableGrid>(MARGIN, curY, tableW, cols,
|
|
headerH, rowH, static_cast<int>(validSpells.size()), blueStyle));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + CELL_L, curY + CELL_T,
|
|
FONT_TINY, ETextAlignment::TOPLEFT, Colors::YELLOW,
|
|
LIBRARY->generaltexth->translate("vcmi.wiki.hero.column.spell")));
|
|
curY += headerH;
|
|
|
|
for(const CSpell * sp : validSpells)
|
|
{
|
|
widgets.push_back(std::make_shared<CAnimImage>(
|
|
AnimationPath::builtin("SPELLBON"),
|
|
static_cast<size_t>(sp->getIconIndex()),
|
|
Rect(MARGIN + 2, curY + 2, 32, 32), 0));
|
|
widgets.push_back(std::make_shared<CLabel>(
|
|
MARGIN + iconW + CELL_L, curY + rowH / 2 - 5,
|
|
FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE,
|
|
sp->getNameTranslated()));
|
|
|
|
std::function<void()> lclick;
|
|
if(navigateCallback)
|
|
{
|
|
const std::string spId = sp->getJsonKey();
|
|
lclick = [navigateCallback, spId](){ navigateCallback(WikiCategory::SPELL, spId); };
|
|
}
|
|
const CSpell * spPtr = sp;
|
|
widgets.push_back(std::make_shared<WikiClickable>(
|
|
Rect(MARGIN, curY, tableW, rowH),
|
|
std::move(lclick),
|
|
[spPtr](){
|
|
CRClickPopup::createAndPush(spPtr->getDescriptionTranslated(1));
|
|
},
|
|
blueStyle, clipRect));
|
|
curY += rowH;
|
|
}
|
|
curY += GAP;
|
|
}
|
|
else
|
|
{
|
|
// Has spellbook but no starting spells
|
|
curY += GAP;
|
|
}
|
|
}
|
|
}
|
|
|
|
return widgets;
|
|
}
|