diff --git a/docs/styles/scrollbar.md b/docs/styles/scrollbar.md index 9a11b1cd9..514436eca 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 062dff0ec..881e436bf 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%; + background: green; + width: 140; } #right_pane { - width: 1fr; - background: #f6d55c; -} - -.box:focus { - tint: cyan 40%; -} - -#box1 { - background: green; -} - -#box2 { - offset-y: 3; - background: hotpink; -} - -#box3 { - background: red; -} - - -#box4 { 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 8e6fc3ae7..781c66f67 100644 --- a/sandbox/darren/just_a_box.py +++ b/sandbox/darren/just_a_box.py @@ -3,6 +3,7 @@ from __future__ import annotations 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 @@ class Box(Widget, can_focus=True): 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 f52021c47..4b1184127 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -600,6 +600,7 @@ class StylesBuilder: 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 9db2f5d78..dd0b53e6f 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 ea5d510dd..7ae1dcb45 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -9,6 +9,7 @@ from rich.segment import Segment, Segments 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 @@ class ScrollBar(Widget): 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 f0ea13ad3..55630d8cc 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -17,7 +17,6 @@ import rich.repr 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 @@ if TYPE_CHECKING: 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 @@ class Widget(DOMNode): 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 @@ class Widget(DOMNode): + 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 @@ class Widget(DOMNode): _, 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: