I was taking the scrolling map example helpfully provided by @JonB on the forum and published on github, and looking for ways of creating two views onto the same scrolling map (side by side, for a 2 player mode, kind of thing).
I experimented with a few ways of achieving this, such as trying to add the same node to two parents (nope, that doesn't work), or even writing my own custom shader to render from two different parts of a texture (well, that sort of worked, but when the texture is bigger than the screen you get severe resolution issues.... ....a very kooky solution).
So, I ended up creating a new subclass of a SpriteNode that uses render_to_texture to create texture from another pre-existing pre-existing SpriteNode. This needs to be rendered in every update (well, it does if you want any shaders in the original SpriteNode to work). But, it does seem to work well. I share the code below in case anybody else is trying to do something similar (or in case anybody would observe that I missed a more obvious or more efficient solution). By rendering in this way, you can't use the built-in animations, so I also included a very simple 'move_to' animation.
You can see the whole thing integrated into something that grew out of jsbain's original example here on github: https://github.com/py-mrw/pythonista/blob/main/mapgame2p.py.
And thank you once again to omz for making the wonderful Pythonista, and to all you nice folk on the forums, which are a goldmine of helpful information and ideas.
# class WindowNode # # Create a new view which is a rectangular window on an existing sprite node # - spritenode : The existing sprite node # - position : A Vector2 that is the origin of the window, within the existing sprite node # - size : A Vector2 that is the size of the window # Note that the scale is originally the scale of the existing sprite node, but is deliberately # not kept in synch, since this is an independent view. # # The refresh() function should be called from scene.update(), since this view needs to be refreshed # every update if you want any shaders to work properly. # # To reposition the origin, simply set render_position, or you can use move_to() if you want a simple # animation effect as you move to the new position # class WindowNode(SpriteNode): def __init__(self, spritenode, position, size, **kwargs): self.spritenode = spritenode self.render_position = Vector2(position, position) self.render_size = Vector2(size, size) self.render_rect = Rect(position, position, size/spritenode.scale, size/spritenode.scale) texture = spritenode.render_to_texture(self.render_rect) super(WindowNode, self).__init__(texture, size, **kwargs) self.scale = self.spritenode.scale self.animating = 0 def move_to(self, newpos, t, dt): if self.animating == 1: self.render_position = self.oldpos + self.deltapos self.oldpos = self.render_position self.deltapos = Vector2(newpos,newpos) - self.render_position self.animating = 1 self.t_start = t self.dt = dt def refresh(self, t): if (self.animating): progress = (t-self.t_start)/self.dt if (progress >= 1.0): progress = 1.0 self.animating = 0 self.render_position = self.oldpos + self.deltapos * progress self.texture = self.spritenode.render_to_texture((self.render_position, self.render_position, self.render_size/self.scale, self.render_size/self.scale))
Thank you @mikael, that was helpful. Certainly as a workaround, then a combination of
on_main_thread, my debugging
print()calls, and the saving/restoring of
sv.image_view.imageall (when used together) seem to solve the instance of corruption. Any two of them without the third is less successful, which is all very odd: I’m guessing there’s a race condition somewhere, or that this is some kind of heisenbug.
I’ll run with this workaround for now, thank you once again for your insights and suggestions.
omz very helpfully shared here how to use ObjCInstance to get additional information about a touch (e.g., to distinguish between a pencil and finger touch event).
I have found that simply calling this function in touch_began, in the Examples/User Interface/Sketch.py app, causes the image_view.image to somehow be corrupted (to None) after around 18 touches.
Original code in Examples/User Interface/Sketch.py:
def touch_began(self, touch): x, y = touch.location self.path = ui.Path() self.path.line_width = penwidth self.path.line_join_style = ui.LINE_JOIN_ROUND self.path.line_cap_style = ui.LINE_CAP_ROUND self.path.move_to(x, y)
One line addition which causes corruption:
def touch_began(self, touch): # Why does this break it after around 18 strokes? ui_touch = ObjCInstance(touch) x, y = touch.location self.path = ui.Path() self.path.line_width = penwidth self.path.line_join_style = ui.LINE_JOIN_ROUND self.path.line_cap_style = ui.LINE_CAP_ROUND self.path.move_to(x, y)
During debugging, I found that this call seemed to be reseting sv.image_view.image to be None. (In my own code, I go on to use ui_touch to look at the pencil attributes, but that's not necessary for the test case: simply calling the function causes the problem)
Weirdly, if I save and restore the image view, it (sometimes....) seems to avoid this problem, or at least change the behaviour. Not sure what to make of that, but thought it was worth mentioning
Weird fix, maybe:
def touch_began(self, touch): print(getframeinfo(currentframe()).lineno, sv.image_view.image) # Debug backup = sv.image_view.image ui_touch = ObjCInstance(touch) sv.image_view.image = backup print(getframeinfo(currentframe()).lineno, sv.image_view.image) # Debug x, y = touch.location self.path = ui.Path() self.path.line_width = penwidth self.path.line_join_style = ui.LINE_JOIN_ROUND self.path.line_cap_style = ui.LINE_CAP_ROUND self.path.move_to(x, y)
I thought this was worth reporting, and appreciate any thoughts folk have about what might be going on. Pythonista is just wonderful, by the way, thank you omz.