// 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 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 (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 (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 (index) / static_cast (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 (value); setBit (bitset, (1 << index), state); value = static_cast (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 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 (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 (static_cast (segments.size () - 1), static_cast (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 (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