ui: redraw screens after moving from one screen to another

Wieh urwid > 2.1.2, the screen is only redrawn after urwid handles an
event (e.g., keypress, resize, ...) or an urwid alarm (it is like a
timer).

All other changes made to the UI do not trigger a screen redraw. We now
redraw the screen properly after moving from one screen to another.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
Olivier Gayot 2024-03-18 13:39:50 +01:00
parent eca86c58da
commit 020dcc0b88
27 changed files with 96 additions and 65 deletions

View File

@ -270,10 +270,10 @@ class ExampleController(SubiquityTuiController):
return ExampleView(self, thing)
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()
def done(self, thing):
self.app.next_screen(self.endpoint.POST(thing))
self.app.request_next_screen(self.endpoint.POST(thing))
```
Setting `endpoint_name` means that self.client gets set to an implementation of

View File

@ -70,7 +70,7 @@ class RecoveryChooserController(RecoveryChooserBaseController):
def select(self, system, action):
self.model.select(system, action)
self.app.next_screen()
self.app.request_next_screen()
def more_options(self):
self._current_view = self._all_view
@ -96,4 +96,4 @@ class RecoveryChooserConfirmController(RecoveryChooserBaseController):
def back(self):
self.model.unselect()
self.app.prev_screen()
self.app.request_prev_screen()

View File

@ -74,7 +74,7 @@ class TestChooserConfirmController(unittest.TestCase):
c.back()
app.respond.assert_not_called()
app.exit.assert_not_called()
app.prev_screen.assert_called()
app.request_prev_screen.assert_called()
c.model.unselect.assert_called()
def test_confirm(self):
@ -106,7 +106,7 @@ class TestChooserController(unittest.TestCase):
)
self.assertEqual(c.model.selection, exp)
app.next_screen.assert_called()
app.request_next_screen.assert_called()
app.respond.assert_not_called()
app.exit.assert_not_called()

View File

@ -24,7 +24,7 @@ class WelcomeController(TuiController):
return self.welcome_view(self)
def done(self):
self.app.next_screen()
self.app.request_next_screen()
def cancel(self):
# Can't go back from here!

View File

@ -483,7 +483,7 @@ class SubiquityClient(TuiApplication):
self._remove_last_screen()
super().exit()
def select_initial_screen(self):
async def select_initial_screen(self):
last_screen = None
if self.updated:
state_path = self.state_path("last-screen")
@ -495,7 +495,7 @@ class SubiquityClient(TuiApplication):
for i, controller in enumerate(self.controllers.instances):
if controller.name == last_screen:
index = i
run_bg_task(self._select_initial_screen(index))
await self._select_initial_screen(index)
async def _select_initial_screen(self, index):
endpoint_names = []
@ -507,7 +507,7 @@ class SubiquityClient(TuiApplication):
if self.variant:
await self.client.meta.client_variant.POST(self.variant)
self.controllers.index = index - 1
self.next_screen()
await self.next_screen()
async def move_screen(self, increment, coro):
try:

View File

@ -62,8 +62,10 @@ class DriversController(SubiquityTuiController):
click(view.form.done_btn.base_widget)
def cancel(self) -> None:
self.app.prev_screen()
self.app.request_prev_screen()
def done(self, install: bool) -> None:
log.debug("DriversController.done next_screen install=%s", install)
self.app.next_screen(self.endpoint.POST(DriversPayload(install=install)))
self.app.request_next_screen(
self.endpoint.POST(DriversPayload(install=install))
)

View File

@ -260,7 +260,7 @@ class FilesystemController(SubiquityTuiController, FilesystemManipulator):
async def _guided_choice(self, choice: GuidedChoiceV2):
coro = self.endpoint.guided.POST(choice)
if not choice.capability.supports_manual_customization():
self.app.next_screen(coro)
await self.app.next_screen(coro)
return
status = await self.app.wait_with_progress(coro)
self.model = FilesystemModel(status.bootloader, root="/")
@ -294,11 +294,11 @@ class FilesystemController(SubiquityTuiController, FilesystemManipulator):
self.ui.set_body(FilesystemView(self.model, self))
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()
def finish(self):
log.debug("FilesystemController.finish next_screen")
self.app.next_screen(
self.app.request_next_screen(
self.endpoint.POST(
self.model._render_actions(mode=ActionRenderMode.FOR_API_CLIENT)
)

View File

@ -43,11 +43,11 @@ class IdentityController(SubiquityTuiController):
self.done(identity)
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()
def done(self, identity_data):
log.debug("IdentityController.done next_screen user_spec=%s", identity_data)
self.app.next_screen(self.endpoint.POST(identity_data))
self.app.request_next_screen(self.endpoint.POST(identity_data))
async def validate_username(self, username):
return await self.endpoint.validate_username.GET(username)

View File

@ -49,7 +49,7 @@ class KeyboardController(SubiquityTuiController):
await self.endpoint.POST(setting)
def done(self):
self.app.next_screen()
self.app.request_next_screen()
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()

View File

@ -67,8 +67,8 @@ class MirrorController(SubiquityTuiController):
self.app.ui.body.form._click_done(None)
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()
def done(self, mirror):
log.debug("MirrorController.done next_screen mirror=%s", mirror)
self.app.next_screen(self.endpoint.POST(MirrorPost(elected=mirror)))
self.app.request_next_screen(self.endpoint.POST(MirrorPost(elected=mirror)))

View File

@ -118,10 +118,10 @@ class NetworkController(SubiquityTuiController, NetworkAnswersMixin):
run_bg_task(self.unsubscribe())
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()
def done(self):
self.app.next_screen(self.endpoint.POST())
self.app.request_next_screen(self.endpoint.POST())
def set_static_config(
self, dev_name: str, ip_version: int, static_config: StaticConfig

View File

@ -33,8 +33,8 @@ class ProxyController(SubiquityTuiController):
self.done(self.answers["proxy"])
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()
def done(self, proxy):
log.debug("ProxyController.done next_screen proxy=%s", proxy)
self.app.next_screen(self.endpoint.POST(proxy))
self.app.request_next_screen(self.endpoint.POST(proxy))

View File

@ -77,7 +77,7 @@ class RefreshController(SubiquityTuiController):
def done(self, sender=None):
log.debug("RefreshController.done next_screen")
self.app.next_screen()
self.app.request_next_screen()
def cancel(self, sender=None):
self.app.prev_screen()
self.app.request_prev_screen()

View File

@ -39,7 +39,7 @@ class SerialController(SubiquityTuiController):
def done(self, rich):
log.debug("SerialController.done rich %s next_screen", rich)
self.app.set_rich(rich)
self.app.next_screen()
self.app.request_next_screen()
def cancel(self):
# Can't go back from here!

View File

@ -47,10 +47,10 @@ class SnapListController(SubiquityTuiController):
def done(self, selections: List[SnapSelection]):
log.debug("SnapListController.done next_screen snaps_to_install=%s", selections)
self.app.next_screen(self.endpoint.POST(selections))
self.app.request_next_screen(self.endpoint.POST(selections))
def cancel(self, sender=None):
self.app.prev_screen()
self.app.request_prev_screen()
async def get_list_wait(self):
return await self.endpoint.GET(wait=True)

View File

@ -43,7 +43,7 @@ class SourceController(SubiquityTuiController):
form._click_done(None)
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()
def done(self, source_id, search_drivers: bool):
log.debug(
@ -51,4 +51,4 @@ class SourceController(SubiquityTuiController):
source_id,
search_drivers,
)
self.app.next_screen(self.endpoint.POST(source_id, search_drivers))
self.app.request_next_screen(self.endpoint.POST(source_id, search_drivers))

View File

@ -84,7 +84,7 @@ class SSHController(SubiquityTuiController):
form._click_done(None)
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()
def _fetch_cancel(self):
if self._fetch_task is None:
@ -123,4 +123,4 @@ class SSHController(SubiquityTuiController):
def done(self, result):
log.debug("SSHController.done next_screen result=%s", result)
self.app.next_screen(self.endpoint.POST(result))
self.app.request_next_screen(self.endpoint.POST(result))

View File

@ -216,13 +216,13 @@ class UbuntuProController(SubiquityTuiController):
schedule_task(inner())
def cancel(self) -> None:
self.app.prev_screen()
self.app.request_prev_screen()
def done(self, token: str) -> None:
"""Submit the token and move on to the next screen."""
self.app.next_screen(self.endpoint.POST(UbuntuProInfo(token=token)))
self.app.request_next_screen(self.endpoint.POST(UbuntuProInfo(token=token)))
def next_screen(self) -> None:
"""Move on to the next screen. Assume the token should not be
submitted (or has already been submitted)."""
self.app.next_screen()
self.app.request_next_screen()

View File

@ -48,10 +48,10 @@ class WelcomeController(SubiquityTuiController):
log.debug("WelcomeController.done %s next_screen", code)
i18n.switch_language(code)
self.app.native_language = display_name
self.app.next_screen(self.endpoint.POST(code))
self.app.request_next_screen(self.endpoint.POST(code))
def cancel(self, sender=None):
if not self.serial:
# Can't go back from here unless we're on serial!
pass
self.app.prev_screen()
self.app.request_prev_screen()

View File

@ -33,11 +33,11 @@ class ZdevController(SubiquityTuiController):
self.done()
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()
def done(self):
# switch to next screen
self.app.next_screen()
self.app.request_next_screen()
async def chzdev(self, action, zdevinfo):
return await self.endpoint.chzdev.POST(action, zdevinfo)

View File

@ -296,7 +296,7 @@ class InstallConfirmation(Stretchy):
if self.app.controllers.Progress.showing:
run_bg_task(self.app.confirm_install())
else:
self.app.next_screen(self.app.confirm_install())
self.app.request_next_screen(self.app.confirm_install())
def cancel(self, sender):
self.app.remove_global_overlay(self)

View File

@ -663,7 +663,7 @@ class NetworkController(BaseNetworkController, TuiController, NetworkAnswersMixi
def done(self):
log.debug("NetworkController.done next_screen")
self.model.has_network = self.network_event_receiver.has_default_route
self.app.next_screen()
self.app.request_next_screen()
def cancel(self):
self.app.prev_screen()

View File

@ -41,8 +41,8 @@ def make_app(model=None):
app.context = Context.new(app)
app.exit = mock.Mock()
app.respond = mock.Mock()
app.next_screen = mock.Mock()
app.prev_screen = mock.Mock()
app.request_next_screen = mock.Mock()
app.request_prev_screen = mock.Mock()
app.hub = MessageHub()
app.opts = mock.Mock()
app.opts.dry_run = True

View File

@ -156,7 +156,7 @@ class TuiApplication(Application):
self.ui.block_input = False
nonlocal min_show_task
min_show_task = asyncio.create_task(asyncio.sleep(MIN_SHOW_PROGRESS_TIME))
show()
await show()
self.ui.block_input = True
show_task = asyncio.create_task(_show())
@ -166,7 +166,7 @@ class TuiApplication(Application):
if min_show_task:
await min_show_task
if hide is not None:
hide()
await hide()
else:
self.ui.block_input = False
show_task.cancel()
@ -191,18 +191,24 @@ class TuiApplication(Application):
else:
task_to_cancel = None
def show_load():
async def show_load():
nonlocal ld
ld = LoadingDialog(self.ui.body, message, task_to_cancel)
self.ui.body.show_overlay(ld, width=ld.width)
await self.redraw_screen()
def hide_load():
async def hide_load():
ld.close()
await self.redraw_screen()
return await self._wait_with_indication(awaitable, show_load, hide_load)
async def wait_with_progress(self, awaitable):
return await self._wait_with_indication(awaitable, self.show_progress)
async def show_progress():
self.show_progress()
await self.redraw_screen()
return await self._wait_with_indication(awaitable, show_progress)
async def _move_screen(
self, increment, coro
@ -243,14 +249,36 @@ class TuiApplication(Application):
view = view_or_callable
self.ui.set_body(view)
def next_screen(self, coro=None):
run_bg_task(self.move_screen(1, coro))
async def redraw_screen(self):
self.urwid_loop.draw_screen()
def prev_screen(self):
run_bg_task(self.move_screen(-1, None))
async def next_screen(self, coro=None):
await self.move_screen(1, coro)
def select_initial_screen(self):
self.next_screen()
async def prev_screen(self):
await self.move_screen(-1, None)
async def select_initial_screen(self):
await self.next_screen()
def request_next_screen(self, coro=None, *, redraw=True):
async def next_screen():
await self.next_screen(coro)
if redraw:
await self.redraw_screen()
run_bg_task(next_screen())
def request_prev_screen(self, *, redraw=True):
async def prev_screen():
await self.prev_screen()
if redraw:
await self.redraw_screen()
run_bg_task(prev_screen())
def request_screen_redraw(self):
run_bg_task(self.redraw_screen())
def set_rich(self, rich):
if rich == self.rich_mode:
@ -321,7 +349,7 @@ class TuiApplication(Application):
# By default, basic on serial - rich otherwise.
return not self.opts.run_on_serial
def start_urwid(self, input=None, output=None):
async def start_urwid(self, input=None, output=None):
# This stops the tcsetpgrp call in run_command_in_foreground from
# suspending us. See the rant there for more details.
signal.signal(signal.SIGTTOU, signal.SIG_IGN)
@ -339,12 +367,13 @@ class TuiApplication(Application):
extend_dec_special_charmap()
self.set_rich(self.get_initial_rich_mode())
self.urwid_loop.start()
self.select_initial_screen()
await self.select_initial_screen()
await self.redraw_screen()
async def start(self, start_urwid=True):
await super().start()
if start_urwid:
self.start_urwid()
await self.start_urwid()
async def run(self):
try:

View File

@ -47,7 +47,7 @@ class WSLConfigurationAdvancedController(SubiquityTuiController):
"WSLConfigurationAdvancedController.done next_screen user_spec=%s",
reconf_data,
)
self.app.next_screen(self.endpoint.POST(reconf_data))
self.app.request_next_screen(self.endpoint.POST(reconf_data))
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()

View File

@ -32,7 +32,7 @@ class WSLConfigurationBaseController(SubiquityTuiController):
"WSLConfigurationBaseController.done next_screen user_spec=%s",
configuration_data,
)
self.app.next_screen(self.endpoint.POST(configuration_data))
self.app.request_next_screen(self.endpoint.POST(configuration_data))
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()

View File

@ -41,7 +41,7 @@ class WSLSetupOptionsController(SubiquityTuiController):
"WSLSetupOptionsController.done next_screen user_spec=%s",
configuration_data,
)
self.app.next_screen(self.endpoint.POST(configuration_data))
self.app.request_next_screen(self.endpoint.POST(configuration_data))
def cancel(self):
self.app.prev_screen()
self.app.request_prev_screen()