Initial release
This commit is contained in:
@@ -0,0 +1,483 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user