diff --git a/docs/styles/scrollbar.md b/docs/styles/scrollbar.md index 9a11b1cd95..514436eca7 100644 --- a/docs/styles/scrollbar.md +++ b/docs/styles/scrollbar.md @@ -3,13 +3,15 @@ There are a number of rules to set the colors used in Textual scrollbars. You won't typically need to do this, as the default themes have carefully chosen colors, but you can if you want to. | Rule | Color | -| ----------------------------- | ------------------------------------------------------- | +|-------------------------------|---------------------------------------------------------| | `scrollbar-color` | Scrollbar "thumb" (movable part) | | `scrollbar-color-hover` | Scrollbar thumb when the mouse is hovering over it | | `scrollbar-color-active` | Scrollbar thumb when it is active (being dragged) | | `scrollbar-background` | Scrollbar background | | `scrollbar-background-hover` | Scrollbar background when the mouse is hovering over it | | `scrollbar-background-active` | Scrollbar background when the thumb is being dragged | +| `scrollbar-corner-color` | The gap between the horizontal and vertical scrollbars | + ## Example diff --git a/sandbox/darren/just_a_box.css b/sandbox/darren/just_a_box.css index 062dff0ec7..881e436bf5 100644 --- a/sandbox/darren/just_a_box.css +++ b/sandbox/darren/just_a_box.css @@ -1,61 +1,24 @@ Screen { - height: 100vh; - width: 100%; - background: red; -} - -#horizontal { - width: 100%; -} - -.box { - height: 5; - width: 5; - margin: 1 10; + background: lightcoral; } #left_pane { - width: 1fr; - background: $background; + background: red; + width: 20; + overflow: scroll scroll; } #middle_pane { - margin-top: 4; - width: 1fr; - background: #173f5f; -} - -#middle_pane:focus { - tint: cyan 40%; -} - -#right_pane { - width: 1fr; - background: #f6d55c; -} - -.box:focus { - tint: cyan 40%; -} - -#box1 { background: green; + width: 140; } -#box2 { - offset-y: 3; - background: hotpink; -} - -#box3 { - background: red; -} - - -#box4 { +#right_pane { background: blue; + width: 30; } -#box5 { - background: darkviolet; +.box { + height: 12; + width: 30; } diff --git a/sandbox/darren/just_a_box.py b/sandbox/darren/just_a_box.py index 8e6fc3ae79..781c66f67f 100644 --- a/sandbox/darren/just_a_box.py +++ b/sandbox/darren/just_a_box.py @@ -3,6 +3,7 @@ from rich.console import RenderableType from rich.panel import Panel +from textual import events from textual.app import App, ComposeResult from textual.layout import Horizontal, Vertical from textual.widget import Widget @@ -21,26 +22,37 @@ def render(self) -> RenderableType: class JustABox(App): - dark = True - def compose(self) -> ComposeResult: yield Horizontal( Vertical( Box(id="box1", classes="box"), Box(id="box2", classes="box"), - Box(id="box3", classes="box"), + # Box(id="box3", classes="box"), + # Box(id="box4", classes="box"), + # Box(id="box5", classes="box"), + # Box(id="box6", classes="box"), + # Box(id="box7", classes="box"), + # Box(id="box8", classes="box"), + # Box(id="box9", classes="box"), + # Box(id="box10", classes="box"), id="left_pane", ), Box(id="middle_pane"), Vertical( - Box(id="box", classes="box"), - Box(id="box4", classes="box"), - Box(id="box5", classes="box"), + Box(id="boxa", classes="box"), + Box(id="boxb", classes="box"), + Box(id="boxc", classes="box"), id="right_pane", ), id="horizontal", ) + def key_p(self): + print(self.query("#horizontal").first().styles.layout) + + async def on_key(self, event: events.Key) -> None: + await self.dispatch_key(event) + if __name__ == "__main__": app = JustABox(css_path="just_a_box.css", watch_css=True) diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index f52021c472..4b1184127b 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -600,6 +600,7 @@ def process_color(self, name: str, tokens: list[Token]) -> None: process_scrollbar_color = process_color process_scrollbar_color_hover = process_color process_scrollbar_color_active = process_color + process_scrollbar_corner_color = process_color process_scrollbar_background = process_color process_scrollbar_background_hover = process_color process_scrollbar_background_active = process_color diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 9db2f5d789..dd0b53e6f4 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -125,6 +125,8 @@ class RulesMap(TypedDict, total=False): scrollbar_color_hover: Color scrollbar_color_active: Color + scrollbar_corner_color: Color + scrollbar_background: Color scrollbar_background_hover: Color scrollbar_background_active: Color @@ -228,6 +230,8 @@ class StylesBase(ABC): scrollbar_color_hover = ColorProperty("ansi_yellow") scrollbar_color_active = ColorProperty("ansi_bright_yellow") + scrollbar_corner_color = ColorProperty("#666666") + scrollbar_background = ColorProperty("#555555") scrollbar_background_hover = ColorProperty("#444444") scrollbar_background_active = ColorProperty("black") diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index ea5d510dd4..7ae1dcb45b 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -9,6 +9,7 @@ from rich.style import Style, StyleType from textual.reactive import Reactive +from textual.renderables.blank import Blank from . import events from ._types import MessageTarget from .geometry import Offset @@ -287,6 +288,19 @@ async def on_mouse_move(self, event: events.MouseMove) -> None: await self.emit(ScrollTo(self, x=x, y=y)) +class ScrollBarCorner(Widget): + """Widget which fills the gap between horizontal and vertical scrollbars, + should they both be present.""" + + def __init__(self, name: str | None = None): + super().__init__(name=name) + + def render(self) -> RenderableType: + styles = self.parent.styles + color = styles.scrollbar_corner_color + return Blank(color) + + if __name__ == "__main__": from rich.console import Console diff --git a/src/textual/widget.py b/src/textual/widget.py index f0ea13ad3d..55630d8cc0 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -17,7 +17,6 @@ from rich.align import Align from rich.console import Console, RenderableType from rich.measure import Measurement - from rich.segment import Segment from rich.style import Style from rich.styled import Styled @@ -46,6 +45,7 @@ ScrollRight, ScrollTo, ScrollUp, + ScrollBarCorner, ) @@ -73,6 +73,7 @@ class Widget(DOMNode): scrollbar-background-hover: $panel-darken-2; scrollbar-color: $primary-lighten-1; scrollbar-color-active: $warning-darken-1; + scrollbar-corner-color: $panel-darken-3; scrollbar-size-vertical: 2; scrollbar-size-horizontal: 1; } @@ -102,6 +103,7 @@ def __init__( self._vertical_scrollbar: ScrollBar | None = None self._horizontal_scrollbar: ScrollBar | None = None + self._scrollbar_corner: ScrollBarCorner | None = None self._render_cache = RenderCache(Size(0, 0), []) # Regions which need to be updated (in Widget) @@ -353,6 +355,19 @@ def max_scroll_y(self) -> int: + self.scrollbar_size_horizontal, ) + @property + def scrollbar_corner(self) -> ScrollBarCorner: + """Return the ScrollBarCorner - the cells that appear between the + horizontal and vertical scrollbars (only when both are visible). + """ + from .scrollbar import ScrollBarCorner + + if self._scrollbar_corner is not None: + return self._scrollbar_corner + self._scrollbar_corner = ScrollBarCorner() + self.app.start_widget(self, self._scrollbar_corner) + return self._scrollbar_corner + @property def vertical_scrollbar(self) -> ScrollBar: """Get a vertical scrollbar (create if necessary) @@ -918,15 +933,18 @@ def _arrange_scrollbars(self, region: Region) -> Iterable[tuple[Widget, Region]] _, vertical_scrollbar_region, horizontal_scrollbar_region, - _, + scrollbar_corner_gap, ) = region.split( -scrollbar_size_vertical, -scrollbar_size_horizontal, ) + if scrollbar_corner_gap: + yield self.scrollbar_corner, scrollbar_corner_gap if vertical_scrollbar_region: yield self.vertical_scrollbar, vertical_scrollbar_region if horizontal_scrollbar_region: yield self.horizontal_scrollbar, horizontal_scrollbar_region + elif show_vertical_scrollbar: _, scrollbar_region = region.split_vertical(-scrollbar_size_vertical) if scrollbar_region: