mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
dictionary example
This commit is contained in:
29
docs/examples/events/dictionary.css
Normal file
29
docs/examples/events/dictionary.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
Screen {
|
||||||
|
background: $panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextInput {
|
||||||
|
dock: top;
|
||||||
|
border: tall $background;
|
||||||
|
width: 100%;
|
||||||
|
height: 1;
|
||||||
|
padding: 0 1;
|
||||||
|
margin: 1 1 0 1;
|
||||||
|
background: $boost;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextInput:focus {
|
||||||
|
border: tall $accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#results {
|
||||||
|
width: auto;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#results-container {
|
||||||
|
background: $background 50%;
|
||||||
|
overflow: auto;
|
||||||
|
margin: 1 2;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
43
docs/examples/events/dictionary.py
Normal file
43
docs/examples/events/dictionary.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
try:
|
||||||
|
import httpx
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError("Please install http with 'pip install httpx' ")
|
||||||
|
|
||||||
|
from rich.json import JSON
|
||||||
|
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.layout import Vertical
|
||||||
|
from textual.widgets import Static, TextInput
|
||||||
|
|
||||||
|
|
||||||
|
class DictionaryApp(App):
|
||||||
|
"""Searches ab dictionary API as-you-type."""
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield TextInput(placeholder="Search for a word")
|
||||||
|
yield Vertical(Static(id="results", fluid=False), id="results-container")
|
||||||
|
|
||||||
|
async def on_text_input_changed(self, message: TextInput.Changed) -> None:
|
||||||
|
"""A coroutine to handle a text changed message."""
|
||||||
|
if message.value:
|
||||||
|
# Look up the word in the background
|
||||||
|
asyncio.create_task(self.lookup_word(message.value))
|
||||||
|
else:
|
||||||
|
# Clear the results
|
||||||
|
self.query_one("#results", Static).update()
|
||||||
|
|
||||||
|
async def lookup_word(self, word: str) -> None:
|
||||||
|
"""Looks up a word."""
|
||||||
|
url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
results = (await client.get(url)).text
|
||||||
|
|
||||||
|
if word == self.query_one(TextInput).value:
|
||||||
|
self.query_one("#results", Static).update(JSON(results))
|
||||||
|
|
||||||
|
|
||||||
|
app = DictionaryApp(css_path="dictionary.css")
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
||||||
@@ -110,9 +110,24 @@ The message class is defined within the widget class itself. This is not strictl
|
|||||||
- If reduces the amount of imports. If you were to import ColorButton, you have access to the message class via `ColorButton.Selected`.
|
- If reduces the amount of imports. If you were to import ColorButton, you have access to the message class via `ColorButton.Selected`.
|
||||||
- It creates a namespace for the handler. So rather than `on_selected`, the handler name becomes `on_color_button_selected`. This makes it less likely that your chosen name will clash with another message.
|
- It creates a namespace for the handler. So rather than `on_selected`, the handler name becomes `on_color_button_selected`. This makes it less likely that your chosen name will clash with another message.
|
||||||
|
|
||||||
|
|
||||||
|
## Sending events
|
||||||
|
|
||||||
|
In the previous example we used [emit()][textual.message_pump.MessagePump.emit] to send an event to it's parent. We could also have used [emit_no_wait()][textual.message_pump.MessagePump.emit_no_wait] for non async code. Sending messages in this way allows you to write custom widgets without needing to know in what context they will be used.
|
||||||
|
|
||||||
|
There are other ways of sending (posting) messages, which you may need to use less frequently.
|
||||||
|
|
||||||
|
- [post_message][textual.message_pump.MessagePump.post_message] To post a message to a particular event.
|
||||||
|
- [post_message_no_wait][textual.message_pump.MessagePump.post_message_no_wait] The non-async version of `post_message`.
|
||||||
|
|
||||||
|
|
||||||
|
## Message handlers
|
||||||
|
|
||||||
|
Most of the logic in a Textual app will be written in message handlers. Let's explore handlers in more detail.
|
||||||
|
|
||||||
### Handler naming
|
### Handler naming
|
||||||
|
|
||||||
Let's recap on the scheme that Textual uses to map messages classes on to a Python method name.
|
Textual uses the following scheme to map messages classes on to a Python method.
|
||||||
|
|
||||||
- Start with `"on_"`.
|
- Start with `"on_"`.
|
||||||
- Add the messages namespace (if any) converted from CamelCase to snake_case plus an underscore `"_"`
|
- Add the messages namespace (if any) converted from CamelCase to snake_case plus an underscore `"_"`
|
||||||
@@ -122,12 +137,56 @@ Let's recap on the scheme that Textual uses to map messages classes on to a Pyth
|
|||||||
--8<-- "docs/images/events/naming.excalidraw.svg"
|
--8<-- "docs/images/events/naming.excalidraw.svg"
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Sending events
|
### Handler arguments
|
||||||
|
|
||||||
In the previous example we used [emit()][textual.message_pump.MessagePump.emit] to send an event to it's parent. We could also have used [emit_no_wait()][textual.message_pump.MessagePump.emit_no_wait] for non async code. Sending messages in this way allows you to write custom widgets without needing to know in what context they will be used.
|
Message handler methods can be written with or without a positional argument. If you add a positional argument, Textual will call the handler with the event object. The following handler (taken from custom01.py above) contains a `message` parameter. The body of the code makes use of the message to set a preset color.
|
||||||
|
|
||||||
There are other ways of sending (posting) messages, which you may need to use less frequently.
|
```python
|
||||||
|
def on_color_button_selected(self, message: ColorButton.Selected) -> None:
|
||||||
|
self.screen.styles.animate("background", message.color, duration=0.5)
|
||||||
|
```
|
||||||
|
|
||||||
- [post_message][textual.message_pump.MessagePump.post_message] To post a message to a particular event.
|
If the body of your handler doesn't require any information in the message you can omit it from the method signature. If we just want to play a bell noise when the button is clicked, we could write our handler like this:
|
||||||
- [post_message_no_wait][textual.message_pump.MessagePump.post_message_no_wait] The non-async version of `post_message`.
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
def on_color_button_selected(self) -> None:
|
||||||
|
self.app.bell()
|
||||||
|
```
|
||||||
|
|
||||||
|
This pattern is a convenience that saves writing out a parameter that may not be used.
|
||||||
|
|
||||||
|
### Async handlers
|
||||||
|
|
||||||
|
Method handlers may be coroutines. If you prefix your handlers with the `async` keyword, Textual will `await` them. This lets your handler use the `await` keyword for asynchronous APIs.
|
||||||
|
|
||||||
|
If your event handlers are coroutines it will allow multiple events to be processed concurrently, but bear in mind an individual widget (or app) will not be able to pick up a new message from the message queue until the handler has returned. This is rarely a problem in practice; as long has handlers return within a few milliseconds the UI will remain responsive. But slow handlers might make your app hard to use.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
|
||||||
|
To re-use the chef analogy, if an order comes in for beef wellington (which takes a while to cook), orders may start to pile up and customers may have to wait for their meal. The _solution_ would be to have another chef work on the wellington while the first chef picks up new orders.
|
||||||
|
|
||||||
|
Network access is a common cause of slow handlers. If you try to retrieve a file from the internet, the message handler may take anything up to a few seconds to return, which would prevent the widget or app from updating during that time. The solution is to launch a new asyncio task to do the network task in the background.
|
||||||
|
|
||||||
|
Let's look at an example which looks up word definitions from an [api](https://dictionaryapi.dev/) as you type.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
You will need to install [httpx](https://www.python-httpx.org/) with `pip install httpx` to run this example.
|
||||||
|
|
||||||
|
=== "dictionary.py"
|
||||||
|
|
||||||
|
```python title="dictionary.py" hl_lines="26"
|
||||||
|
--8<-- "docs/examples/events/dictionary.py"
|
||||||
|
```
|
||||||
|
=== "dictionary.css"
|
||||||
|
|
||||||
|
```python title="dictionary.css"
|
||||||
|
--8<-- "docs/examples/events/dictionary.css"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Output"
|
||||||
|
|
||||||
|
```{.textual path="docs/examples/events/dictionary.py" press="tab,t,e,x,t,_,_,_,_,_,_,_,_,_,_,_"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the highlighted line in the above code which calls `asyncio.create_task` to run coroutine in the background. Without this you would find typing in to the text box to be unresponsive.
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class FileSearchApp(App):
|
|||||||
self.mount(file_table_wrapper=Widget(self.file_table))
|
self.mount(file_table_wrapper=Widget(self.file_table))
|
||||||
self.mount(search_bar=self.search_bar)
|
self.mount(search_bar=self.search_bar)
|
||||||
|
|
||||||
def on_text_widget_base_changed(self, event: TextWidgetBase.Changed) -> None:
|
def on_text_input_changed(self, event: TextInput.Changed) -> None:
|
||||||
self.file_table.filter = event.value
|
self.file_table.filter = event.value
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class InputApp(App[str]):
|
|||||||
)
|
)
|
||||||
self.mount(text_area=TextArea())
|
self.mount(text_area=TextArea())
|
||||||
|
|
||||||
def on_text_widget_base_changed(self, event: TextWidgetBase.Changed) -> None:
|
def on_text_input_changed_changed(self, event: TextInput.Changed) -> None:
|
||||||
try:
|
try:
|
||||||
value = float(event.value)
|
value = float(event.value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|||||||
@@ -1305,21 +1305,6 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self._end_update()
|
self._end_update()
|
||||||
console.file.flush()
|
console.file.flush()
|
||||||
|
|
||||||
def measure(self, renderable: RenderableType, max_width=100_000) -> int:
|
|
||||||
"""Get the optimal width for a widget or renderable.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
renderable (RenderableType): A renderable (including Widget)
|
|
||||||
max_width ([type], optional): Maximum width. Defaults to 100_000.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: Number of cells required to render.
|
|
||||||
"""
|
|
||||||
measurement = Measurement.get(
|
|
||||||
self.console, self.console.options.update(max_width=max_width), renderable
|
|
||||||
)
|
|
||||||
return measurement.maximum
|
|
||||||
|
|
||||||
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
|
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
|
||||||
"""Get the widget under the given coordinates.
|
"""Get the widget under the given coordinates.
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class EasingApp(App):
|
|||||||
self.animated_bar.position = value
|
self.animated_bar.position = value
|
||||||
self.opacity_widget.styles.opacity = 1 - value / END_POSITION
|
self.opacity_widget.styles.opacity = 1 - value / END_POSITION
|
||||||
|
|
||||||
def on_text_widget_base_changed(self, event: TextWidgetBase.Changed):
|
def on_text_input_changed(self, event: TextInput.Changed):
|
||||||
if event.sender.id == "duration-input":
|
if event.sender.id == "duration-input":
|
||||||
new_duration = _try_float(event.value)
|
new_duration = _try_float(event.value)
|
||||||
if new_duration is not None:
|
if new_duration is not None:
|
||||||
|
|||||||
@@ -174,8 +174,6 @@ class ColorSystem:
|
|||||||
for name, color in COLORS:
|
for name, color in COLORS:
|
||||||
is_dark_shade = dark and name in DARK_SHADES
|
is_dark_shade = dark and name in DARK_SHADES
|
||||||
spread = luminosity_spread
|
spread = luminosity_spread
|
||||||
if name == "panel":
|
|
||||||
spread /= 2
|
|
||||||
for shade_name, luminosity_delta in luminosity_range(spread):
|
for shade_name, luminosity_delta in luminosity_range(spread):
|
||||||
if is_dark_shade:
|
if is_dark_shade:
|
||||||
dark_background = background.blend(color, 0.15)
|
dark_background = background.blend(color, 0.15)
|
||||||
@@ -188,8 +186,8 @@ class ColorSystem:
|
|||||||
colors[f"{name}{shade_name}"] = shade_color.hex
|
colors[f"{name}{shade_name}"] = shade_color.hex
|
||||||
|
|
||||||
colors["text"] = "auto 95%"
|
colors["text"] = "auto 95%"
|
||||||
colors["text-muted"] = "auto 80%"
|
colors["text-muted"] = "auto 50%"
|
||||||
colors["text-disabled"] = "auto 60%"
|
colors["text-disabled"] = "auto 30%"
|
||||||
|
|
||||||
return colors
|
return colors
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from rich.console import Console, RenderableType
|
from rich.console import Console, RenderableType
|
||||||
|
from rich.protocol import rich_cast
|
||||||
|
|
||||||
|
|
||||||
def measure(console: Console, renderable: RenderableType, default: int) -> int:
|
def measure(console: Console, renderable: RenderableType, default: int) -> int:
|
||||||
@@ -12,6 +13,7 @@ def measure(console: Console, renderable: RenderableType, default: int) -> int:
|
|||||||
Returns:
|
Returns:
|
||||||
int: Width in cells
|
int: Width in cells
|
||||||
"""
|
"""
|
||||||
|
renderable = rich_cast(renderable)
|
||||||
get_console_width = getattr(renderable, "__rich_measure__", None)
|
get_console_width = getattr(renderable, "__rich_measure__", None)
|
||||||
if get_console_width is not None:
|
if get_console_width is not None:
|
||||||
render_width = get_console_width(console, console.options).normalize().maximum
|
render_width = get_console_width(console, console.options).normalize().maximum
|
||||||
|
|||||||
@@ -7,8 +7,13 @@ from operator import attrgetter
|
|||||||
from typing import TYPE_CHECKING, ClassVar, Collection, Iterable, NamedTuple, cast
|
from typing import TYPE_CHECKING, ClassVar, Collection, Iterable, NamedTuple, cast
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.console import Console, ConsoleRenderable, JustifyMethod, RenderableType
|
from rich.console import (
|
||||||
from rich.measure import Measurement
|
Console,
|
||||||
|
ConsoleRenderable,
|
||||||
|
RichCast,
|
||||||
|
JustifyMethod,
|
||||||
|
RenderableType,
|
||||||
|
)
|
||||||
from rich.segment import Segment
|
from rich.segment import Segment
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
from rich.styled import Styled
|
from rich.styled import Styled
|
||||||
@@ -74,7 +79,7 @@ class Widget(DOMNode):
|
|||||||
scrollbar-background-hover: $panel-darken-2;
|
scrollbar-background-hover: $panel-darken-2;
|
||||||
scrollbar-color: $primary-lighten-1;
|
scrollbar-color: $primary-lighten-1;
|
||||||
scrollbar-color-active: $warning-darken-1;
|
scrollbar-color-active: $warning-darken-1;
|
||||||
scrollbar-corner-color: $panel-darken-3;
|
scrollbar-corner-color: $panel-darken-1;
|
||||||
scrollbar-size-vertical: 2;
|
scrollbar-size-vertical: 2;
|
||||||
scrollbar-size-horizontal: 1;
|
scrollbar-size-horizontal: 1;
|
||||||
}
|
}
|
||||||
@@ -321,8 +326,6 @@ class Widget(DOMNode):
|
|||||||
self.get_content_width,
|
self.get_content_width,
|
||||||
self.get_content_height,
|
self.get_content_height,
|
||||||
)
|
)
|
||||||
self.log(self)
|
|
||||||
self.log(box_model)
|
|
||||||
return box_model
|
return box_model
|
||||||
|
|
||||||
def get_content_width(self, container: Size, viewport: Size) -> int:
|
def get_content_width(self, container: Size, viewport: Size) -> int:
|
||||||
@@ -346,7 +349,7 @@ class Widget(DOMNode):
|
|||||||
return self._content_width_cache[1]
|
return self._content_width_cache[1]
|
||||||
|
|
||||||
console = self.app.console
|
console = self.app.console
|
||||||
renderable = self.post_render(self.render())
|
renderable = self._render()
|
||||||
|
|
||||||
width = measure(console, renderable, container.width)
|
width = measure(console, renderable, container.width)
|
||||||
if self.fluid:
|
if self.fluid:
|
||||||
@@ -1427,13 +1430,14 @@ class Widget(DOMNode):
|
|||||||
if isinstance(renderable, str):
|
if isinstance(renderable, str):
|
||||||
renderable = Text.from_markup(renderable, justify=text_justify)
|
renderable = Text.from_markup(renderable, justify=text_justify)
|
||||||
|
|
||||||
rich_style = self.rich_style
|
if (
|
||||||
if isinstance(renderable, Text):
|
isinstance(renderable, Text)
|
||||||
renderable.stylize(rich_style)
|
and text_justify is not None
|
||||||
if text_justify is not None and renderable.justify is None:
|
and renderable.justify is None
|
||||||
renderable.justify = text_justify
|
):
|
||||||
else:
|
renderable.justify = text_justify
|
||||||
renderable = Styled(renderable, rich_style)
|
|
||||||
|
renderable = Styled(renderable, self.rich_style)
|
||||||
|
|
||||||
return renderable
|
return renderable
|
||||||
|
|
||||||
@@ -1613,6 +1617,12 @@ class Widget(DOMNode):
|
|||||||
render = "" if self.is_container else self.css_identifier_styled
|
render = "" if self.is_container else self.css_identifier_styled
|
||||||
return render
|
return render
|
||||||
|
|
||||||
|
def _render(self) -> ConsoleRenderable | RichCast:
|
||||||
|
renderable = self.render()
|
||||||
|
if isinstance(renderable, str):
|
||||||
|
return Text(renderable)
|
||||||
|
return renderable
|
||||||
|
|
||||||
async def action(self, action: str) -> None:
|
async def action(self, action: str) -> None:
|
||||||
"""Perform a given action, with this widget as the default namespace.
|
"""Perform a given action, with this widget as the default namespace.
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
from rich.protocol import is_renderable
|
from rich.protocol import is_renderable
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
|
from ..reactive import reactive
|
||||||
from ..errors import RenderError
|
from ..errors import RenderError
|
||||||
from ..widget import Widget
|
from ..widget import Widget
|
||||||
|
|
||||||
@@ -41,21 +43,40 @@ class Static(Widget):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
fluid = reactive(True, layout=True)
|
||||||
|
_renderable: RenderableType
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
renderable: RenderableType = "",
|
renderable: RenderableType = "",
|
||||||
*,
|
*,
|
||||||
fluid: bool = True,
|
fluid: bool = True,
|
||||||
|
markup: bool = True,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
id: str | None = None,
|
id: str | None = None,
|
||||||
classes: str | None = None,
|
classes: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
super().__init__(name=name, id=id, classes=classes)
|
super().__init__(name=name, id=id, classes=classes)
|
||||||
self._renderable = renderable
|
|
||||||
self.fluid = fluid
|
self.fluid = fluid
|
||||||
|
self.markup = markup
|
||||||
|
self.renderable = renderable
|
||||||
_check_renderable(renderable)
|
_check_renderable(renderable)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def renderable(self) -> RenderableType:
|
||||||
|
return self._renderable or ""
|
||||||
|
|
||||||
|
@renderable.setter
|
||||||
|
def renderable(self, renderable: RenderableType) -> None:
|
||||||
|
if isinstance(renderable, str):
|
||||||
|
if self.markup:
|
||||||
|
self._renderable = Text.from_markup(renderable)
|
||||||
|
else:
|
||||||
|
self._renderable = Text(renderable)
|
||||||
|
else:
|
||||||
|
self._renderable = renderable
|
||||||
|
|
||||||
def render(self) -> RenderableType:
|
def render(self) -> RenderableType:
|
||||||
"""Get a rich renderable for the widget's content.
|
"""Get a rich renderable for the widget's content.
|
||||||
|
|
||||||
@@ -64,12 +85,12 @@ class Static(Widget):
|
|||||||
"""
|
"""
|
||||||
return self._renderable
|
return self._renderable
|
||||||
|
|
||||||
def update(self, renderable: RenderableType) -> None:
|
def update(self, renderable: RenderableType = "", home: bool = False) -> None:
|
||||||
"""Update the widget contents.
|
"""Update the widget contents.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
renderable (RenderableType): A new rich renderable.
|
renderable (RenderableType): A new rich renderable.
|
||||||
"""
|
"""
|
||||||
_check_renderable(renderable)
|
_check_renderable(renderable)
|
||||||
self._renderable = renderable
|
self.renderable = renderable
|
||||||
self.refresh(layout=True)
|
self.refresh(layout=True)
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ class TextWidgetBase(Widget):
|
|||||||
return display_text
|
return display_text
|
||||||
|
|
||||||
class Changed(Message, bubble=True):
|
class Changed(Message, bubble=True):
|
||||||
|
namespace = "text_input"
|
||||||
|
|
||||||
def __init__(self, sender: MessageTarget, value: str) -> None:
|
def __init__(self, sender: MessageTarget, value: str) -> None:
|
||||||
"""Message posted when the user changes the value in a TextInput
|
"""Message posted when the user changes the value in a TextInput
|
||||||
|
|
||||||
@@ -116,9 +118,17 @@ class TextInput(TextWidgetBase, can_focus=True):
|
|||||||
padding: 1;
|
padding: 1;
|
||||||
background: $surface;
|
background: $surface;
|
||||||
content-align: left middle;
|
content-align: left middle;
|
||||||
|
color: $text;
|
||||||
|
}
|
||||||
|
TextInput .text-input--placeholder {
|
||||||
|
color: $text-muted;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
COMPONENT_CLASSES = {
|
||||||
|
"text-input--placeholder",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -269,7 +279,10 @@ class TextInput(TextWidgetBase, can_focus=True):
|
|||||||
else:
|
else:
|
||||||
# The user has not entered text - show the placeholder
|
# The user has not entered text - show the placeholder
|
||||||
display_text = Text(
|
display_text = Text(
|
||||||
self.placeholder, "dim", no_wrap=True, overflow="ignore"
|
self.placeholder,
|
||||||
|
self.get_component_rich_style("text-input--placeholder"),
|
||||||
|
no_wrap=True,
|
||||||
|
overflow="ignore",
|
||||||
)
|
)
|
||||||
if show_cursor:
|
if show_cursor:
|
||||||
display_text = self._apply_cursor_to_text(display_text, 0)
|
display_text = self._apply_cursor_to_text(display_text, 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user