Files
oai/oai/tui/screens/dialogs.py
2026-02-04 11:22:53 +01:00

237 lines
5.9 KiB
Python

"""Modal dialog screens for oAI TUI."""
from typing import Callable, Optional
from textual.app import ComposeResult
from textual.containers import Container, Horizontal, Vertical
from textual.screen import ModalScreen
from textual.widgets import Button, Input, Label, Static
class ConfirmDialog(ModalScreen[bool]):
"""A confirmation dialog with Yes/No buttons."""
DEFAULT_CSS = """
ConfirmDialog {
align: center middle;
}
ConfirmDialog > Container {
width: 60;
height: auto;
background: #2d2d2d;
border: solid #555555;
padding: 2;
}
ConfirmDialog Label {
width: 100%;
content-align: center middle;
margin-bottom: 2;
color: #cccccc;
}
ConfirmDialog Horizontal {
width: 100%;
height: auto;
align: center middle;
}
ConfirmDialog Button {
margin: 0 1;
}
"""
def __init__(
self,
message: str,
title: str = "Confirm",
yes_label: str = "Yes",
no_label: str = "No",
):
super().__init__()
self.message = message
self.title = title
self.yes_label = yes_label
self.no_label = no_label
def compose(self) -> ComposeResult:
"""Compose the dialog."""
with Container():
yield Static(f"[bold]{self.title}[/]", classes="dialog-title")
yield Label(self.message)
with Horizontal():
yield Button(self.yes_label, id="yes", variant="success")
yield Button(self.no_label, id="no", variant="error")
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button press."""
if event.button.id == "yes":
self.dismiss(True)
else:
self.dismiss(False)
def on_key(self, event) -> None:
"""Handle keyboard shortcuts."""
if event.key == "escape":
self.dismiss(False)
elif event.key == "enter":
self.dismiss(True)
class InputDialog(ModalScreen[Optional[str]]):
"""An input dialog for text entry."""
DEFAULT_CSS = """
InputDialog {
align: center middle;
}
InputDialog > Container {
width: 70;
height: auto;
background: #2d2d2d;
border: solid #555555;
padding: 2;
}
InputDialog Label {
width: 100%;
margin-bottom: 1;
color: #cccccc;
}
InputDialog Input {
width: 100%;
margin-bottom: 2;
background: #3a3a3a;
border: solid #555555;
}
InputDialog Input:focus {
border: solid #888888;
}
InputDialog Horizontal {
width: 100%;
height: auto;
align: center middle;
}
InputDialog Button {
margin: 0 1;
}
"""
def __init__(
self,
message: str,
title: str = "Input",
default: str = "",
placeholder: str = "",
):
super().__init__()
self.message = message
self.title = title
self.default = default
self.placeholder = placeholder
def compose(self) -> ComposeResult:
"""Compose the dialog."""
with Container():
yield Static(f"[bold]{self.title}[/]", classes="dialog-title")
yield Label(self.message)
yield Input(
value=self.default,
placeholder=self.placeholder,
id="input-field"
)
with Horizontal():
yield Button("OK", id="ok", variant="primary")
yield Button("Cancel", id="cancel")
def on_mount(self) -> None:
"""Focus the input field when mounted."""
input_field = self.query_one("#input-field", Input)
input_field.focus()
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button press."""
if event.button.id == "ok":
input_field = self.query_one("#input-field", Input)
self.dismiss(input_field.value)
else:
self.dismiss(None)
def on_input_submitted(self, event: Input.Submitted) -> None:
"""Handle Enter key in input field."""
self.dismiss(event.value)
def on_key(self, event) -> None:
"""Handle keyboard shortcuts."""
if event.key == "escape":
self.dismiss(None)
class AlertDialog(ModalScreen[None]):
"""A simple alert/message dialog."""
DEFAULT_CSS = """
AlertDialog {
align: center middle;
}
AlertDialog > Container {
width: 60;
height: auto;
background: #2d2d2d;
border: solid #555555;
padding: 2;
}
AlertDialog Label {
width: 100%;
content-align: center middle;
margin-bottom: 2;
color: #cccccc;
}
AlertDialog Horizontal {
width: 100%;
height: auto;
align: center middle;
}
"""
def __init__(self, message: str, title: str = "Alert", variant: str = "default"):
super().__init__()
self.message = message
self.title = title
self.variant = variant
def compose(self) -> ComposeResult:
"""Compose the dialog."""
# Choose color based on variant (using design system)
color = "$primary"
if self.variant == "error":
color = "$error"
elif self.variant == "success":
color = "$success"
elif self.variant == "warning":
color = "$warning"
with Container():
yield Static(f"[bold {color}]{self.title}[/]", classes="dialog-title")
yield Label(self.message)
with Horizontal():
yield Button("OK", id="ok", variant="primary")
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handle button press."""
self.dismiss()
def on_key(self, event) -> None:
"""Handle keyboard shortcuts."""
if event.key in ("escape", "enter"):
self.dismiss()