1
0
mirror of https://github.com/vcmi/vcmi.git synced 2026-06-19 22:57:37 +02:00
Files
2026-05-09 18:40:54 +02:00

249 lines
8.3 KiB
C++

/*
* WikiCommon.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 "WikiCommon.h"
#include "../../widgets/Images.h"
#include "../../widgets/TextControls.h"
#include "../../render/Canvas.h"
#include "../../render/Colors.h"
#include "../../render/EFont.h"
#include "../../render/IFont.h"
#include "../../render/IRenderHandler.h"
#include "../../GameEngine.h"
#include "../../eventsSDL/InputHandler.h"
#include "../../../lib/filesystem/ResourcePath.h"
#include "../../../lib/GameLibrary.h"
#include "../../../lib/texts/CGeneralTextHandler.h"
#include "../../../lib/TerrainHandler.h"
#include "../../../lib/mapping/MapEditUtils.h"
// ─────────────────────────────────────────────────────────────────────────────
// WikiClickable
// ─────────────────────────────────────────────────────────────────────────────
WikiClickable::WikiClickable(Rect area,
std::function<void()> lclick,
std::function<void()> rclick,
bool blue,
std::optional<Rect> clip)
: CIntObject(LCLICK | SHOW_POPUP | HOVER, area.topLeft())
, onLeftClick(std::move(lclick))
, onRightClick(std::move(rclick))
, clipRect(clip)
, blueTheme(blue)
{
pos.w = area.w;
pos.h = area.h;
}
void WikiClickable::clickPressed(const Point & cur)
{
if(clipRect && !clipRect->isInside(cur))
return;
if(onLeftClick)
onLeftClick();
}
void WikiClickable::showPopupWindow(const Point & cur)
{
if(clipRect && !clipRect->isInside(cur))
return;
if(onRightClick)
onRightClick();
}
void WikiClickable::hover(bool on)
{
if(hovered != on)
{
hovered = on;
redraw();
}
}
void WikiClickable::showAll(Canvas & to)
{
const Point cur = ENGINE->getCursorPosition();
const bool inClip = !clipRect || clipRect->isInside(cur);
hovered = pos.isInside(cur) && inClip && ENGINE->input().getCurrentInputMode() != InputMode::TOUCH;
if(hovered)
{
const ColorRGBA hoverCol = blueTheme
? ColorRGBA(60, 100, 180, 60)
: ColorRGBA(180, 160, 100, 60);
to.drawColorBlended(pos, hoverCol);
}
CIntObject::showAll(to);
}
// ─────────────────────────────────────────────────────────────────────────────
// WikiTableGrid
// ─────────────────────────────────────────────────────────────────────────────
WikiTableGrid::WikiTableGrid(
int x, int y, int totalW,
const std::vector<int> & colWidths,
int headerH,
int dataRowH,
int dataRowCount,
bool blue)
: GraphicalPrimitiveCanvas(Rect(x, y, totalW, headerH + dataRowH * dataRowCount))
{
const ColorRGBA border = wikiBorderColor(blue);
const ColorRGBA inner = wikiInnerColor(blue);
const int totalH = headerH + dataRowH * dataRowCount;
if(headerH > 0)
{
const ColorRGBA header = blue
? ColorRGBA{20, 40, 80, 140}
: ColorRGBA{60, 50, 30, 140};
addBox(Point(0, 0), Point(totalW, headerH), header);
}
addRectangle(Point(0, 0), Point(totalW, totalH), border);
if(headerH > 0)
addLine(Point(0, headerH), Point(totalW, headerH), border);
for(int r = 1; r < dataRowCount; ++r)
{
const int lineY = headerH + r * dataRowH;
addLine(Point(1, lineY), Point(totalW - 2, lineY), inner);
}
int cx = 0;
for(int i = 0; i + 1 < static_cast<int>(colWidths.size()); ++i)
{
cx += colWidths[i];
addLine(Point(cx, 1), Point(cx, totalH - 2), inner);
}
}
WikiTableGrid::WikiTableGrid(
int x, int y, int totalW,
std::initializer_list<int> colWidths,
int headerH,
int dataRowH,
int dataRowCount,
bool blue)
: WikiTableGrid(x, y, totalW, std::vector<int>(colWidths), headerH, dataRowH, dataRowCount, blue)
{}
// ─────────────────────────────────────────────────────────────────────────────
// WikiResourceCost
// ─────────────────────────────────────────────────────────────────────────────
static constexpr int RES_ICON_W = 14;
static constexpr int RES_GAP = 3;
static constexpr int RES_ENTRY_W = RES_ICON_W + 26 + RES_GAP;
WikiResourceCost::WikiResourceCost(
const TResources & cost,
int startX, int y,
int maxWidth)
: CIntObject(0, Point(startX, y))
{
OBJECT_CONSTRUCTION;
struct ResEntry { GameResID id; int amount; };
std::vector<ResEntry> entries;
TResources::nziterator it(cost);
while(it.valid())
{
entries.push_back({it->resType, static_cast<int>(it->resVal)});
++it;
}
if(entries.empty())
return;
std::stable_sort(entries.begin(), entries.end(), [](const ResEntry & a, const ResEntry & b)
{
if((a.id == GameResID::GOLD) != (b.id == GameResID::GOLD))
return a.id == GameResID::GOLD;
return a.id < b.id;
});
int relX = 0;
for(const auto & e : entries)
{
if(relX + RES_ENTRY_W > maxWidth)
break;
items.push_back(std::make_shared<CAnimImage>(
AnimationPath::builtin("SMALRES"), e.id.getNum(), 0, relX, 0));
items.push_back(std::make_shared<CLabel>(
relX + RES_ICON_W + 7, 1,
FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE,
std::to_string(e.amount)));
relX += RES_ENTRY_W;
}
pos.w = relX;
pos.h = 16;
}
// ─────────────────────────────────────────────────────────────────────────────
// terrainIconInfo
// ─────────────────────────────────────────────────────────────────────────────
WikiIconInfo terrainIconInfo(const TerrainType * terrain)
{
size_t frame = 0;
if(terrain)
{
// Use the same frame selection as the battle terrain selector (BattleOnlyModeTab):
// find the "n1" (plain/normal) pattern and use its first mapped frame index.
const auto & patterns = LIBRARY->terviewh->getTerrainViewPatterns(terrain->getId());
for(const auto & p : patterns)
if(!p.empty() && p[0].id == "n1" && !p[0].mapping.empty())
{ frame = static_cast<size_t>(p[0].mapping[0].first); break; }
}
return WikiIconInfo{terrain ? terrain->tilesFilename : AnimationPath{}, frame, 0};
}
// ─────────────────────────────────────────────────────────────────────────────
// wikiGenderSpan
// ─────────────────────────────────────────────────────────────────────────────
std::string wikiGenderSpan(bool isFemale)
{
// Check once whether FONT_SMALL can render ♂ (U+2642, UTF-8: e2 99 82).
static std::optional<bool> glyphSupported;
if(!glyphSupported)
{
auto font = ENGINE->renderHandler().loadFont(EFonts::FONT_SMALL);
const char maleGlyph[] = "\xe2\x99\x82";
glyphSupported = font && font->canRepresentCharacter(maleGlyph);
}
const char * femaleColor = "#FF69B4";
const char * maleColor = "#5080FF";
const char * color = isFemale ? femaleColor : maleColor;
if(*glyphSupported)
{
const char * glyph = isFemale ? "\xe2\x99\x80" : "\xe2\x99\x82";
return std::string("{") + color + "|" + glyph + "}";
}
else
{
const std::string fallback = isFemale
? LIBRARY->generaltexth->translate("vcmi.wiki.gender.female")
: LIBRARY->generaltexth->translate("vcmi.wiki.gender.male");
return std::string("{") + color + "|" + fallback + "}";
}
}