TabControl in IPlug2 framework

Is it realistic to create a TabControl within the IPlug2 framework.
If so, what is the best parent control?

We are working on container controls on the multitouch controls branch, on master, you can use an IVTabControl and Hide/Show each control on the tabs. You can assign controls to “groups”, to manage that more easily

This is difficult. I would like how it is done in RAD Studio - TPageControl Class
However, thanks for the hint, I’ll keep it in mind.

I did find some things difficult in IPlug 2 but “Hide/Show” was easy.

1 Like

Thank you! I am about the same direction and made an implementation. However, all child controls were placed on the parent panel, and already, depending on the tab index, the visibility of the required panel and its child controls was set.

Indeed, tab index is something you have to keep track of. However, Hide/Show was the way for me to go with the qyooo’s Filter & Envelope & LFO tabs, too. I even used it for the “modal” Details view (here I had to take care of the Target RECT as well…).

qyooo_tabs

qyooo_details

I’m looking for a solution to do something like this. I want to put a panel with new controls over the existing ones.

Can someone give me a hint where to start looking, what method should I use, or even better share some code examples?

1 Like

Thank you so much :slight_smile:

I’m so glad that this is possible :heart_eyes:

I use my own implementation of TabControl, which I use in my plugins.

page_control

I am providing my source code, but it will not work for you without other files that are used in my SVLib library. But this is not critical, as you will be able to adapt this code to your needs.

TabControl.h

#pragma once

#include "Controls.h"
#include <vector>
#include <string>

BEGIN_SVLIB_NAMESPACE

using namespace iplug;
using namespace igraphics;

class TTab
{
private:
  struct TTabItem
  {
    bool Hidden = false;
    IControl* Control;
    TTabItem(IControl* control) : Control(control) { Hidden = control->IsHidden(); }
  };

  std::vector<TTabItem> fItems;
  std::string fName;
  int fIndex;
  IRECT fTabRect;
  void UpdateItem(TTabItem& item, const bool visible);
public:
  TTab(const char* name, int index, IRECT tabrect);
  const char* Name() const noexcept;
  int Index() const noexcept;
  IRECT TabRect() const noexcept;
  void Add(IControl* control);
  void Show();
  void Hide();
  void SetVisible(const bool visible);
};

class TTabControl : public TControl
{
public:
  enum ETabPosition { tpTop, tpBottom };
  enum ETabBtnStyle { tsTab, tpButton };
private:
  std::vector<TTab> fTabs;
  ETabPosition fTabPosition = tpTop;
  ETabBtnStyle fTabBtnStyle = tsTab;
  int fTabIndex = -1;  
  float fTabWidth = -1;
  float fTabHeight = -1;
  float fTabRadius = 4;
  IRECT fWorkRect;
  IActionFunction fTabsBuildFunc = nullptr;
  IActionFunction fTabChangeFunc = nullptr;
  int GetTabIndex(float x, float y);
public:
  TTabControl(const IRECT& bounds, IActionFunction tabsinit, IActionFunction tabchange = nullptr);
  int TabIndex() const noexcept { return fTabIndex; }
  void AddTab(const char* name);
  void AddTabs(const std::initializer_list<const char*>& names);
  void AddControl(int index, IControl* control);
  void SetTabIndex(int index);
  void SetTabPos(ETabPosition tabpos);
  void SetTabWidth(float tabwidth);
  void SetTabHeight(float tabheight);
  void SetTabRadius(float tabradius);
  void SetTabBtnStyle(ETabBtnStyle tabbtnstyle);
  void OnAttached() override;
  void OnResize() override;
  void OnMouseDown(float x, float y, const IMouseMod& mod) override;
  void Draw(IGraphics& g) override;
  
};

END_SVLIB_NAMESPACE

TabControl.cpp

#include "TabControl.h"
#include "Design.h"

BEGIN_SVLIB_NAMESPACE

// TTab

TTab::TTab(const char* name, int index, IRECT tabrect):
  fName(name),
  fIndex(index),
  fTabRect(tabrect)
{
}

const char* TTab::Name() const noexcept
{
  return fName.c_str();
}

int TTab::Index() const noexcept
{
  return fIndex;
}

IRECT TTab::TabRect() const noexcept
{
  return fTabRect;
}

void TTab::Add(IControl* control)
{
  fItems.push_back(TTabItem(control));
}

void TTab::UpdateItem(TTabItem& item, const bool visible)
{
  if (visible)
  {
    if (!item.Hidden)
      item.Control->Hide(false);
  }
  else
  {
    item.Hidden = item.Control->IsHidden();
    if (!item.Hidden)
      item.Control->Hide(true);
  }
}

void TTab::Show()
{
  for (auto item : fItems)
    UpdateItem(item, true);
}

void TTab::Hide()
{
  for (auto item : fItems)
    UpdateItem(item, false);
}

void TTab::SetVisible(const bool visible)
{
  for (auto item : fItems)
    UpdateItem(item, visible);
}

// TTabControl

TTabControl::TTabControl(const IRECT& bounds, IActionFunction tabsinit, IActionFunction tabchange) :
  TControl(bounds),
  fTabsBuildFunc(tabsinit),
  fTabChangeFunc(tabchange)
{
  mText = TDesign::TextTab;
}

int TTabControl::GetTabIndex(float x, float y)
{
  for (auto tab : fTabs)
  {
    if (tab.TabRect().Contains(x, y))
      return tab.Index();
  }
  return fTabIndex;
}


void TTabControl::AddTab(const char* name)
{
  const int i = fTabs.size();
  const float l = i > 0? fTabs.back().TabRect().R : fWorkRect.L;
  const float h = fTabHeight;
  const float w = fTabWidth < 0 ? TextWidth(mText, name) + 20 : fTabWidth;
  const IRECT r = IRECT::MakeXYWH(l, fTabPosition == tpTop ? fWorkRect.T : fWorkRect.B - h, w, h);
  TTab tab(name, i, r);
  fTabs.push_back(tab);
}

void TTabControl::AddTabs(const std::initializer_list<const char*>& names)
{
  for (auto& name : names)
  {
    AddTab(name);
  }
}

void TTabControl::AddControl(int index, IControl* control)
{
  if (!GetUI() || index < 0 || index >= fTabs.size())
    return;
  fTabs.at(index).Add(control);
  GetUI()->AttachControl(control);
  control->Hide(true);
}

void TTabControl::SetTabIndex(int index)
{
  if (fTabIndex == index)
    return;
  fTabIndex = index;
  for (auto tab : fTabs)
    tab.SetVisible(tab.Index() == fTabIndex);
  SetDirty(false);
  if (fTabChangeFunc)
    fTabChangeFunc(this);
}

void TTabControl::SetTabPos(ETabPosition tabpos)
{
  if (fTabPosition == tabpos)
    return;
  fTabPosition = tabpos;
  SetDirty(false);
}

void TTabControl::SetTabWidth(float tabwidth)
{
  if (fTabWidth == tabwidth)
    return;
  fTabWidth = tabwidth;
  SetDirty(false);
}

void TTabControl::SetTabHeight(float tabheight)
{
  if (fTabHeight == tabheight)
    return;
  fTabHeight = tabheight;
  SetDirty(false);
}

void TTabControl::SetTabRadius(float tabradius)
{
  if (fTabRadius == tabradius)
    return;
  fTabRadius = tabradius;
  SetDirty(false);
}

void TTabControl::SetTabBtnStyle(ETabBtnStyle tabbtnstyle)
{
  if (fTabBtnStyle == tabbtnstyle)
    return;
  fTabBtnStyle = tabbtnstyle;
  SetDirty(false);
}

void TTabControl::OnAttached()
{
  fTabHeight = fTabHeight < 0 ? TextHeight(mText, "Xy") + 16 : fTabHeight;
  if (fTabsBuildFunc)
    fTabsBuildFunc(this);  
  if (fTabs.size() > 0 && fTabIndex < int(fTabs.size()))
    SetTabIndex(fTabIndex < 0 ? 0 : fTabIndex);
}

void TTabControl::OnResize()
{
  fWorkRect = mRECT.GetPixelAligned().GetPadded(-0.5);  
}

void TTabControl::OnMouseDown(float x, float y, const IMouseMod& mod)
{
  if (mod.L)
    SetTabIndex(GetTabIndex(x, y));
}

void TTabControl::Draw(IGraphics& g)
{
  if (fTabs.empty())
    return;

  const float h = fTabHeight;
  // draw inactive tabs
  for (int i = 0; i < fTabs.size(); ++i)
  {
    if (i == fTabIndex)
      continue;
    TTab tab = fTabs[i];
    IRECT r = tab.TabRect();
    const float w = r.W();
    if (fTabBtnStyle == tsTab)
    {
      r.L += 0.5f;
      
      g.PathClear();
      if (fTabPosition == tpTop)
      {
        r.B += fTabRadius;
        const float y = r.T + 0.5f;
        g.PathMoveTo(r.L, y + fTabRadius);
        g.PathArc(r.L + fTabRadius, y + fTabRadius, fTabRadius, 270.f, 360.f);
        g.PathArc(r.L + w - fTabRadius, y + fTabRadius, fTabRadius, 0.f, 90.f);
        g.PathLineTo(r.R, r.B);
        g.PathLineTo(r.L, r.B);
      }
      else
      {
        r.B -= fTabRadius;
        const float y = r.T - 0.5f;
        g.PathMoveTo(r.L, y);
        g.PathLineTo(r.L + w, y);
        g.PathArc(r.L + w - fTabRadius, r.B, fTabRadius, 90.f, 180.f);
        g.PathArc(r.L + fTabRadius, r.B, fTabRadius, 180.f, 270.f); 
      }
      g.PathClose();
      g.PathFill(IPattern(TDesign::ColorDark2), IFillOptions(true), &mBlend);
      g.PathStroke(IPattern(TDesign::ColorDark1), 1.f, IStrokeOptions(), &mBlend);
    }
    else
    {
      r.Pad(-4.f);
      g.FillRoundRect(TDesign::ColorDark2, r, fTabRadius, &mBlend);
      g.DrawRoundRect(TDesign::ColorDark1, r, fTabRadius, &mBlend);
    }    
  }

  if (fTabIndex < 0)
    return;
  
  TTab tab = fTabs.at(fTabIndex);
  IRECT r = tab.TabRect();
  const float w = r.W();
  if (fTabBtnStyle == tsTab)
  {   
    g.PathClear();
    if (fTabPosition == tpTop)
    {
      const float y = r.B - h;
      g.PathMoveTo(r.L, y + fTabRadius);
      g.PathArc(r.L + fTabRadius, y + fTabRadius, fTabRadius, 270.f, 360.f);
      g.PathArc(r.L + w - fTabRadius, y + fTabRadius, fTabRadius, 0.f, 90.f);
      g.PathArc(r.L + w + fTabRadius, y + h - fTabRadius, fTabRadius, 270.f, 180.f, EWinding::CCW);
      g.PathArc(fWorkRect.R - fTabRadius, r.B + fTabRadius, fTabRadius, 0.f, 90.f);
      g.PathArc(fWorkRect.R - fTabRadius, fWorkRect.B - fTabRadius, fTabRadius, 90.f, 180.f);
      g.PathArc(fWorkRect.L + fTabRadius, fWorkRect.B - fTabRadius, fTabRadius, 180.f, 270.f);
      if (fTabIndex > 0)
      {
        g.PathArc(fWorkRect.L + fTabRadius, r.B + fTabRadius, fTabRadius, 270.f, 360.f);
        g.PathArc(r.L - fTabRadius, y + h - fTabRadius, fTabRadius, 180.f, 90.f, EWinding::CCW);
      }
    }
    else
    {
      const float y = r.T + h;
      g.PathMoveTo(r.L, y - fTabRadius);
      g.PathArc(r.L + fTabRadius, y - fTabRadius, fTabRadius, 270.f, 180.f, EWinding::CCW);
      g.PathArc(r.L + w - fTabRadius, y - fTabRadius, fTabRadius, 180.f, 90.f, EWinding::CCW);
      g.PathArc(r.L + w + fTabRadius, y - h + fTabRadius, fTabRadius, 270.f, 360.f);
      g.PathArc(fWorkRect.R - fTabRadius, r.T - fTabRadius, fTabRadius, 180.f, 90.f, EWinding::CCW);
      g.PathArc(fWorkRect.R - fTabRadius, fWorkRect.T + fTabRadius, fTabRadius, 90.f, 0.f, EWinding::CCW);
      g.PathArc(fWorkRect.L + fTabRadius, fWorkRect.T + fTabRadius, fTabRadius, 360.f, 270.f, EWinding::CCW);
      if (fTabIndex > 0)
      {
        g.PathArc(fWorkRect.L + fTabRadius, r.T - fTabRadius, fTabRadius, 270.f, 180.f, EWinding::CCW);
        g.PathArc(r.L - fTabRadius, y - h + fTabRadius, fTabRadius, 0.f, 90.f);
      }
    }
    g.PathClose();
    g.PathFill(IPattern(TDesign::ColorBack), IFillOptions(true), &mBlend);
    g.PathStroke(IPattern(TDesign::ColorDark2), 1.f, IStrokeOptions(), &mBlend);
  }
  else
  {
    r.Pad(-4.f);
    g.FillRoundRect(TDesign::ColorAgua, r, fTabRadius, &mBlend);
    g.DrawRoundRect(TDesign::ColorSelect, r, fTabRadius, &mBlend);
  }
 

  for (auto tab : fTabs)
  {
    g.DrawText(mText, tab.Name(), tab.TabRect());
  }
  
}


END_SVLIB_NAMESPACE

UPD: Additionally, this control takes into account hidden controls, which, when changing the position of the tab index, will remain hidden until their visibility is changed from the outside.

Thank you so much for sharing. For some unknown reason I didn’t notice that I got your answer. I will look in to your code later. I kind of got stucked in my work with tabs and have give it up for a temporary amount of time, but I’m sure I take up the work again sometime :slightly_smiling_face: