Build info overlay for the tools with build preview

This commit is contained in:
dP
2024-02-07 13:55:16 +05:30
parent 97b2885e90
commit 36001f85d1
10 changed files with 402 additions and 16 deletions

View File

@@ -46,6 +46,8 @@ add_files(
cm_misc_gui.cpp
cm_rail_gui.hpp
cm_rail_gui.cpp
cm_overlays.hpp
cm_overlays.cpp
cm_station_gui.hpp
cm_station_gui.cpp
cm_tooltips.hpp

View File

@@ -25,7 +25,7 @@
namespace citymania {
bool Intersects(PointDimension rect, Point pos, Point size) {
static bool Intersects(PointDimension rect, Point pos, Point size) {
return (
pos.x + size.x >= rect.x &&
pos.x <= rect.x + rect.width &&
@@ -34,7 +34,7 @@ bool Intersects(PointDimension rect, Point pos, Point size) {
);
}
bool Intersects(PointDimension rect, int left, int top, int right, int bottom) {
static bool Intersects(PointDimension rect, int left, int top, int right, int bottom) {
return (
right >= rect.x &&
left <= rect.x + rect.width &&

View File

@@ -5,6 +5,7 @@
#include "cm_blueprint.hpp"
#include "cm_commands.hpp"
#include "cm_main.hpp"
#include "cm_overlays.hpp"
#include "cm_station_gui.hpp"
#include "cm_zoning.hpp"
@@ -35,6 +36,11 @@
#include "../table/autorail.h"
#include "../table/industry_land.h"
#include "../debug.h"
#include "station_gui.h"
#include "station_type.h"
#include "table/sprites.h"
#include "table/strings.h"
#include "tile_type.h"
#include <set>
@@ -85,6 +91,7 @@ extern RoadStopGUISettings _roadstop_gui_settings;
namespace citymania {
extern CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad);
extern void (*DrawTileSelectionRect)(const TileInfo *ti, PaletteID pal);
extern void (*DrawAutorailSelection)(const TileInfo *ti, HighLightStyle autorail_type, PaletteID pal);
@@ -348,6 +355,33 @@ void ObjectHighlight::AddTile(TileIndex tile, ObjectTileHighlight &&oh) {
this->tiles.insert(std::make_pair(tile, std::move(oh)));
}
void ObjectHighlight::AddStationOverlayData(TileIndex tile, int w, int h, int rad, StationCoverageType sct) {
if (!_settings_game.station.modified_catchment) rad = CA_UNMODIFIED;
auto production = citymania::GetProductionAroundTiles(this->tile, w, h, rad);
bool has_header = false;
for (CargoID i = 0; i < NUM_CARGO; i++) {
if (production[i] == 0) continue;
switch (sct) {
case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break;
case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break;
case SCT_ALL: break;
default: NOT_REACHED();
}
const CargoSpec *cs = CargoSpec::Get(i);
if (cs == nullptr) continue;
if (!has_header) {
this->overlay_data.emplace_back(PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_SUPPLIES));
has_header = true;
}
SetDParam(0, i);
SetDParam(1, production[i] >> 8);
this->overlay_data.emplace_back(cs->GetCargoIcon(), GetString(CM_STR_BUILD_INFO_OVERLAY_STATION_CARGO));
}
}
void ObjectHighlight::UpdateTiles() {
this->tiles.clear();
this->sprites.clear();
@@ -411,6 +445,7 @@ void ObjectHighlight::UpdateTiles() {
tile_track += tile_delta ^ TileDiffXY(1, 1); // perpendicular to tile_delta
} while (--numtracks);
this->AddStationOverlayData(this->tile, ta.w, ta.h, CA_TRAIN, SCT_ALL);
break;
}
case Type::ROAD_STOP: {
@@ -432,6 +467,9 @@ void ObjectHighlight::UpdateTiles() {
for (TileIndex tile : ta) {
this->AddTile(tile, ObjectTileHighlight::make_road_stop(palette, this->roadtype, this->ddir, this->is_truck, this->road_stop_spec_class, this->road_stop_spec_index));
}
auto sct = (this->is_truck ? SCT_NON_PASSENGERS_ONLY : SCT_PASSENGERS_ONLY);
auto rad = (this->is_truck ? CA_BUS : CA_TRUCK);
this->AddStationOverlayData(this->tile, ta.w, ta.h, rad, sct);
break;
}
@@ -465,6 +503,7 @@ void ObjectHighlight::UpdateTiles() {
for (AirportTileTableIterator iter(as->table[this->airport_layout], this->tile); iter != INVALID_TILE; ++iter) {
this->AddTile(iter, ObjectTileHighlight::make_airport_tile(palette, iter.GetStationGfx()));
}
this->AddStationOverlayData(this->tile, w, h, as->catchment, SCT_ALL);
break;
}
case Type::BLUEPRINT:
@@ -578,10 +617,43 @@ void ObjectHighlight::UpdateTiles() {
default:
NOT_REACHED();
}
}
if (this->cost.Succeeded()) {
// TODO overlay size
void ObjectHighlight::UpdateOverlay() {
HideBuildInfoOverlay();
auto w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
if (w == nullptr) return;
auto vp = IsPtInWindowViewport(w, _cursor.pos.x, _cursor.pos.y);
if (vp == nullptr) return;
if (this->tile == INVALID_TILE) {
HideBuildInfoOverlay();
return;
}
auto err = this->cost.GetErrorMessage();
auto extra_err = this->cost.GetExtraErrorMessage();
bool no_money = (err == STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY);
SetDParam(0, this->cost.GetCost());
this->overlay_data.emplace_back(PAL_NONE, GetString(no_money ? CM_STR_BUILD_INFO_OVERLAY_COST_NO_MONEY : CM_STR_BUILD_INFO_OVERLAY_COST_OK));
// if (this->cost.Failed() && err != STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY) {
// if (err == INVALID_STRING_ID) {
// this->overlay_data.emplace_back(PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_ERROR_UNKNOWN));
// } else {
// SetDParam(0, err);
// this->overlay_data.emplace_back(PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_ERROR));
// }
// if (extra_err != INVALID_STRING_ID) {
// SetDParam(0, extra_err);
// this->overlay_data.emplace_back(PAL_NONE, GetString(CM_STR_BUILD_INFO_OVERLAY_ERROR));
// }
// }
// Point pt = RemapCoords2(TileX(this->tile) * TILE_SIZE + TILE_SIZE / 2, TileY(this->tile) * TILE_SIZE + TILE_SIZE / 2);
Point pt = RemapCoords2(TileX(this->tile) * TILE_SIZE, TileY(this->tile) * TILE_SIZE);
pt.x = UnScaleByZoom(pt.x - vp->virtual_left, vp->zoom) + vp->left;
pt.y = UnScaleByZoom(pt.y - vp->virtual_top, vp->zoom) + vp->top;
// this->overlay_pos = pt;
ShowBuildInfoOverlay(pt.x, pt.y, this->overlay_data);
}
void ObjectHighlight::MarkDirty() {
@@ -1823,6 +1895,7 @@ HighLightStyle UpdateTileSelection(HighLightStyle new_drawstyle) {
_thd.cm.MarkDirty();
_thd.cm = _thd.cm_new;
_thd.cm.UpdateTiles();
_thd.cm.UpdateOverlay();
_thd.cm.MarkDirty();
}
return new_drawstyle;

View File

@@ -9,6 +9,7 @@
#include "../signal_type.h"
#include "../station_map.h"
#include "../station_type.h"
#include "../station_gui.h" // StationCoverageType
#include "../tile_cmd.h"
#include "../tile_type.h"
#include "../track_type.h"
@@ -318,6 +319,8 @@ protected:
bool tiles_updated = false;
std::multimap<TileIndex, ObjectTileHighlight> tiles;
std::vector<DetachedHighlight> sprites = {};
std::vector<std::pair<SpriteID, std::string>> overlay_data = {};
// Point overlay_pos = {0, 0};
void AddTile(TileIndex tile, ObjectTileHighlight &&oh);
// void AddSprite(TileIndex tile, ObjectTileHighlight &&oh);
void PlaceExtraDepotRail(TileIndex tile, DiagDirection dir, Track track);
@@ -342,7 +345,9 @@ public:
void Draw(const TileInfo *ti);
void DrawSelectionOverlay(DrawPixelInfo *dpi);
void DrawOverlay(DrawPixelInfo *dpi);
void AddStationOverlayData(TileIndex tile, int w, int h, int rad, StationCoverageType sct);
void UpdateTiles();
void UpdateOverlay();
void MarkDirty();
};

View File

@@ -0,0 +1,283 @@
#include "../stdafx.h"
#include "cm_overlays.hpp"
#include "3rdparty/fmt/core.h"
#include "cm_client_list_gui.hpp"
#include "../blitter/factory.hpp" // Blitter BlitterFactory
#include "../core/geometry_func.hpp" // maxdim
#include "../core/geometry_type.hpp" // PointDimension
#include "../core/alloc_type.hpp" // ReusableBuffer
#include "../gfx_func.h" // _screen
#include "../network/network.h" // _networking
#include "../video/video_driver.hpp" // VideoDriver
#include "../sprite.h" // PALETTE_TO_TRANSPARENT
#include "../window_gui.h" // Window
#include "../window_func.h" // FindWindowByID
#include "../zoom_func.h"
#include "../safeguards.h"
#include "gfx_type.h"
#include "table/sprites.h"
namespace citymania {
static bool Intersects(PointDimension rect, Point pos, Point size) {
return (
pos.x + size.x >= rect.x &&
pos.x <= rect.x + rect.width &&
pos.y + size.y >= rect.y &&
pos.y <= rect.y + rect.height
);
}
static bool Intersects(PointDimension rect, int left, int top, int right, int bottom) {
return (
right >= rect.x &&
left <= rect.x + rect.width &&
bottom >= rect.y &&
top <= rect.y + rect.height
);
}
class OverlayWindow {
protected:
PointDimension box;
bool drawn = false;
bool dirty = true;
PointDimension backup_box;
ReusableBuffer<uint8_t> backup;
int padding;
int x = 0;
int y = 0;
public:
OverlayWindow() {}
void SetDirty() {
this->dirty = true;
}
void UpdateSize() {
this->padding = ScaleGUITrad(3);
auto dim = this->GetContentDimension();
this->box.width = dim.width + 2 * this->padding;
this->box.height = dim.height + 2 * this->padding;
this->box.x = this->x - this->box.width / 2;
this->box.y = this->y - this->box.height;
// Window *toolbar = FindWindowById(WC_MAIN_TOOLBAR, 0);
// if (toolbar != nullptr && toolbar->left < this->box.x + this->box.width + this->padding) {
// this->box.y = toolbar->top + toolbar->height + this->padding;
// }
if (this->box.x < 0) this->box.x = 0;
if (this->box.y < 0) this->box.y = 0;
if (this->box.x >= _screen.width) this->box.x = _screen.width - 1;
if (this->box.y >= _screen.height) this->box.y = _screen.height - 1;
if (this->box.x + this->box.width > _screen.width)
this->box.width = _screen.width - this->box.x;
if (this->box.y + this->box.height > _screen.height)
this->box.height = _screen.height - this->box.y;
}
virtual Dimension GetContentDimension()=0;
void Undraw(int left, int top, int right, int bottom) {
auto &box = this->backup_box;
if (!this->drawn) return;
if (!Intersects(box, left, top, right, bottom)) return;
if (box.x + box.width > _screen.width) return;
if (box.y + box.height > _screen.height) return;
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
/* Put our 'shot' back to the screen */
blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, box.x, box.y), this->backup.GetBuffer(), box.width, box.height);
/* And make sure it is updated next time */
VideoDriver::GetInstance()->MakeDirty(box.x, box.y, box.width, box.height);
this->drawn = false;
this->dirty = true;
}
virtual bool IsVisible()=0;
virtual void DrawContent(Rect rect)=0;
void Draw() {
if (!this->dirty) return;
this->dirty = false;
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
bool was_drawn = this->drawn;
if (this->drawn) {
/* Put our 'shot' back to the screen */
blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, this->backup_box.x, this->backup_box.y), this->backup.GetBuffer(), this->backup_box.width, this->backup_box.height);
/* And make sure it is updated next time */
VideoDriver::GetInstance()->MakeDirty(this->backup_box.x, this->backup_box.y, this->backup_box.width, this->backup_box.height);
this->drawn = false;
}
if (!this->IsVisible()) return;
this->UpdateSize();
if (this->box.width <= 0 || this->box.height <= 0) return;
if (!was_drawn ||
this->box.x != this->backup_box.x ||
this->box.y != this->backup_box.y ||
this->box.width != this->backup_box.width ||
this->box.height != this->backup_box.height) {
uint8_t *buffer = this->backup.Allocate(BlitterFactory::GetCurrentBlitter()->BufferSize(this->box.width, this->box.height));
blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, this->box.x, this->box.y), buffer, this->box.width, this->box.height);
this->backup_box = this->box;
}
_cur_dpi = &_screen; // switch to _screen painting
auto rect = Rect{this->box.x, this->box.y, this->box.x + this->box.width, this->box.y + this->box.height};
/* Paint a half-transparent box behind the client list */
GfxFillRect(rect.left, rect.top, rect.right - 1, rect.bottom - 1,
PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR // black, but with some alpha for background
);
this->DrawContent(rect.Shrink(this->padding));
/* Make sure the data is updated next flush */
VideoDriver::GetInstance()->MakeDirty(this->box.x, this->box.y, this->box.width, this->box.height);
this->drawn = true;
}
};
class IconTextAligner {
public:
int line_height;
int max_height;
int padding;
int text_ofs_x;
int text_ofs_y;
Dimension Init(BuildInfoOverlayData &data, int padding, FontSize font_size) {
// TODO rtl
auto font_height = GetCharacterHeight(font_size);
this->max_height = font_height;
this->line_height = this->max_height + padding;
this->text_ofs_x = 0;
this->text_ofs_y = 0;
this->padding = padding;
if (data.size() == 0) { return {0, 0}; }
Dimension text_dim{0, 0};
Dimension icon_dim{0, 0};
for (const auto &[icon, s] : data) {
text_dim = maxdim(text_dim, GetStringBoundingBox(s));
if (icon != PAL_NONE)
icon_dim = maxdim(icon_dim, GetSpriteSize(icon));
}
this->max_height = std::max<int>(this->max_height, icon_dim.height);
this->text_ofs_y = (this->max_height - font_height) / 2;
this->line_height = max_height + padding;
if (icon_dim.width > 0)
this->text_ofs_x = icon_dim.width + this->padding;
return {
text_dim.width + this->text_ofs_x,
data.size() * this->line_height - padding
};
}
std::pair<Point, Rect> GetPositions(Rect rect, int row, SpriteID icon) {
auto icon_height = this->max_height;
auto ofs_x = 0;
if (icon != PAL_NONE) {
icon_height = GetSpriteSize(icon).height;
ofs_x = this->text_ofs_x;
}
return {
{
rect.left,
rect.top + row * this->line_height + (this->max_height - icon_height) / 2,
},
{
rect.left + ofs_x,
rect.top + row * this->line_height + this->text_ofs_y,
rect.right,
rect.bottom
},
};
}
};
class BuildInfoOverlay: public OverlayWindow {
bool visible = false;
int line_height = 0;
int text_ofs_x = 0;
BuildInfoOverlayData data;
IconTextAligner aligner;
public:
void Show(int x, int y, BuildInfoOverlayData data) {
this->x = x;
this->y = y;
this->data = data;
this->visible = true;
this->SetDirty();
}
void Hide() {
this->visible = false;
}
bool IsVisible() override {
return this->visible;
}
Dimension GetContentDimension() override {
return aligner.Init(this->data, this->padding, FS_NORMAL);
}
void DrawContent(Rect rect) override {
int row = 0;
for (const auto &[icon, s] : this->data) {
auto [ipos, srect] = this->aligner.GetPositions(rect, row, icon);
DrawString(srect.left, srect.right, srect.top, s);
if (icon != PAL_NONE)
DrawSprite(icon, PAL_NONE, ipos.x, ipos.y);
row++;
}
}
};
static BuildInfoOverlay _build_info_overlay{};
void UndrawOverlays(int left, int top, int right, int bottom) {
_build_info_overlay.Undraw(left, top, right, bottom);
if (_networking) UndrawClientList(left, top, right, bottom);
}
void DrawOverlays() {
_build_info_overlay.Draw();
if (_networking) DrawClientList();
}
void ShowBuildInfoOverlay(int x, int y, BuildInfoOverlayData data) {
_build_info_overlay.Show(x, y, data);
}
void HideBuildInfoOverlay() {
_build_info_overlay.Hide();
}
} // namespace citymania

View File

@@ -0,0 +1,18 @@
#ifndef CM_OVERLAYS_HPP
#define CM_OVERLAYS_HPP
#include "cm_client_list_gui.hpp" // to reexport SetClientListDirty
#include "../sprite.h" // SpriteID
namespace citymania {
typedef std::vector<std::pair<SpriteID, std::string>> BuildInfoOverlayData;
void UndrawOverlays(int left, int top, int right, int bottom);
void DrawOverlays();
void ShowBuildInfoOverlay(int x, int y, BuildInfoOverlayData data);
void HideBuildInfoOverlay();
} // namespace citymania
#endif

View File

@@ -586,7 +586,7 @@ CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad)
{36, 48, 64, 96, 196}
};
CargoArray produced;
CargoArray produced{};
std::set<IndustryID> industries;
TileArea ta = TileArea(tile, w, h).Expand(rad);
@@ -617,9 +617,8 @@ CargoArray GetProductionAroundTiles(TileIndex tile, int w, int h, int rad)
/* Skip industry with neutral station */
if (i->neutral_station != nullptr && !_settings_game.station.serve_neutral_industries) continue;
for (auto &p : i->produced) {
CargoID cargo = p.cargo;
if (cargo != CT_INVALID) produced[p.cargo] += ((uint)p.history[LAST_MONTH].production) << 8;
for (const auto &p : i->produced) {
if (IsValidCargoID(p.cargo)) produced[p.cargo] += ((uint)p.history[LAST_MONTH].production) << 8;
}
}
@@ -633,18 +632,17 @@ std::string GetStationCoverageProductionText(TileIndex tile, int w, int h, int r
s << GetString(CM_STR_STATION_BUILD_SUPPLIES);
bool first = true;
for (CargoID i = 0; i < NUM_CARGO; i++) {
if (production[i] == 0) continue;
switch (sct) {
case SCT_PASSENGERS_ONLY: if (!IsCargoInClass(i, CC_PASSENGERS)) continue; break;
case SCT_NON_PASSENGERS_ONLY: if (IsCargoInClass(i, CC_PASSENGERS)) continue; break;
case SCT_ALL: break;
default: NOT_REACHED();
}
if (production[i] == 0) continue;
if (!first) s << ", ";
first = false;
SetDParam(0, i);
SetDParam(1, production[i] >> 8);
// GetString(buffer, CM_STR_STATION_BUILD_SUPPLIES_CARGO, lastof(buffer));
s << GetString(STR_JUST_CARGO);
}
return s.str();

View File

@@ -33,7 +33,7 @@
#include "table/sprites.h"
#include "table/control_codes.h"
#include "citymania/cm_client_list_gui.hpp"
#include "citymania/cm_overlays.hpp"
#include "safeguards.h"
@@ -101,7 +101,7 @@ void GfxScroll(int left, int top, int width, int height, int xo, int yo)
if (_cursor.visible) UndrawMouseCursor();
if (_networking) citymania::UndrawClientList(left, top, left + width, top + height);
citymania::UndrawOverlays(left, top, left + width, top + height);
if (_networking) NetworkUndrawChatMessage();
blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
@@ -1412,7 +1412,7 @@ void RedrawScreenRect(int left, int top, int right, int bottom)
}
}
if (_networking) citymania::UndrawClientList(left, top, right, bottom);
citymania::UndrawOverlays(left, top, right, bottom);
if (_networking) NetworkUndrawChatMessage();
DrawOverlappedWindowForAll(left, top, right, bottom);
@@ -1510,7 +1510,7 @@ void DrawDirtyBlocks()
auto clear_overlays = [&]() {
if (cleared_overlays) return;
if (_cursor.visible) UndrawMouseCursor();
if (_networking) citymania::UndrawClientList(0, 0, _screen.width, _screen.height);
citymania::UndrawOverlays(0, 0, _screen.width, _screen.height);
if (_networking) NetworkUndrawChatMessage();
cleared_overlays = true;
};

View File

@@ -6280,3 +6280,10 @@ CM_BUILDING_PREVIEW_COST_ENOUGH :Cost
CM_STR_NO_BLUEPRINT_IN_SLOT :{WHITE}No blueprint in slot {NUM}
CM_STR_ABOUT_MENU_LOGIN_WINDOW :CityMania-Serverlogin
CM_STR_BUILD_INFO_OVERLAY_COST_OK :{WHITE}Cost: {CURRENCY_LONG}
CM_STR_BUILD_INFO_OVERLAY_COST_NO_MONEY :{RED}Cost: {CURRENCY_LONG}
CM_STR_BUILD_INFO_OVERLAY_STATION_ACCEPTS :{WHITE}Accepts:
CM_STR_BUILD_INFO_OVERLAY_STATION_SUPPLIES :{WHITE}Supplies:
CM_STR_BUILD_INFO_OVERLAY_STATION_CARGO :{WHITE}{CARGO_LONG}
CM_STR_BUILD_INFO_OVERLAY_ERROR :{RED}{STRING}
CM_STR_BUILD_INFO_OVERLAY_ERROR_UNKNOWN :{RED}Unknown Error!

View File

@@ -42,8 +42,8 @@
/* CityMania code start */
#include <limits>
#include "window_gui.h"
#include "citymania/cm_client_list_gui.hpp"
#include "citymania/cm_hotkeys.hpp"
#include "citymania/cm_overlays.hpp"
#include "citymania/cm_tooltips.hpp"
/* CityMania code end */
@@ -3095,7 +3095,7 @@ void UpdateWindows()
/* Update viewport only if window is not shaded. */
if (w->viewport != nullptr && !w->IsShaded()) UpdateViewportPosition(w, delta_ms.count());
}
citymania::DrawClientList();
citymania::DrawOverlays();
NetworkDrawChatMessage();
/* Redraw mouse cursor in case it was hidden */
DrawMouseCursor();