Unbake the special help texts.

The text in the various help dialogs that was previously baked into an image is now drawn as text, meaning that it benefits from text unblurring when scaling is active.

This entails some small changes to layout, since it's a different font.

Dialog engine changes:
* A new picture type allowing to draw the inventory button icons directly into a dialog.
* A new widget type that simply draws a line between two points.
This commit is contained in:
2025-03-09 11:34:38 -04:00
committed by Celtic Minstrel
parent 09e5649a61
commit 8b08b46ea0
27 changed files with 368 additions and 20 deletions

View File

@@ -21,6 +21,7 @@
#include "dialogxml/widgets/button.hpp"
#include "dialogxml/widgets/field.hpp"
#include "dialogxml/widgets/ledgroup.hpp"
#include "dialogxml/widgets/line.hpp"
#include "dialogxml/widgets/message.hpp"
#include "dialogxml/widgets/scrollbar.hpp"
#include "dialogxml/widgets/scrollpane.hpp"
@@ -212,6 +213,8 @@ void cDialog::loadFromFile(const DialogDefn& file){
inserted = controls.insert(parse<cButton>(*node, *this)).first;
else if(type == "led")
inserted = controls.insert(parse<cLed>(*node, *this)).first;
else if(type == "line")
inserted = controls.insert(parse<cConnector>(*node, *this)).first;
else if(type == "group")
inserted = controls.insert(parse<cLedGroup>(*node, *this)).first;
else if(type == "stack") {
@@ -256,6 +259,7 @@ void cDialog::loadFromFile(const DialogDefn& file){
case CTRL_UNKNOWN: ctrlType = "???"; break;
case CTRL_BTN: ctrlType = "button"; break;
case CTRL_LED: ctrlType = "led"; break;
case CTRL_LINE: ctrlType = "line"; break;
case CTRL_PICT: ctrlType = "pict"; break;
case CTRL_FIELD: ctrlType = "field"; break;
case CTRL_TEXT: ctrlType = "text"; break;
@@ -279,6 +283,7 @@ void cDialog::loadFromFile(const DialogDefn& file){
case CTRL_UNKNOWN: ctrlType = "???"; break;
case CTRL_BTN: ctrlType = "button"; break;
case CTRL_LED: ctrlType = "led"; break;
case CTRL_LINE: ctrlType = "line"; break;
case CTRL_PICT: ctrlType = "pict"; break;
case CTRL_FIELD: ctrlType = "field"; break;
case CTRL_TEXT: ctrlType = "text"; break;

View File

@@ -11,6 +11,7 @@
#include "dialogxml/widgets/button.hpp"
#include "dialogxml/widgets/field.hpp"
#include "dialogxml/widgets/ledgroup.hpp"
#include "dialogxml/widgets/line.hpp"
#include "dialogxml/widgets/message.hpp"
#include "dialogxml/widgets/pict.hpp"
#include "dialogxml/widgets/scrollbar.hpp"
@@ -48,6 +49,10 @@ bool cContainer::parseChildControl(ticpp::Element& elem, std::map<std::string,cC
auto led = getDialog()->parse<cLed>(elem, *this);
inserted = controls.insert(led).first;
id = led.first;
} else if(tag == "line") {
auto line = getDialog()->parse<cConnector>(elem, *this);
inserted = controls.insert(line).first;
id = line.first;
} else if(tag == "group") {
auto group = getDialog()->parse<cLedGroup>(elem, *this);
inserted = controls.insert(group).first;

View File

@@ -66,6 +66,7 @@ enum eControlType {
CTRL_SCROLL,///< A scrollbar
CTRL_PANE, ///< A scroll pane
CTRL_MAP, ///< A 2-dimensional grid of identical controls
CTRL_LINE, ///< Just a line connecting two points
};
enum ePosition {

View File

@@ -0,0 +1,97 @@
//
// line.cpp
// BoE
//
// Created by Celtic Minstrel on 2025-03-08.
//
#include "line.hpp"
#include "dialogxml/dialogs/dialog.hpp"
#include "gfx/render_shapes.hpp"
bool cConnector::manageFormat(eFormat prop, bool set, boost::any* val) {
switch(prop) {
case TXT_SIZE:
if(val) {
if(set) thickness = boost::any_cast<short>(*val);
else *val = thickness;
}
break;
case TXT_COLOUR:
if(val) {
if(set) color = boost::any_cast<sf::Color>(*val);
else *val = color;
}
break;
default: return false;
}
return true;
}
bool cConnector::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) {
if(attr.Name() == "slope") {
if(attr.Value() == "positive") flip = true;
else if(attr.Value() == "negative") flip = false;
else throw xBadVal(tagName, attr.Name(), attr.Value(), attr.Row(), attr.Column(), fname);
return true;
}
return cControl::parseAttribute(attr, tagName, fname);
}
void cConnector::validatePostParse(ticpp::Element& who, std::string fname, const std::set<std::string>& attrs, const std::multiset<std::string>& nodes) {
cControl::validatePostParse(who, fname, attrs, nodes);
if(!attrs.count("color") && !attrs.count("colour") && getDialog()->getBg() == cDialog::BG_DARK)
setColour(sf::Color::White);
// Width or height can be zero, but not both
if(frame.width() == 0 && frame.height() == 0)
throw xBadVal("line", "width", "0", who.Row(), who.Column(), fname);
// slope attribute forbidden if either width or height is zero
if(attrs.count("slope") && (frame.width() == 0 || frame.height() == 0))
throw xBadAttr("line", "slope", who.Row(), who.Column(), fname);
}
cConnector::cConnector(iComponent& parent)
: cControl(CTRL_TEXT,parent)
, color(parent.getDefTextClr())
{}
bool cConnector::isClickable() const {
return false;
}
bool cConnector::isFocusable() const {
return false;
}
bool cConnector::isScrollable() const {
return false;
}
void cConnector::draw(){
getWindow().setActive();
if(visible){
location first(flip ? frame.right : frame.left, frame.top);
location end(flip ? frame.left : frame.right, frame.bottom);
draw_line(getWindow(), first, end, thickness, color);
// sf::ConvexShape convex;
// convex.setPointCount(4);
// convex.setFillColor(color);
// convex.setOutlineThickness(0);
// if(x2 - x1 == 0) {
// convex.setPoint(0, sf::Vector2f(x1 - thickness / 2, y1));
// convex.setPoint(1, sf::Vector2f(x1 + thickness / 2, y1));
// convex.setPoint(2, sf::Vector2f(x2 + thickness / 2, y2));
// convex.setPoint(3, sf::Vector2f(x2 - thickness / 2, y2));
// } else {
// float alpha = atan2(y2 - y1, x2 - x1);
// convex.setPoint(0, sf::Vector2f(x1 - (thickness / 2) * sin(alpha), y1 + (thickness / 2) * cos(alpha)));
// convex.setPoint(1, sf::Vector2f(x1 + (thickness / 2) * sin(alpha), y1 - (thickness / 2) * cos(alpha)));
// convex.setPoint(2, sf::Vector2f(x2 + (thickness / 2) * sin(alpha), y2 - (thickness / 2) * cos(alpha)));
// convex.setPoint(3, sf::Vector2f(x2 - (thickness / 2) * sin(alpha), y2 + (thickness / 2) * cos(alpha)));
// }
// getWindow().draw(convex);
}
}
cConnector::~cConnector() {}

View File

@@ -0,0 +1,42 @@
//
// line.hpp
// BoE
//
// Created by Celtic Minstrel on 2025-03-08.
//
#ifndef BOE_DIALOG_LINE_HPP
#define BOE_DIALOG_LINE_HPP
#include "control.hpp"
/// A simple line drawn between two points.
class cConnector : public cControl {
public:
bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override;
void validatePostParse(ticpp::Element& who, std::string fname, const std::set<std::string>& attrs, const std::multiset<std::string>& nodes) override;
bool manageFormat(eFormat prop, bool set, boost::any* val) override;
/// Create a connector.
/// @param parent The parent dialog.
explicit cConnector(iComponent& parent);
bool isClickable() const override;
bool isFocusable() const override;
bool isScrollable() const override;
virtual ~cConnector();
void draw() override;
/// @copydoc cControl::getSupportedHandlers
///
/// @todo Document possible handlers
std::set<eDlogEvt> getSupportedHandlers() const override {
return {EVT_CLICK};
}
cConnector& operator=(cConnector& other) = delete;
cConnector(cConnector& other) = delete;
private:
sf::Color color;
rectangle to_rect;
size_t thickness = 2;
bool flip = false;
};
#endif

View File

@@ -68,6 +68,7 @@ void cPict::init(){
drawPict()[PIC_PARTY_MONST_WIDE] = &cPict::drawPartyMonstWide;
drawPict()[PIC_PARTY_MONST_TALL] = &cPict::drawPartyMonstTall;
drawPict()[PIC_PARTY_MONST_LG] = &cPict::drawPartyMonstLg;
drawPict()[PIC_BTN] = &cPict::drawInvenBtn;
}
std::map<ePicType,void(cPict::*)(short,rectangle)>& cPict::drawPict(){
@@ -473,6 +474,8 @@ bool cPict::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::str
picType = PIC_TER_MAP;
else if(val == "status")
picType = PIC_STATUS;
else if(val == "btn")
picType = PIC_BTN;
else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname);
return true;
} else if(name == "num") {
@@ -593,6 +596,25 @@ void cPict::recalcRect() {
bounds.width() = 12;
bounds.height() = 12;
break;
case PIC_BTN:
switch(picNum) {
case 0: case 1:
bounds.width() = 12;
bounds.height() = 12;
break;
case 2: case 3: case 4: case 5:
bounds.width() = 14;
bounds.height() = 12;
break;
case 6: case 7: case 8: case 9:
bounds.width() = 30;
bounds.height() = 12;
break;
case 10: case 11:
bounds.width() = 35;
bounds.height() = 15;
break;
} break;
case PIC_FULL:
case PIC_CUSTOM_FULL:
if(drawScaled) break;
@@ -675,18 +697,12 @@ std::shared_ptr<const sf::Texture> cPict::getSheetInternal(eSheetType type, size
case SHEET_CUSTOM:
// TODO: Implement this
break;
case SHEET_INVENBTN:
sout << "invenbtns";
break;
case SHEET_FULL:
purgeable = true;
switch(n) {
case 1100:
sout << "invenhelp";
break;
case 1200:
sout << "stathelp";
break;
case 1300:
sout << "actionhelp";
break;
case 1400:
sout << "outhelp";
break;
@@ -697,7 +713,7 @@ std::shared_ptr<const sf::Texture> cPict::getSheetInternal(eSheetType type, size
sout << "townhelp";
break;
default:
// TODO: The scenario should be allowed to define a sheet1100.png without it being ignored in favour of invenhelp.png
// TODO: The scenario should be allowed to define a sheet1400.png without it being ignored in favour of outhelp.png
purgeable = false;
sout << "sheet" << n;
}
@@ -1351,6 +1367,29 @@ void cPict::drawPartyPc(short num, rectangle to_rect){
rect_draw_some_item(*from_gw, from_rect, getWindow(), to_rect, sf::BlendAlpha);
}
void cPict::drawInvenBtn(short num, rectangle to_rect){
static rectangle from_rect[]{
{0, 1, 12, 13}, // pc info
{0, 13, 12, 25}, // pc switch
{12, 0, 24, 14}, // item use
{12, 14, 24, 28}, // item give
{12, 28, 24, 42}, // item drop
{12, 42, 24, 56}, // item info
{24, 0, 36, 30}, // ID
{36, 0, 48, 30}, // Sell
{48, 0, 60, 30}, // Enchant
{60, 0, 72, 30}, // Recharge
{0, 60, 15, 95}, // Special Items
{15, 60, 30, 95}, // Jobs
};
static const size_t n_pics = std::distance(std::begin(from_rect), std::end(from_rect));
if(num >= n_pics) return;
auto from_gw = getSheet(SHEET_INVENBTN);
if(!from_gw) return;
if(filled) fill_rect(getWindow(), to_rect, fillClr);
rect_draw_some_item(*from_gw, from_rect[num], getWindow(), to_rect, sf::BlendAlpha);
}
cPict::~cPict() {}
void cPict::drawAt(sf::RenderWindow& win, rectangle dest, pic_num_t which_g, ePicType type_g, bool framed) {

View File

@@ -143,6 +143,7 @@ private:
void drawPartyScen(short num, rectangle to_rect);
void drawPartyItem(short num, rectangle to_rect);
void drawPartyPc(short num, rectangle to_rect);
void drawInvenBtn(short num, rectangle to_rect);
static std::map<ePicType,void(cPict::*)(short,rectangle)>& drawPict();
};

View File

@@ -33,6 +33,7 @@ enum ePicType {
PIC_TER_MAP = 15, ///< 12x12 map graphic from the terrain map sheet, expanded to 24x24
PIC_STATUS = 16, ///< 12x12 status icon
PIC_TINY_ITEM = 17, ///< 18x18 item graphic from the small item sheet
PIC_BTN = 18, ///< Button graphic from the inventory buttons sheet (various sizes)
PIC_MONST_WIDE = 23, ///< 56x36 monster graphic from the preset sheets, resized to fit and centred in a 28x36 space
PIC_MONST_TALL = 43, ///< 28x72 monster graphic from the preset sheets, resized to fit and centred in a 28x36 space
PIC_MONST_LG = 63, ///< 56x72 monster graphic from the preset sheets, resized to fit in a 28x36 space
@@ -93,6 +94,7 @@ enum eSheetType {
SHEET_TER_MAP, ///< The terrain map icons sheet, termap.png
SHEET_FULL, ///< Any full sheet
SHEET_STATUS, ///< The status icons sheet, staticons.png
SHEET_INVENBTN, ///< Inventory buttons sheet, invenbtns.png
SHEET_CUSTOM, ///< Any custom graphics sheet
// TODO: Vehicle sheet is missing.
// TODO: Documentation of full, custom, header, and exported sheets is still lacking.