Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Aligning scene nodes while ui sceneView is animately resized
-
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 -
@rownn are you trying to shrink the scene also or just hide it?
-
@rownn Also you dont need to import uiwhen you import all from scene 🤓 scene already imports ui implicitly
-
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 -
@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 thescripter
to drive the animation. Like always, the regularui.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.
-
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?
-
@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')
-
@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 😎☺️ -
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.
-
@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 inupdate
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. ☺️
-
Hey guys,
amazing again. I think I will study you snippets over a glass of wine this evening :)
-
@rownn said:
Hey guys,
amazing again. I think I will study you snippets over a glass of wine this evening :)
🍻
-
-
-
-
Pythonic not Pythonish. ;-) 500+ pages of results!
-
@rownn In my very old Fortan past, I used an integer 1 or 0, thus
flag = 1 - flag
-
@cvp, I vote for your version, which I plain forgot.