/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { namespace RelativeRectangleHelpers { inline void skipComma (String::CharPointerType& s) { s.incrementToEndOfWhitespace(); if (*s == ',') ++s; } static bool dependsOnSymbolsOtherThanThis (const Expression& e) { if (e.getType() == Expression::operatorType && e.getSymbolOrFunction() == ".") return true; if (e.getType() == Expression::symbolType) { switch (RelativeCoordinate::StandardStrings::getTypeOf (e.getSymbolOrFunction())) { case RelativeCoordinate::StandardStrings::x: case RelativeCoordinate::StandardStrings::y: case RelativeCoordinate::StandardStrings::left: case RelativeCoordinate::StandardStrings::right: case RelativeCoordinate::StandardStrings::top: case RelativeCoordinate::StandardStrings::bottom: return false; case RelativeCoordinate::StandardStrings::width: case RelativeCoordinate::StandardStrings::height: case RelativeCoordinate::StandardStrings::parent: case RelativeCoordinate::StandardStrings::unknown: default: break; } return true; } else { for (int i = e.getNumInputs(); --i >= 0;) if (dependsOnSymbolsOtherThanThis (e.getInput(i))) return true; } return false; } } //============================================================================== RelativeRectangle::RelativeRectangle() { } RelativeRectangle::RelativeRectangle (const RelativeCoordinate& left_, const RelativeCoordinate& right_, const RelativeCoordinate& top_, const RelativeCoordinate& bottom_) : left (left_), right (right_), top (top_), bottom (bottom_) { } RelativeRectangle::RelativeRectangle (const Rectangle& rect) : left (rect.getX()), right (Expression::symbol (RelativeCoordinate::Strings::left) + Expression ((double) rect.getWidth())), top (rect.getY()), bottom (Expression::symbol (RelativeCoordinate::Strings::top) + Expression ((double) rect.getHeight())) { } RelativeRectangle::RelativeRectangle (const String& s) { String error; String::CharPointerType text (s.getCharPointer()); left = RelativeCoordinate (Expression::parse (text, error)); RelativeRectangleHelpers::skipComma (text); top = RelativeCoordinate (Expression::parse (text, error)); RelativeRectangleHelpers::skipComma (text); right = RelativeCoordinate (Expression::parse (text, error)); RelativeRectangleHelpers::skipComma (text); bottom = RelativeCoordinate (Expression::parse (text, error)); } bool RelativeRectangle::operator== (const RelativeRectangle& other) const noexcept { return left == other.left && top == other.top && right == other.right && bottom == other.bottom; } bool RelativeRectangle::operator!= (const RelativeRectangle& other) const noexcept { return ! operator== (other); } //============================================================================== // An expression context that can evaluate expressions using "this" class RelativeRectangleLocalScope : public Expression::Scope { public: RelativeRectangleLocalScope (const RelativeRectangle& rect_) : rect (rect_) {} Expression getSymbolValue (const String& symbol) const { switch (RelativeCoordinate::StandardStrings::getTypeOf (symbol)) { case RelativeCoordinate::StandardStrings::x: case RelativeCoordinate::StandardStrings::left: return rect.left.getExpression(); case RelativeCoordinate::StandardStrings::y: case RelativeCoordinate::StandardStrings::top: return rect.top.getExpression(); case RelativeCoordinate::StandardStrings::right: return rect.right.getExpression(); case RelativeCoordinate::StandardStrings::bottom: return rect.bottom.getExpression(); case RelativeCoordinate::StandardStrings::width: case RelativeCoordinate::StandardStrings::height: case RelativeCoordinate::StandardStrings::parent: case RelativeCoordinate::StandardStrings::unknown: default: break; } return Expression::Scope::getSymbolValue (symbol); } private: const RelativeRectangle& rect; JUCE_DECLARE_NON_COPYABLE (RelativeRectangleLocalScope) }; const Rectangle RelativeRectangle::resolve (const Expression::Scope* scope) const { if (scope == nullptr) { RelativeRectangleLocalScope defaultScope (*this); return resolve (&defaultScope); } else { const double l = left.resolve (scope); const double r = right.resolve (scope); const double t = top.resolve (scope); const double b = bottom.resolve (scope); return Rectangle ((float) l, (float) t, (float) jmax (0.0, r - l), (float) jmax (0.0, b - t)); } } void RelativeRectangle::moveToAbsolute (const Rectangle& newPos, const Expression::Scope* scope) { left.moveToAbsolute (newPos.getX(), scope); right.moveToAbsolute (newPos.getRight(), scope); top.moveToAbsolute (newPos.getY(), scope); bottom.moveToAbsolute (newPos.getBottom(), scope); } bool RelativeRectangle::isDynamic() const { using namespace RelativeRectangleHelpers; return dependsOnSymbolsOtherThanThis (left.getExpression()) || dependsOnSymbolsOtherThanThis (right.getExpression()) || dependsOnSymbolsOtherThanThis (top.getExpression()) || dependsOnSymbolsOtherThanThis (bottom.getExpression()); } String RelativeRectangle::toString() const { return left.toString() + ", " + top.toString() + ", " + right.toString() + ", " + bottom.toString(); } void RelativeRectangle::renameSymbol (const Expression::Symbol& oldSymbol, const String& newName, const Expression::Scope& scope) { left = left.getExpression().withRenamedSymbol (oldSymbol, newName, scope); right = right.getExpression().withRenamedSymbol (oldSymbol, newName, scope); top = top.getExpression().withRenamedSymbol (oldSymbol, newName, scope); bottom = bottom.getExpression().withRenamedSymbol (oldSymbol, newName, scope); } //============================================================================== class RelativeRectangleComponentPositioner : public RelativeCoordinatePositionerBase { public: RelativeRectangleComponentPositioner (Component& comp, const RelativeRectangle& r) : RelativeCoordinatePositionerBase (comp), rectangle (r) { } bool registerCoordinates() override { bool ok = addCoordinate (rectangle.left); ok = addCoordinate (rectangle.right) && ok; ok = addCoordinate (rectangle.top) && ok; ok = addCoordinate (rectangle.bottom) && ok; return ok; } bool isUsingRectangle (const RelativeRectangle& other) const noexcept { return rectangle == other; } void applyToComponentBounds() override { for (int i = 32; --i >= 0;) { ComponentScope scope (getComponent()); const Rectangle newBounds (rectangle.resolve (&scope).getSmallestIntegerContainer()); if (newBounds == getComponent().getBounds()) return; getComponent().setBounds (newBounds); } jassertfalse; // Seems to be a recursive reference! } void applyNewBounds (const Rectangle& newBounds) override { if (newBounds != getComponent().getBounds()) { ComponentScope scope (getComponent()); rectangle.moveToAbsolute (newBounds.toFloat(), &scope); applyToComponentBounds(); } } private: RelativeRectangle rectangle; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativeRectangleComponentPositioner) }; void RelativeRectangle::applyToComponent (Component& component) const { if (isDynamic()) { RelativeRectangleComponentPositioner* current = dynamic_cast (component.getPositioner()); if (current == nullptr || ! current->isUsingRectangle (*this)) { RelativeRectangleComponentPositioner* p = new RelativeRectangleComponentPositioner (component, *this); component.setPositioner (p); p->apply(); } } else { component.setPositioner (nullptr); component.setBounds (resolve (nullptr).getSmallestIntegerContainer()); } } } // namespace juce