-
Notifications
You must be signed in to change notification settings - Fork 242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expanded Documentation and Examples for handling user input #343
Comments
Ok... Let's work through it hereband then I'll update the docs... Asciimatics uses raw input with no delay from the terminal (assuming you're familiar with curses). This means when you call get_event(), it will either return you a key (or mouse event) if there is one queued, or nothing if there isn't. The wait_for_input() function simply waits for any mouse or key input for the time specified, returning early if there is anything, or delaying for the specified time otherwise. I use it to get a more responsive UI for the widgets. So, you just need to call get_event() whenever you want to process any queued input and wait_for_input() when you have nothing else to do. If you look in Screen.play(), you'll see that this is how it handles each frame... Check for any events, process them, update the screen and then wait until the next frame is due (or some input comes in if you allowed that option). Asciimatics' high-level objects are more like an asynchronous API, unlike the curses TextBox API. Each component in the widgets/effects/sprites/etc fits into that same basic input/output loop, doing stuff right now as prompted by the current state, or returning nothing because it is not the right time/state. The closest you'll get to a synchronous call like TextBox is to create a Scene, add a Frame, play it, wait for play to end and then check the data in the Frame. Any clearer? |
Thanks for the response! I didn't expect one so quick.
Initially for some reason, I thought it actually returned the input it received, essentially waiting for user to type something then hit enter. I'm not sure why I had it that way in my mind.
I'm a bit confused here. As far as I understand,
I guess what's really throwing me off is the
I don't quite follow. I've at least gotten as far as creating a scene and playing it, but waiting for play to end and checking the data is where I get lost. I think there are some fundamentals about scenes and play that I have overlooked or misunderstood. At the moment when I use Side note, if I do My brain is in swirls from trying to sort things out today, so I'm having a hard time testing new things at the moment. I'm starting to think that asciimatics is too over my head and/or may not be right for the project. I realize from your response that handling input is not the only thing I'm running into roadblocks with, it just seemed like that was the issue. I'm going to try and read more of the docs tomorrow regarding scenes and screens. Do you have any examples, or know of any projects I could look into, that use |
You're confusing the low level (input/output) API with the high level (object based) API. If you have already created a Frame with a Text widget, that widget will handle all the low level logic to string text together, output it in the right location and store off the full entered text as the value on the widget. You just need to access the value at the time you want it. |
To get a better idea of various ways to check Frame.data and Widget.value, take a look at the contact_list.py demo. If you want to have a simple form that returns on pressing enter, you need to create your Scene with suitable Widgets laid out in a Frame, then add a global handler (https://asciimatics.readthedocs.io/en/latest/widgets.html#global-key-handling) to stop the animation on receiving enter. You can then just play this Scene and look at Frame.data when play() returns. |
@peterbrittain Thanks again for being willing and taking some time out of your day to help me sort this out. I was getting discouraged last night, mainly because I'm self-taught and I still think there are some fundamentals in python I'm missing that makes understanding asciimatics a little more difficult. I really want to use it, though, so I'm gonna keep trying and learning.
I suppose I was, but it came from how I understand Just curious as I'm still confused on that point (and in the spirit of the thread trying to clear things up), but I think that using the Text widget and then getting the stored value is ultimately the right way for me to go in this project.
From this explanation it sounds like the Text widget can work like I expected it to initially. I see the code in
I think this part of the docs is something I kept overlooking, but it answers a curiosity I had last night regarding the I believe that part of what I'm doing wrong is 1) misunderstanding the Frames effect and 2) not treating Scenes like an ordered list of frames in an animation, but rather just a list of windows to display onscreen. Regarding Frames, I have been operating under the impression that Frames are the equivalent to curses Windows or Sub Windows (description here makes me think this). Do you intend the Frame effect to be a window analogue, or is it intended to represent a single frame in an animation? I see the word "frames" used many times in the docs related to animation, but because of the explanation and the effect being named that same thing, I'm getting the two confused. Unless it is intended to be like frames in an animation, and not a Window analogue. Perhaps this is the real reason I'm all turned around. I have 5 frames that get drawn onscreen at the same time right now (because I have 5 distinct sections of data to display to the user). Is this where I'm going wrong? Maybe it was intended that only 1 frame be onscreen at a given time? Or perhaps there's a way to run 5 simultaneous animations, each affecting their own Frame? I can condense all my display down to a single frame, but the look of the full borders is so much better than the dividers (due to connecting, solid lines), I can have padding between the sections, and can set color palettes separately between each frame. Maybe sending you the curses version of my game would help clear up what exactly I'm trying to do, and might allow you to see where/how I'm approaching asciimatics incorrectly. I don't want to bog you down, though, you're already helping me way more than I expected and probably have a job/family to pay attention to. I've actually got a couple other questions but somewhat unrelated to the topic of this thread, may be real feature requests. Would you rather I just post them here first, or go ahead and make separate threads? |
I kind of have something a little more coherent working, but there's still a disconnect I'm having somewhere. I can't seem to get my function to trigger when I hit ENTER using the example in the docs. Even if I click and change focus outside of the Text widget. EDIT: The code below checks for 10, 13, or 343 Side note: I can't seem to get my static titles to display if I use Here's the code I'm running. Maybe you can point out any glaring issues with how I've put it together. click to expand ascii_test.py
from time import sleep
from asciimatics.widgets import Frame, Layout, Divider, VerticalDivider, Text
from asciimatics.scene import Scene
from asciimatics.screen import Screen
from asciimatics.effects import Print, Background
from asciimatics.renderers import StaticRenderer
from asciimatics.event import KeyboardEvent, MouseEvent
from asciimatics.exceptions import StopApplication
ui_x = 110
ui_y = 40
custom_palette = {
"background": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"borders": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"button": ( Screen.COLOUR_BLACK, Screen.A_NORMAL, Screen.COLOUR_WHITE ),
"control": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"disabled": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"edit_text": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"field": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"focus_button": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"focus_control": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"focus_edit_text": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"focus_field": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"invalid": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"label": ( Screen.COLOUR_RED, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"scroll": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"selected_control": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"selected_field": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"selected_focus_control": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"selected_focus_field": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK ),
"title": ( Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK )
}
player_command = None
def get_player_command(event):
global player_command
if isinstance(event, KeyboardEvent):
c = event.key_code
if c in (10, 13, 343):
player_command = command.value
raise StopApplication("Player entered a command")
class eventsFrame(Frame):
def __init__(self, screen):
super(eventsFrame, self).__init__( screen,
dimensions["events"]["height"],
dimensions["events"]["width"],
has_border=True,
name="events",
x=dimensions["events"]["x"],
y=dimensions["events"]["y"],
reduce_cpu=True,
can_scroll=False)
event_layout = Layout([100])
self.add_layout(event_layout)
self.palette = custom_palette
event_layout.add_widget(Divider(draw_line=False), 0)
event_layout.add_widget(Divider(), 0)
self.fix()
class groundInfoFrame(Frame):
def __init__(self, screen):
super(groundInfoFrame, self).__init__( screen,
dimensions["ground_info"]["height"],
dimensions["ground_info"]["width"],
has_border=True,
name="ground_info",
x=dimensions["ground_info"]["x"],
y=dimensions["ground_info"]["y"],
reduce_cpu=True,
can_scroll=False)
ground_info_layout = Layout([49, 1, 30])
self.add_layout(ground_info_layout)
self.palette = custom_palette
ground_info_layout.add_widget(VerticalDivider(height=10), 1)
self.fix()
class characterFrame(Frame):
def __init__(self, screen):
super(characterFrame, self).__init__( screen,
dimensions["character"]["height"],
dimensions["character"]["width"],
has_border=True,
name="character",
x=dimensions["character"]["x"],
y=dimensions["character"]["y"],
reduce_cpu=True,
can_scroll=False)
character_layout = Layout([5, 28, 7])
self.add_layout(character_layout)
self.palette = custom_palette
self.fix()
class inputFrame(Frame):
def __init__(self, screen):
global command
super(inputFrame, self).__init__( screen,
dimensions["input"]["height"],
dimensions["input"]["width"],
has_border=True,
name="input",
x=dimensions["input"]["x"],
y=dimensions["input"]["y"],
reduce_cpu=True,
can_scroll=False)
input_layout = Layout([2, 77])
self.add_layout(input_layout)
self.palette = custom_palette
command = Text(label="\m/:", name="command", max_length=75)
input_layout.add_widget(command, 1)
command.focus()
self.fix()
def main(screen):
global dimensions
begin_x = (screen.width - ui_x) // 2
begin_y = (screen.height - ui_y) // 2
dimensions = {
"events": {
"width": 79,
"height": 27,
"x": begin_x,
"y": begin_y
},
"ground_info": {
"width": 79,
"height": 10,
"x": begin_x,
"y": begin_y + 27
},
"input": {
"width": 79,
"height": 3,
"x": begin_x,
"y": begin_y + 37
},
"character": {
"width": 30,
"height": 40,
"x": begin_x + 80,
"y": begin_y
}
}
screen.play( [
Scene( [
Background(screen),
eventsFrame(screen),
groundInfoFrame(screen),
characterFrame(screen),
inputFrame(screen),
Print(screen, StaticRenderer(images=[" TED MOONCHILD "]), dimensions["character"]["y"], x=(dimensions["character"]["x"] + 2), speed=0, transparent=False),
Print(screen, StaticRenderer(images=["{0: ^28}".format("EQUIPPED")]), (dimensions["character"]["y"] + 14), x=(dimensions["character"]["x"] + 1), colour=Screen.COLOUR_BLACK, bg=Screen.COLOUR_CYAN, speed=0, transparent=False),
Print(screen, StaticRenderer(images=["{0: ^28}".format("INVENTORY")]), (dimensions["character"]["y"] + 20), x=(dimensions["character"]["x"] + 1), colour=Screen.COLOUR_BLACK, bg=Screen.COLOUR_WHITE, speed=0, transparent=False),
Print(screen, StaticRenderer(images=["{0: >5} {1: <14} {2: <7}".format("#", "ITEM", "EFFECT")]), (dimensions["character"]["y"] + 21), x=(dimensions["character"]["x"] + 1), colour=Screen.COLOUR_BLACK, bg=Screen.COLOUR_WHITE, speed=0, transparent=False),
Print(screen, StaticRenderer(images=[" ITEMS ON GROUND "]), dimensions["ground_info"]["y"], x=(dimensions["ground_info"]["x"] + 2), speed=0, transparent=False),
Print(screen, StaticRenderer(images=[" AVAILABLE EXITS "]), dimensions["ground_info"]["y"], x=(dimensions["ground_info"]["x"] + 60), speed=0, transparent=False)
], -1) ],
unhandled_input=get_player_command,
allow_int=True)
if player_command != None:
screen.print_at("You entered {0} into the field".format(player_command), (dimensions["events"]["x"] + 1), (dimensions["events"]["y"] + 1))
screen.refresh()
if __name__ == "__main__":
while True:
Screen.wrapper(main) |
Going through your points/questions...
|
You might also see undesirable things as you click the mouse around the Screen - e.g. losing focus on your input Frame, bringing your other Frames in front of your printed Effects. Net is that I would recommend your UI design to treat each Frame as if it was a separate Window in a GUI desktop app. If that doesn't give you the sort of input and navigation you need, I suspect you should consider a custom Effect to handle your game output and overlay a single Frame over the top for input handling. This is similar to what I did for the ray casting demo, so that should give you some pointers on how to create your new Effect. |
The MiniMap class is a nice simple example of how to create your new Effect. You could go very modular and create your own custom display logic for each one (using a Canvas for each so that they are truly independent UI elements), or push it all into one Effect that uses a single Canvas (or just direct to the Screen if so desired). |
If you have other questions, why not carry on on in gitter (https://gitter.im/asciimatics/Lobby)? |
Thanks! I have a good bit to mull over here. I may join the chat at some point after I digest. I treat my UI sections as separate windows in curses, now, so that's definitely the approach I want to continue with.
What do you recommend for printing static items, like headers and such? My sample code is applying the Print effects to the Scene at the moment. Perhaps digging into the MiniMap and Ray Casting demos will answer this question. |
The problem with (or great benefit of) Frames is that they try to act like Windows (in the GUI sense), so each is aware of its position in the Z order and whether it has the focus or not. They will coordinate with each other in ways that you might not intend - e.g. moving input focus away from your text line. If your current curses program has one global input loop and lots of subwindows (in the curses sense), you may well be better off using multiple Canvases (inside one or more Effects) and just one Frame. That way your input focus will always be in the one Frame and it will handle events as expected. It also gives you a single location to do any custom input handling for your game. I had to use my Scene to do that in the ray casting demo... which is another way to do it, but may be getting over complicated for your UI. For static content inside a Frame, see https://asciimatics.readthedocs.io/en/stable/widgets.html#disabling-widgets. If you are using Effects, layering of Print Effects is fine, though may be overcomplicating it. It's possible you just want a new custom Effect to draw your new content directly. The MiniMap class in the ray caster demo should show you what I mean... |
Just going through the questions here to see if docs need updating...
Is there anything else that would have helped you get up to speed faster on how to use asciimatics? |
Hey Peter, sorry for the silence. I got deep into a few things and got side-tracked. I think your bullet points sum it up pretty well. The only thing I can think of that may have really helped (besides me reading a few more times) is adding one additional sample that uses multiple frames onscreen at the same time, or at least explaining what you mentioned about gobbling keys. Especially the bit about "no unhandled input" in this scenario:
You can go ahead and close this if you'd like. Thanks for your help so far, I'm sure once I'm able to clear my head and try asciimatics again I'll be in gitter asking questions. Cheers! |
Is your feature request related to a problem? Please describe.
Full disclosure, I'm not a python power user, so I may well be missing something obvious with python itself.
I'm building a video game, and have the engine mostly done in curses. I wanted to add fancy animations, so I started trying to port things over to asciimatics. However, I'm finding it hard to follow the docs in some places. For a specific example, I'm finding it hard to sort out how to use
wait_for_input()
in conjunction withget_event()
as outlined in the docs, but there are no examples to pull from. It's not clear to me how I should even begin to get a user's input and then process it using these two methods.I can see the timeout of
wait_for_input()
is working in my script, but when I press any character the app just exits immediately. From the explanation in the docs, it made it seem as though I could just keep typing and the app would block execution until something is entered (presumably just hitting enter, but the docs aren't clear here either). I don't think this is a bug, but rather my misunderstanding of how it is supposed to function.The one example I've tried to follow that sort of has what I'm looking for is
forms.py
, but I don't see that there is anyget_event()
orwait_for_input()
calls. None of the examples I've dug through seem to have these calls in them.Describe the solution you'd like
Some example of using
wait_for_input()
andget_event()
, and expand on the docs here:https://asciimatics.readthedocs.io/en/stable/io.html#input
Would it be possible, at some point, to get an example that allows the user to input a word or short phrase in one frame, then display that entered text as
FigletText
in its own, separate frame?Additional context
In my particular application, I've got several frames on-screen where one of those frames is a single-line input. Like a text adventure game, I want to have the user input a simple command then process it to change the db and then update the display with new info.
I've been able to sort out a lot of other details from the docs, but the user input handling is one where I am a little lost. In curses, I was able to do what I needed to do with the
textpad.Textbox
object with relative ease. I thought that I might be able to use asciimatics'sText
widget, but so far have been unsuccessful.EDIT:
I'm currently running python 3.10 with asciimatics v1.13.0, and I am being careful to dig through examples in the 1.13.0 tag.
The text was updated successfully, but these errors were encountered: