Skip to content

Commit

Permalink
Truncate displayed ship names to prevent overflowing containers (endl…
Browse files Browse the repository at this point in the history
…ess-sky#2631)

* Apply truncation to the front of ship names if selling more than one

Avoids awkward-looking line wraps caused by long ship names when coupled with list-style confirmation dialog.
Refs endless-sky#2249

* Extend truncation to other appearances of ship names

Adds front or end truncation to the following locations:
 - Hailing screen: no overflow off right edge for combinations of long ship names and/or long government names.
 - Targeting reticle: no overflow off left edge of ship name
 - Ship detail screen: no overlap of "ship:" and the ship name
 - Shipyard/Outfitter ship details screen: no/minimal overflow out of rounded square border
 - Player Info fleet list: no overflow of ship name across category

Front truncation used where possible, to preserve possible identifying numbers on the tail of the ship name, e.g. the "1" in 'My Ship 1'

* Remove all overlap from Shipyard/Outfitter shared panel

* Font::TruncateMiddle

Allow truncation of strings from the middle out. No special handling to cut in "pretty" locations to do things such as prefer whole-word removal, 2nd-letter starting, etc.
Changes in border characters from "..." to alphanumerics, plus the slightly reduced width precision of TruncateMiddle vs Truncate(Front) made adjusting some targeted widths necessary.
  • Loading branch information
tehhowch authored and TJesionowski committed Oct 2, 2018
1 parent e69d8e7 commit 9556f87
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 21 deletions.
3 changes: 2 additions & 1 deletion source/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,10 +520,11 @@ void Engine::Step(bool isActive)
}
else
{
const Font &font = FontSet::Get(14);
if(target->GetSystem() == player.GetSystem() && target->Cloaking() < 1.)
targetUnit = target->Facing().Unit();
info.SetSprite("target sprite", target->GetSprite(), targetUnit, target->GetFrameIndex(step));
info.SetString("target name", target->Name());
info.SetString("target name", font.TruncateMiddle(target->Name(), 150));
info.SetString("target type", target->ModelName());
if(!target->GetGovernment())
info.SetString("target government", "No Government");
Expand Down
40 changes: 40 additions & 0 deletions source/Font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,46 @@ string Font::TruncateFront(const string &str, int width) const



string Font::TruncateMiddle(const string &str, int width) const
{
int prevChars = str.size();
int prevWidth = Width(str);
if(prevWidth <= width)
return str;

width -= Width("...");
// As a safety against infinite loops (even though they won't be possible if
// this implementation is correct), limit the number of loops to the number
// of characters in the string.
for(size_t i = 0; i < str.length(); ++i)
{
// Loop until the previous width we tried was too long and this one is
// too short, or vice versa. Each time, the next string length we try is
// interpolated from the previous width.
int nextChars = (prevChars * width) / prevWidth;
bool isSame = (nextChars == prevChars);
bool prevWorks = (prevWidth <= width);
nextChars += (prevWorks ? isSame : -isSame);

int leftChars = nextChars / 2;
int rightChars = nextChars - leftChars;
int nextWidth = Width(str.substr(0, leftChars) + str.substr(str.size() - rightChars));
bool nextWorks = (nextWidth <= width);
if(prevWorks != nextWorks && abs(nextChars - prevChars) == 1)
{
leftChars = min(prevChars,nextChars) / 2;
rightChars = min(prevChars, nextChars) - leftChars;
return str.substr(0, leftChars) + "..." + str.substr(str.size() - rightChars);
}

prevChars = nextChars;
prevWidth = nextWidth;
}
return str;
}



int Font::Height() const
{
return height;
Expand Down
1 change: 1 addition & 0 deletions source/Font.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Font {
int Width(const char *str, char after = ' ') const;
std::string Truncate(const std::string &str, int width) const;
std::string TruncateFront(const std::string &str, int width) const;
std::string TruncateMiddle(const std::string &str, int width) const;

int Height() const;

Expand Down
4 changes: 3 additions & 1 deletion source/HailPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ PARTICULAR PURPOSE. See the GNU General Public License for more details.
#include "HailPanel.h"

#include "DrawList.h"
#include "Font.h"
#include "FontSet.h"
#include "Format.h"
#include "GameData.h"
Expand Down Expand Up @@ -43,8 +44,9 @@ HailPanel::HailPanel(PlayerInfo &player, const shared_ptr<Ship> &ship)
SetInterruptible(false);

const Government *gov = ship->GetGovernment();
const Font &font = FontSet::Get(14);
if(!ship->Name().empty())
header = gov->GetName() + " " + ship->Noun() + " \"" + ship->Name() + "\":";
header = font.Truncate(gov->GetName() + " " + ship->Noun() + " \"" + ship->Name(), 328) + "\":";
else
header = ship->ModelName() + " (" + gov->GetName() + "): ";
// Drones are always unpiloted, so they never respond to hails.
Expand Down
3 changes: 2 additions & 1 deletion source/PlayerInfoPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ void PlayerInfoPanel::DrawFleet(const Rectangle &bounds)
// Loop through all the player's ships.
int index = scroll;
auto sit = player.Ships().begin() + scroll;
const Font &font = FontSet::Get(14);
for( ; sit < player.Ships().end(); ++sit)
{
// Bail out if we've used out the whole drawing area.
Expand All @@ -472,7 +473,7 @@ void PlayerInfoPanel::DrawFleet(const Rectangle &bounds)
zones.emplace_back(table.GetCenterPoint(), table.GetRowSize(), index);

// Indent the ship name if it is a fighter or drone.
table.Draw(ship.CanBeCarried() ? " " + ship.Name() : ship.Name());
table.Draw(font.TruncateMiddle(ship.CanBeCarried() ? " " + ship.Name() : ship.Name(), 217));
table.Draw(ship.ModelName());

const System *system = ship.GetSystem();
Expand Down
32 changes: 18 additions & 14 deletions source/ShipInfoPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ PARTICULAR PURPOSE. See the GNU General Public License for more details.

using namespace std;

namespace {
static const double WIDTH = 250.;
}


ShipInfoPanel::ShipInfoPanel(PlayerInfo &player, int index)
Expand Down Expand Up @@ -273,24 +276,25 @@ void ShipInfoPanel::UpdateInfo()
void ShipInfoPanel::DrawShipStats(const Rectangle &bounds)
{
// Check that the specified area is big enough.
if(bounds.Width() < 250.)
if(bounds.Width() < WIDTH)
return;

// Colors to draw with.
Color dim = *GameData::Colors().Get("medium");
Color bright = *GameData::Colors().Get("bright");
const Ship &ship = **shipIt;
const Font &font = FontSet::Get(14);

// Table attributes.
Table table;
table.AddColumn(0, Table::LEFT);
table.AddColumn(230, Table::RIGHT);
table.SetUnderline(0, 230);
table.AddColumn(WIDTH - 20, Table::RIGHT);
table.SetUnderline(0, WIDTH - 20);
table.DrawAt(bounds.TopLeft() + Point(10., 8.));

// Draw the ship information.
table.Draw("ship:", dim);
table.Draw(ship.Name(), bright);
table.Draw(font.TruncateMiddle(ship.Name(), WIDTH - 50), bright);

table.Draw("model:", dim);
table.Draw(ship.ModelName(), bright);
Expand All @@ -303,7 +307,7 @@ void ShipInfoPanel::DrawShipStats(const Rectangle &bounds)
void ShipInfoPanel::DrawOutfits(const Rectangle &bounds, Rectangle &cargoBounds)
{
// Check that the specified area is big enough.
if(bounds.Width() < 250.)
if(bounds.Width() < WIDTH)
return;

// Colors to draw with.
Expand All @@ -314,8 +318,8 @@ void ShipInfoPanel::DrawOutfits(const Rectangle &bounds, Rectangle &cargoBounds)
// Table attributes.
Table table;
table.AddColumn(0, Table::LEFT);
table.AddColumn(230, Table::RIGHT);
table.SetUnderline(0, 230);
table.AddColumn(WIDTH - 20, Table::RIGHT);
table.SetUnderline(0, WIDTH - 20);
Point start = bounds.TopLeft() + Point(10., 8.);
table.DrawAt(start);

Expand All @@ -330,8 +334,8 @@ void ShipInfoPanel::DrawOutfits(const Rectangle &bounds, Rectangle &cargoBounds)
// plus at least one outfit.
if(table.GetRowBounds().Bottom() + 40. > bounds.Bottom())
{
start += Point(250., 0.);
if(start.X() + 230. > bounds.Right())
start += Point(WIDTH, 0.);
if(start.X() + WIDTH - 20 > bounds.Right())
break;
table.DrawAt(start);
}
Expand All @@ -344,8 +348,8 @@ void ShipInfoPanel::DrawOutfits(const Rectangle &bounds, Rectangle &cargoBounds)
// Check if we've gone below the bottom of the bounds.
if(table.GetRowBounds().Bottom() > bounds.Bottom())
{
start += Point(250., 0.);
if(start.X() + 230. > bounds.Right())
start += Point(WIDTH, 0.);
if(start.X() + WIDTH - 20 > bounds.Right())
break;
table.DrawAt(start);
table.Draw(category, bright);
Expand Down Expand Up @@ -385,7 +389,7 @@ void ShipInfoPanel::DrawWeapons(const Rectangle &bounds)
const Sprite *sprite = ship.GetSprite();
double scale = 0.;
if(sprite)
scale = min(240. / sprite->Width(), 240. / sprite->Height());
scale = min((WIDTH - 10) / sprite->Width(), (WIDTH - 10) / sprite->Height());

// Figure out the left- and right-most hardpoints on the ship. If they are
// too far apart, the scale may need to be reduced.
Expand Down Expand Up @@ -497,8 +501,8 @@ void ShipInfoPanel::DrawCargo(const Rectangle &bounds)
const CargoHold &cargo = (player.Cargo().Used() ? player.Cargo() : ship.Cargo());
Table table;
table.AddColumn(0, Table::LEFT);
table.AddColumn(230, Table::RIGHT);
table.SetUnderline(-5, 235);
table.AddColumn(WIDTH - 20, Table::RIGHT);
table.SetUnderline(-5, WIDTH - 15);
table.DrawAt(bounds.TopLeft() + Point(10., 8.));

double endY = bounds.Bottom() - 30. * (cargo.Passengers() != 0);
Expand Down
9 changes: 6 additions & 3 deletions source/ShipyardPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,12 @@ bool ShipyardPanel::CanSell() const
void ShipyardPanel::Sell()
{
static const int MAX_LIST = 20;
static const int MAX_NAME_WIDTH = 250 - 30;

int count = playerShips.size();
int initialCount = count;
string message = "Sell ";
const Font &font = FontSet::Get(14);
if(count == 1)
message += playerShip->Name();
else if(count <= MAX_LIST)
Expand All @@ -255,16 +257,17 @@ void ShipyardPanel::Sell()
else
{
while(count-- > 1)
message += ",\n" + (*it++)->Name();
message += ",\n" + font.TruncateMiddle((*it++)->Name(), MAX_NAME_WIDTH);
message += ",\nand ";
}
message += (*it)->Name();
}
else
{
auto it = playerShips.begin();
for(int i = 0; i < MAX_LIST - 1; ++i)
message += (*it++)->Name() + ",\n";
message += (*it++)->Name() + ",\n";
for(int i = 1; i < MAX_LIST - 1; ++i)
message += font.TruncateMiddle((*it++)->Name(), MAX_NAME_WIDTH) + ",\n";

message += "and " + Format::Number(count - (MAX_LIST - 1)) + " other ships";
}
Expand Down
2 changes: 1 addition & 1 deletion source/ShopPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,8 @@ void ShopPanel::DrawShip(const Ship &ship, const Point &center, bool isSelected)
float zoomSize = SHIP_SIZE - 60.f;

// Draw the ship name.
const string &name = ship.Name().empty() ? ship.ModelName() : ship.Name();
const Font &font = FontSet::Get(14);
const string &name = ship.Name().empty() ? ship.ModelName() : font.TruncateMiddle(ship.Name(), SIDE_WIDTH - 61);
Point offset(-.5f * font.Width(name), -.5f * SHIP_SIZE + 10.f);
font.Draw(name, center + offset, *GameData::Colors().Get("bright"));

Expand Down

0 comments on commit 9556f87

Please sign in to comment.