484 lines
14 KiB
C++
484 lines
14 KiB
C++
// This file is part of VSTGUI. It is subject to the license terms
|
|
// in the LICENSE file found in the top-level directory of this
|
|
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
|
|
|
#include "ctextlabel.h"
|
|
#include "../platform/iplatformfont.h"
|
|
#include "../cdrawmethods.h"
|
|
#include "../cdrawcontext.h"
|
|
#include <sstream>
|
|
|
|
namespace VSTGUI {
|
|
|
|
//------------------------------------------------------------------------
|
|
// CTextLabel
|
|
//------------------------------------------------------------------------
|
|
/*! @class CTextLabel
|
|
*/
|
|
//------------------------------------------------------------------------
|
|
/**
|
|
* CTextLabel constructor.
|
|
* @param size the size of this view
|
|
* @param txt the initial text as c string (UTF-8 encoded)
|
|
* @param background the background bitmap
|
|
* @param style the display style (see CParamDisplay for styles)
|
|
*/
|
|
//------------------------------------------------------------------------
|
|
CTextLabel::CTextLabel (const CRect& size, UTF8StringPtr txt, CBitmap* background, const int32_t style)
|
|
: CParamDisplay (size, background, style)
|
|
, textTruncateMode (kTruncateNone)
|
|
{
|
|
setText (txt);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
CTextLabel::CTextLabel (const CTextLabel& v)
|
|
: CParamDisplay (v)
|
|
, textTruncateMode (v.textTruncateMode)
|
|
{
|
|
setText (v.getText ());
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CTextLabel::registerTextLabelListener (ITextLabelListener* listener)
|
|
{
|
|
if (!listeners)
|
|
listeners = std::unique_ptr<TextLabelListenerList> (new TextLabelListenerList ());
|
|
listeners->add (listener);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CTextLabel::unregisterTextLabelListener (ITextLabelListener* listener)
|
|
{
|
|
if (listeners)
|
|
listeners->remove (listener);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CTextLabel::setText (const UTF8String& txt)
|
|
{
|
|
if (text == txt)
|
|
return;
|
|
text = txt;
|
|
if (textTruncateMode != kTruncateNone)
|
|
calculateTruncatedText ();
|
|
setDirty (true);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CTextLabel::setTextTruncateMode (TextTruncateMode mode)
|
|
{
|
|
if (textTruncateMode != mode)
|
|
{
|
|
textTruncateMode = mode;
|
|
calculateTruncatedText ();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CTextLabel::calculateTruncatedText ()
|
|
{
|
|
if (textRotation != 0.) // currently truncation is only supported when not rotated
|
|
{
|
|
truncatedText = "";
|
|
return;
|
|
}
|
|
if (!(textTruncateMode == kTruncateNone || text.empty () || fontID == nullptr || fontID->getPlatformFont () == nullptr || fontID->getPlatformFont ()->getPainter () == nullptr))
|
|
{
|
|
CDrawMethods::TextTruncateMode mode = textTruncateMode == kTruncateHead ? CDrawMethods::kTextTruncateHead : CDrawMethods::kTextTruncateTail;
|
|
truncatedText = CDrawMethods::createTruncatedText (mode, text, fontID, getWidth () - getTextInset ().x * 2.);
|
|
if (truncatedText == text)
|
|
truncatedText.clear ();
|
|
if (listeners)
|
|
{
|
|
listeners->forEach (
|
|
[this] (ITextLabelListener* l) { l->onTextLabelTruncatedTextChanged (this); });
|
|
}
|
|
}
|
|
else if (!truncatedText.empty ())
|
|
truncatedText.clear ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
const UTF8String& CTextLabel::getText () const
|
|
{
|
|
return text;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CTextLabel::draw (CDrawContext *pContext)
|
|
{
|
|
drawBack (pContext);
|
|
drawPlatformText (pContext, truncatedText.empty () ? text : truncatedText);
|
|
setDirty (false);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool CTextLabel::sizeToFit ()
|
|
{
|
|
if (fontID == nullptr || fontID->getPlatformFont () == nullptr || fontID->getPlatformFont ()->getPainter () == nullptr)
|
|
return false;
|
|
CCoord width = fontID->getPlatformFont ()->getPainter ()->getStringWidth (nullptr, text.getPlatformString (), true);
|
|
if (width > 0)
|
|
{
|
|
width += (getTextInset ().x * 2.);
|
|
CRect newSize = getViewSize ();
|
|
newSize.setWidth (width);
|
|
setViewSize (newSize);
|
|
setMouseableArea (newSize);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CTextLabel::setViewSize (const CRect& rect, bool invalid)
|
|
{
|
|
CRect current (getViewSize ());
|
|
CParamDisplay::setViewSize (rect, invalid);
|
|
if (textTruncateMode != kTruncateNone && current.getWidth () != getWidth ())
|
|
{
|
|
calculateTruncatedText ();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CTextLabel::drawStyleChanged ()
|
|
{
|
|
if (textTruncateMode != kTruncateNone)
|
|
{
|
|
calculateTruncatedText ();
|
|
}
|
|
CParamDisplay::drawStyleChanged ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CTextLabel::valueChanged ()
|
|
{
|
|
if (valueToStringFunction)
|
|
{
|
|
std::string string;
|
|
if (valueToStringFunction (getValue (), string, this))
|
|
setText (UTF8String (std::move (string)));
|
|
}
|
|
CParamDisplay::valueChanged ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------
|
|
CMultiLineTextLabel::CMultiLineTextLabel (const CRect& size)
|
|
: CTextLabel (size)
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::setValue (float val)
|
|
{
|
|
CTextLabel::setValue (val);
|
|
|
|
if (valueToStringFunction)
|
|
{
|
|
std::string string;
|
|
if (valueToStringFunction (value, string, this))
|
|
setText (UTF8String (string));
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::setTextTruncateMode (TextTruncateMode)
|
|
{
|
|
// not supported on multi line labels
|
|
CTextLabel::setTextTruncateMode (kTruncateNone);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::setLineLayout (LineLayout layout)
|
|
{
|
|
if (lineLayout == layout)
|
|
return;
|
|
lineLayout = layout;
|
|
lines.clear ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::setAutoHeight (bool state)
|
|
{
|
|
if (autoHeight == state)
|
|
return;
|
|
autoHeight = state;
|
|
if (autoHeight && isAttached ())
|
|
{
|
|
if (lines.empty ())
|
|
recalculateLines (nullptr);
|
|
recalculateHeight ();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::setVerticalCentered (bool state)
|
|
{
|
|
if (verticalCentered == state)
|
|
return;
|
|
verticalCentered = state;
|
|
lines.clear ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
CCoord CMultiLineTextLabel::getMaxLineWidth ()
|
|
{
|
|
if (lines.empty () && getText ().empty () == false)
|
|
recalculateLines (nullptr);
|
|
CCoord maxWidth {};
|
|
for (const auto& line : lines)
|
|
{
|
|
if (line.r.getWidth () > maxWidth)
|
|
maxWidth = line.r.getWidth ();
|
|
}
|
|
return maxWidth;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::drawRect (CDrawContext* pContext, const CRect& updateRect)
|
|
{
|
|
if (getText ().empty () == false && lines.empty ())
|
|
recalculateLines (pContext);
|
|
drawBack (pContext);
|
|
|
|
CRect newClip (updateRect);
|
|
newClip.inset (getTextInset ());
|
|
ConcatClip clip (*pContext, newClip);
|
|
newClip = clip.get ();
|
|
|
|
pContext->setDrawMode (kAntiAliasing);
|
|
pContext->setFont (getFont ());
|
|
|
|
newClip.offsetInverse (getViewSize().getTopLeft ());
|
|
|
|
CDrawContext::Transform t (*pContext, CGraphicsTransform ().translate (getViewSize ().getTopLeft ()));
|
|
|
|
if (style & kShadowText)
|
|
{
|
|
CDrawContext::Transform t2 (*pContext, CGraphicsTransform ().translate (shadowTextOffset));
|
|
pContext->setFontColor (getShadowColor ());
|
|
for (const auto& line : lines)
|
|
{
|
|
if (line.r.rectOverlap (newClip))
|
|
pContext->drawString (line.str.getPlatformString (), line.r, getHoriAlign (), getAntialias ());
|
|
}
|
|
}
|
|
|
|
pContext->setFontColor (getFontColor ());
|
|
for (const auto& line : lines)
|
|
{
|
|
if (line.r.rectOverlap (newClip))
|
|
pContext->drawString (line.str.getPlatformString (), line.r, getHoriAlign (), getAntialias ());
|
|
else if (line.r.bottom > newClip.bottom)
|
|
break;
|
|
}
|
|
|
|
setDirty (false);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
bool CMultiLineTextLabel::sizeToFit ()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::setText (const UTF8String& txt)
|
|
{
|
|
if (getText () == txt)
|
|
return;
|
|
CTextLabel::setText (txt);
|
|
lines.clear ();
|
|
if (autoHeight && isAttached ())
|
|
{
|
|
recalculateLines (nullptr);
|
|
recalculateHeight ();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::recalculateHeight ()
|
|
{
|
|
auto viewSize = getViewSize ();
|
|
if (lines.empty ())
|
|
viewSize.setHeight (0.);
|
|
else
|
|
{
|
|
auto lastLine = lines.back ().r;
|
|
viewSize.setHeight (lastLine.bottom + getTextInset ().y);
|
|
}
|
|
CTextLabel::setViewSize (viewSize);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::setViewSize (const CRect& rect, bool invalid)
|
|
{
|
|
auto viewSize = getViewSize ();
|
|
auto normRect = rect;
|
|
viewSize.originize ();
|
|
normRect.originize ();
|
|
if (viewSize != normRect)
|
|
{
|
|
if (lineLayout != LineLayout::clip ||
|
|
(lineLayout == LineLayout::clip &&
|
|
viewSize.getHeight () != normRect.getHeight ()))
|
|
{
|
|
lines.clear ();
|
|
}
|
|
}
|
|
CTextLabel::setViewSize (rect, invalid);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::drawStyleChanged ()
|
|
{
|
|
lines.clear ();
|
|
CTextLabel::drawStyleChanged ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
inline bool isLineBreakSeparator (char32_t c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '-': return true;
|
|
case '_': return true;
|
|
case '/': return true;
|
|
case '\\': return true;
|
|
case '.': return true;
|
|
case ',': return true;
|
|
case ':': return true;
|
|
case ';': return true;
|
|
case '?': return true;
|
|
case '!': return true;
|
|
case '*': return true;
|
|
case '+': return true;
|
|
case '&': return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::calculateWrapLine (CDrawContext* context,
|
|
std::pair<UTF8String, double>& element,
|
|
const IFontPainter* const& fontPainter, double lineHeight,
|
|
double lineWidth, double maxWidth, const CPoint& textInset,
|
|
CCoord& y)
|
|
{
|
|
auto start = element.first.begin ();
|
|
auto lastSeparator = start;
|
|
auto pos = start;
|
|
while (pos != element.first.end () && *pos != 0)
|
|
{
|
|
if (isspace (*pos))
|
|
lastSeparator = pos;
|
|
else if (isLineBreakSeparator (*pos))
|
|
lastSeparator = ++pos;
|
|
if (pos == element.first.end ())
|
|
break;
|
|
auto tmpEnd = pos;
|
|
UTF8String tmp ({start.base (), (++tmpEnd).base ()});
|
|
auto width = fontPainter->getStringWidth (
|
|
context ? context->getPlatformDeviceContext () : nullptr, tmp.getPlatformString ());
|
|
if (width > maxWidth)
|
|
{
|
|
if (lastSeparator == element.first.end ())
|
|
lastSeparator = pos;
|
|
if (start == lastSeparator)
|
|
lastSeparator = pos;
|
|
lines.emplace_back (
|
|
Line {CRect (textInset.x, y, lineWidth, y + lineHeight + textInset.y),
|
|
UTF8String ({start.base (), lastSeparator.base ()})});
|
|
y += lineHeight;
|
|
pos = lastSeparator;
|
|
start = pos;
|
|
if (isspace (*start))
|
|
++start;
|
|
lastSeparator = element.first.end ();
|
|
}
|
|
++pos;
|
|
}
|
|
if (start != element.first.end ())
|
|
{
|
|
lines.emplace_back (Line {CRect (textInset.x, y, lineWidth, y + lineHeight + textInset.y),
|
|
UTF8String ({start.base (), element.first.end ().base ()})});
|
|
y += lineHeight;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CMultiLineTextLabel::recalculateLines (CDrawContext* context)
|
|
{
|
|
const auto& font = getFont ()->getPlatformFont ();
|
|
const auto& fontPainter = getFont ()->getFontPainter ();
|
|
auto ascent = font->getAscent ();
|
|
auto descent = font->getDescent ();
|
|
auto leading = font->getLeading ();
|
|
auto lineHeight = ascent + descent + leading;
|
|
|
|
const auto& textInset = getTextInset ();
|
|
auto maxWidth = getWidth () - (textInset.x * 2);
|
|
|
|
std::vector<std::pair<UTF8String, CCoord>> elements;
|
|
std::stringstream stream (getText ().getString ());
|
|
std::string line;
|
|
while (std::getline (stream, line, '\n'))
|
|
{
|
|
UTF8String str (std::move (line));
|
|
auto width = fontPainter->getStringWidth (
|
|
context ? context->getPlatformDeviceContext () : nullptr, str.getPlatformString ());
|
|
elements.emplace_back (std::move (str), width);
|
|
}
|
|
|
|
CCoord y = textInset.y;
|
|
|
|
auto lineWidth = getWidth () - textInset.x;
|
|
|
|
for (auto& element : elements)
|
|
{
|
|
if (lineLayout == LineLayout::clip)
|
|
{
|
|
lines.emplace_back (Line {
|
|
CRect (textInset.x, y, element.second + textInset.x, y + lineHeight + textInset.y),
|
|
std::move (element.first)});
|
|
}
|
|
else
|
|
{
|
|
if (element.second > maxWidth)
|
|
{
|
|
if (lineLayout == LineLayout::truncate)
|
|
{
|
|
element.first = CDrawMethods::createTruncatedText (
|
|
CDrawMethods::kTextTruncateTail, element.first, fontID, maxWidth);
|
|
}
|
|
else // wrap
|
|
{
|
|
calculateWrapLine (context, element, fontPainter, lineHeight, lineWidth,
|
|
maxWidth, textInset, y);
|
|
continue;
|
|
}
|
|
}
|
|
lines.emplace_back (
|
|
Line {CRect (textInset.x, y, lineWidth, y + lineHeight + textInset.y),
|
|
std::move (element.first)});
|
|
}
|
|
y += lineHeight;
|
|
}
|
|
if (getVerticalCentered () && !lines.empty ())
|
|
{
|
|
auto maxHeight = lines.back ().r.bottom;
|
|
auto offset = ((getHeight () - textInset.y) - maxHeight) / 2.;
|
|
if (offset > 0)
|
|
{
|
|
for (auto& l : lines)
|
|
l.r.offset (0, offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // VSTGUI
|