Skip to content
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

Added methods to set the cursor type/image #827

Merged
merged 5 commits into from Jul 18, 2017
Merged

Conversation

binary1248
Copy link
Member

Supersedes #784.

Thanks to @fundies for the initial draft implementation. I originally wanted to attribute the commit to him, but there were simply too many changes in the end.

Resource leaks should be gone now, and general code style is fixed up. Like #784, this is still missing the OS X implementation. Hopefully @mantognini or someone else can fill in the gaps here and here. It can't be nearly as horrible as the Windows or Unix implementations. 😛 This might help a bit.

This implements #269.

@fundies
Copy link

fundies commented Mar 15, 2015

I don't care about credit, all I wanted was an implementation. Thanks.

@LaurentGomila
Copy link
Member

What would be nice now is to get rid of Window::setMouseCursorVisible, and just add None to the Window::Cursor enum.

@MarioLiebisch
Copy link
Member

@LaurentGomila I like that idea.

But before adding/changing something twice, how about creating a sf::Cursor class rather than an enum? Cursors aren't just images, there's also a position for the clicks.

It could be expanded later on to load custom textures as hardware cursors. But on the other hand this could be a 1:1 replacement of the existing enum as well. What do you guys think?

Edit: Just seen that there is a member for loading cursor images, but I'd rather see this wrapped into their own representation similar to things like sf::Color.

@binary1248
Copy link
Member Author

What would be nice now is to get rid of Window::setMouseCursorVisible, and just add None to the Window::Cursor enum.

We can't get rid of it just yet, since it would break the API. We can however make it have the same function as setting the cursor to None or something other than None just for the transition phase.

But before adding/changing something twice, how about creating a sf::Cursor class rather than an enum? Cursors aren't just images, there's also a position for the clicks.

Aren't you over-engineering a bit? For us, and the operating system, the "mouse" is what is used to represent an input device. It has a position that can be queried and set. Strictly speaking, we don't even have to see anything to be able to use it. The cursor is nothing other than a sprite that just tracks the position of the mouse. Combining both input and output into the same class would be coupling functionality where it isn't really necessary.

It could be expanded later on to load custom textures as hardware cursors.

So... how is that any different to what this PR does? This PR already loads custom image data into the operating system cursor which is as "hardware" as hardware gets. There isn't anything else that is more hardware than this.

But on the other hand this could be a 1:1 replacement of the existing enum as well.

What existing enum?

@LaurentGomila
Copy link
Member

We can't get rid of it just yet, since it would break the API. We can however make it have the same function as setting the cursor to None or something other than None just for the transition phase.

Ah right. So either we do nothing and implement it in SFML 3, or we add None, mark setMouseCursorVisible as deprecated and forward it to setMouseCursor(None).

@MarioLiebisch
Copy link
Member

Combining both input and output into the same class would be coupling functionality where it isn't really necessary.

You misunderstood me. It would be just another resource, representing a cursor by holding/using a sf::Image (similar to the relation of sf::Image and sf::Texture) or a system specific constant (i.e. built-in cursors).

So... how is that any different to what this PR does?

I wrote that line before noticing that this is already in the PR, I just didn't edit it out.

What existing enum?

The one introduced by this PR.

@mantognini
Copy link
Member

@binary1248 I'll look into it hopefully in a week or two but no promise there. The implementation looks quite easy so if there's another Mac dev around, (s)he could probably do it too since the link in the description gives 75% of the implementation. 😉

@binary1248
Copy link
Member Author

What do the others think of providing a separate sf::Cursor class? It could be useful in situations where developers have to switch between cursor types very often and don't want the overhead of having to reload them every single time. Also, like @MarioLiebisch said, the system cursors can be provided as constant value sf::Cursors, although I still don't know how they would be constructed in that case without enums or some really ugly hacks.

@mantognini: 75% of the implementation for the setting the cursor to a system cursor. 😉 The bigger part is creating a cursor from a custom bitmap, and that part isn't really mentioned in there much.

@LaurentGomila
Copy link
Member

What do the others think of providing a separate sf::Cursor class? It could be useful in situations where developers have to switch between cursor types very often and don't want the overhead of having to reload them every single time.

I'd say let's start simple. It's less convenient to have to load and handle the lifetime of an additional resource, than call a simple function. We can go back to this if feedback shows that it is needed.

@mantognini
Copy link
Member

The bigger part is creating a cursor from a custom bitmap, and that part isn't really mentioned in there much.

Yes, but since setting an icon of a window already provides the code to load a bitmap and that we have -[NSCursor initWithImage:(NSImage *)newImage hotSpot:(NSPoint)aPoint], that should not be more than 25% of the total work. :-)

As for sf::Cursor class, I tend to agree with Laurent.

@fundies
Copy link

fundies commented Mar 15, 2015

I started to attempt an os x implementation. Setting the cursor was fairly easy. However, I had trouble getting it to reset when the pointer was outside the window and gave up...

@mantognini
Copy link
Member

@fundies This is (or at least should be) already handled by the gist (indirectly) linked in the description. ;-)

@binary1248
Copy link
Member Author

It's less convenient to have to load and handle the lifetime of an additional resource, than call a simple function.

That is if the user really only bothers having a single cursor they never intend to change in the first place. As soon as they start interchanging cursors, they will have to manage the lifetime of their image data anyway, and having cursor objects will save them a (potentially) expensive pixel copy each time they change.

We can go back to this if feedback shows that it is needed.

The problem is that if we wait for feedback after this is in the public API, we won't be able to get rid of it that easily. 😉 And if we really expect people to come onto the pull request tracker to tell us what they think, well... we might end up waiting a while for opinions. 😛

@mantognini
Copy link
Member

I guess an extra level of abstraction couldn't hurt... it would also ease the creation of cursors from image/texture but more importantly be more consistent with the way we handle resources.

@LaurentGomila
Copy link
Member

In this case, can the one-liner call still work?

void Window::setMouseCursor(const sf::Cursor& cursor);

// with implicit constructor:
window.setMouseCursor(sf::Cursor::Arrow);

// with explicit constructor:
window.setMouseCursor(sf::Cursor(sf::Cursor::Arrow));

If this is compatible with the way internal cursor resources are managed, this could be nice to have, for all these users that won't need to optimize this bit.

@mantognini
Copy link
Member

I guess there would be two constructors in sf::Cursor, one implicit taking a enum and an explicit one taking an image or a bitmap. So both line would work. What do you think?

@MarioLiebisch
Copy link
Member

Yep, sounds good. :)

@binary1248
Copy link
Member Author

I guess we could make that work. XCB cursor resources get copied over to the server, so they could be destroyed directly after the call and nothing would go wrong. Windows cursors on the other hand might require a bit of reference counting in the Impl since it isn't a good idea to destroy the image store before the cursor is put out of use (haven't tried, but documentation warns about this). Don't know about the lifetime requirements on OS X though, but if it's the same as Windows, we can just use the same reference counting on all platforms.

@mantognini
Copy link
Member

Don't know about the lifetime requirements on OS X though, but if it's the same as Windows, we can just use the same reference counting on all platforms.

Generally speaking with OS X, the reference counter is already tangled in the API so there is not much extra work to do. Thus you don't need to worry about it when implementing the Windows part. 😉

@binary1248 binary1248 added this to the 2.4 milestone Mar 29, 2015
@binary1248 binary1248 removed their assignment Apr 16, 2015
@eXpl0it3r
Copy link
Member

What's the final verdict here? Also the branch needs rebasing.

Arrow, ///< Arrow cursor (default)
ArrowWait, ///< Busy arrow cursor
Wait, ///< Busy cursor
Text, ///< I-beam, cursor when hovering over a field allowing text entry
Copy link
Member

Choose a reason for hiding this comment

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

Is that not called "caret"? Or is caret only the blinking thing indicating where text is going to be inserted?

Copy link
Member Author

Choose a reason for hiding this comment

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

the blinking thing indicating where text is going to be inserted

This.

Copy link
Member

Choose a reason for hiding this comment

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

I would rename 'Text' to 'IBeam' directly in the enum. It makes more sense IMO. (not sure if it helps, but it is the way the .NET framework also calls it)

Choose a reason for hiding this comment

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

I personally think "Text" is fine as a name. It describes the cursor's purpose pretty well.

@Bromeon
Copy link
Member

Bromeon commented Sep 16, 2015

Regarding the signature

void setMouseCursor(const Uint8* pixels, unsigned int width, unsigned int height,
                    Uint16 hotspotX, Uint16 hotspotY); 

Too bad this is done in the window module, otherwise we could have used

void setMouseCursor(const Image& image, Vector2u hotspot); 

But even when sticking to the Uint8 pointer, it's weird to mix unsigned int with Uint16, and unnecessary to do everything on a low level. Why not

void setMouseCursor(const Uint8* pixels, Vector2u size, Vector2u hotspot);

@LaurentGomila
Copy link
Member

Didn't we want to add Cursor::None and deprecate setMouseCursorVisible?

@Bromeon
Copy link
Member

Bromeon commented Sep 17, 2015

Didn't we want to add Cursor::None and deprecate setMouseCursorVisible?

Sounds good, the question is: what would setMouseCursorVisible(true) do? I'd say setMouseCursor(Arrow) if the cursor is not visible, and do nothing otherwise.

Also, there was a discussion about a dedicated sf::Cursor class. Even if we don't implement it, I would suggest to move the sf::Window::Cursor enum out of the sf::Window class into a sf::Cursor namespace, for the following reasons:

  1. sf::Cursor::Text is much more expressive than sf::Window::Text.
  2. We don't bloat the sf::Window and sf::RenderWindow scopes with attributes not directly related to a window.
  3. If we decide to go with a dedicated cursor class later, we can simply transform the namespace sf::Cursor into a class, and code will continue to work.

@mantognini
Copy link
Member

This PR was rebased onto master, the Windows implementation was rewritten based on @binary1248's previous work and the X11 implementation is new. Please do test and commend on both of those implementations as this is not my area of expertise!

You can use mantognini/SFML-Test-Events on the set_cursor branch as a basis to test those features. (The relevant bits are around line 500 of main.cpp.)

Copy link
Member

@eXpl0it3r eXpl0it3r left a comment

Choose a reason for hiding this comment

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

This is a pure code-reading review. I'll test the implementation later on Windows.

@@ -25,6 +25,7 @@
#ifndef SFML_CONTEXTSETTINGS_HPP
#define SFML_CONTEXTSETTINGS_HPP

#include <SFML/Config.hpp>
Copy link
Member

Choose a reason for hiding this comment

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

Is that some left-over, or why is this here?

Copy link
Member

Choose a reason for hiding this comment

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

That might well be, yes! I'll mark this as a requested change and deal with it later.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, now I remember: because I changed the order of some includes, this file no longer compiled (it's using Uint32). So this is actually needed and abides to our documented inclusion order.

/// \return a reference to the OS-specific implementation
///
////////////////////////////////////////////////////////////
const priv::CursorImpl& getImpl() const;
Copy link
Member

Choose a reason for hiding this comment

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

Do we do this similarly in other classes?

Copy link
Member

Choose a reason for hiding this comment

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

You mean deferring to an Impl class? Yes, we did it for thread, input, sensor, window, ...

Copy link
Member

Choose a reason for hiding this comment

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

I meant, providing an explicit getImpl() function for a friended class.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, that's just to dispatch in one place the actual CursorImpl for the WindowImpl. I think we didn't need this before because all platform dependent types were already "inside" WindowImpl, but this is not the case here.

/// \see sf::Cursor::loadFromPixels
///
////////////////////////////////////////////////////////////
void setMouseCursor(const Cursor& cursor);
Copy link
Member

Choose a reason for hiding this comment

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

Did we discuss why we only have a setter and no getter?

Is this a real reference or are we copying the cursor internally? We might want to document whether the cursor object can be discarded or needs to live on.

Copy link
Member

Choose a reason for hiding this comment

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

Did we discuss why we only have a setter and no getter?

I don't think we did. But in any case, we don't do it for the other properties (e.g. visibility, ...).

We might want to document whether the cursor object can be discarded or needs to live on.

The doc reads:

The cursor must not be destroyed while in use

and

The behaviour is undefined if the cursor is destroyed while in use by the window.

;-)

/// hotspot is the pixel coordinate within the cursor image
/// which will be located exactly where the mouse pointer
/// position is. Any mouse actions that are performed will
/// return the window/screen location of the hotspot.
Copy link
Member

Choose a reason for hiding this comment

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

I assume hotspot is a technical and commonly used term, correct?

Copy link
Member

Choose a reason for hiding this comment

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

Correct: it's used by Linux, Windows and Apple.

////////////////////////////////////////////////////////////
bool Cursor::loadFromPixels(const Uint8* pixels, Vector2u size, Vector2u hotspot)
{
if ((pixels == 0) || (size.x == 0) || (size.y == 0))
Copy link
Member

Choose a reason for hiding this comment

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

What's with the case where the hotspot isn't within the given size?
If it can be outside the given size, why can't there be negative values (Vector2u)?
If it can't be outside the given size, we probably want to either fail (don't hide error, but unclear failure) or limit to the max size (hiding error).

Copy link
Member

Choose a reason for hiding this comment

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

Some OS might support a hotspot outside the image, but it wouldn't make sense in practice. Here I think I followed what @binary1248 did in his initial version, but for V3 we might reconsider this and throw something instead when the values are not the one expected.

{
switch (type)
{
default: return false;
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't the default be at the end of the case list? (Isn't it even legal to always use default if the cases are written after the default? Or maybe that's just some old C stuff, I've once read somewhere.)

Copy link
Member

Choose a reason for hiding this comment

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

It's perfectly valid C++98 code. I think we do elsewhere as well, but in any case I think it improves readability. What do you think?

/// \warning On Unix, the pixels are mapped into a monochrome
/// bitmap: pixels with an alpha channel to 0 are
/// transparent, black if the RGB channel are close
/// to zero, and white otherwise.
Copy link
Member

Choose a reason for hiding this comment

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

So Linux only supports monochrome cursors?

Copy link
Member

Choose a reason for hiding this comment

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

As far as I could find in the X11 documentation, yes.

Copy link
Member

Choose a reason for hiding this comment

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

On Windows it's hardware dependent, although I don't think we've still got any current and supported GPUs that don't support colored cursors.

Copy link
Member

Choose a reason for hiding this comment

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

I took @binary1248 impl for Win32 so I didn't look at the documentation that much, but it appears you're right:

Cursors can be either monochrome or color, and either static or animated. The type of cursor used on a particular computer system depends on the system's display. Old displays such as VGA do not support color or animated cursors. New displays, whose display drivers use the device-independent bitmap (DIB) engine, do support them.

Question is, should we document this or do we assume such old systems are not used with SFML?

Copy link
Member

Choose a reason for hiding this comment

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

I'd say it's safe to ignore, considering we want to use modern OpenGL anyway? Even my old 1 MB Cirrus Logic from 1996 supported colored/animated hardware cursors, although I remember some later games claiming otherwise. :)

XFreePixmap(m_display, maskPixmap);

// We assume everything went fine...
return true;
Copy link
Member

Choose a reason for hiding this comment

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

So there's really no way this function could ever fail?

Copy link
Member

@mantognini mantognini Apr 4, 2017

Choose a reason for hiding this comment

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

X11 being async, I think we simply cannot check for failure. Maybe some X11-guru can confirm that, but I've found nothing in the doc while writing this code to catch errors, sadly.

unsigned int shape;
switch (type)
{
default: return false;
Copy link
Member

Choose a reason for hiding this comment

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

Again, should come after the cases.

Copy link
Member

Choose a reason for hiding this comment

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

same. :-)

@@ -265,7 +274,8 @@ class WindowImplX11 : public WindowImpl
XIC m_inputContext; ///< Input context used to get unicode input in our window
bool m_isExternal; ///< Tell whether the window has been created externally or by SFML
int m_oldVideoMode; ///< Video mode in use before we switch to fullscreen
Cursor m_hiddenCursor; ///< As X11 doesn't provide cursor hidding, we must create a transparent one
::Cursor m_hiddenCursor; ///< As X11 doesn't provide cursor hidding, we must create a transparent one
::Cursor m_lastCursor; ///< Last cursor used -- this data is not owned by the window and is required to be always valid
Copy link
Member

Choose a reason for hiding this comment

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

Why exactly is it ::Cursor here?

Copy link
Member

Choose a reason for hiding this comment

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

To avoid a clash with sf::Cursor and X11 cursor, which lives in the global namespace.

Copy link
Member

Choose a reason for hiding this comment

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

While at those lines, I'm pretty sure it's "hiding" vs. "hidding". ;)

Copy link
Member

Choose a reason for hiding this comment

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

huhu, yes!

Copy link
Member

@mantognini mantognini left a comment

Choose a reason for hiding this comment

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

Remove #include <SFML/Config.hpp> from ContextSettings.hpp.

@mantognini mantognini dismissed their stale review April 4, 2017 16:53

this change is actually needed.

Copy link
Member

@mantognini mantognini left a comment

Choose a reason for hiding this comment

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

Typo to fix

@@ -265,7 +274,8 @@ class WindowImplX11 : public WindowImpl
XIC m_inputContext; ///< Input context used to get unicode input in our window
bool m_isExternal; ///< Tell whether the window has been created externally or by SFML
int m_oldVideoMode; ///< Video mode in use before we switch to fullscreen
Cursor m_hiddenCursor; ///< As X11 doesn't provide cursor hidding, we must create a transparent one
::Cursor m_hiddenCursor; ///< As X11 doesn't provide cursor hidding, we must create a transparent one
Copy link
Member

Choose a reason for hiding this comment

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

s/hidding/hiding

@eXpl0it3r eXpl0it3r moved this from Requires Adjustments to Review & Testing in SFML 2.5.0 Apr 25, 2017
@mantognini
Copy link
Member

I've fixed the type and rebased. Let me know if there's anything else!

@eXpl0it3r
Copy link
Member

I tested this change on Windows 10 with the small testing code from this gist.

Everything seems to work fine. All the system cursors are showing and my custom cursor are working. During testing I came up with two questions:

  • When you create a window, the cursor is set to the system arrow cursor, but once you start using an sf::Cursor is there a way to reset the default cursor? Or do you now have to keep sf::Cursor alive throughout the whole application?
  • The wait cursor (and maybe others) are usually animated cursors (spinning circle, hour glass, etc.), but with SFML the cursors are just static. Has this been done just for consistency among platforms? Or were there other reasons? What would it take to make them animated?

@mantognini
Copy link
Member

Thanks for giving it a shot! :-)

When you create a window, the cursor is set to the system arrow cursor, but once you start using an sf::Cursor is there a way to reset the default cursor? Or do you now have to keep sf::Cursor alive throughout the whole application?

While defined as "undefined behaviour", if you reset the cursor to the default arrow one, I think all implementation will survive the destruction of the Cursor instance. This is not perfect, but could easily be improved with the help of shared_ptr -- I just don't think the investment is worth it for SFML 2.x branch to implement something similar.

The wait cursor (and maybe others) are usually animated cursors (spinning circle, hour glass, etc.), but with SFML the cursors are just static. Has this been done just for consistency among platforms? Or were there other reasons? What would it take to make them animated?

This wasn't implemented intentionally, but supporting it is tricky. On Linux, it would be a pain in the neck to do it from what I recall (well, like anything I should say...). With Win32, it should be easier to do it, I guess, but I don't know how to get the right resource for the animated cursor. And I think this is a feature not (publicly) exposed on Mac.

@eXpl0it3r
Copy link
Member

Thanks for clarifying.

I think both points are valid discussions we should come back to at one point, but as a first step, I think this is ready to be merged.

@eXpl0it3r eXpl0it3r moved this from Review & Testing to Ready in SFML 2.5.0 Jun 5, 2017
@mantognini
Copy link
Member

I think both points are valid discussions we should come back to at one point

Yes, definitely.

@eXpl0it3r eXpl0it3r merged commit 34ea68b into master Jul 18, 2017
@eXpl0it3r eXpl0it3r moved this from Ready to Merged in SFML 2.5.0 Jul 18, 2017
@eXpl0it3r eXpl0it3r deleted the feature/set_cursor branch July 18, 2017 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
SFML 2.5.0
  
Merged / Superseded
Development

Successfully merging this pull request may close these issues.

None yet