How to draw a text containing the path to the file so that if it does not fit completely into the rectangle, replace the characters in the middle of the line with an ellipsis, similar to how it is done in WinAPI using the DrawText function with the DT_PATH_ELLIPSIS flag?
This should be a cross platform solution. If anyone has a solution, please share.
You can do it with a layer…

The WDL_String::Ellipsize() method is also there for you
The WDL_String::Ellipsize() method is won’t fit. If string too long to fit, it should be shortened, e.g.
"C:\Users\Bla\Documents and Settings\Projects\Bla\bla.wav" should become
"C:\Users\Bla\...\Projects\Bla\bla.wav"
If you’d like to make PR for some kind of utility to do it how you think it should be done, i’ll take a look
This will do the trick (tested)
auto FitText = [&](const IText &text, const char* str, const IRECT& bounds, bool breakInMiddle = false, const char* indicatorString = "...")
{
IRECT measureBounds;
WDL_String textString;
textString.Set(str);
// Add indicator at the middle
if (breakInMiddle && textString.GetLength() >= 2)
{
for (int i = 0; i < textString.GetLength() - 1; i++)
{
const int textHalfLength = (textString.GetLength() - i) / 2;
WDL_String lowerText = textString;
lowerText.SetLen(textHalfLength);
WDL_String upperText = textString;
upperText.DeleteSub(0, textString.GetLength() - textHalfLength);
WDL_String combinedText = lowerText;
combinedText.Append(indicatorString);
combinedText.Append(upperText.Get());
if (g.MeasureText(text, combinedText.Get(), measureBounds) <= bounds.W())
return combinedText;
}
}
// Add indicator at the end
if (!breakInMiddle && textString.GetLength() >= 2)
{
for (int i = 0; i < textString.GetLength() - 1; i++)
{
WDL_String lowerText = textString;
lowerText.SetLen(textString.GetLength() - i);
WDL_String upperText;
upperText.Set(indicatorString);
WDL_String combinedText = lowerText;
combinedText.Append(upperText.Get());
if (g.MeasureText(text, combinedText.Get(), measureBounds) <= bounds.W())
return combinedText;
}
}
// If the above thing is not possible, try to clip the text
for (int i = 0; i < textString.GetLength(); i++)
{
WDL_String clippedText = textString;
clippedText.SetLen(textString.GetLength() - i);
if (g.MeasureText(text, clippedText.Get(), measureBounds) <= bounds.W())
return clippedText;
}
// If nothing is possible, return empty string
textString.Set("");
return textString;
};
IText textProps = IText(18, COLOR_WHITE, "Roboto-Regular");
WDL_String textDraw = FitText(textProps, path.Get(), GetRECT());
g.DrawText(textProps, textDraw.Get(), GetRECT());
Solution found.![]()
To solve the problem, I made TFilePathBox a visual component class.

Here is the source code. Maybe someone will come in handy.
P.S. Didn’t check performance on macOS.
// interface:
class TFilePathBox : public IControl
{
private:
IRECT fContentRect;
WDL_String fCaption;
float CaptionWidth();
public:
TFilePathBox(const IRECT& bounds);
void SetPath(const char* filename);
void OnResize() override;
void Draw(IGraphics& g) override;
};
// implementation:
bool IsLeadingLastUTF8Byte(const WDL_String str)
{
const char* s = str.Get();
const char* c = s + str.GetLength() - 1;
auto first = (*c & 0x80) != 0;
auto second = (*c & 0x40) != 0;
return !first || second;
}
void RemoveLastChar(WDL_String& str)
{
while (!IsLeadingLastUTF8Byte(str))
str.DeleteSub(str.GetLength() - 1, 1);
str.DeleteSub(str.GetLength() - 1, 1);
}
TFilePathBox::TFilePathBox(const IRECT& bounds):
IControl(bounds, nullptr)
{
}
float TFilePathBox::CaptionWidth()
{
IRECT r;
GetUI()->MeasureText(mText, fCaption.Get(), r);
return r.W();
}
void TFilePathBox::SetPath(const char* filename)
{
mDirty = true;
fCaption.Set(filename);
// first check caption width
if (CaptionWidth() <= fContentRect.W())
return;
// explode path to slices
#ifdef _WIN32
const char sep[2] = "\\";
#else
const char sep[2] = "/";
#endif
// list of slices
WDL_PtrList<char> list;
// get the first token
char* str = strdup(fCaption.Get());
char* token = strtok(str, sep);
// walk through other tokens
while (token)
{
list.Add(strdup(token));
token = strtok(NULL, sep);
}
free(str);
// make new fCaption
bool truncate = false;
while (CaptionWidth() > fContentRect.W())
{
int n = list.GetSize();
if (n <= 2)
{
// 2 slice minimum - truncate needed
truncate = true;
break;
}
// middle slice index
n /= 2;
list.Delete(n, free);
// clear fCaption
fCaption.SetLen(0);
// build fCaption
const int count = list.GetSize();
for (int i = 0; i < count; ++i)
{
// add ... in middle
if (i == n)
{
fCaption.Append("...");
fCaption.Append(sep);
}
// add slice
fCaption.Append(list.Get(i));
// add directory separator if not is last slice
if (i < count - 1)
fCaption.Append(sep);
}
}
list.Empty(true, free);
// if truncate path needed
if (truncate)
{
bool clip = false;
float w = fContentRect.W();
IRECT r;
GetUI()->MeasureText(mText, "...", r);
while (CaptionWidth() > w)
{
RemoveLastChar(fCaption);
if (!clip)
{
clip = true;
w -= r.W();
}
}
if (clip)
fCaption.Append("...");
}
}
void TFilePathBox::OnResize()
{
fContentRect = mRECT.GetPadded(-1);
}
void TFilePathBox::Draw(IGraphics& g)
{
g.DrawText(mText, fCaption.Get(), fContentRect, &mBlend);
g.DrawRect(COLOR_BLACK, mRECT, &mBlend);
}
Mine is a bit worse implementation as it doesn’t search for “/” in paths.
I have not yet tested this control so well with different paths to the file. So it might need to be improved. And also test the work on macOS.