-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add function for positioning and aligning objects using specific origin points #3259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
IMO this is out of SFML's scope. This can be an extension on top of SFML. It does not involve abstraction of platform-specific details.
|
As discussed in #3260 (comment), any solution to this problem would need to be simple and not rely on The direction I would explore would be a set of utility function templates that can be invoked on any drawable, such as: sf::Vector2f getNWCorner(const auto& drawable);
sf::Vector2f getSWCorner(const auto& drawable);
// ...
void setNWCorner(auto& drawable, sf::Vector2f position);
void setSWCorner(auto& drawable, sf::Vector2f position); |
I think this is a problem worth discussing. I'd like to see some more concrete use cases for this functionality though. |
I used utility functions like the ones mentioned above extensively in Open Hexagon to help myself align and layout UI elements and HUD elements. They were all SFML drawables. It was extremely convenient to say: "hey, put the top-left corner of this text next to the bottom-left corner of this shape, with 10 pixels of Y spacing", or "hey, make sure that the horizontal center of this rectangle shape is aligned with the horizontal right coordinate of this text". The way I solved this problem back then isn't pretty, but worked: I would encourage thinking about extending I would love to be able to say: someText.setNWCorner(someShape.getSWCorner() + sf::Vector2f{0.f, 10.f}); |
I appreciate the feedback. This is my first time contributing to the library and not just using it. So I’m sure there are better ways to do what I did. But I think this feature is definitely something SFML needs. When learning how to use it for the first time it was a pain to align objects. Having to set the origin alone is tedious, but then once that’s figured out aligning it to another sprite is extremely frustrating especially if you don’t want to make permanent changes to the origin of another drawable or do multiple operations to find the right spot. |
This would be a great opportunity to use C++23's "deducing class Transformable
{
// ...
Vector2f getTopLeft(this const auto& self)
{
return self.getGlobalBounds().position;
}
Vector2f getTopRight(this const auto& self)
{
const auto& globalBounds = self.getGlobalBounds();
return globalBounds.position + Vector2f{globalBounds.size.x, 0.f};
}
Vector2f getBottomLeft(this const auto& self)
{
const auto& globalBounds = self.getGlobalBounds();
return globalBounds.position + Vector2f{0.f, globalBounds.size.y};
}
Vector2f getBottomRight(this const auto& self)
{
const auto& globalBounds = self.getGlobalBounds();
return globalBounds.position + globalBounds.size;
}
// ...
}; Basically, invoking the above "explicit In C++17, this could be done using CRTP. |
In the end, I decided to just use simple free functions. This is what I came up with in my fork: Usage: sf::RenderWindow window(graphicsContext, {.size{800u, 600u}, .title = "example"});
sf::RectangleShape rs0(
{.position = {250.f, 250.f},
.origin = {0.f, 0.f}, // <= any origin works
.fillColor = sf::Color::Red,
.outlineColor = sf::Color::Yellow,
.outlineThickness = 3.f, // <= taken into account
.size = {64.f, 64.f}}); // <= taken into account
sf::RectangleShape cs0(
{.position = {450.f, 450.f},
.origin = {-25.f, 50.f}, // <= any origin works
.fillColor = sf::Color::Blue,
.outlineColor = sf::Color::Yellow,
.outlineThickness = 2.f, // <= taken into account
.size = {36.f, 36.f}}); // <= taken into account
sf::setTopRight(rs0, sf::getTopRight(window)); // <=
sf::setCenter(cs0, sf::getBottomLeft(rs0)); // <= This will put the top-right corner of Since I'll expand one of the macros and add some comments just to make it easier to understand: // generic helper that gets the bottom-left coordinate of a rect
Vector2f getBottomLeft(const FloatRect& bounds)
{
const auto& [pos, size] = bounds;
return pos + Vector2f{0.f, size.y};
}
// template 1: given an object that supports `.getGlobalBounds()`,
// invoke the generic helper with those bounds
// (this template targets shapes, texts, sprites, etc...)
Vector2f getBottomLeft(const auto& object)
requires(requires { object.getGlobalBounds(); })
{
return getBottomLeft(object.getGlobalBounds());
}
// template 2: given an object that supports `.getSize()` but not `.position`,
// invoke the generic helper with `{ {0, 0}, .getSize() }`
// (this template targets windows and rejects transformables)
Vector2f getBottomLeft(const auto& object)
requires(requires { object.getSize(); } && !requires { object.position; })
{
return getBottomLeft({{0.f, 0.f}, object.getSize().toVector2f()});
}
// template 3: given an object that supports `.getGlobalBounds()`,
// change its position to the desired one taking the
// bottom-left position as the origin
// (this template targets shapes, texts, sprites, etc...)
void setBottomLeft(auto& object, Vector2f newPos)
requires(requires { object.getGlobalBounds(); })
{
const auto& [pos, size] = object.getGlobalBounds();
object.position += newPos - pos - Vector2f{0.f, size.y};
} This is fairly straightforward to port to upstream SFML and I'd be happy to create a PR if there's interest. Thank you @ZXShady for leading me towards the free function direction compared to the CRTP/mixin approach. |
I think this feature is a compelling candidate for v3.1. After we ship v3.0 I'll be interested in diving into the details of designing such an API. |
I've made some more progress on this in my fork, I added a general getAnchorPoint function that takes arbitrary factors, and a mixin that allows nicer left-to-right syntax on user objects.
I think that for upstream SFML the free function API would be worthwhile including in 3.1, the mixin one will likely be a bit more controversial. |
I've so far not really been sold on this idea. Layouting can be a complex topic. I don't really see the value in shipping some free functions to package a few math operations. Layouting/positioning/anchoring/etc. I believe is better done as part of the implementing domain, where these functions would get more meaning and likely cover more and more specific cases. And I haven't found the proposed APIs very appealing/easy to understand. Looking at the functions it's not immediately clear whether:
In general we may need to define a strategy for such "helper" features, i.e. things that are merely built on top of SFML without needing any new "lower-level" API changes. Maybe we need some sort of incubator project to try out certain APIs and if they prove themselves, we could consider them for inclusion. |
Prerequisite Checklist
Describe your feature request here
Description:
Currently, SFML requires users to manually calculate positions when aligning sprites or other transformables. This process is tedious and repetitive, often requiring users to write several lines of code to align objects based on different points (e.g., top-left, middle-center, bottom-right). This is particularly cumbersome when aligning objects relative to each other or the window.
Problem:
Aligning a sprite, text, or shape to a reference point on another object (or window) requires complex and repetitive calculations. For example, aligning one sprite’s center to another sprite's center or aligning the middle of a shape to a specific point on the window involves unnecessary complexity, which could be simplified with built-in functions.
Proposed Solution:
Introduce a setPosition function that allows users to specify alignment points on both the transformable object and its reference (another transformable or a window). The function will allow users to easily align an object to a specified point (top-left, middle-center, bottom-right, etc.) relative to another object or the window.
Use Cases
Simplifies the code for positioning and aligning sprites, shapes, and texts.
Reduces repetitive tasks and makes the code more readable.
Improves development speed and reduces the likelihood of calculation errors when aligning objects.
Maintains the flexibility of SFML by working with any sf::Transformable, including sf::Sprite, sf::Text, and sf::Shape.
API Example
// Align the center of one sprite to the center of another sprite
sprite1.setPosition(MidMid, MidMid, sprite2);
// Align the top-left corner of a shape to the middle of the window
shape.setPosition(TopLeft, MidMid, window);
The text was updated successfully, but these errors were encountered: