Ren'Py How to force a Ren'py button hover state to true?

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,190
I need force a ren'py button's hover state to true.
Why ? :/


Example, when you mouse over a button, its hover state is set to true.
Yes, to tell the player that the cursor is actually hover this button. Forcing the hovered state mean that, not only the state itself lost its meaning, but also the player will then be deprived of a way to know when the button is hovered.

I'm pretty sure that what you want is not to force the hovered state, but to find a way to differentiate buttons accordingly to an external state ; like by example, "you can use this action without fear", "there's risk of failure is you try this", and "you really shouldn't do that now, but well, still you can". And this is not done by changing the meaning of a predefined state that exist since the start of visual interfaces.
Instead, this is done with an if structure that change the style applied to the button. Something like:
Code:
screen whatever():
    textbutton "this":
        if actionIsSure:
            style "noPossibleFailure"
        elif actionCanFail:
            style "possibleFailure"
        else:
            style "areYouCrazy"

style noPossibleFailure is button
style noPossibleFailure_text:
    color "#00FF00"

[...]
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,190
I'm trying to make Ren'py 100% playable with the keyboard in a VERY effective way,
What is the interest exactly ?


but Ren'py's current key system is bugged to hell.
The only problem of the key system is that its too strict ; a vertical move should go to the next line, not to the next line in the actual column. The rest is just due to the fact that the quick menu is made of buttons.
But if really you want to make it works fully with the keyboard, just use the keysym and alternate_keysym button's properties.


If I could set a button's hover state to true programmatically, it will solve all my problems, and I could create a system that works 100% perfectly.
No, it would solve nothing and your system would not works.


I'm close, I can feel it.
And you feel this for what reason exactly ?

Here's the four reasons why you are still far from the solution:

1)
Your use of renpy.get_widget is wrong ; it expect an id as second parameter.
So far, whatever you'll try, you'll continue to get the same NoneType object has no attribute named "[whatever]" exception. Both the documentation and error being explicit enough, you shouldn't even have tried something more than your initial b.focus = True.

2)
Assuming that you solve the previous problem, the widget you'll then get would be a Text object, more precisely a renpy.text.text.Text object. Its focus method expect to find a hyperlink.

3)
Once you'll solve the previous problem, and finally get the Button object you're looking for, you'll still be far to the solution. You found the focus method in the behavior.py script, but apparently misunderstood its code. So I'll give you a commented version :
Python:
    def focus(self, default=False):
        #  Call the father method ; see below.
        super(Button, self).focus(default)

        #  Return value set at None by default.
        rv = None

        #  If the button is focused, but not because the screen is
        # displayed for the first time and it's the button declared
        # as "default button" for this screen
        if not default:
            #  Assign to /rv/ the value of the /hovered/ property
            # for this button.
            rv = run(self.hovered)

        #  Apply the "hover" ATL event related to this button.
        self.set_transform_event(self.role + "hover")

        #  If the button have a child, also apply the "hover"
        # ATL event to it.
        if self.child is not None:
            self.child.set_transform_event(self.role + "hover")

        #  Return the value of /rv/
        return rv
If you trace back the inheritance, the only father with a focus method is renpy.display.core.Displayable, and it's code is:
Python:
    def focus(self, default=False):
        """
        Called to indicate that this widget has the focus.
        """

        #  Change the style of the button for it to now
        # use the "hover_" variation of it.
        self.set_style_prefix(self.role + "hover_", True)

        #  If the button is focused, but not because the screen is
        # displayed for the first time and it's the button declared
        # as "default button" for this screen
        if not default:
            #  Play the sound defined by the /hover_sound/
            # property, if defined.
            renpy.exports.play(self.style.hover_sound)
How do you expect this to help you in any possible way to solve your problem ?

4)
What you want isn't to tell a button that it is hovered, but to tell Ren'py, that "this button" is the one actually hovered. Therefore, not only what you are searching isn't at all where you are looking, but also the whole process you are following is wrong from the start.
 
  • Like
Reactions: hakarlman

hakarlman

Engaged Member
Jul 30, 2017
2,130
3,346
What is the interest exactly ?




The only problem of the key system is that its too strict ; a vertical move should go to the next line, not to the next line in the actual column. The rest is just due to the fact that the quick menu is made of buttons.
But if really you want to make it works fully with the keyboard, just use the keysym and alternate_keysym button's properties.




No, it would solve nothing and your system would not works.




And you feel this for what reason exactly ?

Here's the four reasons why you are still far from the solution:

1)
Your use of renpy.get_widget is wrong ; it expect an id as second parameter.
So far, whatever you'll try, you'll continue to get the same NoneType object has no attribute named "[whatever]" exception. Both the documentation and error being explicit enough, you shouldn't even have tried something more than your initial b.focus = True.

2)
Assuming that you solve the previous problem, the widget you'll then get would be a Text object, more precisely a renpy.text.text.Text object. Its focus method expect to find a hyperlink.

3)
Once you'll solve the previous problem, and finally get the Button object you're looking for, you'll still be far to the solution. You found the focus method in the behavior.py script, but apparently misunderstood its code. So I'll give you a commented version :
Python:
    def focus(self, default=False):
        #  Call the father method ; see below.
        super(Button, self).focus(default)

        #  Return value set at None by default.
        rv = None

        #  If the button is focused, but not because the screen is
        # displayed for the first time and it's the button declared
        # as "default button" for this screen
        if not default:
            #  Assign to /rv/ the value of the /hovered/ property
            # for this button.
            rv = run(self.hovered)

        #  Apply the "hover" ATL event related to this button.
        self.set_transform_event(self.role + "hover")

        #  If the button have a child, also apply the "hover"
        # ATL event to it.
        if self.child is not None:
            self.child.set_transform_event(self.role + "hover")

        #  Return the value of /rv/
        return rv
If you trace back the inheritance, the only father with a focus method is renpy.display.core.Displayable, and it's code is:
Python:
    def focus(self, default=False):
        """
        Called to indicate that this widget has the focus.
        """

        #  Change the style of the button for it to now
        # use the "hover_" variation of it.
        self.set_style_prefix(self.role + "hover_", True)

        #  If the button is focused, but not because the screen is
        # displayed for the first time and it's the button declared
        # as "default button" for this screen
        if not default:
            #  Play the sound defined by the /hover_sound/
            # property, if defined.
            renpy.exports.play(self.style.hover_sound)
How do you expect this to help you in any possible way to solve your problem ?

4)
What you want isn't to tell a button that it is hovered, but to tell Ren'py, that "this button" is the one actually hovered. Therefore, not only what you are searching isn't at all where you are looking, but also the whole process you are following is wrong from the start.
I abandoned the approach and will heed your wisdom :)
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,190
I abandoned the approach and will heed your wisdom :)
I'll give you another piece of wisdom, that I learned from a mentor, who surely himself learned it from a mentor:
[Note: I translate from memory what he said, so not sure that it will feel as wise as it was when I heard it]
"Whatever the interest you see in the addition of a feature in your program, you should always look at this addition from a pain/benefit point of view. If the pain you'll have to implement it is higher than the benefit, then don't even try."

It's the reason why I so often start my answer to a question like yours with "why ?".
Basically speaking, offering an easy way to use the UI from the keyboard can be interesting. But when you put it in perspective to the pain it imply, it loose a big part of its interest.
To be honest, I'm not even sure that I would be able to do it myself. I know where to look, but I doubt that there's an easy way to change the right variable, and even less to hook to the code in order to get around this problem.

This being said, there's still the keysym and alternate_keysym. They rely on a different approach that browsing the UI with the arrow keys, but still offer a possibility to link the keyboard to the UI buttons.
It would need a little more works for the menus, but even there's it shouldn't be really difficult.
 
  • Like
Reactions: hakarlman