Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -0,0 +1,634 @@
// 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 "csegmentbutton.h"
#include "../cdrawcontext.h"
#include "../cframe.h"
#include "../cgraphicspath.h"
#include "../events.h"
#include <algorithm>
namespace VSTGUI {
//-----------------------------------------------------------------------------
CSegmentButton::CSegmentButton (const CRect& size, IControlListener* listener, int32_t tag)
: CControl (size, listener, tag), font (kNormalFont)
{
setWantsFocus (true);
}
//-----------------------------------------------------------------------------
bool CSegmentButton::canAddOneMoreSegment () const
{
return (getSelectionMode () != SelectionMode::kMultiple || segments.size () < 32);
}
//-----------------------------------------------------------------------------
bool CSegmentButton::addSegment (const Segment& segment, uint32_t index)
{
if (!canAddOneMoreSegment ())
return false;
if (index == kPushBack && segments.size () < kPushBack)
segments.emplace_back (segment);
else if (index < segments.size ())
{
auto it = segments.begin ();
std::advance (it, index);
segments.insert (it, segment);
}
updateSegmentSizes ();
return true;
}
//-----------------------------------------------------------------------------
bool CSegmentButton::addSegment (Segment&& segment, uint32_t index)
{
if (!canAddOneMoreSegment ())
return false;
if (index == kPushBack && segments.size () < kPushBack)
segments.emplace_back (std::move (segment));
else if (index < segments.size ())
{
auto it = segments.begin ();
std::advance (it, index);
segments.insert (it, std::move (segment));
}
updateSegmentSizes ();
return true;
}
//-----------------------------------------------------------------------------
void CSegmentButton::removeSegment (uint32_t index)
{
if (index < segments.size ())
{
auto it = segments.begin ();
std::advance (it, index);
segments.erase (it);
}
updateSegmentSizes ();
}
//-----------------------------------------------------------------------------
void CSegmentButton::removeAllSegments ()
{
segments.clear ();
invalid ();
}
//-----------------------------------------------------------------------------
void CSegmentButton::valueChanged ()
{
switch (getSelectionMode ())
{
case SelectionMode::kSingle:
case SelectionMode::kSingleToggle:
{
auto index = static_cast<int64_t> (getSelectedSegment ());
for (auto& segment : segments)
{
bool state = index == 0;
if (state != segment.selected)
{
segment.selected = state;
invalidRect (segment.rect);
}
--index;
}
break;
}
case SelectionMode::kMultiple:
{
auto bitset = static_cast<uint32_t> (value);
size_t index = 0;
for (auto& segment : segments)
{
bool state = (hasBit (bitset, 1 << index));
if (state != segment.selected)
{
segment.selected = state;
invalidRect (segment.rect);
}
++index;
}
break;
}
}
CControl::valueChanged ();
}
//-----------------------------------------------------------------------------
void CSegmentButton::setSelectedSegment (uint32_t index)
{
if (index >= segments.size ())
return;
beginEdit ();
setValueNormalized (static_cast<float> (index) / static_cast<float> (segments.size () - 1));
valueChanged ();
endEdit ();
}
//-----------------------------------------------------------------------------
uint32_t CSegmentButton::getSelectedSegment () const
{
return getSegmentIndex (getValueNormalized ());
}
//-----------------------------------------------------------------------------
void CSegmentButton::setStyle (Style newStyle)
{
if (style != newStyle)
{
style = newStyle;
updateSegmentSizes ();
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setSelectionMode (SelectionMode mode)
{
if (mode != selectionMode)
{
selectionMode = mode;
if (isAttached ())
{
verifySelections ();
invalid ();
}
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setTextTruncateMode (CDrawMethods::TextTruncateMode mode)
{
if (textTruncateMode != mode)
{
textTruncateMode = mode;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setGradient (CGradient* newGradient)
{
if (gradient != newGradient)
{
gradient = newGradient;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setGradientHighlighted (CGradient* newGradient)
{
if (gradientHighlighted != newGradient)
{
gradientHighlighted = newGradient;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setRoundRadius (CCoord newRoundRadius)
{
if (roundRadius != newRoundRadius)
{
roundRadius = newRoundRadius;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setFont (CFontRef newFont)
{
if (font != newFont)
{
font = newFont;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setTextAlignment (CHoriTxtAlign newAlignment)
{
if (textAlignment != newAlignment)
{
textAlignment = newAlignment;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setTextMargin (CCoord newMargin)
{
if (textMargin != newMargin)
{
textMargin = newMargin;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setTextColor (CColor newColor)
{
if (textColor != newColor)
{
textColor = newColor;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setTextColorHighlighted (CColor newColor)
{
if (textColorHighlighted != newColor)
{
textColorHighlighted = newColor;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setFrameColor (CColor newColor)
{
if (frameColor != newColor)
{
frameColor = newColor;
invalid ();
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::setFrameWidth (CCoord newWidth)
{
if (frameWidth != newWidth)
{
frameWidth = newWidth;
invalid ();
}
}
//-----------------------------------------------------------------------------
bool CSegmentButton::attached (CView* parent)
{
if (CControl::attached (parent))
{
verifySelections ();
updateSegmentSizes ();
return true;
}
return false;
}
//-----------------------------------------------------------------------------
void CSegmentButton::setViewSize (const CRect& rect, bool invalid)
{
CControl::setViewSize (rect, invalid);
updateSegmentSizes ();
}
//------------------------------------------------------------------------
void CSegmentButton::selectSegment (uint32_t index, bool state)
{
beginEdit ();
auto bitset = static_cast<uint32_t> (value);
setBit (bitset, (1 << index), state);
value = static_cast<float> (bitset);
valueChanged ();
endEdit ();
}
//------------------------------------------------------------------------
bool CSegmentButton::isSegmentSelected (uint32_t index) const
{
return segments[index].selected;
}
//-----------------------------------------------------------------------------
CMouseEventResult CSegmentButton::onMouseDown (CPoint& where, const CButtonState& buttons)
{
if (buttons.isLeftButton ())
{
float newValue = 0;
float valueOffset = 1.f / (segments.size () - 1);
for (auto& segment : segments)
{
if (segment.rect.pointInside (where))
{
uint32_t newIndex = getSegmentIndex (newValue);
switch (selectionMode)
{
case SelectionMode::kSingle:
{
uint32_t currentIndex = getSegmentIndex (getValueNormalized ());
if (newIndex != currentIndex)
setSelectedSegment (newIndex);
break;
}
case SelectionMode::kSingleToggle:
{
uint32_t currentIndex = getSegmentIndex (getValueNormalized ());
if (newIndex != currentIndex)
setSelectedSegment (newIndex);
else
{
++currentIndex;
if (getSegments ().size () - 1 < currentIndex)
currentIndex = 0;
setSelectedSegment (currentIndex);
}
break;
}
case SelectionMode::kMultiple:
{
selectSegment (newIndex, !segment.selected);
break;
}
}
break; // out of for loop
}
newValue += valueOffset;
// Last segment can lead to newValue > 1.0
newValue = std::min(newValue, 1.f);
}
}
return kMouseDownEventHandledButDontNeedMovedOrUpEvents;
}
//-----------------------------------------------------------------------------
void CSegmentButton::onKeyboardEvent (KeyboardEvent& event)
{
if (event.type != EventType::KeyDown || event.modifiers.empty () == false ||
event.character != 0)
return;
if (selectionMode != SelectionMode::kMultiple)
{
uint32_t newIndex = getSegmentIndex (getValueNormalized ());
uint32_t oldIndex = newIndex;
switch (event.virt)
{
case VirtualKey::Left:
{
if (style == Style::kHorizontal && newIndex > 0)
newIndex--;
else if (style == Style::kHorizontalInverse && newIndex < segments.size () - 1)
newIndex++;
event.consumed = true;
break;
}
case VirtualKey::Right:
{
if (style == Style::kHorizontal && newIndex < segments.size () - 1)
newIndex++;
else if (style == Style::kHorizontalInverse && newIndex > 0)
newIndex--;
event.consumed = true;
break;
}
case VirtualKey::Up:
{
if (style == Style::kVertical && newIndex > 0)
newIndex--;
else if (style == Style::kVerticalInverse && newIndex < segments.size () - 1)
newIndex++;
event.consumed = true;
break;
}
case VirtualKey::Down:
{
if (style == Style::kVertical && newIndex < segments.size () - 1)
newIndex++;
else if (style == Style::kVerticalInverse && newIndex > 0)
newIndex--;
event.consumed = true;
break;
}
default: return;
}
if (newIndex != oldIndex)
{
setSelectedSegment (newIndex);
}
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::draw (CDrawContext* pContext)
{
CView::draw (pContext);
}
//-----------------------------------------------------------------------------
void CSegmentButton::drawRect (CDrawContext* pContext, const CRect& dirtyRect)
{
if (getOldValue () != getValue ())
verifySelections ();
bool isHorizontal = isHorizontalStyle (style);
bool drawLines = getFrameWidth () != 0. && getFrameColor ().alpha != 0;
auto lineWidth = getFrameWidth ();
if (lineWidth < 0.)
{
lineWidth = pContext->getHairlineSize ();
}
SharedPointer<CGraphicsPath> path;
if (gradient || gradientHighlighted || drawLines)
{
CRect r (getViewSize ());
r.inset (lineWidth / 2., lineWidth / 2.);
path = owned (pContext->createGraphicsPath ());
if (!path)
return;
path->addRoundRect (r, getRoundRadius ());
}
pContext->setDrawMode (kAntiAliasing);
if (drawLines)
{
pContext->setLineStyle (kLineSolid);
pContext->setLineWidth (lineWidth);
pContext->setFrameColor (getFrameColor ());
}
if (gradient)
{
if (isHorizontal)
{
pContext->fillLinearGradient (path, *gradient, getViewSize ().getTopLeft (),
getViewSize ().getBottomLeft ());
}
else
{
pContext->fillLinearGradient (path, *gradient, getViewSize ().getTopLeft (),
getViewSize ().getTopRight ());
}
}
auto lineIndexStart = 1u;
auto lineIndexEnd = segments.size ();
if (isInverseStyle (style))
{
--lineIndexStart;
--lineIndexEnd;
}
for (uint32_t index = 0u, end = static_cast<uint32_t> (segments.size ()); index < end; ++index)
{
const auto& segment = segments[index];
if (!dirtyRect.rectOverlap (segment.rect))
continue;
drawClipped (pContext, segment.rect, [&] () {
if (segment.selected && gradientHighlighted)
{
if (isHorizontal)
{
pContext->fillLinearGradient (path, *gradientHighlighted,
segment.rect.getTopLeft (),
segment.rect.getBottomLeft ());
}
else
{
pContext->fillLinearGradient (path, *gradientHighlighted,
segment.rect.getTopLeft (),
segment.rect.getTopRight ());
}
}
if (segment.selected && segment.backgroundHighlighted)
{
segment.backgroundHighlighted->draw (pContext, segment.rect);
}
else if (segment.background)
{
segment.background->draw (pContext, segment.rect);
}
CDrawMethods::drawIconAndText (
pContext, segment.selected ? segment.iconHighlighted : segment.icon,
segment.iconPosition, textAlignment, textMargin, segment.rect, segment.name, font,
segment.selected ? textColorHighlighted : textColor, textTruncateMode);
});
if (drawLines && index >= lineIndexStart && index < lineIndexEnd)
{
path->beginSubpath (segment.rect.getTopLeft ());
path->addLine (isHorizontal ? segment.rect.getBottomLeft () :
segment.rect.getTopRight ());
}
}
if (drawLines)
pContext->drawGraphicsPath (path, CDrawContext::kPathStroked);
setDirty (false);
}
//-----------------------------------------------------------------------------
uint32_t CSegmentButton::getSegmentIndex (float value) const
{
if (value < 0.f || value > 1.f)
return kPushBack;
return std::min<uint32_t> (static_cast<uint32_t> (segments.size () - 1),
static_cast<uint32_t> (value * (segments.size ())));
}
//-----------------------------------------------------------------------------
void CSegmentButton::updateSegmentSizes ()
{
if (isAttached () && !segments.empty ())
{
switch (style)
{
case Style::kHorizontal:
{
CCoord width = getWidth () / segments.size ();
CRect r (getViewSize ());
r.setWidth (width);
for (auto& segment : segments)
{
segment.rect = r;
r.offset (width, 0);
}
break;
}
case Style::kHorizontalInverse:
{
CCoord width = getWidth () / segments.size ();
CRect r (getViewSize ());
r.setWidth (width);
for (auto it = segments.rbegin(); it != segments.rend(); ++it)
{
(*it).rect = r;
r.offset (width, 0);
}
break;
}
case Style::kVertical:
{
CCoord height = getHeight () / segments.size ();
CRect r (getViewSize ());
r.setHeight (height);
for (auto& segment : segments)
{
segment.rect = r;
r.offset (0, height);
}
break;
}
case Style::kVerticalInverse:
{
CCoord height = getHeight () / segments.size ();
CRect r (getViewSize ());
r.setHeight (height);
for (auto it = segments.rbegin(); it != segments.rend(); ++it)
{
(*it).rect = r;
r.offset (0, height);
}
break;
}
}
}
}
//-----------------------------------------------------------------------------
void CSegmentButton::verifySelections ()
{
if (selectionMode == SelectionMode::kMultiple)
{
auto bitset = static_cast<uint32_t> (value);
for (auto index = 0u; index < segments.size (); ++index)
{
segments[index].selected = (bitset & (1 << index)) != 0;
}
}
else
{
auto selectedIndex = getSelectedSegment ();
if (selectedIndex > segments.size ())
selectedIndex = 0;
for (auto& segment : segments)
segment.selected = false;
segments[selectedIndex].selected = true;
}
}
//-----------------------------------------------------------------------------
bool CSegmentButton::drawFocusOnTop ()
{
return false;
}
//-----------------------------------------------------------------------------
bool CSegmentButton::getFocusPath (CGraphicsPath& outPath)
{
auto lineWidth = getFrameWidth ();
if (lineWidth < 0.)
lineWidth = 1.;
CRect r (getViewSize ());
r.inset (lineWidth / 2., lineWidth / 2.);
outPath.addRoundRect (r, getRoundRadius ());
CCoord focusWidth = getFrame ()->getFocusWidth ();
r.extend (focusWidth, focusWidth);
outPath.addRoundRect (r, getRoundRadius ());
return true;
}
} // VSTGUI