request/clear_application + InteractiveSession, displays components in global application + example

- InteractiveSession can be run inside of another session, application will exit only when all components are cleared.
This commit is contained in:
MaXFeeD
2025-04-19 19:16:55 +03:00
parent 8dbf6b3ffe
commit cf8dc1665f
2 changed files with 124 additions and 7 deletions
+2 -2
View File
@@ -128,7 +128,7 @@ def startup() -> None:
Interactable("Today we will finalize setup of your own modding environment. Use arrows and Enter/Space to move through that list in console."), Interactable("Today we will finalize setup of your own modding environment. Use arrows and Enter/Space to move through that list in console."),
Window(height=1) Window(height=1)
] ]
username_editable = Editable("Who are you? ", hint=get_username()) username_editable = Editable("Who are you?", hint=get_username())
contents += [ contents += [
username_editable, username_editable,
Interactable("This username, or alias, will be used when creating a project. Author name identifies you on Inner Core Mods.", style="class:editable.hint"), Interactable("This username, or alias, will be used when creating a project. Author name identifies you on Inner Core Mods.", style="class:editable.hint"),
@@ -141,7 +141,7 @@ def startup() -> None:
Interactable("This will allow your code to be transpiled by TypeScript Compiler to use ESNext's features, but may increase reassembly time.", style="class:editable.hint"), Interactable("This will allow your code to be transpiled by TypeScript Compiler to use ESNext's features, but may increase reassembly time.", style="class:editable.hint"),
Window(height=1) Window(height=1)
] ]
import_editable = Editable("Where should we look for projects? ") import_editable = Editable("Where should we look for projects?")
contents += [ contents += [
import_editable, import_editable,
Interactable("If you have used Inner Core Mod Toolchain earlier, you may choose where to search for projects. Either import an obsolete project or modification for Inner Core.", style="class:editable.hint"), Interactable("If you have used Inner Core Mod Toolchain earlier, you may choose where to search for projects. Either import an obsolete project or modification for Inner Core.", style="class:editable.hint"),
+122 -5
View File
@@ -3,9 +3,9 @@ import platform
from datetime import datetime, timedelta from datetime import datetime, timedelta
from io import StringIO from io import StringIO
from typing import (Any, Callable, Dict, List, Literal, NoReturn, Optional, from typing import (Any, Callable, Dict, List, Literal, NoReturn, Optional,
Union, overload) TypeVar, Union, overload)
from prompt_toolkit import print_formatted_text from prompt_toolkit import Application, print_formatted_text
from prompt_toolkit.buffer import (Buffer, BufferAcceptHandler, from prompt_toolkit.buffer import (Buffer, BufferAcceptHandler,
BufferEventHandler) BufferEventHandler)
from prompt_toolkit.document import Document from prompt_toolkit.document import Document
@@ -17,11 +17,16 @@ from prompt_toolkit.formatted_text import (AnyFormattedText,
to_formatted_text) to_formatted_text)
from prompt_toolkit.key_binding import (KeyBindings, KeyBindingsBase, from prompt_toolkit.key_binding import (KeyBindings, KeyBindingsBase,
KeyPressEvent, merge_key_bindings) KeyPressEvent, merge_key_bindings)
from prompt_toolkit.key_binding.bindings.focus import (focus_next,
focus_previous)
from prompt_toolkit.keys import Keys from prompt_toolkit.keys import Keys
from prompt_toolkit.layout import (BufferControl, ConditionalMargin, Container, from prompt_toolkit.layout import (AnyContainer, BufferControl,
Dimension, FormattedTextControl, Margin, ConditionalMargin, Container, Dimension,
FormattedTextControl, HSplit, Layout,
Margin, ScrollablePane, ScrollOffsets,
SearchBufferControl, UIContent, UIControl, SearchBufferControl, UIContent, UIControl,
Window, WindowAlign, WindowRenderInfo) Window, WindowAlign, WindowRenderInfo,
to_container)
from prompt_toolkit.layout.processors import (AfterInput, BeforeInput, from prompt_toolkit.layout.processors import (AfterInput, BeforeInput,
ConditionalProcessor, Processor) ConditionalProcessor, Processor)
from prompt_toolkit.lexers import Lexer from prompt_toolkit.lexers import Lexer
@@ -104,6 +109,93 @@ else:
UNICODE_SNOWFLAKE = "*" UNICODE_SNOWFLAKE = "*"
UNICODE_INTERMEDIATE_PROGRESS = ["▀", "▄"] UNICODE_INTERMEDIATE_PROGRESS = ["▀", "▄"]
interactive_application: Optional[Application] = None
def request_application(*content: Optional[AnyContainer]) -> Application:
global interactive_application
if interactive_application is not None:
if interactive_application.is_running:
if len(content) != 0:
container = interactive_application.layout.container
while hasattr(container, "content"):
container = container.content # type: ignore
assert hasattr(container, "children")
children: List[Container] = container.children # type: ignore
for control in content:
if control and not control in children:
children.append(to_container(control))
return interactive_application
interactive_application = None
bindings = KeyBindings()
bindings.add(Keys.Down)(focus_next)
bindings.add(Keys.Up)(focus_previous)
@bindings.add("c-c")
@bindings.add("<sigint>")
def _(event: KeyPressEvent) -> None:
event.app.exit(exception=KeyboardInterrupt())
layout = Layout(
ScrollablePane(
HSplit([
to_container(control) for control in content if control
]),
scroll_offsets=ScrollOffsets(3, 3),
display_arrows=False,
)
)
interactive_application = Application(
layout=layout,
style=get_toolchain_style(),
include_default_pygments_style=False,
key_bindings=bindings,
full_screen=False,
mouse_support=True,
erase_when_done=True,
)
return interactive_application
def clear_application(*content: Optional[AnyContainer], force_exit: bool = False) -> None:
global interactive_application
if interactive_application is None:
return
if interactive_application.is_running and len(content) != 0:
container = interactive_application.layout.container
while hasattr(container, "content"):
container = container.content # type: ignore
assert hasattr(container, "children")
children: List[Container] = container.children # type: ignore
for control in content:
if control and not control in children:
children.remove(to_container(control))
if len(children) == 0:
interactive_application.exit()
if not interactive_application.is_running:
interactive_application = None
_SCT = TypeVar("_SCT", bound=Optional[AnyContainer])
class InteractiveSession(dict[str, _SCT]):
application: Application
def __init__(self, **content: _SCT) -> None:
dict.__init__(self, **content)
def __enter__(self, *args, **kwargs) -> 'InteractiveSession[_SCT]':
self.application = request_application(*self.values())
from threading import Thread
self.thread = Thread(target=lambda: self.application.run())
self.thread.start()
return self
def __exit__(self, *args, **kwargs) -> None:
clear_application(*self.values())
if hasattr(self, "thread"):
self.thread.join()
del self.thread
class InteractableMargin(Margin): class InteractableMargin(Margin):
def __init__( def __init__(
self, self,
@@ -775,3 +867,28 @@ def abort(*values: object, sep: Optional[str] = " ", code: int = 255, cause: Opt
except IOError: except IOError:
pass pass
exit(code) exit(code)
if __name__ == "__main__":
preparing = Progress(intermediate=True)
progress = Progress(intermediate=True)
progress2 = Progress("I'm abobus", percentage=100.0)
application = request_application(preparing, progress, progress2)
import asyncio
async def in_coroutine():
async def update_progress():
while progress.percentage < 100:
progress.update(progress.percentage + 1.5)
progress2.update(progress.percentage - 1.5)
await asyncio.sleep(0.1)
application.exit()
application.create_background_task(update_progress())
await application.run_async()
asyncio.run(in_coroutine())
clear_application(preparing, progress, progress2)
import time
with InteractiveSession(preparing=Progress(intermediate=True), progress=Progress("I'm aboba"), progress2=Progress("I'm abobus", percentage=100.0)) as session:
while session["progress"].percentage < 100:
session["progress"].update(session["progress"].percentage + 1.5)
session["progress2"].update(session["progress2"].percentage - 1.5)
time.sleep(0.1)