Where is the mouse?

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Message
Author
herenvardo
Veteran
Posts: 359
Joined: Sat Feb 25, 2006 11:09 am
Location: Sant Cugat del Vallès (Barcelona, Spain)
Contact:

Where is the mouse?

#1 Post by herenvardo »

The topic subject is quite literal: I would need to retrieve the position of the mouse cursor from certain points of my code. After searching the reference and these forums, and even trying to look through Ren'py's source, the only thing I've been able to find are the events for custom Displayables, but I don't feel that converting my UI functions into Displayables would be the appropriate solution, and I don't know if I could even do it at all.

To the specifics: on my "inventory" and "shopping" UIs, I would like to add a small popup window with information about an item when the user hovers the mouse over it. If you take a look at the attached code, you'll see that it already uses the hovered and unhovered arguments for the appropriate buttons and keeps track of the item to "popup", if any. The code to construct the popup itself will be almost trivial compared with the rest of the UI, so I only need to find out how to place it "hanging" from the cursor.
Attachments
shop.rpy
The code of the shopping UI; I hope it helps to clarify what I'm trying to do (specially lines 97-99).
(7.79 KiB) Downloaded 164 times
I have failed to meet my deadlines so many times I'm not announcing my projects anymore. Whatever I'm working on, it'll be released when it is ready :P

User avatar
PyTom
Ren'Py Creator
Posts: 16097
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Where is the mouse?

#2 Post by PyTom »

Um... that information is not made available to user code. In fact, Ren'Py is capable of operating without a mouse, or without the mouse in the window.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
Software > Drama • https://www.patreon.com/renpytom

herenvardo
Veteran
Posts: 359
Joined: Sat Feb 25, 2006 11:09 am
Location: Sant Cugat del Vallès (Barcelona, Spain)
Contact:

Re: Where is the mouse?

#3 Post by herenvardo »

:cry: what a pity... well, I was actually expecting an answer like that; since it doesn't make much sense for ren'py games to mess up with such stuff... but I had to try, because that feature would be really useful for the player (specially in the shopping window, where there is no way to make room and allow the "popup" to be placed statically).

I think I've come up with an alternative approach... do all the UI widget functions return the widget they create, like ui.viewport() does? (I know viewport does only because an example uses it... there is no mention to return values in the reference for any of these functions). If so, is it possible to find out the actual position of those widget objects?

And, another issue I have realized by your answer... do the hovered and unhovered functions of an ui.button rely on actual mouse hovering of just care about gaining/losing focus, regardless of input method? I hope it's the later, although then the names are a bit missleading.
I have failed to meet my deadlines so many times I'm not announcing my projects anymore. Whatever I'm working on, it'll be released when it is ready :P

User avatar
PyTom
Ren'Py Creator
Posts: 16097
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Where is the mouse?

#4 Post by PyTom »

Most of the ui functions return the displayable they create. (The exceptions are things like ui.menu, which creates multiple displayables.)

hovered and unhovered are just poor names for focused and unfocused, from back in the days before I supported keyboard and joystick input.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
Software > Drama • https://www.patreon.com/renpytom

herenvardo
Veteran
Posts: 359
Joined: Sat Feb 25, 2006 11:09 am
Location: Sant Cugat del Vallès (Barcelona, Spain)
Contact:

Re: Where is the mouse?

#5 Post by herenvardo »

herenvardo wrote:If so, is it possible to find out the actual position of those widget objects?
That's the important question ^^;
I've checked the reference for no avail; so I've taken a look through the Ren'py source, finding out the classes that represent these widgets, following up the hierarchy looking for "interesting" fields, until reached Displayable... (between this, and my attempt to mess up with text tags two days ago, I think I'm getting used to sneak through these sources :lol: ). Once I've found a function that seems to be the answer, I've checked the reference again... and once again, I found nothing ^^; (Why references are always missing exactly what I need? or, better: Why do I always need what's missing in the references? :lol: ). I hope you can shed some light on this:
what would myButton.get_placement() return?, where myButton would be a renpy.display.behavior.Button. Ok, I know it will return a tuple, and the name of these fields; but it'd be good to know their meanings (ie: are they absolute positions, relative to the container, or relative to something obscure that I haven't found yet?; what do the offset fields exactly mean?)
I have failed to meet my deadlines so many times I'm not announcing my projects anymore. Whatever I'm working on, it'll be released when it is ready :P

User avatar
PyTom
Ren'Py Creator
Posts: 16097
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Where is the mouse?

#6 Post by PyTom »

get_position returns the xpos, ypos, xanchor, yanchor, xoffset, yoffset tuple take from style properties. (Except for moves, in which case it returns a moving equivalent.)

There's no way to know from a displayable where it will wind up on the screen. All of the displayables it's inside get a shot at moving it, and so the actual positions aren't computed until right before things are drawn to the screen.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
Software > Drama • https://www.patreon.com/renpytom

herenvardo
Veteran
Posts: 359
Joined: Sat Feb 25, 2006 11:09 am
Location: Sant Cugat del Vallès (Barcelona, Spain)
Contact:

Re: Where is the mouse?

#7 Post by herenvardo »

Ouch! Plan B also failed, let's try with plan C... oops, first I need to come up with a plan C ^^;
*** after a while wandering through the references and ren'py's source again... ***
got it! If I know beforehand where the buttons will be, I won't need to find it out on runtime :D
*** takes a screenshot of the UI "as is", and starts measuring pixel distances and that kind of stuff for a while...
finally comes up with a formula, translates it to python code, and tests it... ***
Image
yeah!!! It's working!! I've tested using also the keyboard and gamepad, and it's (mostly) going as intended... except for one thing :? When the button loses focus, the popup stays there :cry:
I've attached the full ui.rpy file (the relevant function, Shop(...), begins at line 105), but I'll summarize here the key parts of the code:
The function begins with some initializations and defines a few helper functions; specially this one:

Code: Select all

            def itemPopup(margin=40):
                if itemH is not None:
                    yp = 104 + 56*posH
                    if posH > 4:
                        ya = 1.0
                    else:
                        ya = 0.0
                    if sideH=="left":
                        xa, xp = 0.0, margin
                    else:
                        xa, xp = 1.0, config.screen_width-margin
                    text = itemH.popupdesc()
                    if priceH is not None:
                        text += "\n{b}Buy price{/b}: %s"%(money(priceH))
                    ui.frame(xpos=xp, xanchor=xa, ypos=yp, yanchor=ya, xmaximum=300, background=Solid("#40404080"))
                    ui.text(text, color="#fff")
Speciall emphasis on the "if itemH is not None:" line; it should be the one that "closes" the popup when appropriate.
The list of "item" buttons is produced within a loop, of which the most important statement would be:

Code: Select all

                    ui.textbutton(text, size=14, size_group=id,
                        clicked  =ui.returns(("c"+id, i)),
                        hovered  =ui.returns(("h"+id, i)),
                        unhovered=ui.returns(("u"+id, None))
Within this context, id is alwayes one of "Buy", "Buyback", or "Sell" (depending on the list of items that is being produced); and i is the index of the relevant item within the list.
The main work of the function is an endless loop that calls many ui.whatever widget functions, then:

Code: Select all

                # The floating window:
                itemPopup()
                
                ret = ui.interact()
                if isinstance(ret, tuple):
                    # there is some more code here handling other values of ret.
                    if ret[0] == "hBuy":
                        itemH = Stock[ret[1]][0]
                        sideH = "left"
                        posH = ret[1]-LListPos
                        priceH = Stock[ret[1]][1]
                        continue
                    if ret[0] == "hBuyback":
                        itemH = buyback[ret[1]][0]
                        sideH = "left"
                        posH = ret[1]-LListPos
                        priceH = None
                        continue
                    if ret[0] == "hSell":
                        itemH = inventory[ret[1]][0]
                        sideH = "right"
                        posH = ret[1]-RListPos
                        priceH = None
                        continue
                    if ret[0]=="uBuy":
                        itemH = None
                        continue
                    if ret[0]=="uBuyback":
                        itemH = None
                        continue
                    if ret[0]=="uSell":
                        itemH = None
                        continue
The 'h' cases are working properly, so whenever the focus enters a new button, the popup moves acordingly and updates its contents. But when focus leaves the button (ie: moving the mouse to an empty area of the screen), the popup stays there, and it should disappear. So, what am I doing wrong?
Attachments
ui.rpy
The code for all of my UIs; the relevant part starts at line 105.
(15.54 KiB) Downloaded 154 times
I have failed to meet my deadlines so many times I'm not announcing my projects anymore. Whatever I'm working on, it'll be released when it is ready :P

User avatar
PyTom
Ren'Py Creator
Posts: 16097
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Where is the mouse?

#8 Post by PyTom »

Hm... the unhovered callback can't return a value. (IIRC, there was some infinite loop that is easily caused if it can.)

You probably want to use ui.add and renpy.restart_interaction in the hover callback, and ui.remove and renpy.restart_interaction in the unhover callback.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
Software > Drama • https://www.patreon.com/renpytom

herenvardo
Veteran
Posts: 359
Joined: Sat Feb 25, 2006 11:09 am
Location: Sant Cugat del Vallès (Barcelona, Spain)
Contact:

Re: Where is the mouse?

#9 Post by herenvardo »

PyTom wrote:Hm... the unhovered callback can't return a value. (IIRC, there was some infinite loop that is easily caused if it can.)

You probably want to use ui.add and renpy.restart_interaction in the hover callback, and ui.remove and renpy.restart_interaction in the unhover callback.
Well, I'm getting the infinite loop anyway ^^; I changed the unhovered from ui.returns(...) to a new function I've named onUnhover. The first go at coding this function looked like this:

Code: Select all

def onUnhover():
    global itemH # I've made it global to be able to do that
    itemH = None
    renpy.restart_interaction()
I hoped it to do the job, mostly because of the line "if itemH is not None:" at the beginning of itemPopup. But I got the infinite loop :cry: I've tried around 20 or 30 variations for the onUnhover function, and found that: when it calls renpy.restart_interaction, it causes an infinite loop; and when it doesn't, it does nothing useful (I think this would explain why the unhovered callback woul cause the infinite loop if it returns something ^^; ).
So, after plan C blatantly failing, and being to lazy to come up with a plan D, I went for plan C-bis: I took out the renpy.restart_interaction call from onUnhover (the only way to prevent the loop) and found a new home for it: I added this line to the code, just before calling ui.interact():

Code: Select all

ui.timer(0.1, renpy.restart_interaction)
It did nothing, so I then tried to add repeat=True: then it did just one thing: it lagged my UI like hell ^^;
I've gone on, trying variations of this idea, like calling itemPopup (the function that creates the "popup", if approrpriate), and later trying to return some value from itemPopup itself... I was getting some strange behaviour, so I have gone for something simpler to trace what's going on: currently I have this line:

Code: Select all

ui.timer(1, lambda: not None, repeat=True)
(I couldn't come up with a better way to return a non-None ^_^ ) and the result is interesting (that isn't necessarily good, but it's at least curious): when a button gains focus, the popup appears, stays for a second, and then disappears, even if the button still has focus. Tracing my variables, I've noticed that, on each second, (or whatever delay I decide on the call to ui.timer), itemH becomes None. My code only assigns a None to this variable in the first lines of the start label, and in the onUnhover function; and it never calls onUnhover directly, the function is only passed as the unhovered argument for the relevant buttons.
My first hipotesis has been that the timer would be taking away the focus from the button on each "tick", hence causing the onUnhover call... I tried to take it out, and put: debug("onUnhover") (debug is a character object, and actually he's becomming a really good friend of me :lol:)...
RuntimeError: maximum recursion depth exceeded in cmp
That traceback.txt of more than a thousand lines pops up as soon as I try to enter the shop in the game...

So, in summary, two questions:
-> When is the unhovered callback being called? According to the reference:
unhovered - A function that is called with no arguments when the button loses focus.
but I've seen with my own eyes that it's being called more often than that (and this is a problem because I was relying on it to know when does something actually loses focus)

-> Is there any way to find out from the code which widget has focus? With that, I would have enough to avoid all the misschief that the unhovered is bringing.
I have failed to meet my deadlines so many times I'm not announcing my projects anymore. Whatever I'm working on, it'll be released when it is ready :P

User avatar
PyTom
Ren'Py Creator
Posts: 16097
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Where is the mouse?

#10 Post by PyTom »

Unhovered is called at least once per displayable per interaction-restart. The best way is to curry information into the hover and unhover callbacks, and use that to see if what you want is actually hovered. If it is, then you can make use of it.

(Perhaps I should introduce focused and unfocused methods that have saner semantics.)
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
Software > Drama • https://www.patreon.com/renpytom

herenvardo
Veteran
Posts: 359
Joined: Sat Feb 25, 2006 11:09 am
Location: Sant Cugat del Vallès (Barcelona, Spain)
Contact:

Re: Where is the mouse?

#11 Post by herenvardo »

PyTom wrote:Unhovered is called at least once per displayable per interaction-restart.
That explains many things.
PyTom wrote:The best way is to curry information into the hover and unhover callbacks, and use that to see if what you want is actually hovered. If it is, then you can make use of it.
I'm not sure what "to curry information" means (I know my English is not perfect, buy I'm used to curry only some dishes) but anyway; after some loops and many calls to is_focused(), the UI is behaving as expected.
PyTom wrote:(Perhaps I should introduce focused and unfocused methods that have saner semantics.)
Perhaps? Well, I guess it's not usual among Ren'py games to mess up with hovering the way I did, but for these few cases having more "previsible" callbacks to handle this would be really useful. In the meanwhile, I'd suggest updating the reference better explaining when the unhovered callback is called. Maybe adding you own words "Unhovered is called at least once per displayable per interaction-restart." would warn other users before getting themselves into the nightmare I've gone through.

Well, the best part of all this is how well I feel now that it's done and finished! ^^
I have failed to meet my deadlines so many times I'm not announcing my projects anymore. Whatever I'm working on, it'll be released when it is ready :P

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: Where is the mouse?

#12 Post by Jake »

herenvardo wrote: I'm not sure what "to curry information" means (I know my English is not perfect, buy I'm used to curry only some dishes) but anyway; after some loops and many calls to is_focused(), the UI is behaving as expected.
Currying is the practice of collapsing parameters into the function itself. So you might have a function which takes parameters A and B, and returns X; if you curry A into the function, then you're effectively pre-setting the value of A, and you're left with a [curried] function which takes parameter B, and returns X. You could further curry B in and just have a function which generates X when you call it.
Server error: user 'Jake' not found

chronoluminaire
Eileen-Class Veteran
Posts: 1153
Joined: Mon Jul 07, 2003 4:57 pm
Completed: Elven Relations, Cloud Fairy, When I Rule The World
Tumblr: alextfish
Skype: alextfish
Location: Cambridge, UK
Contact:

Re: Where is the mouse?

#13 Post by chronoluminaire »

Specifically, the way to do it in Ren'Py is using renpy.curry, like this:

Code: Select all

python:
  def sum(A, B):
   return A+B

  curried_sum = renpy.curry(sum)

  add3 = curried_sum(3)
  narrator("5 plus 3 is %d" % add3(5))

  return12 = curried_sum(6, 6)
  narrator("There are %d months in the year" % return12())
The version where you curry all the parameters into the function (like return12() above) is useful when you need functions which take no input arguments, such as the hovered= and clicked= parameters of buttons.

If there's a function which you're only ever going to call curried, then you can wrap it up like this:

Code: Select all

python:
  @renpy.curry
  def UnHoverCallback(object=None):
    if object is not None:
      # do something to hide things
PyTom explained the "@renpy.curry" syntax to me in this post.
I released 3 VNs, many moons ago: Elven Relations (IntRenAiMo 2007), When I Rule The World (NaNoRenO 2005), and Cloud Fairy (the Cute Light & Fluffy Project, 2009).
More recently I designed the board game Steam Works (published in 2015), available from a local gaming store near you!

herenvardo
Veteran
Posts: 359
Joined: Sat Feb 25, 2006 11:09 am
Location: Sant Cugat del Vallès (Barcelona, Spain)
Contact:

Re: Where is the mouse?

#14 Post by herenvardo »

Thanks for your replies, they are quite interesting. However, I think this is, in essence, what I'm already doing with lambdas, having code like:

Code: Select all

def onHovering(hover, list, index):
    # some code to check what's hovered and what's not

# ... some more irrelevant code

ui.textbutton(text, size=14, size_group=id,
    clicked  =ui.returns(("c"+id, i)),
    hovered  =lambda: onHovering(True, id, i)
    unhovered=lambda: onHovering(False, id, i)
So I now know a new word for something I had already been doing for a while :P

EDIT: Wooo, I now understand what "renpy.curried_call_in_new_context" stands for :lol:
I have failed to meet my deadlines so many times I'm not announcing my projects anymore. Whatever I'm working on, it'll be released when it is ready :P

User avatar
PyTom
Ren'Py Creator
Posts: 16097
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Where is the mouse?

#15 Post by PyTom »

Note that you can't use lambdas in code that's expected to be saved. (While you can use curries.)
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
Software > Drama • https://www.patreon.com/renpytom

Post Reply

Who is online

Users browsing this forum: Bing [Bot]