Skip to main content

Overview

iPlug2 IGraphics supports dynamic resizing and scaling for responsive user interfaces. This allows your plugin to:

Adapt to Screen Size

Work on small laptop screens and large 4K displays

Support High-DPI

Automatically scale for Retina/4K displays

User Resizing

Let users resize the plugin window

Host Constraints

Respect host-provided view sizes

Basic Resize Handling

Enable automatic layout updates on resize:
mLayoutFunc = [&](IGraphics* pGraphics) {
  const IRECT b = pGraphics->GetBounds();
  
  // Check if controls already exist (resize case)
  if (pGraphics->NControls()) {
    // Update existing control positions/sizes
    for (int i = 0; i < pGraphics->NControls(); i++) {
      IControl* pControl = pGraphics->GetControl(i);
      IRECT newBounds = CalculateNewBounds(i, b);
      pControl->SetTargetAndDrawRECTs(newBounds);
    }
    return;
  }
  
  // Enable automatic layout on resize
  pGraphics->SetLayoutOnResize(true);
  
  // Initial layout - attach controls...
};
When SetLayoutOnResize(true) is enabled, mLayoutFunc is called whenever the graphics context is resized.

Corner Resizer

Add a corner drag handle for user resizing:
pGraphics->AttachCornerResizer(EUIResizerMode::Scale, false);
// or
pGraphics->AttachCornerResizer(EUIResizerMode::Size, false);
Resize Modes:
EUIResizerMode::Scale
Scales all controls proportionally. The drawing scale changes but control positions remain relative.Best for: Fixed-layout UIs where everything should scale together

Layout Strategies

Fractional Layout

Divide the UI into fractional regions:
IPlugResponsiveUI.cpp:22-37
auto GetBounds = [pGraphics](int ctrlIdx, const IRECT& b) {
  IRECT main = b.GetPadded(-5.f);
  IRECT keys = main.FracRectVertical(0.25, false);  // Bottom 25%
  IRECT scope = main.FracRectVertical(0.75, true);  // Top 75%
  IRECT gain = scope.ReduceFromRight(100.f);         // Right side
  
  switch (ctrlIdx) {
    case 1: return keys;
    case 2: return gain;
    case 3: return scope;
    case 0: return b;
    default: return pGraphics->GetControl(ctrlIdx)->GetRECT();
  }
};

// On resize
if (pGraphics->NControls()) {
  for (int i = 0; i < pGraphics->NControls(); i++) {
    pGraphics->GetControl(i)->SetTargetAndDrawRECTs(GetBounds(i, b));
  }
  return;
}
IRECT fractional methods:
  • FracRectVertical(frac, fromTop) - Split vertically
  • FracRectHorizontal(frac, fromLeft) - Split horizontally
  • ReduceFromTop/Bottom/Left/Right(amount) - Reduce by pixels

Grid Layout

Arrange controls in a responsive grid:
const IRECT b = pGraphics->GetBounds().GetPadded(-10);
const int nRows = 4;
const int nCols = 4;

for (int row = 0; row < nRows; row++) {
  for (int col = 0; col < nCols; col++) {
    IRECT cell = b.GetGridCell(row, col, nRows, nCols).GetPadded(-5);
    
    // Attach control in grid cell
    pGraphics->AttachControl(
      new IVKnobControl(cell.GetCentredInside(60), paramIdx, "Knob")
    );
  }
}
Grid cell variations:
// Single cell
IRECT cell = bounds.GetGridCell(row, col, nRows, nCols);

// Span multiple cells
IRECT span = bounds.GetGridCell(row, col, nRows, nCols, 
                                 CellSpan::kMulti, 2, 2); // 2x2 span

// Get sub-grid within cell
IRECT innerCell = cell.GetGridCell(innerRow, innerCol, 2, 2);

Adaptive Layout

Change layout based on aspect ratio or size:
mLayoutFunc = [&](IGraphics* pGraphics) {
  const IRECT b = pGraphics->GetBounds();
  const float aspectRatio = b.W() / b.H();
  
  // Choose layout based on aspect ratio
  if (aspectRatio > 1.5f) {
    // Wide layout - controls side by side
    LayoutWide(pGraphics, b);
  } else if (aspectRatio < 0.75f) {
    // Tall layout - controls stacked
    LayoutTall(pGraphics, b);
  } else {
    // Square layout
    LayoutSquare(pGraphics, b);
  }
};

High-DPI Support

Automatic Scaling

iPlug2 automatically handles high-DPI displays:
mMakeGraphicsFunc = [&]() {
  return MakeGraphics(*this, PLUG_WIDTH, PLUG_HEIGHT, PLUG_FPS, 
                      GetScaleForScreen(PLUG_WIDTH, PLUG_HEIGHT));
  //                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  //                  Automatically detects screen DPI
};
Manual scale control:
// Set specific scale
pGraphics->Resize(width, height, 2.0f);  // 2x scale for Retina

// Get current scale
float scale = pGraphics->GetDrawScale();

// Get screen scale
float screenScale = pGraphics->GetScreenScale();

Bitmap Scaling

Provide multiple resolutions for bitmaps:
// Load base bitmap
const IBitmap knob = pGraphics->LoadBitmap("knob.png", 60);

// iPlug2 automatically loads [email protected] for 2x scale
// and [email protected] for 3x scale if they exist

// Get scaled version
IBitmap scaledKnob = pGraphics->GetScaledBitmap(knob);
Naming convention for high-DPI assets:

SVG for Perfect Scaling

SVG images scale perfectly at any resolution:
const ISVG logo = pGraphics->LoadSVG("logo.svg");

// Renders sharp at any size/scale
pGraphics->DrawSVG(logo, bounds);

Host View Configuration

Responding to Host Requests

Hosts can request specific view sizes:
Plugin.h
class MyPlugin : public Plugin {
#if IPLUG_EDITOR
  bool OnHostRequestingSupportedViewConfiguration(int width, int height) override {
    // Return true if this size is acceptable
    return ConstrainEditorResize(width, height);
  }
  
  void OnHostSelectedViewConfiguration(int width, int height) override {
    // Host selected a size - apply it
    if (GetUI())
      GetUI()->Resize(width, height, 1.f, true);
  }
#endif
};

Constraining Size

Set minimum/maximum dimensions:
Plugin.cpp
bool MyPlugin::ConstrainEditorResize(int& width, int& height) {
  constexpr int minW = 600;
  constexpr int minH = 400;
  constexpr int maxW = 1920;
  constexpr int maxH = 1080;
  
  width = Clip(width, minW, maxW);
  height = Clip(height, minH, maxH);
  
  // Optionally maintain aspect ratio
  const float aspectRatio = 16.f / 9.f;
  if (width / height > aspectRatio)
    width = static_cast<int>(height * aspectRatio);
  else
    height = static_cast<int>(width / aspectRatio);
  
  return true;
}

Custom Control Resize Handling

Make custom controls responsive:
class MyResponsiveControl : public IControl {
public:
  void OnResize() override {
    // Called when control bounds change
    
    // Recalculate layout-dependent values
    mRadius = mRECT.W() * 0.4f;
    mCenter.x = mRECT.MW();
    mCenter.y = mRECT.MH();
    
    // Update font size based on bounds
    float fontSize = std::max(10.f, mRECT.H() * 0.1f);
    mText.mSize = fontSize;
    
    // Invalidate any cached layers
    mLayer = nullptr;
    
    SetDirty(false);
  }
  
  void OnRescale() override {
    // Called when draw scale changes (DPI change)
    
    // Reload bitmaps at new scale
    mBitmap = GetUI()->GetScaledBitmap(mBitmap);
    
    SetDirty(false);
  }
  
private:
  float mRadius;
  IText mText;
  ILayerPtr mLayer;
  IBitmap mBitmap;
};
Resize methods:
  • OnResize() - Bounds changed
  • OnRescale() - Draw scale/DPI changed
  • SetRECT(bounds) - Manually set bounds and trigger OnResize()
  • SetTargetAndDrawRECTs(bounds) - Set both target and draw bounds

Complete Responsive Example

Here’s a full responsive plugin implementation:
MyPlugin::MyPlugin(const InstanceInfo& info)
: Plugin(info, MakeConfig(kNumParams, 1))
{
  GetParam(kGain)->InitDouble("Gain", 0., -70., 12., 0.01, "dB");
  GetParam(kFreq)->InitDouble("Frequency", 1000., 20., 20000., 0.01, "Hz");

#if IPLUG_EDITOR
  mMakeGraphicsFunc = [&]() {
#ifdef OS_WEB
    // Web: Use full screen
    int w, h;
    GetScreenDimensions(w, h);
    return MakeGraphics(*this, w, h, PLUG_FPS, 1.f);
#else
    // Native: Use default size with auto-scaling
    return MakeGraphics(*this, PLUG_WIDTH, PLUG_HEIGHT, PLUG_FPS, 
                        GetScaleForScreen(PLUG_WIDTH, PLUG_HEIGHT));
#endif
  };
  
  mLayoutFunc = [&](IGraphics* pGraphics) {
    LayoutUI(pGraphics);
  };
#endif
}

IRECT Helper Methods

Useful methods for responsive layout:
// Fractional division
IRECT top = bounds.FracRectVertical(0.3f, true);     // Top 30%
IRECT bottom = bounds.FracRectVertical(0.7f, false); // Bottom 70%
IRECT left = bounds.FracRectHorizontal(0.5f, true);  // Left 50%

// Padding
IRECT padded = bounds.GetPadded(-10);                // All sides
IRECT padded2 = bounds.GetPadded(-10, -20, -10, -5); // L, T, R, B
IRECT hPadded = bounds.GetHPadded(-15);              // Horizontal only
IRECT vPadded = bounds.GetVPadded(-20);              // Vertical only

// Centering
IRECT centered = bounds.GetCentredInside(100, 100);  // Fixed size
IRECT centeredW = bounds.GetCentredInside(100);      // Fixed width

// Reduction
IRECT reduced = bounds.ReduceFromTop(50);            // Remove 50px from top
IRECT reduced2 = bounds.ReduceFromBottom(30);        // Remove 30px from bottom
IRECT reduced3 = bounds.ReduceFromLeft(40);          // Remove 40px from left
IRECT reduced4 = bounds.ReduceFromRight(60);         // Remove 60px from right

// Extraction
IRECT topPart = bounds.GetFromTop(100);              // Top 100px
IRECT bottomPart = bounds.GetFromBottom(50);         // Bottom 50px
IRECT leftPart = bounds.GetFromLeft(80);             // Left 80px
IRECT rightPart = bounds.GetFromRight(120);          // Right 120px

// Grid
IRECT cell = bounds.GetGridCell(row, col, nRows, nCols);
IRECT cellPadded = bounds.GetGridCell(row, col, nRows, nCols).GetPadded(-5);

// Union (combine rectangles)
IRECT combined = rect1.Union(rect2);

// Scaling
IRECT scaled = bounds.GetScaledAboutCentre(0.8f);    // 80% of size

Best Practices

  • Test at multiple aspect ratios
  • Support both wide and tall layouts
  • Maintain minimum readable sizes

Platform Considerations

Next Steps

Controls Library

Learn about responsive controls

Custom Controls

Make controls responsive

Getting Started

Set up your first UI

Drawing Backends

Backend-specific considerations

Build docs developers (and LLMs) love