[SOLVED] Viewport that automatically scrolls to bottom

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
User avatar
SinnyROM
Regular
Posts: 166
Joined: Mon Jul 08, 2013 12:25 am
Projects: Blue Birth
Organization: Cosmic Static Games
Contact:

[SOLVED] Viewport that automatically scrolls to bottom

#1 Post by SinnyROM »

EDIT: After rummaging around, I finally found a way to set the adjustment value directly with this action:

Code: Select all

SetField(yadj, "value", yadj.range)
I would've figured it out earlier if I looked at the documentation on ui.adjustment: http://www.renpy.org/doc/html/screen_py ... adjustment
It's a bit vague, but range will return the maximum possible adjustment. Instead of going through with yvalue = 1.0 and having it convert it from float to int, it makes sense to just access the maximum directly, and change the value directly as well.

So here is the final result (minus any refactoring I need to do). Here I made it its own Action instead of using SetField.

Code: Select all

### From text history code, modified
init python:

    class NewAdj(renpy.display.behavior.Adjustment):
        def change(self,value):
            if value > self._range and self._value == self._range:
                return Return()
            else:
                return renpy.display.behavior.Adjustment.change(self, value)                

    # The adjustment
    yadj = NewAdj()

    # The Python function to scroll to the bottom                
    def scroll_bottom():
        yadj.value = yadj.range
        
    # Action equivalent
    class ChatScrollToBottom(Action):
        def __init__(self):
            self.adj = yadj
        def __call__(self):
            self.adj.value = self.adj.range

Code: Select all

### Messages phone screen (active=="messages")
screen scrPhoneMessages():

                ### the button to activate the scrolling before showing chat
                textbutton contact.name action [ChatScrollToBottom(), SetVariable("currentContact", contact), SetScreenVariable("active", "chat")]

Code: Select all

### Chat phone screen (active=="chat")
screen scrPhoneChat():

    tag app
        
    frame:
        style_group "phone_app"
        
        side "t c b":
            text currentContact.name
            side "c r":
                viewport id "vp_chat":
                    mousewheel True
                    draggable True
                    yinitial 1.0
                    yadjustment yadj

                    has vbox
                    for message in currentContact.messages:
                        frame:
                            text "Sent at {}:\n {} ".format(str(message.dateSent), message.body)
                bar adjustment yadj style 'vscrollbar' default
-

I want to make a viewport scroll to the bottom automatically when the screen is opened. Currently, the viewport remembers the position and stays there until the player scrolls. I tried to adapt the text history code (http://www.renpy.org/wiki/renpy/doc/coo ... xt_History) to mine, since it worked for another game when I needed a log, but not this time.

I think it has to do with how my screens are arranged: I have the use statement which switches the screen depending on the variable active (http://www.renpy.org/doc/html/screens.html#use). All those screens have the tag app. I remember getting them to switch took me a while.

Either way, I can't figure out what I'm doing wrong. I think it just needs a new pair of eyes to look over it. I would really appreciate the help!

Code: Select all

### Main phone screen
screen scrPhone():

    tag main

    default active = None
       
    window:
        style_group "phone_layout"
        frame:
            side "t c":
                use scrPhoneStatus id "scrPhoneStatus"
                use scrPhoneActive(active) ### this changes the current screen

        # Back
        textbutton "" action If(active == "chat", SetScreenVariable("active", "messages"), SetScreenVariable("active", None)) xpos 48 ypos 520 xsize 26 ysize 26
        # Home
        textbutton "" action SetScreenVariable("active", None) xpos 181 ypos 520 xsize 26 ysize 26
        # Lock
        textbutton "" action phone.lock xpos 147 ypos 573 xsize 32 ysize 32

Code: Select all

### Active phone screen
screen scrPhoneActive(active):

    if phone.isLocked:
        use scrPhoneLock
    elif active == "messages":
        use scrPhoneMessages
    elif active == "chat":
        use scrPhoneChat
    else:
        use scrPhoneHome

Code: Select all

### Home phone screen (active==None)
screen scrPhoneHome():

    tag app
    
    frame:
        style_group "phone_app"
        ###
        imagebutton auto "images/chat_%s.png" action SetScreenVariable("active", "messages") xalign 0.5 yalign 1.0

Code: Select all

### Messages phone screen (active=="messages)
screen scrPhoneMessages():

    tag app
    
    frame:
        style_group "phone_app"
        
        has vbox
        text "Messages"
        null height 20
        for contact in phone.contacts:
                ### set yvalue to 1.0 so it scrolls down
                textbutton contact.name action [SetVariable("yvalue", 1.0), SetVariable("currentContact", contact), SetScreenVariable("active", "chat")]

Code: Select all

### From text history code
init python:

    yvalue = 1.0
    
    class NewAdj(renpy.display.behavior.Adjustment):
        def change(self,value):
            if value > self._range and self._value == self._range:
                return Return()
            else:
                return renpy.display.behavior.Adjustment.change(self, value)
                
    def store_yvalue(y):
        global yvalue
        yvalue = int(y)
        
    yadj = NewAdj(changed = store_yvalue)

Code: Select all

### Chat phone screen (active=="chat")
screen scrPhoneChat():

    tag app
    
    $ yadj = NewAdj(changed = store_yvalue)
    
    frame:
        style_group "phone_app"
        
        side "t c b":
            text currentContact.name
            side "c r":
                viewport id "vp_chat":
                    mousewheel True
                    draggable True
                    yinitial yvalue
                    yadjustment yadj

                    has vbox
                    for message in currentContact.messages:
                        frame:
                            text "Sent at {}:\n {} ".format(str(message.dateSent), message.body)
                bar adjustment yadj style 'vscrollbar' default
-

Below is some rambling I did while trying to figure out the reason the scrolling works in a regular screen like text_history and not in a screen displayed by use. It didn't get me closer to solving the issue but I'll leave it here for future references.

EDIT: I wanted to see if these screens that are displayed using the use statement actually go through showing and hiding, so I added the on statement to the chat screen, just one for now:

Code: Select all

screen scrPhoneChat():

    on "show" action SetVariable("debug_chat_show", True)
    # on "hide" action SetVariable("debug_chat_hide", True)
    # on "replace" action SetVariable("debug_chat_replace", True)
    # on "replaced" action SetVariable("debug_chat_replaced", True)

    tag app
Then I get the following traceback:

Code: Select all

I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/script.rpy", line 135, in script
    b "Awesome!"
  File "game/phone.rpy", line 291, in execute
    screen scrPhone():
  File "game/phone.rpy", line 301, in execute
    window:
  File "game/phone.rpy", line 304, in execute
    frame:
  File "game/phone.rpy", line 305, in execute
    side "t c":
Exception: Side has been given too many arguments.
I'm even more confused now. Is on actually an element that's counted as a child in side? Even though on is in scrPhoneChat and side is in scrPhone? And more importantly, I thought on was an element that doesn't show on the screen, like key, and therefore wouldn't count as a child. Come to think of it, does key have the same behaviour?

Code: Select all

screen scrPhoneChat():

    key "t" action NullAction() # test

    frame:
        # ...
And same exception. Interesting. I'll have to look into this some more.

mangoduck
Newbie
Posts: 1
Joined: Sat Jul 16, 2016 9:58 pm
Contact:

Re: [SOLVED] Viewport that automatically scrolls to bottom

#2 Post by mangoduck »

I came to a different solution that is significantly simpler, though I don't know enough to say whether there might be unintended consequences. I found your post fairly easily, and found it quite helpful, so I figured I'd post my solution here for others to consider in the future. I'll comment my changes with "###" to make them more apparent and to help explain.

All it requires is a few changes to the default nvl sreen in screen.rpy:

Code: Select all

##############################################################################
# Nvl
#
# Screen used for nvl-mode dialogue and menus.
# http://www.renpy.org/doc/html/screen_special.html#nvl

init python:
    ### Set a variable to infinity, to be used later.
    yadjValue = float("inf")
    ### Create a ui.adjustment object and assign it to a variable so that we can reference it later. I'll assign it to the yadjustment property of our viewport later.
    yadj = ui.adjustment()

screen nvl(dialogue, items=None):
    ### Here is the magic. Every time the screen is redrawn, we set the value of yadjustment to infinity.
    ### The viewport is smart enough to know that means to scroll all the way to the bottom without going any farther.
    python:
        yadj.value = yadjValue
    
    window:
        style "nvl_window"
        
        ### Here is where I added the viewport, and set its yadjustment property to the ui.adjustment object I created earlier.
        ### Of course, the vbox, dialogue, and menu items need to be indented so that they're children of the viewport.
        viewport yadjustment yadj:
           
            has vbox:
                style "nvl_vbox"

            # Display dialogue.
            for who, what, who_id, what_id, window_id in dialogue:
                window:
                    id window_id

                    has hbox:
                        spacing 10

                    if who is not None:
                        text who id who_id

                    text what id what_id

            # Display a menu, if given.
            if items:

                vbox:
                    id "menu"

                    for caption, action, chosen in items:

                        if action:

                            button:
                                style "nvl_menu_choice_button"
                                action action

                                text caption style "nvl_menu_choice"

                        else:

                            text caption style "nvl_dialogue"

    add SideImage() xalign 0.0 yalign 1.0

    use quick_menu
    

##############################################################################
After some cursory tests, it seems to work. It even allows me to scroll manually, since the yadjustment property isn't set to infinity permanently, but only every time nvl screen redraws. Of course that means it will lose your place if you open the menu or something, but it seems to work pretty well for such a simple solution.

I hope that helps someone! It took me a long time to figure out.

Clef
Regular
Posts: 40
Joined: Wed Sep 18, 2013 11:16 am
Contact:

Re: [SOLVED] Viewport that automatically scrolls to bottom

#3 Post by Clef »

I knwo that this is a very old question. But I found it and it was useful so I wanted to add some improvement for anyone that should need it in the future.
My comments are those with five #####

Code: Select all

##############################################################################
# Nvl
#
# Screen used for nvl-mode dialogue and menus.
# http://www.renpy.org/doc/html/screen_special.html#nvl

screen nvl(dialogue, items=None):
	
    ##### yadj can be declared with default inside the screen so that is is only a screen variable.
    ### Create a ui.adjustment object and assign it to a variable so that we can reference it later. I'll assign it to the yadjustment property of our viewport later.
    yadj = ui.adjustment()

    ### Here is the magic. Every time the screen is redrawn, we set the value of yadjustment to infinity.
    ### The viewport is smart enough to know that means to scroll all the way to the bottom without going any farther.
    python:
    	##### The IF makes it so that if you are at the bottom of the viewport and the screen updates you remain at the bottom. And if you are in a middle position you  are not brought again to bottom when the screen updates
    	if yadj.value == yadj.range:
        	yadj.value = float('inf')
    
    window:
        style "nvl_window"
        
        ### Here is where I added the viewport, and set its yadjustment property to the ui.adjustment object I created earlier.
        ### Of course, the vbox, dialogue, and menu items need to be indented so that they're children of the viewport.
        viewport yadjustment yadj:
           
            has vbox:
                style "nvl_vbox"

            # Display dialogue.
            for who, what, who_id, what_id, window_id in dialogue:
                window:
                    id window_id

                    has hbox:
                        spacing 10

                    if who is not None:
                        text who id who_id

                    text what id what_id

            # Display a menu, if given.
            if items:

                vbox:
                    id "menu"

                    for caption, action, chosen in items:

                        if action:

                            button:
                                style "nvl_menu_choice_button"
                                action action

                                text caption style "nvl_menu_choice"

                        else:

                            text caption style "nvl_dialogue"

    add SideImage() xalign 0.0 yalign 1.0

    use quick_menu
    

##############################################################################

User avatar
isobellesophia
Miko-Class Veteran
Posts: 979
Joined: Mon Jan 07, 2019 2:55 am
Completed: None
Projects: Maddox and Friends! (AI Teacher friend), Friendly Universities! (Soon)
Organization: Friendly Teachers series
Deviantart: SophBelle
itch: Child Creation
Location: Philippines, Mindanao
Contact:

Re: [SOLVED] Viewport that automatically scrolls to bottom

#4 Post by isobellesophia »

Pretty cool code you've been making. :)

You can also put this on RenPy Cookbook, because that is where people share their codes to use to other people too.
I am a friendly user, please respect and have a good day.


Image

Image


Post Reply

Who is online

Users browsing this forum: No registered users