Ren'Py [solved] drag 32:9 content horizontal on a 16:9 screen

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
Disclaimer: I've never touched Ren'Py Drag- and Drop before.

I want to drag 3840x1080 content horizontally. For now just the background, later additional imagebuttons must be moved with the background. The game's resolution is 1920x1080.

By default RenPy seems to prevent the user from dragging things off screen. It is assumed that the content is smaller than the screen. In my scenario drag_offscreen True is required to make large content draggable. Otherwise the image will just "jump" to the right side in this example and it stops dragging instantly. I guess this "jump" happens because Ren'Py tries to fit the 3840x1080 background on the 1920x1080 screen (with drag_offscreen False).

Code:
default debugtext = "Drag me!"

init python:
    ##  https://www.renpy.org/doc/html/drag_drop.html
    ##  after the drag has been rendered, the following fields become available:
    ##  x,y,w,h

    def drag_activated(drags):
        store.debugtext = ""
        for d in drags:
            store.debugtext += "drag_activated()->"+d.drag_name+" x,y,w,h=("+str(d.x)+","+str(d.y)+","+str(d.w)+","+str(d.h)+")"
        renpy.restart_interaction() # to update screen
        return


    def drag_dragged(drags, drop):
        store.debugtext = ""
        for d in drags:
            store.debugtext += "drag_dragged()->"+d.drag_name+" x,y,w,h=("+str(d.x)+","+str(d.y)+","+str(d.w)+","+str(d.h)+")"
        renpy.restart_interaction() # to update screen
        return True

    def drag_clicked():
        store.debugtext = "drag_clicked()"
        # no restart_interaction() needed, screen seems to update anyway
        return True

screen photo_beach():
    tag master
    layer "master"

    drag:
        drag_name "photo_beach"
        drag_handle (0,0,1.0,1.0)
        drag_offscreen True # must be True for content larger than screensize
        activated drag_activated
        dragged drag_dragged
        clicked drag_clicked

        image "images/bg photo beach.jpg" # 3840x1080 background image

    text debugtext align(0.5,0.5)

label start:
    scene
    show screen photo_beach
    jump loop

label loop:
    pause
    jump loop
Question:
How can I prevent the user from dragging the content off screen in this example? Is it even possible in THIS scenario (image>screensize->drag_offscreen True)? The x,y position from the Callbacks is the obvious way to start, but these don't seem to "live"-update while dragging (event based?). So locking for example the y to 0 is pointless because the image would "jump" only back to a correct position when an event is fired. While dragging there is no event, no blocking possible? Right?

Test project with background attached (RenPy 7.1.1.929).

edit link for reference:
 

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
next day...
I'm stuck with the Drag and Drop method. Looking for alternative ways to move the content I came up with a scrollbar.

pro:
  • moves smoother compared to dragging
  • can't be dragged off screen
contra:
  • ugly visible scrollbar all the time
It's a barely 'acceptable' solution for me but I will use it for now. I'm not really happy with it.:mad: I'd still prefer dragging without a visible UI-element. On the other hand the movement while dragging is very choppy ... meh ... so that's shit vs shit.
Code:
default debugtext = ""
default photo_screen_bar_value = 0

init python:
    ##  https://www.renpy.org/doc/html/drag_drop.html
    ##  after the drag has been rendered, the following fields become available:
    ##  x,y,w,h

    def Drag_activated(drags):
        store.debugtext = ""
        for d in drags:
            store.debugtext += "drag_activated()->"+d.drag_name+" x,y,w,h=("+str(d.x)+","+str(d.y)+","+str(d.w)+","+str(d.h)+")"
            d.y = 0
        renpy.restart_interaction() # to update screen
        return

    def Drag_dragged(drags, drop):
        store.debugtext = ""
        for d in drags:
            store.debugtext += "drag_dragged()->"+d.drag_name+" x,y,w,h=("+str(d.x)+","+str(d.y)+","+str(d.w)+","+str(d.h)+")"
            d.y = 0
        renpy.restart_interaction() # to update screen
        return True

    def Drag_clicked():
        store.debugtext = "drag_clicked()"
        # no restart_interaction() needed, screen seems to update anyway
        return True

    def Photo_screen_bar_value_changed(value):
        store.debugtext = "bar value="+str(value)
        store.photo_screen_bar_value = -value
        renpy.restart_interaction() # to update screen
        return

screen draggable_photo_beach():
    tag master
    layer "master"

    drag:
        drag_name "photo_beach"
        drag_handle (0,0,1.0,1.0)
        drag_offscreen True # must be True for content larger than screensize
        activated Drag_activated
        dragged Drag_dragged
        clicked Drag_clicked

        image "images/bg photo beach.jpg" # 3840x1080 background image

    text debugtext align(0.5,0.5)

screen scrollable_photo_screen():
    tag master
    layer "master"
    # red test-background to check for precise positioning
    # I'm not sure whether the bar range must be 1919 or 1920, both show no red pixels for me in fullscreen 1080p
    #add "#F00"
    image "images/bg photo beach.jpg" xpos photo_screen_bar_value # 3840x1080 background image

    $ bar_width = 960 # 960 results in exactly 2 pixels moved per changed value
    bar value 0 range 1920 changed Photo_screen_bar_value_changed area(1920/2-bar_width/2,980,bar_width,64) bottom_bar "#0002" top_bar "#7bf6"

    text debugtext align(0.5,0.5)

label start:
    scene
    menu:
        "draggable photo screen":
            $ debugtext = "Drag me!"
            show screen draggable_photo_beach
        "scrollable photo screen":
            $ debugtext = "Scroll bar!"
            show screen scrollable_photo_screen
    jump loop

label loop:
    pause
    jump loop
Code can be copy-pasted into script.rpy from attached project in post 1.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,976
16,232
contra:
  • ugly visible scrollbar all the time
This is not a problem, just style the bar as invisible :
Code:
style myScrollbar:
    base_bar Solid( "#00000000" )
    thumb    Solid( "#00000000" )
As for the drag&drop, I have no answer sorry, but why exactly do you want this ?
Since a scroll bar can do the trick, I assume that it's not explicitly to drag&drop, but more to simulate some kind of "look right, look left".
If it's that, you can also try with something like this :
Code:
screen myMove:

    default x = 0
    default moveLeft = False
    default moveRight = False
    default nextX = 0
  
    if moveLeft is True:
        timer 0.1 repeat True action If( x <= nextX, SetScreenVariable( "moveLeft", False ), SetScreenVariable( "x", x - 0.01 ) )
    elif moveRight is True:
        timer 0.1 repeat True action If( x >= nextX, SetScreenVariable( "moveRight", False ), SetScreenVariable( "x", x + 0.01 ) )

    key "K_LEFT" action [ SetScreenVariable( "moveLeft", True ), SetScreenVariable( "moveRight", False ), SetScreenVariable( "nextX", x - 0.05 ) ]
    key "K_RIGHT" action [ SetScreenVariable( "moveRight", True ), SetScreenVariable( "moveLeft", False ), SetScreenVariable( "nextX", x + 0.05 ) ]

    add "images/The image of your choice":
        xalign x

    textbutton "ok":
        action Return()
It's not very smooth like this, but you surely can come to a better result by working on the values for both the timer interval and the step for the "x" value.
You can also have it as continuous movement with this :
Code:
screen myMove:

    default x = 0
    default moveLeft = False
    default moveRight = False
  
    if moveLeft is True:
        timer 0.1 repeat True action If( x <= -0.5, SetScreenVariable( "moveLeft", False ), SetScreenVariable( "x", x - 0.01 ) )
    elif moveRight is True:
        timer 0.1 repeat True action If( x >= 1.5, SetScreenVariable( "moveRight", False ), SetScreenVariable( "x", x + 0.01 ) )

    key "K_LEFT" action [ SetScreenVariable( "moveLeft", True ), SetScreenVariable( "moveRight", False ) ]
    key "K_RIGHT" action [ SetScreenVariable( "moveRight", True ), SetScreenVariable( "moveLeft", False ) ]
    key "K_SPACE" action [ SetScreenVariable( "moveRight", False ), SetScreenVariable( "moveLeft", False ) ]

    add "images/The image of your choice":
        xalign x

    textbutton "ok":
        action Return()
 

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
...to simulate some kind of "look right, look left".
Yeah something like that. Preferably without "steps" to look left/right since some "points of interest" would end up on a screen edge. (I'm not sure I can explain this very well with my english).
The idea is to have a large scene where the player would pick various locations for shooting photos. The scene just happens to fit perfectly into a 32:9 format. Splitting it into two 16:9 images would result in a bad image composition. It's just an artistic decision. :)

This is not a problem, just style the bar as invisible :
Code:
style myScrollbar:
base_bar Solid( "#00000000" )
thumb Solid( "#00000000" )
Hmm ... you just gave me the idea for this:
  • make scrollbar invisible
  • make scrollbar take up the entire screen
  • make sure the scrollbar does not "catch" clicks for other clickable things, they must be "above" the scrollbar
Basically a fake horizontal drag. I'm pretty sure this will work ... will try this once I finished other script-stuff and report back.

Thanks for the help!

Edit:
The idea with the invisible fullscreen scrollbar works but has a problem: With the initial click the background image "jumps" based on the bar value (obvious due to how scrollbar work *doh*). In my scenario the jump would be 1920 pixels or an entire screen in the worst case. That's to confusing and not intuitive. After the initial click the fake-drag effect is perfect and smooth. The jumps make this unusable unfortunately. I'm sure this is fixable somehow with some bar-range, bar-value and/or trickery with a custom xoffset (remember last x-click positon and take new x-click position => calculate xoffset => use to keep image at position without jumps) ... this is to hackish. Time to stop. I'll stick with a small visible transparent scrollbar for now.
 

Atmus

New Member
Mar 28, 2018
10
7
Is this what you are looking for? These are the contents of my script.rpy using your test background image.

Code:
init python:
    class Panner(renpy.Displayable):
        def __init__(self, child, **kwargs):
            renpy.Displayable.__init__(self, **kwargs)
            self.child = Image(child)
            self.mouseDown = False
            self.offsetx = 0.0
            self.lastx = 0.0

        def visit(self):
            return [ self.child ]

        def render(self, width, height, st, at):
            render = renpy.Render(width, height)
            child_render = renpy.render(self.child, 3840, 1080, st, at)
            render.blit(child_render, (int(self.offsetx), 0))
            renpy.redraw(self, 0)
            return render

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

            if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
                self.mouseDown = True
                self.lastx = x
            elif ev.type == pygame.MOUSEBUTTONUP and ev.button == 1:
                self.mouseDown = False

            dx = x - self.lastx
            self.lastx = x

            if self.mouseDown == True:
                self.offsetx = self.offsetx + dx
                self.offsetx = max( min( self.offsetx, 0 ), -1920 )

            return self.child.event(ev, x, y, st)

define MyPan = Panner("images/bg photo beach.jpg")

screen panner_test():
    add MyPan

label start:
    scene
    show screen panner_test
    jump loop

label loop:
    pause
    "Test 1"
    "Test 2"
    jump loop
 
  • Like
Reactions: f95zoneuser463

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
@Atmus
Custom Displayables are somewhat above my level of expertise. Very powerful but scary for me. By looking at your code I assume it's suppose to drag the image only when the mousebutton is down. Yep, exactly what I want.
While testing it I found out that event() does never receive a MOUSEBUTTONUP event. That resulted in the image being dragged all the time.

I've added a print(ev) to the start of the event()-function to see all events in the console. The results for a short drag and drop in Ren'Py 7.1.1.929 (latest stable):
panner all events from a short mouse drag and drop.png
I got it working by modifying it:
Code:
if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
    print(ev)
    self.mouseDown = True
    self.lastx = x
elif ev.type == pygame.MOUSEMOTION:# and ev.buttons == 1:
    # do nothing with MOUSEMOTION events, keep self.mouseDown in whatever state it is
    print(ev)
else:
    self.mouseDown = False
Not exactly "clean" but it works around the MOUSEBUTTONUP-problem.
Possibly a bug? May I ask what RenPy-Version you used Atmus?

I've attached my messy script for fiddling. The "scrolling" with this solution is very smooth, better than anything else I've tried. Now I just have to look into how I can drag additional imagebuttons with the image in the "real" project ... shouldn't be a problem, I've a few ideas.

Thanks! I appreciate your help! *marked as solved*
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,976
16,232
Code:
start:
    scene
    show screen panner_test
    jump loop

label loop:
    pause
    "Test 1"
    "Test 2"
    jump loop
I'm curious. How can you use something as advanced than a custom displayable, and in the same time use an endless loop in place of "call screen" ?
 
  • Like
Reactions: f95zoneuser463

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
@anne O'nymous
Good question. I guess it's a habit from a time when I didn't know that screens can be called ... I'm stupid :FeelsBadMan:

Btw. could somebody confirm that MOUSEBUTTONUP is broken for that custom displayable example in the latest RenPy stable version? I find it hard to believe.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,976
16,232
I'm stupid :FeelsBadMan:
Sorry, really, but I must confirm. This said, not for the reason you think, but because you weren't the person I quoted, and so talked to :D
Need some sleep perhaps ?


Btw. could somebody confirm that MOUSEBUTTONUP is broken for that custom displayable example in the latest RenPy stable version? I find it hard to believe.
I don't really have the time, but if my memory don't betray me, there's a thing with the buttons in custom displayable. Something like "to see if the button is up, look if it's not down". I seem to remember having read that on the official forum.
Perhaps that @Palanto know more, he's the ace when it come to the official forum.
 
  • Like
Reactions: Palanto

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
I'm to blame for that loop code, no big deal.;) Atmus probably just copied the end from my first post to help me quickly with custom displayable example.
Need some sleep perhaps ?
I really do ... there is just never enough time ... so much stuff to do ...
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,976
16,232
I'm to blame for that loop code, no big deal.;) Atmus probably just copied the end from my first post to help me quickly with custom displayable example.
Well, apparently I also need to sleep... For the same reason than you.
 

Atmus

New Member
Mar 28, 2018
10
7
f95zoneuser463 is correct, I just copied that loop from his original example. I am still a RenPy noob myself, but I have been a Unity/Unreal game developer for a long time.

As for the MOUSEBUTTONUP not firing, that is strange because it definitely works on my test. I originally had debug messages on screen for showing the mouseDown value, and it was getting set to false on mouse up. I just tested again with my posted code, and the image stops scrolling when I release the mouse button.

I tested this with renpy-7.0.0-sdk, just in case its a renpy version issue.

Here is a screen shot after I added the print(ev) statement at the top of the event function.
 
  • Like
Reactions: f95zoneuser463

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
@Atmus
Just did the test in the 7.0.0 version you used and now I'm sure it's a bug ... it's broken in the Ren'Py 7.1.1.929 (latest stable). In 7.0.0 it works fine for me too. Why does this always happen to me? ... did somebody hide a bug-magnet in my pocket?
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,976
16,232
f95zoneuser463 is correct, I just copied that loop from his original example.
Well, I'll take advantage from the fact that my daughter in not at home during this (here) holiday week to sleep more...



Oh fuck ! I promised to repaint her bedroom :(
 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,841
Sorry, really, but I must confirm. This said, not for the reason you think, but because you weren't the person I quoted, and so talked to :D
Need some sleep perhaps ?




I don't really have the time, but if my memory don't betray me, there's a thing with the buttons in custom displayable. Something like "to see if the button is up, look if it's not down". I seem to remember having read that on the official forum.
Perhaps that @Palanto know more, he's the ace when it come to the official forum.
at least there's nothing in the bug tracker or on any forum page about that problem yet....

But why don't you do it viewport style? I mean, create a screen with a viewport, make it draggable and you should be good to go right?
 
  • Like
Reactions: anne O'nymous

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,976
16,232
But why don't you do it viewport style? I mean, create a screen with a viewport, make it draggable and you should be good to go right?
Because it's too obvious ? Too simple ? Because we are too much in need of sleep ? @f95zoneuser463 tried scroll bar, but it triggered nothing in our minds.

That's obviously the best solution since Ren'py will do all the works by itself. It just need to style the two bar as invisible (see above).
 
  • Like
Reactions: Palanto

Epadder

Programmer
Game Developer
Oct 25, 2016
568
1,064
With Palanto's hint and looking around about viewports a little more, to get the effect you want (dragable and no scrollbar) [Renpy 7.1.1.929]:
Code:
screen photo_beach():
    tag master
    layer "master"
    modal True

    viewport:
        child_size (3840,1080)
        xalign 0.0 yalign 0.0
        draggable True
        add "bg photo beach" xalign 0.0 yalign 0.5
        textbutton "Help" action NullAction() xpos 3580 ypos 540

label start:
    scene
    call screen photo_beach
 

Palanto

Active Member
Game Developer
Oct 4, 2017
964
1,841
With Palanto's hint and looking around about viewports a little more, to get the effect you want (dragable and no scrollbar) [Renpy 7.1.1.929]:
Code:
screen photo_beach():
    tag master
    layer "master"
    modal True

    viewport:
        child_size (3840,1080)
        xalign 0.0 yalign 0.0
        draggable True
        add "bg photo beach" xalign 0.0 yalign 0.5
        textbutton "Help" action NullAction() xpos 3580 ypos 540

label start:
    scene
    call screen photo_beach
Perfect ;) So if you want to start at i.e. the center of the image instead of the most left position of it. Just add
Code:
xinitial 0.5
somewhere like i.e. this:
Code:
screen photo_beach():
    tag master
    layer "master"
    modal True

    viewport:
        child_size (3840,1080)
        xalign 0.0 yalign 0.0
        draggable True
        xinitial 0.5    # starts from the center of the scene image. If you use a decimal number it works like all the align
                           # options 0.0 - 1.0 from left to right... you can also use int to specify the pixel from 0 to 3840
        add "bg photo beach" align(0.0, 0.5)
        textbutton "Help" action NullAction() xpos 3580 ypos 540