Forum Archive

Aligning scene nodes while ui sceneView is animately resized

rownn

Hey everyone,

awkward title of the topic, I know. I struggled with fitting nodes in resizing sceneViews. I have a sceneView, which is resized via animation by pushing a button. The question was how to stick the scene at the top of the view. I have found a solution, but because of the fact I am new to that stuff I‘d like to post the code here with the silent question if there is a better way or if it is a common way.

```
from scene import *
import ui

class MyScene(Scene):
def init(self):
Scene.init(self)
self.background_color= '#eee'
self.frm = ShapeNode(parent=self, fill_color='red', stroke_color='clear')

def setup(self):
    self.viewSizeHasChanged()

def viewSizeHasChanged(self):
    self.frm.position = (150,self.view.height/2-(500-self.view.height)/2)
    self.frm.path = ui.Path.rect(0,0,250,450)

class GUI(ui.View):
def init(self):
self.background_color = '#ddd'
self.scales = (100, 500)
self.state = True
self.separator_H = 10

    self.mainView = ui.View(frame=(50,50,300,500), background_color='#fff')
    self.add_subview(self.mainView)

    self.sn = SceneView()
    self.sn.frame = self.mainView.bounds
    self.mainView.add_subview(self.sn)
    self.sn.scene = MyScene()

    self.btn_Do = ui.Button(name='Do', title='Do', background_color= '#ddd', corner_radius = 12, action = self.btn_tapped)
    self.btn_Do.frame = (175, 275, 50, 50)
    self.add_subview(self.btn_Do)

def btn_tapped(self, sender):
    if sender.name == 'Do':
        self.state = (self.state+1)%2
        self.sn.scene.viewSizeHasChanged()
        self.animate(self.scales[self.state])

def animate(self, H):
    def animation(): self.mainView.frame = (50,(600-H)/2,300,H)
    ui.animate(animation, duration=1.0)

if name == 'main':
GUI().present('fullscreen')
```

Thx for every hint guys
rownn

stephen

@rownn are you trying to shrink the scene also or just hide it?

stephen

@rownn Also you dont need to import uiwhen you import all from scene 🤓 scene already imports ui implicitly

rownn

Hey @stephen,

good question. Actually I wanted to shrink the scene, too. But than I realized that it wouldnt effect the appearance I wish. Besides this I wasnt able to get the scene shinking smoothly with the sceneView :( Is there a way to achieve this?

Thanks for the tip regarding the ui importing, you are right, of course :)
And thanks for looking though the code

mikael

@rownn, here’s how I would do it, to have the scene contents shrink smoothly with the view.

The main changes are the use of Scene’s update method to update the layout, and the use of the scripter to drive the animation. Like always, the regular ui.animate just did not want to work with me.

from scene import *
import ui
from scripter import *

class MyScene(Scene):

    def setup(self):
        self.background_color= '#eee'
        self.frm = ShapeNode(parent=self, fill_color='red', stroke_color='clear')

    def update(self):
        self.frm.path = ui.Path.rect(
            *self.view.bounds.inset(20, 20)
        )
        self.frm.position = self.size/2


class GUI(ui.View):
    def __init__(self):
        self.background_color = '#ddd'
        self.open = True

        self.main_view = ui.View(
            frame=(50,50,300,500),
            flex='RLTB',
            background_color='#fff')
        self.main_view.center = self.bounds.center()
        self.add_subview(self.main_view)

        self.sn = SceneView(
            frame = self.main_view.bounds,
            flex='WH')
        self.main_view.add_subview(self.sn)
        self.sn.scene = MyScene()

        self.btn_Do = ui.Button(
            title='Just Do It', 
            tint_color='black',
            background_color= '#ddd', 
            corner_radius = 12, 
            action = self.btn_tapped,
            flex='RLTB')
        self.btn_Do.size_to_fit()
        self.btn_Do.frame = self.btn_Do.frame.inset(-8, -16)
        self.btn_Do.center = self.bounds.center()
        self.add_subview(self.btn_Do)

    def btn_tapped(self, sender):
        self.open = self.open == False
        self.animation()

    @script
    def animation(self):
        height(self.main_view, 500 if self.open else 100)
        center(self.main_view, self.bounds.center())
        yield


if __name__ == '__main__':
    GUI().present('fullscreen')

There are also several suggested changes to create the layout without calculating pixels.

rownn

Hey @mikael,

thanks alot! It is amazing to see one of my codes in a rewritten most likely better way :) I just flew over it, but there are many very nice looking changes and I‘m excited to get into it deeper soon. Until then I thank you for the time you spent and the insights which I will have.

PS: Thought it would be clever to avoid the update method. Isnt it performance-consuming?

stephen

@rownn Couple options. notvery clean but can be fine tuned


from scene import *

class MyScene1(Scene):
    '''
        unless you plan to make copies of the scene theres no need to override 
        __init__ ☺️ setup has everything needed
    '''
    def setup(self):
        self.background_color= '#eee'
        self.frm = ShapeNode(parent=self, fill_color='red', stroke_color=None)

        lbl=LabelNode(text=f'{self.view.frame}', font=('Chalkboard SE', 16), position=Point(self.size[0]/2, self.size[1]-64), color='#00d412', parent=self)

    def viewSizeHasChanged(self):
        self.frm.position = (150,self.view.height/2-(500-self.view.height)/2)
        self.frm.path = ui.Path.rect(0,0,250,450)

class MyScene2(Scene):
    def setup(self):
        self.background_color= '#eee'
        self.frm = ShapeNode(parent=self, fill_color='blue', stroke_color=None, anchor_point=(0.0, 1))
        self.frm.position = (150, (self.view.height/2-(500-self.view.height)/2))
        self.frm.path = ui.Path.rect(0,0,250,450)
        self.minimized=False
        self.updateSize=True
        self.lbl1=LabelNode(text=f'{self.view.frame}', font=('Chalkboard SE', 16), position=Point(self.size[0]/2, self.size[1]-64), color='#00d412', parent=self)
        self.lbl2=LabelNode(text=f'{self.frm.frame}', font=('Chalkboard SE', 16), position=Point(self.size[0]/2, self.size[1]-32), color='#00d412', parent=self)

    def Resize(self, node, progress):
        size= 50 if self.minimized else 450
        self.frm.size = (self.frm.size[0], size/progress)

    def Toggle(self):
        self.minimized = not self.minimized
        self.frm.run_action(Action.call(self.Resize, 1.0))

    def update(self):
        #self.frm.size=Size(self.view.width-50, self.view.height-50)
        self.frm.position= Point(25, self.size[1]-25)
        self.lbl2.text=f'{self.frm.frame}'

    def viewSizeHasChanged(self):
        pass


class GUI(ui.View):
    def __init__(self):
        self.background_color = '#ddd'
        self.scales = (1, .25)
        self.state = True
        self.separator_H = 10

        self.mainView = ui.View(frame=(50, 50, 300, 500), background_color='#fff')
        self.add_subview(self.mainView)

        self.mainView2 = ui.View(frame=(350, 50, 300, 500), background_color='#fff')
        self.add_subview(self.mainView2)

        self.sn = SceneView()
        self.sn.frame = self.mainView.bounds
        self.mainView.add_subview(self.sn)
        self.sn.scene = MyScene1()

        self.sn2 = SceneView()
        self.sn2.frame = self.mainView2.bounds
        self.mainView2.add_subview(self.sn2)
        self.sn2.scene = MyScene2()

        self.btn_1 = ui.Button(name='1', title='1', background_color= '#ddd', corner_radius = 12, action = self.btn_tapped1)
        self.btn_1.frame = (175, 275, 50, 50)
        self.add_subview(self.btn_1)

        self.btn_2 = ui.Button(name='2', title='2', background_color= '#ddd', corner_radius = 12, action = self.btn_tapped2)
        self.btn_2.frame = (475, 275, 50, 50)
        self.add_subview(self.btn_2)


        '''
                :ui.View.layout():
            now the scene's viewSizeHasChanged() will be called
            anytime the view is altered including initial display
        '''
    def layout(self):
        self.sn.scene.viewSizeHasChanged()
        self.sn2.scene.viewSizeHasChanged()
        pass
    def btn_tapped1(self, sender):
        if sender.name == '1':
            self.state = (self.state+1)%2
            self.animate(self.scales[self.state])

    def btn_tapped2(self, sender):
        self.sn2.scene.Toggle()
        def animation(): 
            self.mainView2.height = 100 if self.sn2.scene.minimized else 500
            self.mainView2.y = 275 if self.sn2.scene.minimized else 50

        ui.animate(animation, duration=1.0)

    def animate(self, H):
        '''
            using Transform with ani ate you can scale, move and rotate the whole 
            sceneView
            alternatively you can create an Action for the scene to change size 
            while changing the views
        '''
        def animation(): 
            self.mainView.transform = ui.Transform.scale(1, H)
        ui.animate(animation, duration=0.5)

if __name__ == '__main__':
    GUI().present('fullscreen')
stephen

@rownn said:

PS: Thought it would be clever to avoid the update method. Isnt it performance-consuming?

i thought this too before but as long as everything inside the update method does not take longer than self.dt your good 😎☺️

JonB

When you look at the code for scene, there is an _update method that is getting called, that handles the Actions. Then it calla update. So performance wise, I think Actions and update methods are similar.

stephen

@JonB said:

When you look at the code for scene, there is an _update method that is getting called, that handles the Actions. Then it calla update. So performance wise, I think Actions and update methods are similar.

exactly. in my games ill actually use this to my advantage by staging animations and checks. for example you can move a node with Action.move_by and have a check in update to make sure node isnt outside the screen. this insures the move is done before the check. Alternativily you can move your check intoScene.did_evaluate_actions() only reason i dont use this method is because your check must wait until the action is finished and that could have been too long depending on current check being used.

heres an example if anyone wants using all three steps to move the node and rmove it if off screen.


def did_evaluate_actions(self):
    self.move_node()

def move_node(self):
    self.run_action(Action.move_by(-100, 0, 1.0, TIMING_EASE_IN_OUT))

def update(self):
    if self.position[0] < -self.size[0]-10:
        self.run_action(Action.remove())



adding a light rotate at the end and begining would give the rocking of a stop and go effect like in a cartoon. ☺️

rownn

Hey guys,

amazing again. I think I will study you snippets over a glass of wine this evening :)

stephen

@rownn said:

Hey guys,

amazing again. I think I will study you snippets over a glass of wine this evening :)

🍻

cvp

@mikael said:

self.open = self.open == False

I prefer 🙄😇

self.open = not self.open
stephen

@cvp said:

@mikael said:

self.open = self.open == False

I prefer 🙄😇
self.open = not self.open

i agree.. it still drives me nuts that i cant use..

boolean = !boolean

catch myself still doing it after about a year of python lol

rownn

For this part of the discussion alone this thread is worthy to exist. :)

@rownn said: (boolean+1)%2
The school math way

@mikael said: boolean = boolean == False
The developers way

@cvp said: boolean = not boolean
The pythonish way

@stephen said: boolean = !boolean
The beautiful but !pythonish way

ccc

Pythonic not Pythonish. ;-) 500+ pages of results!

cvp

@rownn In my very old Fortan past, I used an integer 1 or 0, thus

flag = 1 - flag
mikael

@cvp, I vote for your version, which I plain forgot.