peterbrittain / asciimatics

A cross platform package to do curses-like operations, plus higher level APIs and widgets to create text UIs and ASCII art animations
Apache License 2.0
3.62k stars 237 forks source link

How to update UI from a key event processor? #292

Closed zencd closed 3 years ago

zencd commented 3 years ago

My asciimatics app performs some long lasting operations. I'd like to display "Updating..." before the operation started and "Updated" after all, so a user can be informed what's happening. But I can't manage to get the first title ever displayed - only the second one, and only after all is done. In the example below I use a pause to emulate the operation, but it's all the same with a real code.

How should I force UI to refresh in this case?

import time
from asciimatics.event import KeyboardEvent
from asciimatics.scene import Scene
from asciimatics.screen import Screen
from asciimatics.widgets import Frame, Layout, PopUpDialog, Text

class DemoFrame(Frame):
    def __init__(self, screen):
        super(DemoFrame, self).__init__(
            screen, screen.height, screen.width, has_border=False, name="My Form")
        layout = Layout([1], fill_frame=True)
        self.add_layout(layout)
        self._details = Text()
        self._details.disabled = True
        self._details.custom_colour = "field"
        self._details.value = 'Press a key and wait'
        layout.add_widget(self._details)
        self.fix()

    def process_event(self, event):
        if isinstance(event, KeyboardEvent):
            self._details.value = f'Before {event.key_code}'
            time.sleep(1.0)
            self._details.value = f'After {event.key_code}'
        return super(DemoFrame, self).process_event(event)

def demo(screen, old_scene):
    frame = DemoFrame(screen)
    screen.play([Scene([frame], -1)], stop_on_resize=True, start_scene=old_scene)

Screen.wrapper(demo, catch_interrupt=False, arguments=[None])
peterbrittain commented 3 years ago

Yes - that will be a problem because you are blocking the main thread (which handles both input and updates).

The solution is to move the blocking operation to another thread using the async programming model of your choice. You can see examples of that with the terminal and maps demos.

zencd commented 3 years ago

self.screen.force_update() does the trick. Called from a thread ofc. Thank you!

peterbrittain commented 3 years ago

Great! I assume this is now resolved, so closing.