asciimatics
asciimatics copied to clipboard
Dynamically adding Scenes
Hi! Thank you for the wonderful library. I am building a TUI which uses tab navigation, and after reading through https://github.com/peterbrittain/asciimatics/issues/271 and taking a close look at the tab demo, I realized that there may not be a way to do what I'm looking for.
Basically, I have a dynamic number of tabs. The user can create new tabs, and at the moment I'm strugling to figure out a way to add them to the list of scenes.
This seems to be a typical pattern through the samples:
def demo(screen, scene):
scenes = [
Scene([RootPage(screen)], -1, name="Tab1"),
Scene([AlphaPage(screen)], -1, name="Tab2"),
Scene([BravoPage(screen)], -1, name="Tab3"),
Scene([CharliePage(screen)], -1, name="Tab4"),
]
screen.play(scenes, stop_on_resize=True, start_scene=scene, allow_int=True)
However, short of throwing a custom exception, forcing in a newly instantiated scene, and then going back to screen.play
I don't see a way to dynamically register a new scene to the list and then call raise NextScene("New scene name")
.
Yeah. You're right. The assumption in the current play
API is that you know all your Scenes up front.
The simplest approach for this is probably to create your own play loop. Asciimatics already provides the API to do this. You can see how to use the necessary calls here: https://github.com/peterbrittain/asciimatics/blob/9903cedb5ba67be4083b05e5d286725b8166783a/asciimatics/screen.py#L1599
You could then change that to call screen.set_scenes
to set up your new Scenes at any point in your new loop.
Alternatively, if you're feeling more adventurous, I'd happily help you add a real tab layout to the widgets subsystem.
Thank you for these ideas! Right now I'm thinking the easiest thing to do might be to clear widgets and rebuild on select.
Alternatively, if you're feeling more adventurous, I'd happily help you add a real tab layout to the widgets subsystem.
I'd be interested in giving that a try! I'll probably finish my current project first, then give this a shot. I'll post a link to the project in a week when it's in a working state.
What are you imagining the tab layout API might look like?
Asciimatics already has the concept of a Layout, which is responsible for placing Widgets within a Frame. The current implementation just allows you to create a set of columns (to place the widgets in).
We could create a TabbedLayout that allows you to create multiple tabs - each of which is one column. You then add widgets to each tab in much the same way as you would add one to a column for the current Layout. The display logic then needs to draw the tab header as well as the column for the active tab. Similarly input logic needs to find the active tab rather than the current column. Overall logic should be pretty similar to the existing Layout object.
This will only allow simple forms inside the TabbedLayout, though. if you need something more complex than that, we'd have to start nesting Layouts within TabbedLayout. This would then effectively just be the logic to handle the tab header and route drawing/input to the correct embedded Layout... which probably means we should call it something else.
i can flesh out design ideas as needed...
Okay I finally put my progress on github. There's a lot of room for improvement, but I put a picture on the readme of the tab system I currently have going.
My current implementation is to call this function whenever a tab button is pressed, which clears the central layout and replaces the central widget.
However, this only works because I have two layouts one on top of the other, and the top one only ever holds a single widget. I'm going to switch to having vertical tabs on the side, rather than on the bottom, and I think that means whenever a button is selected I'll have to create all of the tab buttons, which is less ideal :thinking:
if you need something more complex than that, we'd have to start nesting Layouts within TabbedLayout.
You just got me thinking- does Asciimatics allow for nested layouts? That might let me work around some issues.
Good question... Sadly not.
Dynamically adding scenes may be what I was trying to do without realizing (which if it's not, I can open a new issue; just let me know and I'll do that) and I think you answered this for me with your response:
Yeah. You're right. The assumption in the current
play
API is that you know all your Scenes up front.The simplest approach for this is probably to create your own play loop. Asciimatics already provides the API to do this. You can see how to use the necessary calls here:
https://github.com/peterbrittain/asciimatics/blob/9903cedb5ba67be4083b05e5d286725b8166783a/asciimatics/screen.py#L1599
You could then change that to call
screen.set_scenes
to set up your new Scenes at any point in your new loop.
The what I'm trying to do and the why: I was trying to create a wrapper in a sense to create a dynamic container to add scenes to and then "play" the scenes. For example, I'm working on an internal custom tool to factory reset Chromebook devices in an enterprise setting and I thought it'd be neat to have the Fire renderer effect on the screen when the device has been reset, this would require me to switch scenes. This would be fine, but I wouldn't want a fire scene JUST for that one screen; I'd rather make it more generic. For example, I would want to write the scene so that I can call it when factory resetting just the devices profiles AS WELL AS factory resetting completely. I could write two different scenes but that to me seem pretty wasteful (maybe it's my instinct to plan for scaling -- i.e. other screens that may do similar stuff but different effects, etc.)
I think what I'm trying to do is related to dynamic scenes, because even though I'm looking to create effect-scenes that are generically written, it's the active switching (i.e. calling play) to those scenes and then returning back (i.e. getting in the middle of Asciimatic's loop and telling it, return "here" or return "there" when the scene is done -- the dynamic part). I see references to callbacks and I feel like that's kind of what I'm looking for except, I'm not looking to add to the callstack to simulate a return as that would be a memory leak as the callstack would grow and grow as the program is used.
Second question (related to the scene thing only beneath the surface more): Why is Cog an effect, and Fire is just a renderer? I think I was close to getting something to work but there's no register_scene aspect to a renderer and thus I have to place the renderer into something like a Print effect but then that creates other problems.
A real shot in the dark attempt at achieving what I was looking to do (which if you couldn't tell, didn't work lol): I started to think, maybe I need to avoid using Screen.wrapper and just set everything up myself (i.e. using Screen.open and handling the Screen management myself), but that got... weird. Additionally, I think it'd be helpful for the documentation (https://asciimatics.readthedocs.io/en/latest/widgets.html#dynamic-scenes) to have more elaboration and even some samples (using Screen.open and Screen.close and how the program loop would look would also be great to have a sample for -- and on the note of a samples, it'd be good to have the image files in their own directories in the samples folder instead of polluting the code directory with files that aren't code.
Wow! Lots to unpack there...
- There is nothing stopping you from using the same Scene twice for different reasons. If you look at the contact list demo, you'll see one Scene used to add new and edit existing contacts. The trick is in tracking that information in your Model (and triggering the change of Scene as needed).
- I invented Renderers after Effects when I realised that the Print effect was basically all you needed for many use cases. If I was starting from scratch, Cog would probably be a Renderer.
- Sample code already exists for all the widgets code. You may want to look at forms.py or experimental.py.
- You can find sample code for
Screen.open()
here: https://asciimatics.readthedocs.io/en/stable/animation.html?highlight=Asyncio#using-async-frameworks - I'm always happy to merge changes that tidy up the code if you want to submit a PR.