From c0d3faf81965ac5ac15caf4ac2be90c06fe64019 Mon Sep 17 00:00:00 2001 From: Matthias Urlichs Date: Fri, 23 Oct 2020 14:49:42 +0200 Subject: [PATCH] Roomnames (#3992) * Map: Add an option to show room names below their IDs. This is a fairly straightforward extension. TODO: use a separate font. * basic support for a separate font for room names * Use black room names on light background * Room name display and font sizing fixes Room name display is now independent of room number display. The cut-off for the font size is lower (4 instead of 7). Room names are scaled as if the number 88 would be printed in the room box, which works for all practical purposes. Font size discovery is now done in floating point, and by scaling with factors 1.2 (up) and 1.05 (down) which is faster *and* looks way better. * Don't show room names in grid mode * Tweak font size calculation Ensure that we start with a reasonable font size * Added functions to get/set a room name's offset float x/y, relative to the room rectangle (and in its units). TODO: document in the wiki: getRoomNameOffset(id) setRoomNameOffset(id, x_offset, y_offset) * Save file version updated to 21 Added: * global: room name font + size adjustment * per room: label offset * remove LayoutDirection directives * Re-sorted the grid layout items in ui/profile_prefs * Fix room name placement directly under the room's rectangle, no strange offset * Add userdata flag "room.ui_showName" indicating whether to show room name * Move room label position to userdata * Cleanup The flag whether to display room labels (both globally and per-room) is now stored in userdata. Reverted: ad6d3 Re-sorted the grid layout items in ui/profile_prefs 76834 remove LayoutDirection directives 5d81e Save file version updated to 21 TODO: flipping the global flag crashes for no good reason. * Crash workaround Not inlining "setUserDataBool" triggers a double-free bug (Debian amd64, Qt 5.14.2-3, gcc 10.2.0-9). I'm investigating this; in the meantime, this patch should be an acceptable workaround. * Update src/mudlet-lua/lua/GUIUtils.lua * Appease codefactor * Hide showRoomNames checkbox if no room name infrastructure exists as exhibited by map userData "room.ui_showName" not being present at all Co-authored-by: Vadim Peretokin --- src/T2DMap.cpp | 109 ++++++++++++++++++++++++++++---- src/T2DMap.h | 4 +- src/TLuaInterpreter.cpp | 12 +--- src/TMap.cpp | 35 +++++++++- src/TMap.h | 7 ++ src/TRoomDB.cpp | 28 ++++++++ src/TRoomDB.h | 19 ++++++ src/dlgMapper.cpp | 10 +++ src/dlgMapper.h | 1 + src/mudlet-lua/lua/GUIUtils.lua | 45 ++++++++++++- src/ui/mapper.ui | 18 +++++- 11 files changed, 260 insertions(+), 28 deletions(-) diff --git a/src/T2DMap.cpp b/src/T2DMap.cpp index 936d53e9901..ecfc2500ff0 100644 --- a/src/T2DMap.cpp +++ b/src/T2DMap.cpp @@ -627,7 +627,7 @@ void T2DMap::addSymbolToPixmapCache(const QString key, const bool gridMode) * reduced by the margin (0-40) as a percentage). This margin is defaulted to * 10%. */ -bool T2DMap::sizeFontToFitTextInRect( QFont & font, const QRectF & boundaryRect, const QString & text, const quint8 percentageMargin ) +bool T2DMap::sizeFontToFitTextInRect( QFont & font, const QRectF & boundaryRect, const QString & text, const quint8 percentageMargin, qreal minFontSize ) { QFont _font = font; @@ -639,28 +639,28 @@ bool T2DMap::sizeFontToFitTextInRect( QFont & font, const QRectF & boundaryRect, return false; } - qreal fontSize = font.pointSizeF(); + qreal fontSize = qMax(minFontSize, font.pointSizeF()); // protect against too-small initial value QRectF testRect(boundaryRect.width() * (100 - percentageMargin) / 200.0, boundaryRect.height() * (100 - percentageMargin) / 200.0, boundaryRect.width() * (100 - percentageMargin) / 100.0, boundaryRect.height() * (100 - percentageMargin) / 100.); - // Increase the test font by one, then check to see that it does NOT fit + + // Increase the test font (using somewhat-large steps) until it does not fit any more QRectF neededRect; QPixmap _pixmap(qRound(1.0 + boundaryRect.width()), qRound(1.0 + boundaryRect.height())); QPainter _painter(&_pixmap); do { - fontSize = fontSize + 1.0; + fontSize *= 1.2; _font.setPointSizeF(fontSize); _painter.setFont(_font); neededRect = _painter.boundingRect(testRect, Qt::AlignCenter | Qt::TextSingleLine | Qt::TextIncludeTrailingSpaces, text); } while (testRect.contains(neededRect)); - // Now decrease until it does + // Now decrease (using smaller steps) until it fits again bool isSizeTooSmall = false; - static qreal minFontSize = 7.0; do { - fontSize = fontSize - 1.0; + fontSize /= 1.05; _font.setPointSizeF(fontSize); if (fontSize < minFontSize) { isSizeTooSmall = true; @@ -704,16 +704,29 @@ void T2DMap::initiateSpeedWalk(const int speedWalkStartRoomId, const int speedWa // player's room if it is visible. This is so it is drawn LAST (and any effects, // or extra markings for it do not get overwritten by the drawing of the other // rooms)... -inline void T2DMap::drawRoom(QPainter& painter, QFont& roomVNumFont, QPen& pen, TRoom* pRoom, const bool isGridMode, const bool areRoomIdsLegible, const int speedWalkStartRoomId, const float rx, const float ry, const bool picked) +inline void T2DMap::drawRoom(QPainter& painter, QFont& roomVNumFont, QFont& mapNameFont, QPen& pen, TRoom* pRoom, const bool isGridMode, const bool areRoomIdsLegible, bool showRoomName, const int speedWalkStartRoomId, const float rx, const float ry, const bool picked) { const int currentRoomId = pRoom->getId(); pRoom->rendered = false; QRectF roomRectangle; + QRectF roomNameRectangle; + double realHeight; if (isGridMode) { + realHeight = mRoomHeight; roomRectangle = QRectF(rx - mRoomWidth / 2.0, ry - mRoomHeight / 2.0, mRoomWidth, mRoomHeight); } else { - roomRectangle = QRectF(rx - (mRoomWidth * rSize) / 2.0, ry - (mRoomHeight * rSize) / 2.0, mRoomWidth * rSize, mRoomHeight * rSize); + // this dance is necessary to put the name just below the room rect, later + realHeight = mRoomHeight * rSize; + roomRectangle = QRectF(rx - (mRoomWidth * rSize) / 2.0, ry - realHeight / 2.0, mRoomWidth * rSize, realHeight); } + + roomNameRectangle = roomRectangle.adjusted(-2000, realHeight, 2000, realHeight); + + painter.save(); + painter.setFont(mapNameFont); + roomNameRectangle = painter.boundingRect(roomNameRectangle, Qt::TextSingleLine|Qt::AlignTop|Qt::AlignCenter, pRoom->name); + painter.restore(); + // We should be using the full area for testing for clicks even though // we only show a smaller one if the user has dialed down the room size // on NON-grid mode areas: @@ -903,6 +916,49 @@ inline void T2DMap::drawRoom(QPainter& painter, QFont& roomVNumFont, QPen& pen, } } + // If there is a room name, draw it? + if (showRoomName) { + showRoomName = getUserDataBool(pRoom->userData, ROOM_UI_SHOWNAME, false); + } + if (showRoomName) { + painter.save(); + + QString namePosData = pRoom->userData.value(ROOM_UI_NAMEPOS); + if (!namePosData.isEmpty()) { + QPointF nameOffset {0, 0}; + QStringList posXY = namePosData.split(" "); + bool ok1, ok2; + double posX, posY; + + switch (posXY.count()) { + case 1: + // one value: treat as Y offset + posY = posXY[0].toDouble(&ok1); + if (ok1) { + nameOffset.setY(posY); + } + break; + case 2: + posX = posXY[0].toDouble(&ok1); + posY = posXY[1].toDouble(&ok2); + if (ok1 && ok2) { + nameOffset.setX(posX); + nameOffset.setY(posY); + } + break; + } + roomNameRectangle.adjust(mRoomWidth * nameOffset.x(), + mRoomHeight * nameOffset.y(), + mRoomWidth * nameOffset.x(), + mRoomHeight * nameOffset.y()); + } + auto roomNameColor = QColor((mpHost->mBgColor_2.lightness() > 127) + ? Qt::black : Qt::white); + painter.setPen(QPen(roomNameColor)); + painter.setFont(mapNameFont); + painter.drawText(roomNameRectangle, Qt::AlignCenter, pRoom->name); + painter.restore(); + } // Change these from const to static to tweak them whilst running in a debugger...! const float allInsideTipOffsetFactor = 1 / 20.0f; @@ -1230,6 +1286,22 @@ void T2DMap::paintEvent(QPaintEvent* e) mMapSymbolFont.setOverline(false); mMapSymbolFont.setStrikeOut(false); + // the room name's font defaults to the symbol's + // but may be overridden + auto mapNameFont = mpMap->mMapSymbolFont; + QString fontName = mpMap->mUserData.value(ROOM_UI_NAMEFONT); + if (!fontName.isEmpty()) { + QFont font; + if (font.fromString(fontName)) { + mapNameFont = font; + } + } + mapNameFont.setBold(false); + mapNameFont.setItalic(false); + mapNameFont.setUnderline(false); + mapNameFont.setOverline(false); + mapNameFont.setStrikeOut(false); + QList exitList; QList oneWayExits; int playerRoomId = mpMap->mRoomIdHash.value(mpMap->mProfileName); @@ -1292,6 +1364,7 @@ void T2DMap::paintEvent(QPaintEvent* e) mRX = qRound(mRoomWidth * ((xspan / 2.0) - ox)); mRY = qRound(mRoomHeight * ((yspan / 2.0) - oy)); QFont roomVNumFont = mpMap->mMapSymbolFont; + bool isFontBigEnoughToShowRoomVnum = false; if (mShowRoomID) { /* @@ -1326,6 +1399,20 @@ void T2DMap::paintEvent(QPaintEvent* e) isFontBigEnoughToShowRoomVnum = sizeFontToFitTextInRect(roomVNumFont, roomTestRect, QStringLiteral("8").repeated(mMaxRoomIdDigits), roomVnumMargin); } + bool showRoomNames = mpMap->getRoomNamesShown() && !playerArea->gridMode; + if (showRoomNames) { + /* + * Like above, except that we use the room height as the font size. + */ + mapNameFont.setBold(true); + + mapNameFont.setStyleStrategy(QFont::StyleStrategy(QFont::PreferNoShaping|QFont::PreferAntialias|QFont::PreferOutline)); + + double sizeAdjust = 0; // TODO add userdata setting to adjust this + mapNameFont.setPointSizeF(static_cast(mRoomWidth) * rSize * pow(1.1, sizeAdjust) / 2.0); + showRoomNames = (mapNameFont.pointSizeF() > 3.0); + } + TArea* pArea = playerArea; int zLevel = mOz; @@ -1423,12 +1510,12 @@ void T2DMap::paintEvent(QPaintEvent* e) playerRoomOnWidgetCoordinates = QPointF(static_cast(rx), static_cast(ry)); } else { // Not the player's room: - drawRoom(painter, roomVNumFont, pen, room, pArea->gridMode, isFontBigEnoughToShowRoomVnum, playerRoomId, rx, ry, __Pick); + drawRoom(painter, roomVNumFont, mapNameFont, pen, room, pArea->gridMode, isFontBigEnoughToShowRoomVnum, showRoomNames, playerRoomId, rx, ry, __Pick); } } // End of while loop for each room in area if (isPlayerRoomVisible) { - drawRoom(painter, roomVNumFont, pen, playerRoom, pArea->gridMode, isFontBigEnoughToShowRoomVnum, playerRoomId, static_cast(playerRoomOnWidgetCoordinates.x()), static_cast(playerRoomOnWidgetCoordinates.y()), __Pick); + drawRoom(painter, roomVNumFont, mapNameFont, pen, playerRoom, pArea->gridMode, isFontBigEnoughToShowRoomVnum, showRoomNames, playerRoomId, static_cast(playerRoomOnWidgetCoordinates.x()), static_cast(playerRoomOnWidgetCoordinates.y()), __Pick); painter.save(); QPen transparentPen(Qt::transparent); QPainterPath myPath; diff --git a/src/T2DMap.h b/src/T2DMap.h index f34257da22c..df2f7ed0df7 100644 --- a/src/T2DMap.h +++ b/src/T2DMap.h @@ -214,8 +214,8 @@ public slots: void resizeMultiSelectionWidget(); std::pair getMousePosition(); bool checkButtonIsForGivenDirection(const QPushButton*, const QString&, const int&); - bool sizeFontToFitTextInRect(QFont&, const QRectF&, const QString&, const quint8 percentageMargin = 10); - void drawRoom(QPainter&, QFont&, QPen&, TRoom*, const bool isGridMode, const bool areRoomIdsLegible, const int, const float, const float, const bool); + bool sizeFontToFitTextInRect(QFont&, const QRectF&, const QString&, const quint8 percentageMargin = 10, const qreal minFontSize = 7.0); + void drawRoom(QPainter&, QFont&, QFont&, QPen&, TRoom*, const bool isGridMode, const bool areRoomIdsLegible, bool showRoomNames, const int, const float, const float, const bool); void paintMapInfo(const QElapsedTimer& renderTimer, QPainter& painter, const bool showingCurrentArea, QColor& infoColor); void paintAreaExits(QPainter& painter, QPen& pen, QList& exitList, QList& oneWayExits, const TArea* pArea, int zLevel, float exitWidth); void initiateSpeedWalk(const int speedWalkStartRoomId, const int speedWalkTargetRoomId); diff --git a/src/TLuaInterpreter.cpp b/src/TLuaInterpreter.cpp index 5dbeda90caf..fde664776c7 100644 --- a/src/TLuaInterpreter.cpp +++ b/src/TLuaInterpreter.cpp @@ -1411,17 +1411,7 @@ int TLuaInterpreter::updateMap(lua_State* L) { Host& host = getHostFromLua(L); if (host.mpMap) { -#if defined(INCLUDE_3DMAPPER) - if (host.mpMap->mpM) { - host.mpMap->mpM->update(); - } -#endif - if (host.mpMap->mpMapper) { - if (host.mpMap->mpMapper->mp2dMap) { - host.mpMap->mpMapper->mp2dMap->mNewMoveAction = true; - host.mpMap->mpMapper->mp2dMap->update(); - } - } + host.mpMap->update(); } return 0; } diff --git a/src/TMap.cpp b/src/TMap.cpp index f04993ceaf3..dd34e6e26fb 100644 --- a/src/TMap.cpp +++ b/src/TMap.cpp @@ -1451,7 +1451,6 @@ bool TMap::restore(QString location, bool downloadIfNotFound) |QFont::PreferOutline | QFont::PreferAntialias | QFont::PreferQuality |QFont::PreferNoShaping )); - if (mVersion >= 14) { int areaSize; ifs >> areaSize; @@ -2544,3 +2543,37 @@ QString TMap::getMmpMapLocation() const { return mMmpMapLocation; } + +bool TMap::getRoomNamesPresent() +{ + return mUserData.contains(ROOM_UI_SHOWNAME); +} + +bool TMap::getRoomNamesShown() +{ + return getUserDataBool(mUserData, ROOM_UI_SHOWNAME, false); +} + +void TMap::setRoomNamesShown(bool shown) +{ + setUserDataBool(mUserData, ROOM_UI_SHOWNAME, shown); +} + +void TMap::update() +{ +#if defined(INCLUDE_3DMAPPER) + if (mpM) { + mpM->update(); + } +#endif + if (mpMapper) { + mpMapper->showRoomNames->setVisible(getRoomNamesPresent()); + mpMapper->showRoomNames->setChecked(getRoomNamesShown()); + + if (mpMapper->mp2dMap) { + mpMapper->mp2dMap->mNewMoveAction = true; + mpMapper->mp2dMap->update(); + } + } +} + diff --git a/src/TMap.h b/src/TMap.h index 6f6d4f8626a..6906a683705 100644 --- a/src/TMap.h +++ b/src/TMap.h @@ -102,6 +102,7 @@ class TMap : public QObject void tidyMap(int area); bool setExit(int from, int to, int dir); bool setRoomCoordinates(int id, int x, int y, int z); + void update(); // Was init( Host * ) but host pointer was not used and it does not initialise a map! void audit(); @@ -234,6 +235,12 @@ class TMap : public QObject // Disables font substitution if set: bool mIsOnlyMapSymbolFontToBeUsed; + // has setRoomNamesShown ever been called on this map? + bool getRoomNamesPresent(); + // show room labels on the map? + bool getRoomNamesShown(); + void setRoomNamesShown(bool shown); + // location of an MMP map provided by the game QString mMmpMapLocation; diff --git a/src/TRoomDB.cpp b/src/TRoomDB.cpp index b96055b3c0b..ea2477606e9 100644 --- a/src/TRoomDB.cpp +++ b/src/TRoomDB.cpp @@ -30,6 +30,10 @@ #include #include "post_guard.h" +const QString ROOM_UI_SHOWNAME = QStringLiteral("room.ui_showName"); +const QString ROOM_UI_NAMEPOS = QStringLiteral("room.ui_nameOffset"); +const QString ROOM_UI_NAMEFONT = QStringLiteral("room.ui_nameFont"); +const QString ROOM_UI_NAMESIZE = QStringLiteral("room.ui_nameSize"); TRoomDB::TRoomDB(TMap* pMap) : mpMap(pMap) @@ -1326,3 +1330,27 @@ void TRoomDB::setAreaRooms(const int areaId, const QSet& roomIds) pA->calcSpan(); // The area extents will need recalculation after adding the rooms } + +bool getUserDataBool(const QMap& userData, const QString& key, bool defaultValue) +{ + if (!userData.contains(key)) { + return defaultValue; + } + QString value = userData.value(key); + if (value.isEmpty()) { + return defaultValue; + } + switch (value[0].unicode()) { + case 'y': case 'Y': + case 't': case 'T': + case '1': + return true; + case 'n': case 'N': + case 'f': case 'F': + case '0': + return false; + default: + return defaultValue; + } +} + diff --git a/src/TRoomDB.h b/src/TRoomDB.h index fbc9f6e1072..c09f472cc88 100644 --- a/src/TRoomDB.h +++ b/src/TRoomDB.h @@ -34,6 +34,11 @@ class TArea; class TMap; class TRoom; +// well-known userData tags +extern const QString ROOM_UI_SHOWNAME; +extern const QString ROOM_UI_NAMEPOS; +extern const QString ROOM_UI_NAMEFONT; // global only +extern const QString ROOM_UI_NAMESIZE; // TODO class TRoomDB { @@ -112,4 +117,18 @@ class TRoomDB friend class XMLimport; }; +// helpers to get/set bools from userdata, required for storing some bool +// values there instead of upticking the map format +bool getUserDataBool(const QMap& userData, const QString& key, bool defaultValue = false); + +// this needs to be inlined due to a compiler and/or Qt bug. +static inline void setUserDataBool(QMap& userData, const QString& key, bool value) +{ + if (value) { + userData[key] = QStringLiteral("1"); + } else { + userData[key] = QStringLiteral("0"); + } +} + #endif // MUDLET_TROOMDB_H diff --git a/src/dlgMapper.cpp b/src/dlgMapper.cpp index 2f33ab55870..e63b7fddfb1 100644 --- a/src/dlgMapper.cpp +++ b/src/dlgMapper.cpp @@ -81,6 +81,9 @@ dlgMapper::dlgMapper( QWidget * parent, Host * pH, TMap * pM ) showRoomIDs->setChecked(mpHost->mShowRoomID); mp2dMap->mShowRoomID = mpHost->mShowRoomID; + showRoomNames->setVisible(mpMap->getRoomNamesPresent()); + showRoomNames->setChecked(mpMap->getRoomNamesShown()); + panel->setVisible(mpHost->mShowPanel); connect(bubbles, &QAbstractButton::clicked, this, &dlgMapper::slot_bubbles); connect(showInfo, &QAbstractButton::clicked, this, &dlgMapper::slot_info); @@ -96,6 +99,7 @@ dlgMapper::dlgMapper( QWidget * parent, Host * pH, TMap * pM ) connect(showArea, qOverload(&QComboBox::activated), mp2dMap, &T2DMap::slot_switchArea); connect(dim2, &QAbstractButton::pressed, this, &dlgMapper::show2dView); connect(showRoomIDs, &QCheckBox::stateChanged, this, &dlgMapper::slot_toggleShowRoomIDs); + connect(showRoomNames, &QCheckBox::stateChanged, this, &dlgMapper::slot_toggleShowRoomNames); // Explicitly set the font otherwise it changes between the Application and // the default System one as the mapper is docked and undocked! @@ -173,6 +177,12 @@ void dlgMapper::slot_toggleShowRoomIDs(int s) mp2dMap->update(); } +void dlgMapper::slot_toggleShowRoomNames(int s) +{ + mpMap->setRoomNamesShown(s == Qt::Checked); + mp2dMap->update(); +} + void dlgMapper::slot_toggleStrongHighlight(int v) { mpHost->mMapStrongHighlight = v == Qt::Checked ? true : false; diff --git a/src/dlgMapper.h b/src/dlgMapper.h index 71e9eeed11d..dd2505dbd7f 100644 --- a/src/dlgMapper.h +++ b/src/dlgMapper.h @@ -57,6 +57,7 @@ public slots: void slot_bubbles(); void slot_info(); void slot_toggleShowRoomIDs(int s); + void slot_toggleShowRoomNames(int s); void slot_toggleStrongHighlight(int v); void show2dView(); void slot_togglePanel(); diff --git a/src/mudlet-lua/lua/GUIUtils.lua b/src/mudlet-lua/lua/GUIUtils.lua index 27cbcf04562..205a63373cb 100644 --- a/src/mudlet-lua/lua/GUIUtils.lua +++ b/src/mudlet-lua/lua/GUIUtils.lua @@ -10,8 +10,6 @@ --- @name gaugesTable gaugesTable = {} - - --- The color_table table holds definition of color names. These are intended to be -- used in conjunction with fg() and bg() colorizer functions. -- Mudlet's original table - going back a few years - differs from the @@ -2071,6 +2069,49 @@ function resizeMapWidget(width, height) openMapWidget(-1, -1, width, height) end +-- +-- functions to manipulate room label display and offsets +-- +-- get offset of room's label (x,y) +-- @param room Room ID +function getRoomNameOffset(room) + assert(type(room) == 'number', 'getRoomNameOffset: bad argument #1 type (room ID as number expected, got '..type(room)..'!)') + + local d = getRoomUserData(room, "room.ui_nameOffset") + if d == nil or d == "" then return 0,0 end + local split = {} + for w in string.gfind(d, '[%.%d]+') do split[#split+1] = tonumber(w) end + if #split == 1 then return 0,split[1] end + if #split >= 2 then return split[1],split[2] end + return 0,0 +end + +-- set offset of room's label (x,y) +-- @param room Room ID +-- @param room X shift (positive = to the right) +-- @param room Y shift (positive = down) +function setRoomNameOffset(room, x, y) + assert(type(room) == 'number', 'setRoomNameOffset: bad argument #1 type (room ID as number expected, got '..type(room)..'!)') + assert(type(x) == 'number', 'setRoomNameOffset: bad argument #2 type (X shift as number expected, got '..type(x)..'!)') + assert(type(y) == 'number', 'setRoomNameOffset: bad argument #3 type (y shift as number expected, got '..type(y)..'!)') + + if x == 0 then + setRoomUserData(room, "room.ui_nameOffset", y) + else + setRoomUserData(room, "room.ui_nameOffset", x .. " " .. y) + end +end + +-- show or hide a room's name +-- @param room Room ID +-- @param flag (bool) +function setRoomNameVisible(room, flag) + assert(type(room) == 'number', 'setRoomNameVisible: bad argument #1 type (room ID as number expected, got '..type(room)..'!)') + assert(type(flag) == 'boolean', 'setRoomNameVisible: bad argument #2 type (flag as boolean expected, got '..type(flag)..'!)') + + setRoomUserData(room, "room.ui_showName", flag and "1" or "0") +end + --wrapper for createButton -- createButton is deprecated better use createLabel instead createButton = createLabel diff --git a/src/ui/mapper.ui b/src/ui/mapper.ui index 19d65cb6fef..b272a41f4fc 100644 --- a/src/ui/mapper.ui +++ b/src/ui/mapper.ui @@ -696,7 +696,23 @@ Qt::LeftToRight - Room IDs + IDs + + + + + + + + 0 + 0 + + + + Qt::LeftToRight + + + Names