mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Simple multiline text widget to help model text base class
This commit is contained in:
26
poetry.lock
generated
26
poetry.lock
generated
@@ -277,7 +277,7 @@ i18n = ["Babel (>=2.7)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown"
|
name = "markdown"
|
||||||
version = "3.3.6"
|
version = "3.3.7"
|
||||||
description = "Python implementation of Markdown."
|
description = "Python implementation of Markdown."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -472,7 +472,7 @@ testing = ["pytest", "pytest-benchmark"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pre-commit"
|
name = "pre-commit"
|
||||||
version = "2.18.1"
|
version = "2.19.0"
|
||||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -516,7 +516,7 @@ markdown = ">=3.2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyparsing"
|
name = "pyparsing"
|
||||||
version = "3.0.8"
|
version = "3.0.9"
|
||||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -642,7 +642,7 @@ pyyaml = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rich"
|
name = "rich"
|
||||||
version = "12.3.0"
|
version = "12.4.1"
|
||||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -765,7 +765,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "d801e69bdd847115e92104a8cdd51ba1f207a1b7c25c4f6c9fb88434594be975"
|
content-hash = "37541ff4aa6aa74d76b10b8183b7e62e34b6c66c8b8f8eec7aad23e9b5793f94"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
aiohttp = [
|
aiohttp = [
|
||||||
@@ -1052,8 +1052,8 @@ jinja2 = [
|
|||||||
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||||
]
|
]
|
||||||
markdown = [
|
markdown = [
|
||||||
{file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"},
|
{file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"},
|
||||||
{file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"},
|
{file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
|
||||||
]
|
]
|
||||||
markupsafe = [
|
markupsafe = [
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
|
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
|
||||||
@@ -1232,8 +1232,8 @@ pluggy = [
|
|||||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||||
]
|
]
|
||||||
pre-commit = [
|
pre-commit = [
|
||||||
{file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"},
|
{file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"},
|
||||||
{file = "pre_commit-2.18.1.tar.gz", hash = "sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"},
|
{file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"},
|
||||||
]
|
]
|
||||||
py = [
|
py = [
|
||||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||||
@@ -1248,8 +1248,8 @@ pymdown-extensions = [
|
|||||||
{file = "pymdown_extensions-9.4.tar.gz", hash = "sha256:1baa22a60550f731630474cad28feb0405c8101f1a7ddc3ec0ed86ee510bcc43"},
|
{file = "pymdown_extensions-9.4.tar.gz", hash = "sha256:1baa22a60550f731630474cad28feb0405c8101f1a7ddc3ec0ed86ee510bcc43"},
|
||||||
]
|
]
|
||||||
pyparsing = [
|
pyparsing = [
|
||||||
{file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"},
|
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||||
{file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"},
|
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||||
]
|
]
|
||||||
pytest = [
|
pytest = [
|
||||||
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
|
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
|
||||||
@@ -1316,8 +1316,8 @@ pyyaml-env-tag = [
|
|||||||
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
||||||
]
|
]
|
||||||
rich = [
|
rich = [
|
||||||
{file = "rich-12.3.0-py3-none-any.whl", hash = "sha256:0eb63013630c6ee1237e0e395d51cb23513de6b5531235e33889e8842bdf3a6f"},
|
{file = "rich-12.4.1-py3-none-any.whl", hash = "sha256:d13c6c90c42e24eb7ce660db397e8c398edd58acb7f92a2a88a95572b838aaa4"},
|
||||||
{file = "rich-12.3.0.tar.gz", hash = "sha256:7e8700cda776337036a712ff0495b04052fb5f957c7dfb8df997f88350044b64"},
|
{file = "rich-12.4.1.tar.gz", hash = "sha256:d239001c0fb7de985e21ec9a4bb542b5150350330bbc1849f835b9cbc8923b91"},
|
||||||
]
|
]
|
||||||
six = [
|
six = [
|
||||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ textual = "textual.cli.cli:run"
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7"
|
python = "^3.7"
|
||||||
rich = "^12.3.0"
|
rich = "^12.4.0"
|
||||||
|
|
||||||
#rich = {git = "git@github.com:willmcgugan/rich", rev = "link-id"}
|
#rich = {git = "git@github.com:willmcgugan/rich", rev = "link-id"}
|
||||||
click = "8.1.2"
|
click = "8.1.2"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from textual.app import App
|
from textual.app import App
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
|
|
||||||
from textual.widgets.text_input import TextInput, TextInputBase
|
from textual.widgets.text_input import TextInput, TextWidgetBase, TextArea
|
||||||
|
|
||||||
|
|
||||||
def celsius_to_fahrenheit(celsius: float) -> float:
|
def celsius_to_fahrenheit(celsius: float) -> float:
|
||||||
@@ -19,8 +19,10 @@ class InputApp(App[str]):
|
|||||||
self.fahrenheit.focus()
|
self.fahrenheit.focus()
|
||||||
text_boxes = Widget(self.fahrenheit, self.celsius)
|
text_boxes = Widget(self.fahrenheit, self.celsius)
|
||||||
self.mount(inputs=text_boxes)
|
self.mount(inputs=text_boxes)
|
||||||
|
self.mount(spacer=Widget())
|
||||||
|
self.mount(text_area=TextArea())
|
||||||
|
|
||||||
def handle_changed(self, event: TextInputBase.Changed) -> None:
|
def handle_changed(self, event: TextWidgetBase.Changed) -> None:
|
||||||
try:
|
try:
|
||||||
value = float(event.value)
|
value = float(event.value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
|
|
||||||
App {
|
App {
|
||||||
layout: dock;
|
|
||||||
docks: top=top bot=bottom;
|
|
||||||
background: $secondary;
|
background: $secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#spacer {
|
||||||
|
height: 1;
|
||||||
|
background: $primary-darken-2;
|
||||||
|
dock: top;
|
||||||
|
}
|
||||||
|
|
||||||
Screen {
|
Screen {
|
||||||
|
layout: dock;
|
||||||
|
docks: top=top bottom=bottom;
|
||||||
background: $secondary;
|
background: $secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,3 +35,7 @@ Screen {
|
|||||||
background: $secondary;
|
background: $secondary;
|
||||||
height: 20;
|
height: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#text_area {
|
||||||
|
dock: bottom;
|
||||||
|
}
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ class Keys(str, Enum):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def values(cls):
|
def values(cls):
|
||||||
"""Returns a set of all the enum values."""
|
"""Returns a set of all the enum values."""
|
||||||
return set(cls._value2member_map_.keys())
|
return set(cls._value2member_map_.values())
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
|
from rich.padding import Padding
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from textual import events
|
from textual import events
|
||||||
from textual._types import MessageTarget
|
from textual._types import MessageTarget
|
||||||
|
from textual.app import ComposeResult
|
||||||
from textual.geometry import Size
|
from textual.geometry import Size
|
||||||
from textual.keys import Keys
|
from textual.keys import Keys
|
||||||
from textual.message import Message
|
from textual.message import Message
|
||||||
@@ -12,8 +14,8 @@ from textual.reactive import Reactive
|
|||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
|
|
||||||
|
|
||||||
class TextInputBase(Widget):
|
class TextWidgetBase(Widget):
|
||||||
ALLOW_PROPAGATE = {}
|
STOP_PROPAGATE = set()
|
||||||
|
|
||||||
current_text = Reactive("", layout=True)
|
current_text = Reactive("", layout=True)
|
||||||
cursor_index = Reactive(0)
|
cursor_index = Reactive(0)
|
||||||
@@ -24,7 +26,7 @@ class TextInputBase(Widget):
|
|||||||
if key == "\x1b":
|
if key == "\x1b":
|
||||||
return
|
return
|
||||||
|
|
||||||
changed = False
|
repaint = False
|
||||||
if key == "ctrl+h" and self.cursor_index != 0:
|
if key == "ctrl+h" and self.cursor_index != 0:
|
||||||
new_text = (
|
new_text = (
|
||||||
self.current_text[: self.cursor_index - 1]
|
self.current_text[: self.cursor_index - 1]
|
||||||
@@ -32,14 +34,14 @@ class TextInputBase(Widget):
|
|||||||
)
|
)
|
||||||
self.current_text = new_text
|
self.current_text = new_text
|
||||||
self.cursor_index = max(0, self.cursor_index - 1)
|
self.cursor_index = max(0, self.cursor_index - 1)
|
||||||
changed = True
|
repaint = True
|
||||||
elif key == "ctrl+d" and self.cursor_index != len(self.current_text):
|
elif key == "ctrl+d" and self.cursor_index != len(self.current_text):
|
||||||
new_text = (
|
new_text = (
|
||||||
self.current_text[: self.cursor_index]
|
self.current_text[: self.cursor_index]
|
||||||
+ self.current_text[self.cursor_index + 1 :]
|
+ self.current_text[self.cursor_index + 1 :]
|
||||||
)
|
)
|
||||||
self.current_text = new_text
|
self.current_text = new_text
|
||||||
changed = True
|
repaint = True
|
||||||
elif key == "left":
|
elif key == "left":
|
||||||
self.cursor_index = max(0, self.cursor_index - 1)
|
self.cursor_index = max(0, self.cursor_index - 1)
|
||||||
elif key == "right":
|
elif key == "right":
|
||||||
@@ -49,44 +51,59 @@ class TextInputBase(Widget):
|
|||||||
elif key == "end":
|
elif key == "end":
|
||||||
self.cursor_index = len(self.current_text)
|
self.cursor_index = len(self.current_text)
|
||||||
elif key not in Keys.values():
|
elif key not in Keys.values():
|
||||||
|
self.insert_at_cursor(key)
|
||||||
|
repaint = True
|
||||||
|
|
||||||
|
if repaint:
|
||||||
|
self.post_message_no_wait(self.Changed(self, value=self.current_text))
|
||||||
|
|
||||||
|
self.refresh(layout=True)
|
||||||
|
|
||||||
|
def insert_at_cursor(self, text: str) -> None:
|
||||||
new_text = (
|
new_text = (
|
||||||
self.current_text[: self.cursor_index]
|
self.current_text[: self.cursor_index]
|
||||||
+ key
|
+ text
|
||||||
+ self.current_text[self.cursor_index :]
|
+ self.current_text[self.cursor_index :]
|
||||||
)
|
)
|
||||||
self.current_text = new_text
|
self.current_text = new_text
|
||||||
self.cursor_index = min(len(self.current_text), self.cursor_index + 1)
|
self.cursor_index = min(len(self.current_text), self.cursor_index + 1)
|
||||||
changed = True
|
|
||||||
|
|
||||||
if changed:
|
def _apply_cursor_to_text(self, display_text: Text, index: int):
|
||||||
self.post_message_no_wait(self.Changed(self, value=self.current_text))
|
# Either write a cursor character or apply reverse style to cursor location
|
||||||
|
at_end_of_text = index == len(display_text)
|
||||||
|
at_end_of_line = index < len(display_text) and display_text[index].plain == "\n"
|
||||||
|
if at_end_of_text or at_end_of_line:
|
||||||
|
display_text = Text.assemble(
|
||||||
|
display_text[:index],
|
||||||
|
"█",
|
||||||
|
display_text[index:],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
display_text.stylize(
|
||||||
|
"reverse",
|
||||||
|
start=index,
|
||||||
|
end=index + 1,
|
||||||
|
)
|
||||||
|
return display_text
|
||||||
|
|
||||||
|
class Changed(Message, bubble=True):
|
||||||
|
def __init__(self, sender: MessageTarget, value: str) -> None:
|
||||||
|
"""Message posted when the user changes the value in a TextInput
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sender (MessageTarget): Sender of the message
|
||||||
|
value (str): The value in the TextInput
|
||||||
|
"""
|
||||||
|
super().__init__(sender)
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
class TextInput(TextInputBase, can_focus=True):
|
class TextInput(Widget):
|
||||||
|
|
||||||
ALLOW_PROPAGATE = {"tab", "shift+tab"}
|
|
||||||
|
|
||||||
CSS = """
|
CSS = """
|
||||||
TextInput {
|
TextInput {
|
||||||
width: auto;
|
overflow: hidden hidden;
|
||||||
background: $primary;
|
|
||||||
height: 3;
|
|
||||||
padding: 0 1;
|
|
||||||
content-align: left middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextInput:hover {
|
|
||||||
background: $primary-darken-1;
|
background: $primary-darken-1;
|
||||||
}
|
scrollbar-color: $primary-darken-2;
|
||||||
|
|
||||||
TextInput:focus {
|
|
||||||
background: $primary-darken-2;
|
|
||||||
border: solid $primary-lighten-1;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
App.-show-focus TextInput:focus {
|
|
||||||
tint: $accent 20%;
|
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -101,8 +118,68 @@ class TextInput(TextInputBase, can_focus=True):
|
|||||||
):
|
):
|
||||||
super().__init__(name=name, id=id, classes=classes)
|
super().__init__(name=name, id=id, classes=classes)
|
||||||
self.placeholder = placeholder
|
self.placeholder = placeholder
|
||||||
|
self.initial = initial
|
||||||
self.current_text = initial if initial else ""
|
self.current_text = initial if initial else ""
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield TextInputChild(
|
||||||
|
placeholder=self.placeholder, initial=self.initial, wrapper=self
|
||||||
|
)
|
||||||
|
|
||||||
|
class Submitted(Message, bubble=True):
|
||||||
|
def __init__(self, sender: MessageTarget, value: str) -> None:
|
||||||
|
"""Message posted when the user presses the 'enter' key while
|
||||||
|
focused on a TextInput widget.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sender (MessageTarget): Sender of the message
|
||||||
|
value (str): The value in the TextInput
|
||||||
|
"""
|
||||||
|
super().__init__(sender)
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
class TextInputChild(TextWidgetBase, can_focus=True):
|
||||||
|
CSS = """
|
||||||
|
TextInputChild {
|
||||||
|
width: auto;
|
||||||
|
background: $primary;
|
||||||
|
height: 3;
|
||||||
|
padding: 0 1;
|
||||||
|
content-align: left middle;
|
||||||
|
background: $primary-darken-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextInputChild:hover {
|
||||||
|
background: $primary-darken-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextInputChild:focus {
|
||||||
|
background: $primary-darken-2;
|
||||||
|
border: hkey $primary-lighten-1;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
App.-show-focus TextInputChild:focus {
|
||||||
|
tint: $accent 20%;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
wrapper: Widget,
|
||||||
|
placeholder: str = "",
|
||||||
|
initial: str = "",
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
):
|
||||||
|
super().__init__(name=name, id=id, classes=classes)
|
||||||
|
self.placeholder = placeholder
|
||||||
|
self.current_text = initial if initial else ""
|
||||||
|
self.wrapper = wrapper
|
||||||
|
|
||||||
def get_content_width(self, container_size: Size, viewport_size: Size) -> int:
|
def get_content_width(self, container_size: Size, viewport_size: Size) -> int:
|
||||||
return (
|
return (
|
||||||
max(len(self.current_text), len(self.placeholder))
|
max(len(self.current_text), len(self.placeholder))
|
||||||
@@ -127,48 +204,62 @@ class TextInput(TextInputBase, can_focus=True):
|
|||||||
display_text = self._apply_cursor_to_text(display_text, 0)
|
display_text = self._apply_cursor_to_text(display_text, 0)
|
||||||
return display_text
|
return display_text
|
||||||
|
|
||||||
def _apply_cursor_to_text(self, display_text: Text, index: int):
|
|
||||||
# Either write a cursor character or apply reverse style to cursor location
|
|
||||||
if index == len(display_text):
|
|
||||||
display_text += "█"
|
|
||||||
else:
|
|
||||||
display_text.stylize(
|
|
||||||
"reverse",
|
|
||||||
start=index,
|
|
||||||
end=index + 1,
|
|
||||||
)
|
|
||||||
return display_text
|
|
||||||
|
|
||||||
def on_focus(self, event: events.Focus) -> None:
|
|
||||||
self.refresh(layout=True)
|
|
||||||
|
|
||||||
def on_key(self, event: events.Key) -> None:
|
def on_key(self, event: events.Key) -> None:
|
||||||
key = event.key
|
key = event.key
|
||||||
if key not in self.ALLOW_PROPAGATE:
|
if key in self.STOP_PROPAGATE:
|
||||||
event.stop()
|
event.stop()
|
||||||
|
|
||||||
if key == "enter" and self.current_text:
|
if key == "enter" and self.current_text:
|
||||||
self.post_message_no_wait(TextInput.Submitted(self, self.current_text))
|
self.post_message_no_wait(TextInput.Submitted(self, self.current_text))
|
||||||
|
elif key == "ctrl+h":
|
||||||
|
self.wrapper.scroll_left()
|
||||||
|
elif key == "home":
|
||||||
|
self.wrapper.scroll_home()
|
||||||
|
elif key == "end":
|
||||||
|
print(self.wrapper.max_scroll_x)
|
||||||
|
self.wrapper.scroll_to(x=self.wrapper.max_scroll_x)
|
||||||
|
elif key not in Keys.values():
|
||||||
|
self.wrapper.scroll_to(x=self.wrapper.scroll_x + 1)
|
||||||
|
|
||||||
class Changed(Message, bubble=True):
|
|
||||||
def __init__(self, sender: MessageTarget, value: str) -> None:
|
|
||||||
"""Message posted when the user changes the value in a TextInput
|
|
||||||
|
|
||||||
Args:
|
class TextArea(Widget):
|
||||||
sender (MessageTarget): Sender of the message
|
CSS = """
|
||||||
value (str): The value in the TextInput
|
TextArea { overflow: auto auto; height: 5; background: $primary-darken-1; }
|
||||||
"""
|
"""
|
||||||
super().__init__(sender)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
class Submitted(Message, bubble=True):
|
def compose(self) -> ComposeResult:
|
||||||
def __init__(self, sender: MessageTarget, value: str) -> None:
|
yield TextAreaChild()
|
||||||
"""Message posted when the user presses the 'enter' key while
|
|
||||||
focused on a TextInput widget.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sender (MessageTarget): Sender of the message
|
class TextAreaChild(TextWidgetBase, can_focus=True):
|
||||||
value (str): The value in the TextInput
|
# TODO: Not nearly ready for prime-time, but it exists to help
|
||||||
"""
|
# model the superclass.
|
||||||
super().__init__(sender)
|
CSS = "TextAreaChild { height: auto; background: $primary-darken-1; }"
|
||||||
self.value = value
|
STOP_PROPAGATE = {"tab", "shift+tab"}
|
||||||
|
|
||||||
|
def render(self) -> RenderableType:
|
||||||
|
# We only show the cursor if the widget has focus
|
||||||
|
show_cursor = self.has_focus
|
||||||
|
display_text = Text(self.current_text, no_wrap=True)
|
||||||
|
if show_cursor:
|
||||||
|
display_text = self._apply_cursor_to_text(display_text, self.cursor_index)
|
||||||
|
return Padding(display_text, pad=1)
|
||||||
|
|
||||||
|
def get_content_height(
|
||||||
|
self, container_size: Size, viewport_size: Size, width: int
|
||||||
|
) -> int:
|
||||||
|
return self.current_text.count("\n") + 1 + 2
|
||||||
|
|
||||||
|
def on_key(self, event: events.Key) -> None:
|
||||||
|
if event.key in self.STOP_PROPAGATE:
|
||||||
|
event.stop()
|
||||||
|
|
||||||
|
if event.key == "enter":
|
||||||
|
self.insert_at_cursor("\n")
|
||||||
|
elif event.key == "tab":
|
||||||
|
self.insert_at_cursor("\t")
|
||||||
|
elif event.key == "\x1b":
|
||||||
|
self.app.focused = None
|
||||||
|
|
||||||
|
def on_focus(self, event: events.Focus) -> None:
|
||||||
|
self.refresh(layout=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user