Double-clicking or shift-clicking a button

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.
Post Reply
Message
Author
goldo
Regular
Posts: 127
Joined: Mon Jan 23, 2017 8:23 am
Contact:

Double-clicking or shift-clicking a button

#1 Post by goldo »

Apologies if this has been covered, I could find an answer to this problem researching the forum.

I am trying to implement an inventory system where items could be equipped/unequipped clickly by double-clicking a button (I'm talking about a run-of-the-mill button or imagebutton).

I know alternate allows one to specify a different action for right-clicking. Is there an equivalent for double-clicking? If not, can it be done in another way?

Finally, if double-clicking is impossible to implement or too tricky, is adding shift-clicking (or ctrl-clicking, or whatever...) possible instead?

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Double-clicking or shift-clicking a button

#2 Post by xela »

Both are "tricky" indeed. It's not that either way is particularly challenging to implement. It's that there is no clean, smooth and intuitive way of doing so.
Like what we're doing? Support us at:
Image

goldo
Regular
Posts: 127
Joined: Mon Jan 23, 2017 8:23 am
Contact:

Re: Double-clicking or shift-clicking a button

#3 Post by goldo »

I see...

What's a dirty way then? I imagine something along the line of switching a local variable on the first click, and triggering the event if a second click happens less than a half-second later or something? This would imply setting some sort of timer to reset the variable... I'm not sure how to do that, though. :(

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Double-clicking or shift-clicking a button

#4 Post by xela »

It's not "dirty," it's just using an undocumented method.

You write a class that inherits from the Button and instantiates it in the same way. Then you copy event method from the original and try to expand it to do work as you see fit.

But it's both advanced Python and Ren'Py. You could cook something up as you describe but that's not something I'd tolerate in my project. Custom Button or even an entirely new CDD would be my only considerations.
Like what we're doing? Support us at:
Image

goldo
Regular
Posts: 127
Joined: Mon Jan 23, 2017 8:23 am
Contact:

Re: Double-clicking or shift-clicking a button

#5 Post by goldo »

Aw... Those are precisely the areas of Ren'py and python I know nothing about... I could never figure out the underlying concepts of the Renpy screen system... :(

I can and will teach myself all those concepts over time but if I'm looking for a quick fix, I was wondering if something like this might work:

Code: Select all

import time

screen mybutton(): # A button which returns True on a double click
	default click_time = 0

	button:
		if time.time() - click_time <= 0.5: # Checks if there was a click less than half a second ago
			action (Return(True), SetScreenVariable("click_time", 0)) # SetScreenVariable is there to avoid triple clicks, not sure if that could be an issue
		else:
			action SetScreenVariable("click_time", time.time())
I'll try it and report on what happens...

goldo
Regular
Posts: 127
Joined: Mon Jan 23, 2017 8:23 am
Contact:

Re: Double-clicking or shift-clicking a button

#6 Post by goldo »

Yeah, it doesn't work... ^^

goldo
Regular
Posts: 127
Joined: Mon Jan 23, 2017 8:23 am
Contact:

Re: Double-clicking or shift-clicking a button

#7 Post by goldo »

Ok so...

After a lot of digging, I found this code somewhere in 'renpy/display/behavior.rpy' which seems to govern buttons:

Code: Select all

##############################################################################
# Button

KEY_EVENTS = (
    pygame.KEYDOWN,
    pygame.KEYUP,
    pygame.TEXTEDITING,
    pygame.TEXTINPUT
    )


class Button(renpy.display.layout.Window):

    keymap = { }
    action = None
    alternate = None

    longpress_start = None
    longpress_x = None
    longpress_y = None

    role_parameter = None

    keysym = None
    alternate_keysym = None

    def __init__(self, child=None, style='button', clicked=None,
                 hovered=None, unhovered=None, action=None, role=None,
                 time_policy=None, keymap={}, alternate=None,
                 selected=None, sensitive=None, keysym=None, alternate_keysym=None,
                 **properties):

	[...]

    def event(self, ev, x, y, st):

        def handle_click(action):
            renpy.exports.play(self.style.activate_sound)

            rv = run(action)

            if rv is not None:
                return rv
            else:
                raise renpy.display.core.IgnoreEvent()

        # Call self.action.periodic()
        timeout = run_periodic(self.action, st)

        if timeout is not None:
            renpy.game.interface.timeout(timeout)

        # If we have a child, try passing the event to it. (For keyboard
        # events, this only happens if we're focused.)
        if (not (ev.type in KEY_EVENTS)) or self.style.key_events:
            rv = super(Button, self).event(ev, x, y, st)
            if rv is not None:
                return rv

        if (self.keysym is not None) and (self.clicked is not None):
            if map_event(ev, self.keysym):
                return handle_click(self.clicked)

        if (self.alternate_keysym is not None) and (self.alternate is not None):
            if map_event(ev, self.alternate_keysym):
                return handle_click(self.alternate)

        # If not focused, ignore all events.
        if not self.is_focused():
            return None

        # Check the keymap.
        for name, action in self.keymap.iteritems():
            if map_event(ev, name):
                return run(action)

        # Handle the longpress event, if necessary.
        if (self.alternate is not None) and renpy.display.touch:

            if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
                self.longpress_start = st
                self.longpress_x = x
                self.longpress_y = y

                renpy.game.interface.timeout(renpy.config.longpress_duration)

            if self.longpress_start is not None:
                if math.hypot(x - self.longpress_x, y - self.longpress_y) > renpy.config.longpress_radius:
                    self.longpress_start = None
                elif st >= (self.longpress_start + renpy.config.longpress_duration):
                    renpy.exports.vibrate(renpy.config.longpress_vibrate)
                    renpy.display.interface.after_longpress()

                    return handle_click(self.alternate)

        # Ignore as appropriate:
        if (self.clicked is not None) and map_event(ev, "button_ignore"):
            raise renpy.display.core.IgnoreEvent()

        if (self.clicked is not None) and map_event(ev, "button_alternate_ignore"):
            raise renpy.display.core.IgnoreEvent()

        # If clicked,
        if (self.clicked is not None) and map_event(ev, "button_select"):
            return handle_click(self.clicked)

        if (self.alternate is not None) and map_event(ev, "button_alternate"):
            return handle_click(self.alternate)

        return None
	
	[...]
I'm guessing the event method is what I should be looking at. Basically my idea is to create a new class MyButton(Button) which would inherit from this class, and replace the event method.

But... My problem is that I can't understand the code here. Adding double clicking seems like a complex proposition because I don't know how to differenciate a simple click from a double one... Adding shift-clicking "should" be easier I guess... Maybe a variation on 'keysim'?

But this code is mysterious to me:

Code: Select all

if (self.keysym is not None) and (self.clicked is not None):
            if map_event(ev, self.keysym):
                return handle_click(self.clicked)
The 'and' clause seems to imply that it already does what I want (i.e. detect a key + a click), but... It can't be that simple, can it?

*sigh* I've got some experimenting to do...

Post Reply

Who is online

Users browsing this forum: No registered users