getContentComponentBorder and getMinimumOnscreenAmounts


#1

To implement my “shim” which automagically makes your ComponentBoundsConstrainer work on the Component size rather than including the entire peer window including its native or non native frame, or menu bar, it needs public access to ResizableWindow::getContentComponentBorder, and it also needs a function added to ComponentBoundsConstrainer

Can we please change getContentComponentBorder to public, and add this function?

    void ComponentBoundsConstrainer::getMinimumOnscreenAmounts (int& minimumWhenOffTheTop,
                                    int& minimumWhenOffTheLeft,
                                    int& minimumWhenOffTheBottom,
                                    int& minimumWhenOffTheRight) throw()
    {
      minimumWhenOffTheTop=minOffTop;
      minimumWhenOffTheLeft=minOffLeft;
      minimumWhenOffTheBottom=minOffBottom;
      minimumWhenOffTheRight=minOffRight;
    }

Here is the shim class I wrote:

// "shim" which goes between your constrainer and a ResizableWindow
// or derived class. This will adjust the parameters of the associated
// constrainer so that all constraints are based on the content component
// and do not include the title bar, native window frame, menu bar, or
// window border.
//
// If you set a minimum size with your constrainer, the content component will
// be constrained to EXACTLY your desired dimensions.
class ContentComponentConstrainer
  : private ComponentBoundsConstrainer
  , private ComponentListener
{
public:
  // we can attach to anything with ResizableWindow as a base
  static void attachTo (ResizableWindow* resizableWindow)
  {
    ContentComponentConstrainer* contentConstrainer =
      new ContentComponentConstrainer (resizableWindow);
    resizableWindow->addComponentListener (contentConstrainer);
  }

private:
  ContentComponentConstrainer (ResizableWindow* resizableWindow)
   : m_resizableWindow (resizableWindow)
   , m_originalConstrainer (0)
  {
    // if you aren't using a custom constrainer, then at least put a
    // constraint on your ResizableWindow using ResizableWindow::setResizeLimits
    // so that it gets the defaultConstrainer.
    m_originalConstrainer = m_resizableWindow->getConstrainer();
    jassert (m_originalConstrainer); // must exist

    m_resizableWindow->setConstrainer (this);
    m_resizableWindow->addComponentListener (this);
  }

  void resizeStart()
  {
    m_originalConstrainer->resizeStart();
    copyConstraints (*m_originalConstrainer);
    adjustConstraints();
  }

  void resizeEnd()
  {
    m_originalConstrainer->resizeEnd();
  }

  void copyConstraints (ComponentBoundsConstrainer& from)
  {
    setMinimumWidth (from.getMinimumWidth());
    setMaximumWidth (from.getMaximumWidth());
    setMinimumHeight (from.getMinimumHeight());
    setMaximumHeight (from.getMaximumHeight());
    setFixedAspectRatio (from.getFixedAspectRatio());

    int minimumWhenOffTheTop;
    int minimumWhenOffTheLeft;
    int minimumWhenOffTheBottom;
    int minimumWhenOffTheRight;

    from.getMinimumOnscreenAmounts (minimumWhenOffTheTop,
                                    minimumWhenOffTheLeft,
                                    minimumWhenOffTheBottom,
                                    minimumWhenOffTheRight);

    setMinimumOnscreenAmounts (minimumWhenOffTheTop,
                               minimumWhenOffTheLeft,
                               minimumWhenOffTheBottom,
                               minimumWhenOffTheRight);
  }

  static int addWithoutOverflow (int a, int b)
  {
    if (a < (0x7ffffff-b))
      return a+b;
    else
      return 0x7fffffff;
  }

  // adjusts the current constraints to take into account decorations
  void adjustConstraints()
  {
    BorderSize peerFrameBorder = m_resizableWindow->getPeer()->getFrameSize();
    BorderSize contentCompBorder = m_resizableWindow->getContentComponentBorder();
    
    int extraWidth = peerFrameBorder.getLeftAndRight() + contentCompBorder.getLeftAndRight();
    int extraHeight = peerFrameBorder.getTopAndBottom() + contentCompBorder.getTopAndBottom();

    setMinimumHeight (m_originalConstrainer->getMinimumHeight() + extraHeight);
    setMaximumHeight (addWithoutOverflow (m_originalConstrainer->getMaximumHeight(), extraHeight));
    setMinimumWidth (m_originalConstrainer->getMinimumWidth() + extraWidth);
    setMaximumWidth (addWithoutOverflow (m_originalConstrainer->getMaximumWidth(), extraWidth));
  }

  void componentBeingDeleted (Component& component)
  {
    delete this;
  }

private:
  ResizableWindow* m_resizableWindow;
  ComponentBoundsConstrainer* m_originalConstrainer;
};

And here is how you use the class

void makeItConstrainTheContentComponentInstead (ResizableWindow* window)
{
  ComponentBoundsConstrainer::attachTo (window);
  // it will delete itself so no need to worry about it
}

Why does it seem that I made things so complicated? Because in my case, the constrained bounds are not static. They are recursively computed based on the layout of the items in the content component. Furthermore, the limits can change, because the user is able to shuffle interface items around and change their appearance. Some components can be switched to “compact” views and this necessarily changes the minimum size on the ResizableWindow.

That is why my constrainer recomputes the constraints every time on resizeStart() and resizeEnd(). It also does that to pick up any changes in the frame, such as if the user decides to change whether or not they are using a native title bar.