Open lighth7015 opened 4 years ago
This is possible by creating multiple Dialog
objects and wrapping them in a prompt_toolkit.layout.Float
in a prompt_toolkit.layout.FloatContainer
.
Ah! I see!
So, something like this (borrowed from the text editor example)?
class Modal(Float):
""" Modal wrapper for dialogs """
def __init__(self, container: AnyContainer, dialog: Dialog):
parent = super(Modal, self)
parent.__init__(content = dialog)
container.floats.insert(0, self)
self._instance = get_app()
self._float_dlg = dialog
self._container = container
async def open(self):
parent = self._instance.layout.current_window
self._instance.layout.focus(self._float_dlg)
result = await self._float_dlg.future
self._instance.layout.focus(parent)
if self in self._container.floats:
self._container.floats.remove(float_)
return result
(For context, this is what I'm working with)
from prompt_toolkit.application import Application, get_app
from prompt_toolkit.formatted_text import HTML, merge_formatted_text
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout import Dimension, FormattedTextControl, HSplit, Layout, VSplit, Window
from prompt_toolkit.layout.containers import Float, FloatContainer, AnyContainer
from prompt_toolkit.layout.margins import ScrollbarMargin
from prompt_toolkit.styles import Style
from prompt_toolkit.widgets import Box, Button, Checkbox, Dialog, Label, Frame, RadioList, TextArea
from prompt_toolkit.shortcuts import message_dialog
def create_title(text: str, dont_extend_width: bool = False) -> Label:
return Label(text, style="fg:ansiblue", dont_extend_width=dont_extend_width)
def indented(container: AnyContainer, amount: int = 2) -> AnyContainer:
return VSplit([Label("", width=amount), container])
class Modal(Float):
""" Modal wrapper for dialogs """
def __init__(self, container: AnyContainer, dialog: Dialog):
parent = super(Modal, self)
parent.__init__(content = dialog)
container.floats.insert(0, self)
self._instance = get_app()
self._float_dlg = dialog
self._container = container
async def open(self):
parent = self._instance.layout.current_window
self._instance.layout.focus(self._float_dlg)
result = await self._float_dlg.future
self._instance.layout.focus(parent)
if self in self._container.floats:
self._container.floats.remove(float_)
return result
class ListBox(Window):
def __init__(self, entries, **bindings):
self.entries = entries
self.event_bindings = bindings
self.selected_line = 1
parent = super(ListBox, self)
parent.__init__(
content=FormattedTextControl(
text= self._get_formatted_text,
focusable=True,
key_bindings=self._get_key_bindings(),
),
style="class:select-box",
cursorline=True,
right_margins=[
ScrollbarMargin(display_arrows = True),
],
)
def _get_formatted_text(self):
result = []
for i, entry in enumerate(self.entries):
if i == self.selected_line:
result.append([("[SetCursorPosition]", "")])
result.append(entry)
result.append("\n")
return merge_formatted_text(result)
def _get_key_bindings(self):
kb = KeyBindings()
@kb.add("enter")
def handle(event) -> None:
def handle(self, item):
pass
callback = self.event_bindings['selected'] or handle
callback(event)
@kb.add("up")
def handle(event) -> None:
if self.selected_line > 0:
self.selected_line = self.selected_line - 1
@kb.add("down")
def handle(event) -> None:
if self.selected_line < len(self.entries) - 1:
self.selected_line = self.selected_line + 1
return kb
def __pt_container__(self):
return self.container
class MenuDialog(Dialog):
width = 75
items = [
"Change BCM data",
"Delete BCM data",
"List Item 5"
]
def selected(self, item):
pass
def handle_exit(self):
get_app().exit()
def create(self):
self.buttons = [
Button("Save"),
Button("Load"),
Button("Exit", handler=self.handle_exit)
]
return HSplit(
[
Box(
create_title("Giza SDK Manager"),
padding = 0,
),
Box(
create_title("─" * self.width),
padding_top = 0,
padding_left = 0,
padding_bottom = 0,
),
Box(
ListBox(self.items, selected = self.selected),
height = 20,
width = self.width,
padding_top = 0,
padding_left = 0,
padding_right = 0,
padding_bottom = 0,
),
Box(
create_title("┌{0}┐\n"
"│ Use the arrow keys to highlight an option from the list above and press │\n"
"│ the Return/Enter key to confirm it.{1} │\n"
"└{0}┘".format(
"─" * ( self.width - 2 ),
' ' * (self.width - 39),
'─' * ( self.width - 2 ))),
padding = 0
),
]
)
def __init__(self, *items):
parent = super(MenuDialog, self)
parent.__init__(
title="SDK Configuration Manager",
with_background=True,
body=Box(self.create(), padding=0),
buttons = self.buttons)
self.modal = Modal(self, message_dialog(title='Example dialog window', text='You selected an item.'))
style = Style.from_dict({
"dialog.body": "bg:#cccccc",
"dialog.body select-box": "bg:#cccccc",
"dialog.body select-box": "bg:#cccccc",
"dialog.body select-box cursor-line": "nounderline bg:ansired fg:ansiwhite",
"dialog.body text-area": "bg:#4444ff fg:white",
"dialog.body text-area": "bg:#4444ff fg:white",
"dialog.body radio-list radio": "bg:#4444ff fg:white",
"dialog.body button.focused": "bg:#4444ff fg:white",
"dialog.body checkbox-list checkbox": "bg:#4444ff fg:white",
})
kb = KeyBindings()
@kb.add("f6")
def _exit(event):
event.app.exit()
@kb.add("f5")
def _next(event):
# TODO
pass
application: Application[MenuDialog] = \
Application(layout=Layout(MenuDialog()), full_screen=True, erase_when_done = True, mouse_support = True, style=style, key_bindings=kb)
application.run()
Hi @lighth7015, I don't have much time to comment in depth, but my main suggestion is to not use inheritance, but only composition instead.
The __pt_container__
method is actually made to expose the Container
object from a class that doesn't inherit from Widow
, VSplit
or other container classes. If you inherit from a container, there shouldn't be a __pt_container__
.
Further, wrapping something into a Box
only makes sense if you need to add padding. (That's what it's made for.)
Calling get_app
during the creation of the layout doesn't make sense. This returns the running application. At this point the application is not yet running. get_app
is mainly used in the key bindings.
It's fine, I borrowed the example and tried my best to adapt it.
And I only inherited from float because for me it's a lot of extra effort to manage a Float
instance. So why not just be a subclass of it?
In see inheritance in Window
and Dialog
too.
The main reason for not using this is because you don't have any control at all of which variables will be added to these classes in the future in prompt_toolkit itself. If in a future version, prompt_toolkit will add a variable named event_bindings
to Window
, this code will break. You have to make sure that there is not a single collision in attribute variable names between your classes and the classes you're inheriting from in the current version, and you'll have to check it again for every minor version upgrade every time.
So, it's up to you, whatever is the least effort ;) prompt_toolkit is not opinionated about the way it's used.
And both of those stem from my belief that if you're going to use a class (and manage it) internally with a bunch of abstractions, wouldn't it be better to simplify and just have that class as your parent?
And if that causes problems, then I'll adjust codebase until it no loner causes problems :p
So what would the preferred way be? I was just preparing to handle events between my listbox and main dialog, so it would get notified when a menu item has been selected, so I'm not at all married to doing it that way. :p
@jonathanslenders Okay, one more update. I have manged to use the float.py
example to accomplish what I'm after... Sort of.. My new issue is that shadows disappear if I change the dialog color via styles to anything other than what the default is.. How do I fix this?
Is it possible to open multiple dialogs open at once? (E.g. a setup process)?
Thanks again!