[FR] In place clip mutations on juce::Rectangle

I’ve been using the removeFrom methods a lot for component layouts in resized. I’m finding it map on Figma designs really nicely and much more readable than a bunch of hardcoded rectangles, or a flexbox which isn’t the best for a mainly static layout.

The issue I keep running into is that sometimes I want to trim away part of my working bounds without actually using the removed slice. At the moment you either assign it to a dummy variable and use [[maybe_unused]] to make the compiler happy, restructure the logic or use the reduce function which sometimes doesn’t map onto the design nicely, all of which add noise to what could be a simple and clean layout.

What I’d like to suggest is two small additions that mirror the existing removeFrom methods:

  • In-place mutations without returning a new juce::Rectangle (similar to the removeFrom functions).
  • withClipped functions that return a reference to the current rectangle to chain variations of bounds adjustment.

I know Rectangle is a lightweight type so this isn’t really a performance thing, it’s more about just keeping the code readable enough for anyone to pick up without getting lost in the bookkeeping. I’ve thrown together the change on a JUCE fork just to give an idea of what I mean, and am happy to make any additional changes, or add documentation if this is something that others may find useful.


// Original way not happy about unused returns
void resized() override
{
   auto bounds = getLocalBounds();
   bounds.removeFromTop (10);
   bounds.removeFromBottom (28);
   bounds.removeFromLeft (60);
   bounds.removeFromRight (52);
}

// Using clipped functions
void resized() override
{
   auto bounds = getLocalBounds();
   bounds.clipTop (10);
   bounds.clipBottom (28);
   bounds.clipLeft (60);
   bounds.clipRight (52);
}

// Cut bounds 
void resized() override
{
   auto bounds = getLocalBounds().withClippedTop (10)
                                 .withClippedBottom (28)
                                 .withClippedLeft (60)
                                 .withClippedRight (52);
}


It’s early here and I’m on my first coffee, but it’s not clear to me how your withClippedXXX() methods differ from the existing withTrimmedXXX() methods other than the naming.

You don’t have to use the result of the removeFrom___() methods. Looking at your PR they’re functionally the same as your clip___() methods.

The main advantage of the withTrimmed___() methods IMO is that they can be chained, e.g.

auto bounds = area.withTrimmedTop(10)
                  .withTrimmedLeft(15)
                  .withTrimmedRight(20);

If something is marked [[nodiscard]] you can just add (void) to the front of the call to ignore the return type which is a little neater than assigning to a dummy variable. With clang, you can also use _ as a variable name and it’ll be ignored if you don’t use it: Compiler Explorer

@bgporter withTrimmed returns a new instance. The examples I gave aren’t the best, but I want to be able to mutate the current rectangle, without creating new rectangles. @ImJimmi Completely valid point about void casting & clang but it still adds noise when comparing a Figma design to the code. The reason is to match the Figma design padding. I don’t know if this is a personal thing, but as a junior being given horrible or very specific layout code, having clipped bounds would have made it more readable. Like I’ve created four rectangles in the top example, but in the bottom I’m just mutating the current bounds.

// Void casting
void resized() override
{
    auto bounds = getLocalBounds();
    (void)bounds.removeFromLeft(10);
    (void)bounds.removeFromRight(54);
    (void)bounds.removeFromTop(20);
    (void)bounds.removeFromBottom(20);

    labelBound.setBounds(bounds.removeFromTop(10));
    (void)bounds.removeFromTop(20);
    slider.setBounds(bounds.removeFromTop(20));
}

// Using clip methods
void resized() override
{
    // I find this clearer to read
    auto bounds = getLocalBounds().withClippedLeft(10)
                                  .withClippedRight(54)
                                  .withClippedTop(10)
                                  .withClippedBottom(20);

    labelBound.setBounds(bounds.removeFromTop(10));
    bounds.clipTop(20);
    slider.setBounds(bounds.removeFromTop(20));
}

To be clear, I know you can achieve the same thing with existing methods, but this is purely about readability. I just find the clip versions cleaner to scan, especially when matching against a Figma design, and think others might too. If other people have other ways of getting this in a similar readabel way I’d love to see.

You shouldn’t need (void) here - you can just ignore the returned value.

Also I think for your use-case a reduce() method that takes multiple arguments could be neat.

bounds.reduce(20, 54, 20, 10);
2 Likes

I so wish this would exist. I use reduce/reduced a lot, and being able to provide values for all four sides would be perfect. Same for expand/expanded (although one could simple use negative values for reduce/reduced).

I think maybe having reduce/reduced or expand/expanded functions would be a good way to get the same outcome. It’s definitely something that should be added. My suggestion was mainly trying to avoid people mixing up argument order when you have both clipping and reducing going on. However, I’m happy to change it to just a reduced/reduce & exclude/excluded style functions for this FR; if others don’t have that type of concern. @ImJimmi Yep you’re right about the void.