Skip to content

Add rounded rectangle support#5405

Merged
dacap merged 21 commits into
aseprite:betafrom
martincapello:rounded-rectangle
Dec 1, 2025
Merged

Add rounded rectangle support#5405
dacap merged 21 commits into
aseprite:betafrom
martincapello:rounded-rectangle

Conversation

@martincapello

@martincapello martincapello commented Sep 1, 2025

Copy link
Copy Markdown
Member

Fix #2184

Introduces a couple of algorithms to draw the rounded corners:

  • Drawing of 4-sliced circle outlines and filled circles, used for drawing the rounded corners of non-rotated rectangles and filled rotated and non-rotated rectangles.
  • Drawing of arcs, used for drawing the rounded corners of rotated rectangles.

How to use it:
Draw a regular rectangle and use the C key as the corner radius modifier. Move the mouse inside/outside the corner to change the radius. This also works for the rectangular marquee tool.

TODO:

  • Add parameter to set the corner radius in rectangle_class.cpp so it can be used from scripts. (Should we make this in a separate issue/PR?)

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/app/tools/intertwiners.h Outdated
Comment thread src/app/tools/intertwiners.h Outdated
Comment thread src/app/tools/intertwiners.h Outdated
Comment thread src/app/tools/intertwiners.h Outdated
Comment thread src/app/tools/intertwiners.h Outdated
Comment thread src/doc/algo.cpp Outdated
Comment thread src/doc/algo.cpp Outdated
Comment thread src/doc/algo.cpp Outdated
Comment thread src/doc/algo.cpp Outdated
Comment thread src/doc/algo.cpp Outdated

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/app/tools/intertwiners.h
Comment thread src/app/tools/intertwiners.h Outdated
Comment thread src/app/tools/intertwiners.h
Comment thread src/app/tools/intertwiners.h
Comment thread src/app/tools/intertwiners.h Outdated
Comment thread src/app/tools/intertwiners.h
Comment thread src/app/tools/intertwiners.h
Comment thread src/doc/algo.cpp
Comment thread src/doc/algo.cpp
Comment thread src/doc/algo.cpp
@martincapello martincapello force-pushed the rounded-rectangle branch 3 times, most recently from 1e882dd to ba65940 Compare September 9, 2025 16:53

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/app/tools/controllers.h Outdated
@martincapello martincapello marked this pull request as ready for review September 9, 2025 17:45
@martincapello martincapello requested a review from dacap as a code owner September 9, 2025 17:45
@Gasparoken

Copy link
Copy Markdown
Member

Great! At first glance it works very well.
However I noticed some details that could be improved:

  1. When holding 'J', the radius should respond to horizontal and vertical gestures. Also, the action of enlarging/shrinking the radius should not be reversed when moving in one direction (apparently, it should be related to an absolute distance from a reference point).
  2. I see that in successive repetitions of the rectangle + J + radius change command, the internal reference value accumulates with the mouse movement, regardless of whether the radius has changed (there is no radius change when we reach the maximum and minimum limits). This makes it seem like the radius modification stops working if too much "internal movement" has accumulated. This mouse movement accumulator should be reset for each new rectangle drawn.

Comments:

  1. I think it's great that the radius is maintained for future rectangles.
  2. Perhaps there should be an entry in the context bar to define the radius. However, as a first implementation, it seems fine to me.

@martincapello

martincapello commented Sep 10, 2025

Copy link
Copy Markdown
Member Author

@Gasparoken thanks for the feedback!

When holding 'J', the radius should respond to horizontal and vertical gestures.

Sure, no problem.

Also, the action of enlarging/shrinking the radius should not be reversed when moving in one direction (apparently, it should be related to an absolute distance from a reference point).

👍

I see that in successive repetitions of the rectangle + J + radius change command, the internal reference value accumulates with the mouse movement, regardless of whether the radius has changed (there is no radius change when we reach the maximum and minimum limits). This makes it seem like the radius modification stops working if too much "internal movement" has accumulated. This mouse movement accumulator should be reset for each new rectangle drawn.

I'm not sure if I'm getting this right. The radius doesn't have a hard limit, basically it is visually limited by the size of the rectangle, but the radius can be bigger than the visible radius. If you set a big corner radius it will be adjusted according to rectangle size. So, are you proposing that the radius should not be increased once the max visible radius for the current rectangle size is reached? In which case, once the user reached the max visible radius they would be forced to resize the rectangle first and then increment the radius, right?

@gasparcapello

Copy link
Copy Markdown

The radius doesn't have a hard limit, basically it is visually limited by the size of the rectangle, but the radius can be bigger than the visible radius.

IMO, this is not convenient because you lose the visual feedback of the radius.

So, are you proposing that the radius should not be increased once the max visible radius for the current rectangle size is reached?

Yes, that's right.

In which case, once the user reached the max visible radius they would be forced to resize the rectangle first and then increment the radius, right?

No, IMO the correct behavior is:

  1. You can modify the rectangle size when the 'J' key isn't pressed, then
  2. When 'J' is pressed the radius size should be modified only

I think it's best for the artist to modify one parameter at a time, rather than enabling a double modification (radius + rectangle size ).

I can imagine your approach, but I found one case that might be annoying for the artist:
When the radius size is near to the maximum, it's hard to know if the radious is actually the max. We'll notice of it when the size of the rectangle increases, then the original rectangle size is loss., then you can decrease the size of the rectangle until the original rectangle size.
It doesn't sound bad, but I prefer the two-step approach.

Anyway, maybe I misinterpreted your proposal, or you've come up with something better. Let me know if that's the case.

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread src/app/tools/controllers.h Outdated
Comment thread src/app/tools/controllers.h Outdated
Comment thread src/app/tools/controllers.h Outdated
@martincapello

martincapello commented Nov 20, 2025

Copy link
Copy Markdown
Member Author

Something has changed...now the slider doesn't show up anymore...I will be fixing that along the other things.

EDIT: Alright, maybe nothing changed...it turns out that in single window UI mode it works, but with multiple windows it doesn't...not sure if it worked before the rebase.

@aseprite-bot aseprite-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

const bool isFloodfill = tool && (tool->getPointShape(0)->isFloodFill() ||
tool->getPointShape(1)->isFloodFill());

const bool hasCornerRadius = tool && !isText &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: implicit conversion 'tools::Tool *' -> 'bool' [readability-implicit-bool-conversion]

Suggested change
const bool hasCornerRadius = tool && !isText &&
( != nullptr)

@martincapello

martincapello commented Nov 21, 2025

Copy link
Copy Markdown
Member Author

@dacap Last commit adds the item 4 you mentioned in your last review, but it doesn't work on macOS (using multiple windows). However, it does work on Windows (I didn't test it on Linux yet).

The issue on macOS occurs because when the user starts dragging something the mouseMove events received are coming from the window that received the mouseDown. Then the position of the mouse is relative to that native window, and not to the popup window that has the widget to which I'm trying to offer the capture, hence the windows manager is not able to pick the slider. It seems that this is due to the fact that in macOS there is no captureMouse() and releaseMouse() implementations. So I'll see if I can do something about this to fix it.

@martincapello

martincapello commented Nov 21, 2025

Copy link
Copy Markdown
Member Author

I couldn't find any fix today. I think it would be better to leave it as is and file a new issue for macOS later. Thus we don't delay the rounded rectangle support anymore.

I've tried:

  • Using a global variable to store the view that captured the mouse and then use it to adjust the mouse position returned in the mouse events...this almost worked.
  • Using events to simulate a mouse up and a mouse down when the mouse capture changes. I've tried with NSEvents and CGEvents...couldn't make them work.

@martincapello martincapello assigned dacap and unassigned martincapello Nov 21, 2025
@dacap

dacap commented Dec 1, 2025

Copy link
Copy Markdown
Member

I've tested on Linux and it works great! I'll merge it as it is, we can take a look to the macOS issue later. I'm not sure if the UI will be kept in this way, or a text field that is expanded in the same context bar might be a better option in the future (so the corner radius button acts like a checkbox to make show/hide the text field in the same context bar).

@dacap dacap merged commit dd660f6 into aseprite:beta Dec 1, 2025
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rounded rectangle

5 participants