mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Implement border (sub)title. (#2064)
* Add Widget.border_title and border_subtitle. Related issues: #1864 * Test setting border_(sub)title. * Add border (sub)title references to StylesCache. These internal references will make it easier for the instance of 'StylesCache' to know which border (sub)title to use, if/when needed. * Add method to render border label. * Add styles to align border (sub)title. * Render border labels. * Update styles template. * Make new 'render_row' parameters optional. * Add (sub)title border snapshot tests. * Document border (sub)title and styles. * Pass (sub)title directly as arguments. Get rid of the watchers to make data flow easier to follow. Related comment: https://github.com/Textualize/textual/pull/2064/files\#r1137746697 * Tweak example. * Fix render_border_label. This was wrong because border labels can be composed of multiple segments if they contain multiple styles. Additionally, we want to render a single blank space of padding around the title. * Ensure we get no label when there's no space. * Add tests for border label rendering. * 'render_border_label' now returns iterable of segments. * Add label to render_row. * Fix calling signature in tests. * Add padding to snapshot tests. * Fix changelog. * Update snapshot tests. * Update snapshot tests. * Border labels expand if there's no corners. * Update CHANGELOG.md * Fix docs. * Remove irrelevant line. * Fix snapshot tests. * Don't share Console among tests. * Simplify example in styles guide. * Avoid expensive function call when possible. * rewording * positive branch first * remove wasteful indirection * fix changelog --------- Co-authored-by: Will McGugan <willmcgugan@gmail.com>
This commit is contained in:
committed by
GitHub
parent
29692736d0
commit
2a810f8c87
@@ -1,6 +1,5 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Static
|
||||
|
||||
from textual.widgets import Label
|
||||
|
||||
TEXT = """I must not fear.
|
||||
Fear is the mind-killer.
|
||||
@@ -13,7 +12,7 @@ Where the fear has gone there will be nothing. Only I will remain."""
|
||||
|
||||
class BorderApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
self.widget = Static(TEXT)
|
||||
self.widget = Label(TEXT)
|
||||
yield self.widget
|
||||
|
||||
def on_mount(self) -> None:
|
||||
|
||||
29
docs/examples/guide/styles/border_title.py
Normal file
29
docs/examples/guide/styles/border_title.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Static
|
||||
|
||||
TEXT = """I must not fear.
|
||||
Fear is the mind-killer.
|
||||
Fear is the little-death that brings total obliteration.
|
||||
I will face my fear.
|
||||
I will permit it to pass over me and through me.
|
||||
And when it has gone past, I will turn the inner eye to see its path.
|
||||
Where the fear has gone there will be nothing. Only I will remain."""
|
||||
|
||||
|
||||
class BorderTitleApp(App[None]):
|
||||
def compose(self) -> ComposeResult:
|
||||
self.widget = Static(TEXT)
|
||||
yield self.widget
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.widget.styles.background = "darkblue"
|
||||
self.widget.styles.width = "50%"
|
||||
self.widget.styles.border = ("heavy", "yellow")
|
||||
self.widget.border_title = "Litany Against Fear"
|
||||
self.widget.border_subtitle = "by Frank Herbert, in “Dune”"
|
||||
self.widget.styles.border_title_align = "center"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = BorderTitleApp()
|
||||
app.run()
|
||||
64
docs/examples/styles/border_sub_title_align_all.css
Normal file
64
docs/examples/styles/border_sub_title_align_all.css
Normal file
@@ -0,0 +1,64 @@
|
||||
Grid {
|
||||
grid-size: 3 3;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
Container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
#lbl1 { /* (1)! */
|
||||
border: vkey $secondary;
|
||||
}
|
||||
|
||||
#lbl2 { /* (2)! */
|
||||
border: round $secondary;
|
||||
border-title-align: right;
|
||||
border-subtitle-align: right;
|
||||
}
|
||||
|
||||
#lbl3 {
|
||||
border: wide $secondary;
|
||||
border-title-align: center;
|
||||
border-subtitle-align: center;
|
||||
}
|
||||
|
||||
#lbl4 {
|
||||
border: ascii $success;
|
||||
border-title-align: center; /* (3)! */
|
||||
border-subtitle-align: left;
|
||||
}
|
||||
|
||||
#lbl5 { /* (4)! */
|
||||
/* No border = no (sub)title. */
|
||||
border: none $success;
|
||||
border-title-align: center;
|
||||
border-subtitle-align: center;
|
||||
}
|
||||
|
||||
#lbl6 { /* (5)! */
|
||||
border-top: solid $success;
|
||||
border-bottom: solid $success;
|
||||
}
|
||||
|
||||
#lbl7 { /* (6)! */
|
||||
border-top: solid $error;
|
||||
border-bottom: solid $error;
|
||||
padding: 1 2;
|
||||
border-subtitle-align: left;
|
||||
}
|
||||
|
||||
#lbl8 {
|
||||
border-top: solid $error;
|
||||
border-bottom: solid $error;
|
||||
border-title-align: center;
|
||||
border-subtitle-align: center;
|
||||
}
|
||||
|
||||
#lbl9 {
|
||||
border-top: solid $error;
|
||||
border-bottom: solid $error;
|
||||
border-title-align: right;
|
||||
}
|
||||
74
docs/examples/styles/border_sub_title_align_all.py
Normal file
74
docs/examples/styles/border_sub_title_align_all.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Container, Grid
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
def make_label_container( # (11)!
|
||||
text: str, id: str, border_title: str, border_subtitle: str
|
||||
) -> Container:
|
||||
lbl = Label(text, id=id)
|
||||
lbl.border_title = border_title
|
||||
lbl.border_subtitle = border_subtitle
|
||||
return Container(lbl)
|
||||
|
||||
|
||||
class BorderSubTitleAlignAll(App[None]):
|
||||
def compose(self):
|
||||
with Grid():
|
||||
yield make_label_container( # (1)!
|
||||
"This is the story of",
|
||||
"lbl1",
|
||||
"[b]Border [i]title[/i][/]",
|
||||
"[u][r]Border[/r] subtitle[/]",
|
||||
)
|
||||
yield make_label_container( # (2)!
|
||||
"a Python",
|
||||
"lbl2",
|
||||
"[b red]Left, but it's loooooooooooong",
|
||||
"[reverse]Center, but it's loooooooooooong",
|
||||
)
|
||||
yield make_label_container( # (3)!
|
||||
"developer that",
|
||||
"lbl3",
|
||||
"[b i on purple]Left[/]",
|
||||
"[r u white on black]@@@[/]",
|
||||
)
|
||||
yield make_label_container(
|
||||
"had to fill up",
|
||||
"lbl4",
|
||||
"", # (4)!
|
||||
"[link=https://textual.textualize.io]Left[/]", # (5)!
|
||||
)
|
||||
yield make_label_container( # (6)!
|
||||
"nine labels", "lbl5", "Title", "Subtitle"
|
||||
)
|
||||
yield make_label_container( # (7)!
|
||||
"and ended up redoing it",
|
||||
"lbl6",
|
||||
"Title",
|
||||
"Subtitle",
|
||||
)
|
||||
yield make_label_container( # (8)!
|
||||
"because the first try",
|
||||
"lbl7",
|
||||
"Title, but really loooooooooong!",
|
||||
"Subtitle, but really loooooooooong!",
|
||||
)
|
||||
yield make_label_container( # (9)!
|
||||
"had some labels",
|
||||
"lbl8",
|
||||
"Title, but really loooooooooong!",
|
||||
"Subtitle, but really loooooooooong!",
|
||||
)
|
||||
yield make_label_container( # (10)!
|
||||
"that were too long.",
|
||||
"lbl9",
|
||||
"Title, but really loooooooooong!",
|
||||
"Subtitle, but really loooooooooong!",
|
||||
)
|
||||
|
||||
|
||||
app = BorderSubTitleAlignAll(css_path="border_sub_title_align_all.css")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
23
docs/examples/styles/border_subtitle_align.css
Normal file
23
docs/examples/styles/border_subtitle_align.css
Normal file
@@ -0,0 +1,23 @@
|
||||
#label1 {
|
||||
border: solid $secondary;
|
||||
border-subtitle-align: left;
|
||||
}
|
||||
|
||||
#label2 {
|
||||
border: dashed $secondary;
|
||||
border-subtitle-align: center;
|
||||
}
|
||||
|
||||
#label3 {
|
||||
border: tall $secondary;
|
||||
border-subtitle-align: right;
|
||||
}
|
||||
|
||||
Screen > Label {
|
||||
width: 100%;
|
||||
height: 5;
|
||||
content-align: center middle;
|
||||
color: white;
|
||||
margin: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
20
docs/examples/styles/border_subtitle_align.py
Normal file
20
docs/examples/styles/border_subtitle_align.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class BorderSubtitleAlignApp(App):
|
||||
def compose(self):
|
||||
lbl = Label("My subtitle is on the left.", id="label1")
|
||||
lbl.border_subtitle = "< Left"
|
||||
yield lbl
|
||||
|
||||
lbl = Label("My subtitle is centered", id="label2")
|
||||
lbl.border_subtitle = "Centered!"
|
||||
yield lbl
|
||||
|
||||
lbl = Label("My subtitle is on the right", id="label3")
|
||||
lbl.border_subtitle = "Right >"
|
||||
yield lbl
|
||||
|
||||
|
||||
app = BorderSubtitleAlignApp(css_path="border_subtitle_align.css")
|
||||
23
docs/examples/styles/border_title_align.css
Normal file
23
docs/examples/styles/border_title_align.css
Normal file
@@ -0,0 +1,23 @@
|
||||
#label1 {
|
||||
border: solid $secondary;
|
||||
border-title-align: left;
|
||||
}
|
||||
|
||||
#label2 {
|
||||
border: dashed $secondary;
|
||||
border-title-align: center;
|
||||
}
|
||||
|
||||
#label3 {
|
||||
border: tall $secondary;
|
||||
border-title-align: right;
|
||||
}
|
||||
|
||||
Screen > Label {
|
||||
width: 100%;
|
||||
height: 5;
|
||||
content-align: center middle;
|
||||
color: white;
|
||||
margin: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
20
docs/examples/styles/border_title_align.py
Normal file
20
docs/examples/styles/border_title_align.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class BorderTitleAlignApp(App):
|
||||
def compose(self):
|
||||
lbl = Label("My title is on the left.", id="label1")
|
||||
lbl.border_title = "< Left"
|
||||
yield lbl
|
||||
|
||||
lbl = Label("My title is centered", id="label2")
|
||||
lbl.border_title = "Centered!"
|
||||
yield lbl
|
||||
|
||||
lbl = Label("My title is on the right", id="label3")
|
||||
lbl.border_title = "Right >"
|
||||
yield lbl
|
||||
|
||||
|
||||
app = BorderTitleAlignApp(css_path="border_title_align.css")
|
||||
Reference in New Issue
Block a user