10000 Add function for positioning and aligning objects using specific origin points · Issue #3259 · SFML/SFML · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

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

Open
3 tasks done
TrentWantman opened this issue Oct 1, 2024 · 11 comments
Open
3 tasks done
Labels
Milestone

Comments

@TrentWantman
Copy link

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);

@kimci86
Copy link
Contributor
kimci86 commented Oct 1, 2024

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.

The rule of thumb is: If a feature can easily be added on top of SFML without modifying the core libra 8000 ry, it is probably not a good candidate to be integrated.

https://www.sfml-dev.org/contribute.php#scope

@vittorioromeo
Copy link
Member

As discussed in #3260 (comment), any solution to this problem would need to be simple and not rely on dynamic_cast.

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);

@ChrisThrasher
Copy link
Member

I think this is a problem worth discussing. I'd like to see some more concrete use cases for this functionality though.

@vittorioromeo
Copy link
Member

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:
https://github.com/vittorioromeo/SSVStart/blob/43ff64fb42f5a325b64e31e267cc894590322f86/include/SSVStart/Utils/SFML.hpp

I would encourage thinking about extending sf::Transformable's API. We already have the helper functions move and rotate. Why not more helper functions?

I would love to be able to say:

someText.setNWCorner(someShape.getSWCorner() + sf::Vector2f{0.f, 10.f});

@github-project-automation github-project-automation bot moved this to Planned in SFML 3.1.0 Oct 1, 2024
@ChrisThrasher ChrisThrasher added this to the 3.1 milestone Oct 1, 2024
@TrentWantman
Copy link
Author
TrentWantman commented Oct 2, 2024

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: https://github.com/vittorioromeo/SSVStart/blob/43ff64fb42f5a325b64e31e267cc894590322f86/include/SSVStart/Utils/SFML.hpp

I would encourage thinking about extending sf::Transformable's API. We already have the helper functions move and rotate. Why not more helper functions?

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.

@vittorioromeo
Copy link
Member

This would be a great opportunity to use C++23's "deducing this" language feature, if we targeted that standard:

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 this" member functions on any sf::Transformable will instantiate them with self being the static type of the transformable. It's a very simple way of implementing the mixin pattern.

In C++17, this could be done using CRTP.

@vittorioromeo
Copy link
Member
vittorioromeo commented Oct 2, 2024

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 rs0 at the top-right corner of the window, and the center of cs0 at the bottom-left corner of rs0:

{C090E976-E5BE-4FB0-9D88-70F4FF5A1F74}

Since getGlobalBounds() is used internally, the .origin of the shapes doesn't matter at all, and the outline thickness is included in the calculation.

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.

@ChrisThrasher
Copy link
Member

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.

@vittorioromeo
Copy link
Member

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.

@eXpl0it3r
Copy link
Member
eXpl0it3r commented Feb 20, 2025

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:

  • It does include the origin and size
  • This top-right is outside or inside
  • Does it consider the view and translate to world coordinates?

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.
Currently, I lean more towards not adding such features. There are many "useful" functionalities than can be built on top of SFML and that many will profit from it, but it's also important to keep a lean API and have others maintain their own extension libraries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Planned
Development

No branches or pull requests

5 participants
0