added auto height

This commit is contained in:
Will McGugan
2022-05-05 14:38:22 +01:00
parent 4ec565074d
commit bc497e0abe
8 changed files with 102 additions and 30 deletions

View File

@@ -10,11 +10,10 @@ Widget {
}
#thing {
width: auto;
height: 10;
height: auto;
background:magenta;
margin: 3;
margin: 1;
padding: 1;
border: solid white;
box-sizing: border-box;

View File

@@ -1,25 +1,29 @@
from rich.text import Text
from textual.app import App
from textual.app import App, ComposeResult
from textual.widget import Widget
from textual.widgets import Static
class Thing(Widget):
def render(self):
return Text.from_markup("Hello, World. [b magenta]Lorem impsum.")
return "Hello, 3434 World.\n[b]Lorem impsum."
class AlignApp(App):
def on_load(self):
self.bind("t", "log_tree")
def on_mount(self) -> None:
self.log("MOUNTED")
self.mount(thing=Thing(), thing2=Static("0123456789"), thing3=Widget())
def compose(self) -> ComposeResult:
yield Thing(id="thing")
yield Static("foo", id="thing2")
yield Widget(id="thing3")
def action_log_tree(self):
self.log(self.screen.tree)
AlignApp.run(css_path="align.css", log_path="textual.log", watch_css=True)
app = AlignApp(css_path="align.css")
if __name__ == "__main__":
app.run()

View File

@@ -76,7 +76,9 @@ class BasicApp(App):
self.focused.styles.border_top = ("solid", "invalid-color")
app = BasicApp(css_path="uber.css", log_path="textual.log", log_verbosity=1)
app = BasicApp(
css_path="uber.css",
)
if __name__ == "__main__":
app.run()

View File

@@ -110,7 +110,7 @@ class App(Generic[ReturnType], DOMNode):
log_verbosity: int = 1,
title: str = "Textual Application",
css_path: str | PurePath | None = None,
watch_css: bool = True,
watch_css: bool = False,
):
"""Textual application base class
@@ -120,7 +120,7 @@ class App(Generic[ReturnType], DOMNode):
log_verbosity (int, optional): Log verbosity from 0-3. Defaults to 1.
title (str, optional): Default title of the application. Defaults to "Textual Application".
css_path (str | PurePath | None, optional): Path to CSS or ``None`` for no CSS file. Defaults to None.
watch_css (bool, optional): Watch CSS for changes. Defaults to True.
watch_css (bool, optional): Watch CSS for changes. Defaults to False.
"""
# N.B. This must be done *before* we call the parent constructor, because MessagePump's
# constructor instantiates a `asyncio.PriorityQueue` and in Python versions older than 3.10
@@ -172,18 +172,20 @@ class App(Generic[ReturnType], DOMNode):
self._require_styles_update = False
self.css_path = css_path
self.css_monitor = (
FileMonitor(css_path, self._on_css_change)
if (watch_css and css_path)
else None
)
self.features: frozenset[FeatureFlag] = parse_features(os.getenv("TEXTUAL", ""))
self.registry: set[MessagePump] = set()
self.devtools = DevtoolsClient()
self._return_value: ReturnType | None = None
self._focus_timer: Timer | None = None
self.css_monitor = (
FileMonitor(css_path, self._on_css_change)
if ((watch_css or self.debug) and css_path)
else None
)
super().__init__()
title: Reactive[str] = Reactive("Textual")
@@ -636,9 +638,6 @@ class App(Generic[ReturnType], DOMNode):
async def process_messages(self) -> None:
self._set_active()
log("---")
log(f"driver={self.driver_class}")
log(f"asyncio running loop={asyncio.get_running_loop()!r}")
if self.devtools_enabled:
try:
@@ -646,6 +645,12 @@ class App(Generic[ReturnType], DOMNode):
self.log(f"Connected to devtools ({self.devtools.url})")
except DevtoolsConnectionError:
self.log(f"Couldn't connect to devtools ({self.devtools.url})")
self.log("---")
self.log(driver=self.driver_class)
self.log(loop=asyncio.get_running_loop())
self.log(features=self.features)
try:
if self.css_path is not None:
self.stylesheet.read(self.css_path)
@@ -666,8 +671,6 @@ class App(Generic[ReturnType], DOMNode):
try:
load_event = events.Load(sender=self)
await self.dispatch_message(load_event)
# Wait for the load event to be processed, so we don't go in to application mode beforehand
# await load_event.wait()
driver = self._driver = self.driver_class(self.console, self)
driver.start_application_mode()

View File

@@ -18,7 +18,7 @@ def get_box_model(
container: Size,
viewport: Size,
get_content_width: Callable[[Size, Size], int],
get_content_height: Callable[[Size, Size], int],
get_content_height: Callable[[Size, Size, int], int],
) -> BoxModel:
"""Resolve the box model for this Styles.
@@ -53,7 +53,9 @@ def get_box_model(
if not has_rule("height"):
height = container.height
elif styles.height.is_auto:
height = get_content_height(container, viewport)
print("get_content_height", container, viewport, width)
height = get_content_height(container, viewport, width)
print("height", height)
if not is_content_box:
height += gutter.height
else:

View File

@@ -376,8 +376,11 @@ class Region(NamedTuple):
"""
x1, y1, x2, y2 = self.corners
ox, oy, ox2, oy2 = other.corners
return (x2 >= ox >= x1 and y2 >= oy >= y1) and (
x2 >= ox2 >= x1 and y2 >= oy2 >= y1
return (
(x2 >= ox >= x1)
and (y2 >= oy >= y1)
and (x2 >= ox2 >= x1)
and (y2 >= oy2 >= y1)
)
def translate(self, x: int = 0, y: int = 0) -> Region:

View File

@@ -145,14 +145,40 @@ class Widget(DOMNode):
)
return box_model
def get_content_width(self, container_size: Size, parent_size: Size) -> int:
def get_content_width(self, container_size: Size, viewport_size: Size) -> int:
"""Gets the width of the content area.
Args:
container_size (Size): Size of the container (immediate parent) widget.
viewport_size (Size): Size of the viewport.
Returns:
int: The optimal width of the content.
"""
console = self.app.console
renderable = self.render()
measurement = Measurement.get(console, console.options, renderable)
return measurement.maximum
def get_content_height(self, container_size: Size, parent_size: Size) -> int:
return container_size.height
def get_content_height(
self, container_size: Size, viewport_size: Size, width: int
) -> int:
"""Gets the height (number of lines) in the content area.
Args:
container_size (Size): Size of the container (immediate parent) widget.
viewport_size (Size): Size of the viewport.
width (int): Width of renderable.
Returns:
int: The height of the content.
"""
renderable = self.render()
options = self.console.options.update_width(width)
segments = self.console.render(renderable, options)
# Cheaper than counting the lines returned from render_lines!
height = sum(segment.text.count("\n") for segment in segments)
return height
async def watch_scroll_x(self, new_value: float) -> None:
self.horizontal_scrollbar.position = int(new_value)

View File

@@ -3,8 +3,9 @@ from decimal import Decimal
import pytest
from textual.app import App
from textual.css.errors import StyleValueError
from textual.css.scalar import Scalar, Unit
from textual.geometry import Size
from textual.widget import Widget
@@ -32,3 +33,35 @@ def test_widget_set_visible_invalid_string():
widget.visible = "nope! no widget for me!"
assert widget.visible
def test_widget_content_width():
class TextWidget(Widget):
def __init__(self, text: str, id: str) -> None:
self.text = text
super().__init__(id=id)
def render(self) -> str:
return self.text
widget1 = TextWidget("foo", id="widget1")
widget2 = TextWidget("foo\nbar", id="widget2")
widget3 = TextWidget("foo\nbar\nbaz", id="widget3")
app = App()
app._set_active()
width = widget1.get_content_width(Size(20, 20), Size(80, 24))
height = widget1.get_content_height(Size(20, 20), Size(80, 24), width)
assert width == 3
assert height == 1
width = widget2.get_content_width(Size(20, 20), Size(80, 24))
height = widget2.get_content_height(Size(20, 20), Size(80, 24), width)
assert width == 3
assert height == 2
width = widget3.get_content_width(Size(20, 20), Size(80, 24))
height = widget3.get_content_height(Size(20, 20), Size(80, 24), width)
assert width == 3
assert height == 3