1
0
mirror of https://github.com/OpenTTD/OpenTTD synced 2025-12-10 06:52:05 +01:00

Feature: Configurable sign text colors in scenario editor. (#14743)

This commit is contained in:
Kuhnovic
2025-12-08 17:52:19 +01:00
committed by GitHub
parent 0d59ecf7de
commit 8e35553208
10 changed files with 98 additions and 30 deletions

View File

@@ -3695,6 +3695,7 @@ STR_EDIT_SIGN_CAPTION :{WHITE}Edit sig
STR_EDIT_SIGN_LOCATION_TOOLTIP :{BLACK}Centre the main view on sign location. Ctrl+Click to open a new viewport on sign location
STR_EDIT_SIGN_NEXT_SIGN_TOOLTIP :{BLACK}Go to next sign
STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP :{BLACK}Go to previous sign
STR_EDIT_SIGN_TEXT_COLOUR_TOOLTIP :{BLACK}Colour of the sign's text
STR_EDIT_SIGN_SIGN_OSKTITLE :{BLACK}Enter a name for the sign

View File

@@ -412,6 +412,7 @@ enum SaveLoadVersion : uint16_t {
SLV_DOCKS_UNDER_BRIDGES, ///< 360 PR#14594 Allow docks under bridges.
SLV_LOCKS_UNDER_BRIDGES, ///< 361 PR#14595 Allow locks under bridges.
SLV_ENGINE_MULTI_RAILTYPE, ///< 362 PR#14357 v15.0 Train engines can have multiple railtypes.
SLV_SIGN_TEXT_COLOURS, ///< 363 PR#14743 Configurable sign text colors in scenario editor.
SL_MAX_VERSION, ///< Highest possible saveload version
};

View File

@@ -19,15 +19,16 @@
/** Description of a sign within the savegame. */
static const SaveLoad _sign_desc[] = {
SLE_CONDVAR(Sign, name, SLE_NAME, SL_MIN_VERSION, SLV_84),
SLE_CONDVAR(Sign, name, SLE_NAME, SL_MIN_VERSION, SLV_84),
SLE_CONDSSTR(Sign, name, SLE_STR | SLF_ALLOW_CONTROL, SLV_84, SL_MAX_VERSION),
SLE_CONDVAR(Sign, x, SLE_FILE_I16 | SLE_VAR_I32, SL_MIN_VERSION, SLV_5),
SLE_CONDVAR(Sign, y, SLE_FILE_I16 | SLE_VAR_I32, SL_MIN_VERSION, SLV_5),
SLE_CONDVAR(Sign, x, SLE_INT32, SLV_5, SL_MAX_VERSION),
SLE_CONDVAR(Sign, y, SLE_INT32, SLV_5, SL_MAX_VERSION),
SLE_CONDVAR(Sign, owner, SLE_UINT8, SLV_6, SL_MAX_VERSION),
SLE_CONDVAR(Sign, z, SLE_FILE_U8 | SLE_VAR_I32, SL_MIN_VERSION, SLV_164),
SLE_CONDVAR(Sign, z, SLE_INT32, SLV_164, SL_MAX_VERSION),
SLE_CONDVAR(Sign, x, SLE_FILE_I16 | SLE_VAR_I32, SL_MIN_VERSION, SLV_5),
SLE_CONDVAR(Sign, y, SLE_FILE_I16 | SLE_VAR_I32, SL_MIN_VERSION, SLV_5),
SLE_CONDVAR(Sign, x, SLE_INT32, SLV_5, SL_MAX_VERSION),
SLE_CONDVAR(Sign, y, SLE_INT32, SLV_5, SL_MAX_VERSION),
SLE_CONDVAR(Sign, owner, SLE_UINT8, SLV_6, SL_MAX_VERSION),
SLE_CONDVAR(Sign, z, SLE_FILE_U8 | SLE_VAR_I32, SL_MIN_VERSION, SLV_164),
SLE_CONDVAR(Sign, z, SLE_INT32, SLV_164, SL_MAX_VERSION),
SLE_CONDVAR(Sign, text_colour, SLE_UINT8, SLV_SIGN_TEXT_COLOURS, SL_MAX_VERSION),
};
struct SIGNChunkHandler : ChunkHandler {

View File

@@ -45,7 +45,7 @@
EnforcePreconditionEncodedText(false, text);
EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_SIGN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG);
return ScriptObject::Command<CMD_RENAME_SIGN>::Do(sign_id, text);
return ScriptObject::Command<CMD_RENAME_SIGN>::Do(sign_id, text, INVALID_COLOUR);
}
/* static */ std::optional<std::string> ScriptSign::GetName(SignID sign_id)
@@ -67,7 +67,7 @@
{
EnforceDeityOrCompanyModeValid(false);
EnforcePrecondition(false, IsValidSign(sign_id));
return ScriptObject::Command<CMD_RENAME_SIGN>::Do(sign_id, "");
return ScriptObject::Command<CMD_RENAME_SIGN>::Do(sign_id, "", INVALID_COLOUR);
}
/* static */ SignID ScriptSign::BuildSign(TileIndex location, Text *name)

View File

@@ -24,7 +24,8 @@ struct Sign : SignPool::PoolItem<&_sign_pool> {
int32_t x = 0;
int32_t y = 0;
int32_t z = 0;
Owner owner = INVALID_OWNER; // placed by this company. Anyone can delete them though. OWNER_NONE for gray signs from old games.
Owner owner = INVALID_OWNER; // Placed by this company. Anyone can delete them though. OWNER_NONE for gray signs from old games.
Colours text_colour = COLOUR_WHITE; // Colour of the sign's text. Only relevant for OWNER_DEITY.
Sign() {}
Sign(Owner owner, int32_t x, int32_t y, int32_t z, const std::string &name) : name(name), x(x), y(y), z(z), owner(owner) {}

View File

@@ -62,9 +62,10 @@ std::tuple<CommandCost, SignID> CmdPlaceSign(DoCommandFlags flags, TileIndex til
* @param flags type of operation
* @param sign_id index of the sign to be renamed/removed
* @param text the new name or an empty string when resetting to the default
* @param text_colour colour of the sign's text. Only relevant for OWNER_DEITY. Use INVALID_COLOUR to keep the current colour.
* @return the cost of this operation or an error
*/
CommandCost CmdRenameSign(DoCommandFlags flags, SignID sign_id, const std::string &text)
CommandCost CmdRenameSign(DoCommandFlags flags, SignID sign_id, const std::string &text, Colours text_colour)
{
Sign *si = Sign::GetIfValid(sign_id);
if (si == nullptr) return CMD_ERROR;
@@ -77,6 +78,7 @@ CommandCost CmdRenameSign(DoCommandFlags flags, SignID sign_id, const std::strin
if (flags.Test(DoCommandFlag::Execute)) {
/* Assign the new one */
si->name = text;
if (text_colour != INVALID_COLOUR) si->text_colour = text_colour;
if (_game_mode != GM_EDITOR) si->owner = _current_company;
si->UpdateVirtCoord();

View File

@@ -12,9 +12,10 @@
#include "command_type.h"
#include "signs_type.h"
#include "gfx_type.h"
std::tuple<CommandCost, SignID> CmdPlaceSign(DoCommandFlags flags, TileIndex tile, const std::string &text);
CommandCost CmdRenameSign(DoCommandFlags flags, SignID sign_id, const std::string &text);
CommandCost CmdRenameSign(DoCommandFlags flags, SignID sign_id, const std::string &text, Colours text_colour);
CommandCost CmdMoveSign(DoCommandFlags flags, SignID sign_id, TileIndex tile);
DEF_CMD_TRAIT(CMD_PLACE_SIGN, CmdPlaceSign, CommandFlag::Deity, CommandType::OtherManagement)

View File

@@ -30,6 +30,8 @@
#include "signs_cmd.h"
#include "timer/timer.h"
#include "timer/timer_window.h"
#include "dropdown_common_type.h"
#include "dropdown_func.h"
#include "widgets/sign_widget.h"
@@ -379,12 +381,13 @@ Window *ShowSignList()
* Actually rename the sign.
* @param index the sign to rename.
* @param text the new name.
* @param text_colour Colour of the text if the sign is owned by OWNER_DEITY.
* @return true if the window will already be removed after returning.
*/
static bool RenameSign(SignID index, std::string_view text)
static bool RenameSign(SignID index, std::string_view text, Colours text_colour)
{
bool remove = text.empty();
Command<CMD_RENAME_SIGN>::Post(remove ? STR_ERROR_CAN_T_DELETE_SIGN : STR_ERROR_CAN_T_CHANGE_SIGN_NAME, index, std::string{text});
Command<CMD_RENAME_SIGN>::Post(remove ? STR_ERROR_CAN_T_DELETE_SIGN : STR_ERROR_CAN_T_CHANGE_SIGN_NAME, index, std::string{text}, text_colour);
return remove;
}
@@ -402,6 +405,7 @@ struct SignWindow : Window, SignList {
QueryString name_editbox;
SignID cur_sign{};
WidgetID last_user_action = INVALID_WIDGET; ///< Last started user action.
std::optional<Colours> new_colour; ///< New colour selected by the user. Will be assigned when the OK button is clicked.
SignWindow(WindowDesc &desc, const Sign *si) : Window(desc), name_editbox(MAX_LENGTH_SIGN_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_SIGN_NAME_CHARS)
{
@@ -412,6 +416,11 @@ struct SignWindow : Window, SignList {
this->InitNested(WN_QUERY_STRING_SIGN);
if (_game_mode != GameMode::GM_EDITOR) {
this->GetWidget<NWidgetStacked>(WID_QES_COLOUR_PANE)->SetDisplayedPlane(SZSP_VERTICAL);
this->ReInit();
}
UpdateSignEditWindow(si);
this->SetFocusedWidget(WID_QES_TEXT);
}
@@ -426,8 +435,10 @@ struct SignWindow : Window, SignList {
}
this->cur_sign = si->index;
this->new_colour.reset();
this->SetWidgetDirty(WID_QES_TEXT);
this->SetWidgetDirty(WID_QES_COLOUR);
this->SetFocusedWidget(WID_QES_TEXT);
}
@@ -464,14 +475,47 @@ struct SignWindow : Window, SignList {
case WID_QES_CAPTION:
return GetString(this->name_editbox.caption);
case WID_QES_COLOUR:
return GetString(STR_COLOUR_DARK_BLUE + this->new_colour.value_or(Sign::Get(this->cur_sign)->text_colour));
default:
return this->Window::GetWidgetString(widget, stringid);
}
}
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override
{
if (widget == WID_QES_COLOUR) {
const Dimension square_size = GetSpriteSize(SPR_SQUARE);
const uint string_padding = square_size.width + WidgetDimensions::scaled.hsep_normal + padding.width;
for (Colours colour = COLOUR_BEGIN; colour != COLOUR_END; ++colour) {
size.width = std::max(size.width, GetStringBoundingBox(STR_COLOUR_DARK_BLUE + colour).width + string_padding);
}
size.width = std::max(size.width, GetStringBoundingBox(STR_COLOUR_DEFAULT).width + string_padding);
return;
}
Window::UpdateWidgetSize(widget, size, padding, fill, resize);
}
void ShowColourDropDownMenu()
{
DropDownList list;
for (Colours colour = COLOUR_BEGIN; colour != COLOUR_END; ++colour) {
list.emplace_back(MakeDropDownListIconItem(SPR_SQUARE, GetColourPalette(colour), STR_COLOUR_DARK_BLUE + colour, colour));
}
const int selected = this->new_colour.value_or(Sign::Get(this->cur_sign)->text_colour);
ShowDropDownList(this, std::move(list), selected, WID_QES_COLOUR);
}
void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
{
switch (widget) {
case WID_QES_COLOUR: {
ShowColourDropDownMenu();
break;
}
case WID_QES_LOCATION: {
const Sign *si = Sign::Get(this->cur_sign);
TileIndex tile = TileVirtXY(si->x, si->y);
@@ -500,7 +544,7 @@ struct SignWindow : Window, SignList {
}
case WID_QES_OK:
if (RenameSign(this->cur_sign, this->name_editbox.text.GetText())) break;
if (RenameSign(this->cur_sign, this->name_editbox.text.GetText(), this->new_colour.value_or(INVALID_COLOUR))) break;
[[fallthrough]];
case WID_QES_CANCEL:
@@ -509,7 +553,7 @@ struct SignWindow : Window, SignList {
case WID_QES_DELETE:
/* Only need to set the buffer to null, the rest is handled as the OK button */
RenameSign(this->cur_sign, "");
RenameSign(this->cur_sign, "", INVALID_COLOUR);
/* don't delete this, we are deleted in Sign::~Sign() -> DeleteRenameSignWindow() */
break;
@@ -524,7 +568,7 @@ struct SignWindow : Window, SignList {
{
switch (this->last_user_action) {
case WID_QES_MOVE: // Place sign button
RenameSign(this->cur_sign, this->name_editbox.text.GetText());
RenameSign(this->cur_sign, this->name_editbox.text.GetText(), this->new_colour.value_or(INVALID_COLOUR));
MoveSign(this->cur_sign, tile);
this->Close();
break;
@@ -537,6 +581,11 @@ struct SignWindow : Window, SignList {
{
this->RaiseButtons();
}
void OnDropdownSelect(WidgetID widget, int index, int) override
{
if (widget == WID_QES_COLOUR) this->new_colour = static_cast<Colours>(index);
}
};
static constexpr std::initializer_list<NWidgetPart> _nested_query_sign_edit_widgets = {
@@ -553,6 +602,10 @@ static constexpr std::initializer_list<NWidgetPart> _nested_query_sign_edit_widg
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_QES_CANCEL), SetMinimalSize(60, 12), SetStringTip(STR_BUTTON_CANCEL),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_QES_DELETE), SetMinimalSize(60, 12), SetStringTip(STR_TOWN_VIEW_DELETE_BUTTON),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_QES_MOVE), SetMinimalSize(60, 12), SetStringTip(STR_BUTTON_MOVE),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_QES_COLOUR_PANE),
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_QES_COLOUR), SetMinimalSize(60, 12), SetToolTip(STR_EDIT_SIGN_TEXT_COLOUR_TOOLTIP),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_QES_PREVIOUS), SetMinimalSize(11, 12), SetArrowWidgetTypeTip(AWV_DECREASE, STR_EDIT_SIGN_PREVIOUS_SIGN_TOOLTIP),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_QES_NEXT), SetMinimalSize(11, 12), SetArrowWidgetTypeTip(AWV_INCREASE, STR_EDIT_SIGN_NEXT_SIGN_TOOLTIP),
EndContainer(),
@@ -575,7 +628,7 @@ void HandleClickOnSign(const Sign *si)
if (!CompanyCanEditSign(si)) return;
if (_ctrl_pressed && (si->owner == _local_company || (si->owner == OWNER_DEITY && _game_mode == GM_EDITOR))) {
RenameSign(si->index, "");
RenameSign(si->index, "", INVALID_COLOUR);
return;
}

View File

@@ -1404,12 +1404,18 @@ static void ViewportAddSignStrings(DrawPixelInfo *dpi, const std::vector<const S
if (small) flags.Set(ViewportStringFlag::Small);
/* Signs placed by a game script don't have a frame. */
ViewportStringFlags deity_flags{flags};
ViewportStringFlags deity_flags{ flags };
deity_flags.Set(ViewportStringFlag::TextColour);
flags.Set(IsTransparencySet(TO_SIGNS) ? ViewportStringFlag::TransparentRect : ViewportStringFlag::ColourRect);
for (const Sign *si : signs) {
/* Workaround to make sure white is actually white. The string drawing logic changes all
* colours that are not INVALID_COLOUR slightly, turning white into a light gray. */
const Colours deity_colour = si->text_colour == COLOUR_WHITE ? INVALID_COLOUR : si->text_colour;
std::string *str = ViewportAddString(dpi, &si->sign, (si->owner == OWNER_DEITY) ? deity_flags : flags,
(si->owner == OWNER_NONE) ? COLOUR_GREY : (si->owner == OWNER_DEITY ? INVALID_COLOUR : _company_colours[si->owner]));
(si->owner == OWNER_NONE) ? COLOUR_GREY : (si->owner == OWNER_DEITY ? deity_colour : _company_colours[si->owner]));
if (str == nullptr) continue;
*str = GetString(STR_SIGN_NAME, si->index);

View File

@@ -22,15 +22,17 @@ enum SignListWidgets : WidgetID {
/** Widgets of the #SignWindow class. */
enum QueryEditSignWidgets : WidgetID {
WID_QES_CAPTION, ///< Caption of the window.
WID_QES_LOCATION, ///< Scroll to sign location.
WID_QES_TEXT, ///< Text of the query.
WID_QES_OK, ///< OK button.
WID_QES_CANCEL, ///< Cancel button.
WID_QES_DELETE, ///< Delete button.
WID_QES_MOVE, ///< Move Sign button.
WID_QES_PREVIOUS, ///< Previous button.
WID_QES_NEXT, ///< Next button.
WID_QES_CAPTION, ///< Caption of the window.
WID_QES_LOCATION, ///< Scroll to sign location.
WID_QES_TEXT, ///< Text of the query.
WID_QES_OK, ///< OK button.
WID_QES_CANCEL, ///< Cancel button.
WID_QES_DELETE, ///< Delete button.
WID_QES_COLOUR_PANE, ///< Pane to show/hide the color dropdown.
WID_QES_COLOUR, ///< Colour selection dropdown.
WID_QES_MOVE, ///< Move Sign button.
WID_QES_PREVIOUS, ///< Previous button.
WID_QES_NEXT, ///< Next button.
};
#endif /* SIGN_WIDGET_H */